##// END OF EJS Templates
Merge pull request #7662 from jdfreder/autorefreshfix...
Min RK -
r20307:9acc2519 merge
parent child Browse files
Show More
@@ -1,771 +1,787 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/dialog',
8 'base/js/dialog',
9 'base/js/events',
9 'base/js/events',
10 'base/js/keyboard',
10 'base/js/keyboard',
11 ], function(IPython, $, utils, dialog, events, keyboard) {
11 ], function(IPython, $, utils, dialog, events, keyboard) {
12 "use strict";
12 "use strict";
13
13
14 var NotebookList = function (selector, options) {
14 var NotebookList = function (selector, options) {
15 /**
15 /**
16 * Constructor
16 * Constructor
17 *
17 *
18 * Parameters:
18 * Parameters:
19 * selector: string
19 * selector: string
20 * options: dictionary
20 * options: dictionary
21 * Dictionary of keyword arguments.
21 * Dictionary of keyword arguments.
22 * session_list: SessionList instance
22 * session_list: SessionList instance
23 * element_name: string
23 * element_name: string
24 * base_url: string
24 * base_url: string
25 * notebook_path: string
25 * notebook_path: string
26 * contents: Contents instance
26 * contents: Contents instance
27 */
27 */
28 var that = this;
28 var that = this;
29 this.session_list = options.session_list;
29 this.session_list = options.session_list;
30 // allow code re-use by just changing element_name in kernellist.js
30 // allow code re-use by just changing element_name in kernellist.js
31 this.element_name = options.element_name || 'notebook';
31 this.element_name = options.element_name || 'notebook';
32 this.selector = selector;
32 this.selector = selector;
33 if (this.selector !== undefined) {
33 if (this.selector !== undefined) {
34 this.element = $(selector);
34 this.element = $(selector);
35 this.style();
35 this.style();
36 this.bind_events();
36 this.bind_events();
37 }
37 }
38 this.notebooks_list = [];
38 this.notebooks_list = [];
39 this.sessions = {};
39 this.sessions = {};
40 this.base_url = options.base_url || utils.get_body_data("baseUrl");
40 this.base_url = options.base_url || utils.get_body_data("baseUrl");
41 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
41 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
42 this.contents = options.contents;
42 this.contents = options.contents;
43 if (this.session_list && this.session_list.events) {
43 if (this.session_list && this.session_list.events) {
44 this.session_list.events.on('sessions_loaded.Dashboard',
44 this.session_list.events.on('sessions_loaded.Dashboard',
45 function(e, d) { that.sessions_loaded(d); });
45 function(e, d) { that.sessions_loaded(d); });
46 }
46 }
47 };
47 };
48
48
49 NotebookList.prototype.style = function () {
49 NotebookList.prototype.style = function () {
50 var prefix = '#' + this.element_name;
50 var prefix = '#' + this.element_name;
51 $(prefix + '_toolbar').addClass('list_toolbar');
51 $(prefix + '_toolbar').addClass('list_toolbar');
52 $(prefix + '_list_info').addClass('toolbar_info');
52 $(prefix + '_list_info').addClass('toolbar_info');
53 $(prefix + '_buttons').addClass('toolbar_buttons');
53 $(prefix + '_buttons').addClass('toolbar_buttons');
54 $(prefix + '_list_header').addClass('list_header');
54 $(prefix + '_list_header').addClass('list_header');
55 this.element.addClass("list_container");
55 this.element.addClass("list_container");
56 };
56 };
57
57
58 NotebookList.prototype.bind_events = function () {
58 NotebookList.prototype.bind_events = function () {
59 var that = this;
59 var that = this;
60 $('#refresh_' + this.element_name + '_list').click(function () {
60 $('#refresh_' + this.element_name + '_list').click(function () {
61 that.load_sessions();
61 that.load_sessions();
62 });
62 });
63 this.element.bind('dragover', function () {
63 this.element.bind('dragover', function () {
64 return false;
64 return false;
65 });
65 });
66 this.element.bind('drop', function(event){
66 this.element.bind('drop', function(event){
67 that.handleFilesUpload(event,'drop');
67 that.handleFilesUpload(event,'drop');
68 return false;
68 return false;
69 });
69 });
70
70
71 // Bind events for singleton controls.
71 // Bind events for singleton controls.
72 if (!NotebookList._bound_singletons) {
72 if (!NotebookList._bound_singletons) {
73 NotebookList._bound_singletons = true;
73 NotebookList._bound_singletons = true;
74 $('#new-file').click(function(e) {
74 $('#new-file').click(function(e) {
75 var w = window.open();
75 var w = window.open();
76 that.contents.new_untitled(that.notebook_path || '', {type: 'file', ext: '.txt'}).then(function(data) {
76 that.contents.new_untitled(that.notebook_path || '', {type: 'file', ext: '.txt'}).then(function(data) {
77 var url = utils.url_join_encode(
77 var url = utils.url_join_encode(
78 that.base_url, 'edit', data.path
78 that.base_url, 'edit', data.path
79 );
79 );
80 w.location = url;
80 w.location = url;
81 }).catch(function (e) {
81 }).catch(function (e) {
82 w.close();
82 w.close();
83 dialog.modal({
83 dialog.modal({
84 title: 'Creating File Failed',
84 title: 'Creating File Failed',
85 body: $('<div/>')
85 body: $('<div/>')
86 .text("An error occurred while creating a new file.")
86 .text("An error occurred while creating a new file.")
87 .append($('<div/>')
87 .append($('<div/>')
88 .addClass('alert alert-danger')
88 .addClass('alert alert-danger')
89 .text(e.message || e)),
89 .text(e.message || e)),
90 buttons: {
90 buttons: {
91 OK: {'class': 'btn-primary'}
91 OK: {'class': 'btn-primary'}
92 }
92 }
93 });
93 });
94 });
94 });
95 that.load_sessions();
95 that.load_sessions();
96 });
96 });
97 $('#new-folder').click(function(e) {
97 $('#new-folder').click(function(e) {
98 that.contents.new_untitled(that.notebook_path || '', {type: 'directory'})
98 that.contents.new_untitled(that.notebook_path || '', {type: 'directory'})
99 .then(function(){
99 .then(function(){
100 that.load_list();
100 that.load_list();
101 }).catch(function (e) {
101 }).catch(function (e) {
102 dialog.modal({
102 dialog.modal({
103 title: 'Creating Folder Failed',
103 title: 'Creating Folder Failed',
104 body: $('<div/>')
104 body: $('<div/>')
105 .text("An error occurred while creating a new folder.")
105 .text("An error occurred while creating a new folder.")
106 .append($('<div/>')
106 .append($('<div/>')
107 .addClass('alert alert-danger')
107 .addClass('alert alert-danger')
108 .text(e.message || e)),
108 .text(e.message || e)),
109 buttons: {
109 buttons: {
110 OK: {'class': 'btn-primary'}
110 OK: {'class': 'btn-primary'}
111 }
111 }
112 });
112 });
113 });
113 });
114 that.load_sessions();
114 that.load_sessions();
115 });
115 });
116
116
117 $('.rename-button').click($.proxy(this.rename_selected, this));
117 $('.rename-button').click($.proxy(this.rename_selected, this));
118 $('.shutdown-button').click($.proxy(this.shutdown_selected, this));
118 $('.shutdown-button').click($.proxy(this.shutdown_selected, this));
119 $('.duplicate-button').click($.proxy(this.duplicate_selected, this));
119 $('.duplicate-button').click($.proxy(this.duplicate_selected, this));
120 $('.delete-button').click($.proxy(this.delete_selected, this));
120 $('.delete-button').click($.proxy(this.delete_selected, this));
121 }
121 }
122 };
122 };
123
123
124 NotebookList.prototype.handleFilesUpload = function(event, dropOrForm) {
124 NotebookList.prototype.handleFilesUpload = function(event, dropOrForm) {
125 var that = this;
125 var that = this;
126 var files;
126 var files;
127 if(dropOrForm =='drop'){
127 if(dropOrForm =='drop'){
128 files = event.originalEvent.dataTransfer.files;
128 files = event.originalEvent.dataTransfer.files;
129 } else
129 } else
130 {
130 {
131 files = event.originalEvent.target.files;
131 files = event.originalEvent.target.files;
132 }
132 }
133 for (var i = 0; i < files.length; i++) {
133 for (var i = 0; i < files.length; i++) {
134 var f = files[i];
134 var f = files[i];
135 var name_and_ext = utils.splitext(f.name);
135 var name_and_ext = utils.splitext(f.name);
136 var file_ext = name_and_ext[1];
136 var file_ext = name_and_ext[1];
137
137
138 var reader = new FileReader();
138 var reader = new FileReader();
139 if (file_ext === '.ipynb') {
139 if (file_ext === '.ipynb') {
140 reader.readAsText(f);
140 reader.readAsText(f);
141 } else {
141 } else {
142 // read non-notebook files as binary
142 // read non-notebook files as binary
143 reader.readAsArrayBuffer(f);
143 reader.readAsArrayBuffer(f);
144 }
144 }
145 var item = that.new_item(0, true);
145 var item = that.new_item(0, true);
146 item.addClass('new-file');
146 item.addClass('new-file');
147 that.add_name_input(f.name, item, file_ext == '.ipynb' ? 'notebook' : 'file');
147 that.add_name_input(f.name, item, file_ext == '.ipynb' ? 'notebook' : 'file');
148 // Store the list item in the reader so we can use it later
148 // Store the list item in the reader so we can use it later
149 // to know which item it belongs to.
149 // to know which item it belongs to.
150 $(reader).data('item', item);
150 $(reader).data('item', item);
151 reader.onload = function (event) {
151 reader.onload = function (event) {
152 var item = $(event.target).data('item');
152 var item = $(event.target).data('item');
153 that.add_file_data(event.target.result, item);
153 that.add_file_data(event.target.result, item);
154 that.add_upload_button(item);
154 that.add_upload_button(item);
155 };
155 };
156 reader.onerror = function (event) {
156 reader.onerror = function (event) {
157 var item = $(event.target).data('item');
157 var item = $(event.target).data('item');
158 var name = item.data('name');
158 var name = item.data('name');
159 item.remove();
159 item.remove();
160 dialog.modal({
160 dialog.modal({
161 title : 'Failed to read file',
161 title : 'Failed to read file',
162 body : "Failed to read file '" + name + "'",
162 body : "Failed to read file '" + name + "'",
163 buttons : {'OK' : { 'class' : 'btn-primary' }}
163 buttons : {'OK' : { 'class' : 'btn-primary' }}
164 });
164 });
165 };
165 };
166 }
166 }
167 // Replace the file input form wth a clone of itself. This is required to
167 // Replace the file input form wth a clone of itself. This is required to
168 // reset the form. Otherwise, if you upload a file, delete it and try to
168 // reset the form. Otherwise, if you upload a file, delete it and try to
169 // upload it again, the changed event won't fire.
169 // upload it again, the changed event won't fire.
170 var form = $('input.fileinput');
170 var form = $('input.fileinput');
171 form.replaceWith(form.clone(true));
171 form.replaceWith(form.clone(true));
172 return false;
172 return false;
173 };
173 };
174
174
175 NotebookList.prototype.clear_list = function (remove_uploads) {
175 NotebookList.prototype.clear_list = function (remove_uploads) {
176 /**
176 /**
177 * Clears the navigation tree.
177 * Clears the navigation tree.
178 *
178 *
179 * Parameters
179 * Parameters
180 * remove_uploads: bool=False
180 * remove_uploads: bool=False
181 * Should upload prompts also be removed from the tree.
181 * Should upload prompts also be removed from the tree.
182 */
182 */
183 if (remove_uploads) {
183 if (remove_uploads) {
184 this.element.children('.list_item').remove();
184 this.element.children('.list_item').remove();
185 } else {
185 } else {
186 this.element.children('.list_item:not(.new-file)').remove();
186 this.element.children('.list_item:not(.new-file)').remove();
187 }
187 }
188 };
188 };
189
189
190 NotebookList.prototype.load_sessions = function(){
190 NotebookList.prototype.load_sessions = function(){
191 this.session_list.load_sessions();
191 this.session_list.load_sessions();
192 };
192 };
193
193
194
194
195 NotebookList.prototype.sessions_loaded = function(data){
195 NotebookList.prototype.sessions_loaded = function(data){
196 this.sessions = data;
196 this.sessions = data;
197 this.load_list();
197 this.load_list();
198 };
198 };
199
199
200 NotebookList.prototype.load_list = function () {
200 NotebookList.prototype.load_list = function () {
201 var that = this;
201 var that = this;
202 this.contents.list_contents(that.notebook_path).then(
202 this.contents.list_contents(that.notebook_path).then(
203 $.proxy(this.draw_notebook_list, this),
203 $.proxy(this.draw_notebook_list, this),
204 function(error) {
204 function(error) {
205 that.draw_notebook_list({content: []}, "Server error: " + error.message);
205 that.draw_notebook_list({content: []}, "Server error: " + error.message);
206 }
206 }
207 );
207 );
208 };
208 };
209
209
210 /**
210 /**
211 * Draw the list of notebooks
211 * Draw the list of notebooks
212 * @method draw_notebook_list
212 * @method draw_notebook_list
213 * @param {Array} list An array of dictionaries representing files or
213 * @param {Array} list An array of dictionaries representing files or
214 * directories.
214 * directories.
215 * @param {String} error_msg An error message
215 * @param {String} error_msg An error message
216 */
216 */
217
217
218
218
219 var type_order = {'directory':0,'notebook':1,'file':2};
219 var type_order = {'directory':0,'notebook':1,'file':2};
220
220
221 NotebookList.prototype.draw_notebook_list = function (list, error_msg) {
221 NotebookList.prototype.draw_notebook_list = function (list, error_msg) {
222 // Remember what was selected before the refresh.
223 var selected_before = this.selected;
224
222 list.content.sort(function(a, b) {
225 list.content.sort(function(a, b) {
223 if (type_order[a['type']] < type_order[b['type']]) {
226 if (type_order[a['type']] < type_order[b['type']]) {
224 return -1;
227 return -1;
225 }
228 }
226 if (type_order[a['type']] > type_order[b['type']]) {
229 if (type_order[a['type']] > type_order[b['type']]) {
227 return 1;
230 return 1;
228 }
231 }
229 if (a['name'] < b['name']) {
232 if (a['name'] < b['name']) {
230 return -1;
233 return -1;
231 }
234 }
232 if (a['name'] > b['name']) {
235 if (a['name'] > b['name']) {
233 return 1;
236 return 1;
234 }
237 }
235 return 0;
238 return 0;
236 });
239 });
237 var message = error_msg || 'Notebook list empty.';
240 var message = error_msg || 'Notebook list empty.';
238 var item = null;
241 var item = null;
239 var model = null;
242 var model = null;
240 var len = list.content.length;
243 var len = list.content.length;
241 this.clear_list();
244 this.clear_list();
242 var n_uploads = this.element.children('.list_item').length;
245 var n_uploads = this.element.children('.list_item').length;
243 if (len === 0) {
246 if (len === 0) {
244 item = this.new_item(0);
247 item = this.new_item(0);
245 var span12 = item.children().first();
248 var span12 = item.children().first();
246 span12.empty();
249 span12.empty();
247 span12.append($('<div style="margin:auto;text-align:center;color:grey"/>').text(message));
250 span12.append($('<div style="margin:auto;text-align:center;color:grey"/>').text(message));
248 }
251 }
249 var path = this.notebook_path;
252 var path = this.notebook_path;
250 var offset = n_uploads;
253 var offset = n_uploads;
251 if (path !== '') {
254 if (path !== '') {
252 item = this.new_item(offset, false);
255 item = this.new_item(offset, false);
253 model = {
256 model = {
254 type: 'directory',
257 type: 'directory',
255 name: '..',
258 name: '..',
256 path: utils.url_path_split(path)[0],
259 path: utils.url_path_split(path)[0],
257 };
260 };
258 this.add_link(model, item);
261 this.add_link(model, item);
259 offset += 1;
262 offset += 1;
260 }
263 }
261 for (var i=0; i<len; i++) {
264 for (var i=0; i<len; i++) {
262 model = list.content[i];
265 model = list.content[i];
263 item = this.new_item(i+offset, true);
266 item = this.new_item(i+offset, true);
264 this.add_link(model, item);
267 this.add_link(model, item);
265 }
268 }
266 // Trigger an event when we've finished drawing the notebook list.
269 // Trigger an event when we've finished drawing the notebook list.
267 events.trigger('draw_notebook_list.NotebookList');
270 events.trigger('draw_notebook_list.NotebookList');
268 this._selection_changed();
271
272 // Reselect the items that were selected before. Notify listeners
273 // that the selected items may have changed. O(n^2) operation.
274 selected_before.forEach(function(item) {
275 var list_items = $('.list_item');
276 for (var i=0; i<list_items.length; i++) {
277 var $list_item = $(list_items[i]);
278 if ($list_item.data('path') == item.path) {
279 $list_item.find('input[type=checkbox]').prop('checked', true);
280 break;
281 }
282 }
283 });
284 this._selection_changed();
269 };
285 };
270
286
271
287
272 /**
288 /**
273 * Creates a new item.
289 * Creates a new item.
274 * @param {integer} index
290 * @param {integer} index
275 * @param {boolean} [selectable] - tristate, undefined: don't draw checkbox,
291 * @param {boolean} [selectable] - tristate, undefined: don't draw checkbox,
276 * false: don't draw checkbox but pad
292 * false: don't draw checkbox but pad
277 * where it should be, true: draw checkbox.
293 * where it should be, true: draw checkbox.
278 * @return {JQuery} row
294 * @return {JQuery} row
279 */
295 */
280 NotebookList.prototype.new_item = function (index, selectable) {
296 NotebookList.prototype.new_item = function (index, selectable) {
281 var row = $('<div/>')
297 var row = $('<div/>')
282 .addClass("list_item")
298 .addClass("list_item")
283 .addClass("row");
299 .addClass("row");
284
300
285 var item = $("<div/>")
301 var item = $("<div/>")
286 .addClass("col-md-12")
302 .addClass("col-md-12")
287 .appendTo(row);
303 .appendTo(row);
288
304
289 var checkbox;
305 var checkbox;
290 if (selectable !== undefined) {
306 if (selectable !== undefined) {
291 checkbox = $('<input/>')
307 checkbox = $('<input/>')
292 .attr('type', 'checkbox')
308 .attr('type', 'checkbox')
293 .attr('title', 'Click here to rename, delete, etc.')
309 .attr('title', 'Click here to rename, delete, etc.')
294 .appendTo(item);
310 .appendTo(item);
295 }
311 }
296
312
297 $('<i/>')
313 $('<i/>')
298 .addClass('item_icon')
314 .addClass('item_icon')
299 .appendTo(item);
315 .appendTo(item);
300
316
301 var link = $("<a/>")
317 var link = $("<a/>")
302 .addClass("item_link")
318 .addClass("item_link")
303 .appendTo(item);
319 .appendTo(item);
304
320
305 $("<span/>")
321 $("<span/>")
306 .addClass("item_name")
322 .addClass("item_name")
307 .appendTo(link);
323 .appendTo(link);
308
324
309 if (selectable === false) {
325 if (selectable === false) {
310 checkbox.css('visibility', 'hidden');
326 checkbox.css('visibility', 'hidden');
311 } else if (selectable === true) {
327 } else if (selectable === true) {
312 var that = this;
328 var that = this;
313 link.click(function(e) {
329 link.click(function(e) {
314 e.stopPropagation();
330 e.stopPropagation();
315 });
331 });
316 checkbox.click(function(e) {
332 checkbox.click(function(e) {
317 e.stopPropagation();
333 e.stopPropagation();
318 that._selection_changed();
334 that._selection_changed();
319 });
335 });
320 row.click(function(e) {
336 row.click(function(e) {
321 e.stopPropagation();
337 e.stopPropagation();
322 checkbox.prop('checked', !checkbox.prop('checked'));
338 checkbox.prop('checked', !checkbox.prop('checked'));
323 that._selection_changed();
339 that._selection_changed();
324 });
340 });
325 }
341 }
326
342
327 var buttons = $('<div/>')
343 var buttons = $('<div/>')
328 .addClass("item_buttons pull-right")
344 .addClass("item_buttons pull-right")
329 .appendTo(item);
345 .appendTo(item);
330
346
331 $('<div/>')
347 $('<div/>')
332 .addClass('running-indicator')
348 .addClass('running-indicator')
333 .text('Running')
349 .text('Running')
334 .css('visibility', 'hidden')
350 .css('visibility', 'hidden')
335 .appendTo(buttons);
351 .appendTo(buttons);
336
352
337 if (index === -1) {
353 if (index === -1) {
338 this.element.append(row);
354 this.element.append(row);
339 } else {
355 } else {
340 this.element.children().eq(index).after(row);
356 this.element.children().eq(index).after(row);
341 }
357 }
342 return row;
358 return row;
343 };
359 };
344
360
345
361
346 NotebookList.icons = {
362 NotebookList.icons = {
347 directory: 'folder_icon',
363 directory: 'folder_icon',
348 notebook: 'notebook_icon',
364 notebook: 'notebook_icon',
349 file: 'file_icon',
365 file: 'file_icon',
350 };
366 };
351
367
352 NotebookList.uri_prefixes = {
368 NotebookList.uri_prefixes = {
353 directory: 'tree',
369 directory: 'tree',
354 notebook: 'notebooks',
370 notebook: 'notebooks',
355 file: 'edit',
371 file: 'edit',
356 };
372 };
357
373
358 /**
374 /**
359 * Handles when any row selector checkbox is toggled.
375 * Handles when any row selector checkbox is toggled.
360 */
376 */
361 NotebookList.prototype._selection_changed = function() {
377 NotebookList.prototype._selection_changed = function() {
362
378
363 // Use a JQuery selector to find each row with a checked checkbox. If
379 // Use a JQuery selector to find each row with a checked checkbox. If
364 // we decide to add more checkboxes in the future, this code will need
380 // we decide to add more checkboxes in the future, this code will need
365 // to be changed to distinguish which checkbox is the row selector.
381 // to be changed to distinguish which checkbox is the row selector.
366 var selected = [];
382 var selected = [];
367 var has_running_notebook = false;
383 var has_running_notebook = false;
368 var has_directory = false;
384 var has_directory = false;
369 var has_file = false;
385 var has_file = false;
370 var that = this;
386 var that = this;
371 $('.list_item :checked').each(function(index, item) {
387 $('.list_item :checked').each(function(index, item) {
372 var parent = $(item).parent().parent();
388 var parent = $(item).parent().parent();
373
389
374 // If the item doesn't have an upload button, it can be selected.
390 // If the item doesn't have an upload button, it can be selected.
375 if (parent.find('.upload_button').length === 0) {
391 if (parent.find('.upload_button').length === 0) {
376 selected.push({
392 selected.push({
377 name: parent.data('name'),
393 name: parent.data('name'),
378 path: parent.data('path'),
394 path: parent.data('path'),
379 type: parent.data('type')
395 type: parent.data('type')
380 });
396 });
381
397
382 // Set flags according to what is selected. Flags are later
398 // Set flags according to what is selected. Flags are later
383 // used to decide which action buttons are visible.
399 // used to decide which action buttons are visible.
384 has_running_notebook = has_running_notebook ||
400 has_running_notebook = has_running_notebook ||
385 (parent.data('type') == 'notebook' && that.sessions[parent.data('path')] !== undefined);
401 (parent.data('type') == 'notebook' && that.sessions[parent.data('path')] !== undefined);
386 has_file = has_file || parent.data('type') == 'file';
402 has_file = has_file || parent.data('type') == 'file';
387 has_directory = has_directory || parent.data('type') == 'directory';
403 has_directory = has_directory || parent.data('type') == 'directory';
388 }
404 }
389 });
405 });
390 this.selected = selected;
406 this.selected = selected;
391
407
392 // Rename is only visible when one item is selected.
408 // Rename is only visible when one item is selected.
393 if (selected.length==1) {
409 if (selected.length==1) {
394 $('.rename-button').css('display', 'inline-block');
410 $('.rename-button').css('display', 'inline-block');
395 } else {
411 } else {
396 $('.rename-button').css('display', 'none');
412 $('.rename-button').css('display', 'none');
397 }
413 }
398
414
399 // Shutdown is only visible when one or more notebooks running notebooks
415 // Shutdown is only visible when one or more notebooks running notebooks
400 // are selected and no non-notebook items are selected.
416 // are selected and no non-notebook items are selected.
401 if (has_running_notebook && !(has_file || has_directory)) {
417 if (has_running_notebook && !(has_file || has_directory)) {
402 $('.shutdown-button').css('display', 'inline-block');
418 $('.shutdown-button').css('display', 'inline-block');
403 } else {
419 } else {
404 $('.shutdown-button').css('display', 'none');
420 $('.shutdown-button').css('display', 'none');
405 }
421 }
406
422
407 // Duplicate isn't visible when a directory is selected.
423 // Duplicate isn't visible when a directory is selected.
408 if (selected.length > 0 && !has_directory) {
424 if (selected.length > 0 && !has_directory) {
409 $('.duplicate-button').css('display', 'inline-block');
425 $('.duplicate-button').css('display', 'inline-block');
410 } else {
426 } else {
411 $('.duplicate-button').css('display', 'none');
427 $('.duplicate-button').css('display', 'none');
412 }
428 }
413
429
414 // Delete is visible if one or more items are selected.
430 // Delete is visible if one or more items are selected.
415 if (selected.length > 0) {
431 if (selected.length > 0) {
416 $('.delete-button').css('display', 'inline-block');
432 $('.delete-button').css('display', 'inline-block');
417 } else {
433 } else {
418 $('.delete-button').css('display', 'none');
434 $('.delete-button').css('display', 'none');
419 }
435 }
420 };
436 };
421
437
422 NotebookList.prototype.add_link = function (model, item) {
438 NotebookList.prototype.add_link = function (model, item) {
423 var path = model.path,
439 var path = model.path,
424 name = model.name;
440 name = model.name;
425 var running = (model.type == 'notebook' && this.sessions[path] !== undefined);
441 var running = (model.type == 'notebook' && this.sessions[path] !== undefined);
426
442
427 item.data('name', name);
443 item.data('name', name);
428 item.data('path', path);
444 item.data('path', path);
429 item.data('type', model.type);
445 item.data('type', model.type);
430 item.find(".item_name").text(name);
446 item.find(".item_name").text(name);
431 var icon = NotebookList.icons[model.type];
447 var icon = NotebookList.icons[model.type];
432 if (running) {
448 if (running) {
433 icon = 'running_' + icon;
449 icon = 'running_' + icon;
434 }
450 }
435 var uri_prefix = NotebookList.uri_prefixes[model.type];
451 var uri_prefix = NotebookList.uri_prefixes[model.type];
436 item.find(".item_icon").addClass(icon).addClass('icon-fixed-width');
452 item.find(".item_icon").addClass(icon).addClass('icon-fixed-width');
437 var link = item.find("a.item_link")
453 var link = item.find("a.item_link")
438 .attr('href',
454 .attr('href',
439 utils.url_join_encode(
455 utils.url_join_encode(
440 this.base_url,
456 this.base_url,
441 uri_prefix,
457 uri_prefix,
442 path
458 path
443 )
459 )
444 );
460 );
445
461
446 item.find(".item_buttons .running-indicator").css('visibility', running ? '' : 'hidden');
462 item.find(".item_buttons .running-indicator").css('visibility', running ? '' : 'hidden');
447
463
448 // directory nav doesn't open new tabs
464 // directory nav doesn't open new tabs
449 // files, notebooks do
465 // files, notebooks do
450 if (model.type !== "directory") {
466 if (model.type !== "directory") {
451 link.attr('target','_blank');
467 link.attr('target','_blank');
452 }
468 }
453 };
469 };
454
470
455
471
456 NotebookList.prototype.add_name_input = function (name, item, icon_type) {
472 NotebookList.prototype.add_name_input = function (name, item, icon_type) {
457 item.data('name', name);
473 item.data('name', name);
458 item.find(".item_icon").addClass(NotebookList.icons[icon_type]).addClass('icon-fixed-width');
474 item.find(".item_icon").addClass(NotebookList.icons[icon_type]).addClass('icon-fixed-width');
459 item.find(".item_name").empty().append(
475 item.find(".item_name").empty().append(
460 $('<input/>')
476 $('<input/>')
461 .addClass("filename_input")
477 .addClass("filename_input")
462 .attr('value', name)
478 .attr('value', name)
463 .attr('size', '30')
479 .attr('size', '30')
464 .attr('type', 'text')
480 .attr('type', 'text')
465 .keyup(function(event){
481 .keyup(function(event){
466 if(event.keyCode == 13){item.find('.upload_button').click();}
482 if(event.keyCode == 13){item.find('.upload_button').click();}
467 else if(event.keyCode == 27){item.remove();}
483 else if(event.keyCode == 27){item.remove();}
468 })
484 })
469 );
485 );
470 };
486 };
471
487
472
488
473 NotebookList.prototype.add_file_data = function (data, item) {
489 NotebookList.prototype.add_file_data = function (data, item) {
474 item.data('filedata', data);
490 item.data('filedata', data);
475 };
491 };
476
492
477
493
478 NotebookList.prototype.shutdown_selected = function() {
494 NotebookList.prototype.shutdown_selected = function() {
479 var that = this;
495 var that = this;
480 this.selected.forEach(function(item) {
496 this.selected.forEach(function(item) {
481 if (item.type == 'notebook') {
497 if (item.type == 'notebook') {
482 that.shutdown_notebook(item.path);
498 that.shutdown_notebook(item.path);
483 }
499 }
484 });
500 });
485 };
501 };
486
502
487 NotebookList.prototype.shutdown_notebook = function(path) {
503 NotebookList.prototype.shutdown_notebook = function(path) {
488 var that = this;
504 var that = this;
489 var settings = {
505 var settings = {
490 processData : false,
506 processData : false,
491 cache : false,
507 cache : false,
492 type : "DELETE",
508 type : "DELETE",
493 dataType : "json",
509 dataType : "json",
494 success : function () {
510 success : function () {
495 that.load_sessions();
511 that.load_sessions();
496 },
512 },
497 error : utils.log_ajax_error,
513 error : utils.log_ajax_error,
498 };
514 };
499
515
500 var session = this.sessions[path];
516 var session = this.sessions[path];
501 if (session) {
517 if (session) {
502 var url = utils.url_join_encode(
518 var url = utils.url_join_encode(
503 this.base_url,
519 this.base_url,
504 'api/sessions',
520 'api/sessions',
505 session
521 session
506 );
522 );
507 $.ajax(url, settings);
523 $.ajax(url, settings);
508 }
524 }
509 };
525 };
510
526
511 NotebookList.prototype.rename_selected = function() {
527 NotebookList.prototype.rename_selected = function() {
512 if (this.selected.length != 1) return;
528 if (this.selected.length != 1) return;
513
529
514 var that = this;
530 var that = this;
515 var path = this.selected[0].path;
531 var path = this.selected[0].path;
516 var input = $('<input/>').attr('type','text').attr('size','25').addClass('form-control')
532 var input = $('<input/>').attr('type','text').attr('size','25').addClass('form-control')
517 .val(path);
533 .val(path);
518 var dialog_body = $('<div/>').append(
534 var dialog_body = $('<div/>').append(
519 $("<p/>").addClass("rename-message")
535 $("<p/>").addClass("rename-message")
520 .text('Enter a new directory name:')
536 .text('Enter a new directory name:')
521 ).append(
537 ).append(
522 $("<br/>")
538 $("<br/>")
523 ).append(input);
539 ).append(input);
524 var d = dialog.modal({
540 var d = dialog.modal({
525 title : "Rename directory",
541 title : "Rename directory",
526 body : dialog_body,
542 body : dialog_body,
527 buttons : {
543 buttons : {
528 OK : {
544 OK : {
529 class: "btn-primary",
545 class: "btn-primary",
530 click: function() {
546 click: function() {
531 that.contents.rename(path, input.val()).then(function() {
547 that.contents.rename(path, input.val()).then(function() {
532 that.load_list();
548 that.load_list();
533 }).catch(function(e) {
549 }).catch(function(e) {
534 dialog.modal({
550 dialog.modal({
535 title: "Rename Failed",
551 title: "Rename Failed",
536 body: $('<div/>')
552 body: $('<div/>')
537 .text("An error occurred while renaming \"" + path + "\" to \"" + input.val() + "\".")
553 .text("An error occurred while renaming \"" + path + "\" to \"" + input.val() + "\".")
538 .append($('<div/>')
554 .append($('<div/>')
539 .addClass('alert alert-danger')
555 .addClass('alert alert-danger')
540 .text(e.message || e)),
556 .text(e.message || e)),
541 buttons: {
557 buttons: {
542 OK: {'class': 'btn-primary'}
558 OK: {'class': 'btn-primary'}
543 }
559 }
544 });
560 });
545 });
561 });
546 }
562 }
547 },
563 },
548 Cancel : {}
564 Cancel : {}
549 },
565 },
550 open : function () {
566 open : function () {
551 // Upon ENTER, click the OK button.
567 // Upon ENTER, click the OK button.
552 input.keydown(function (event) {
568 input.keydown(function (event) {
553 if (event.which === keyboard.keycodes.enter) {
569 if (event.which === keyboard.keycodes.enter) {
554 d.find('.btn-primary').first().click();
570 d.find('.btn-primary').first().click();
555 return false;
571 return false;
556 }
572 }
557 });
573 });
558 input.focus().select();
574 input.focus().select();
559 }
575 }
560 });
576 });
561 };
577 };
562
578
563 NotebookList.prototype.delete_selected = function() {
579 NotebookList.prototype.delete_selected = function() {
564 var message;
580 var message;
565 if (this.selected.length == 1) {
581 if (this.selected.length == 1) {
566 message = 'Are you sure you want to permanently delete: ' + this.selected[0].name + '?';
582 message = 'Are you sure you want to permanently delete: ' + this.selected[0].name + '?';
567 } else {
583 } else {
568 message = 'Are you sure you want to permanently delete the ' + this.selected.length + ' files/folders selected?';
584 message = 'Are you sure you want to permanently delete the ' + this.selected.length + ' files/folders selected?';
569 }
585 }
570 var that = this;
586 var that = this;
571 dialog.modal({
587 dialog.modal({
572 title : "Delete",
588 title : "Delete",
573 body : message,
589 body : message,
574 buttons : {
590 buttons : {
575 Delete : {
591 Delete : {
576 class: "btn-danger",
592 class: "btn-danger",
577 click: function() {
593 click: function() {
578 // Shutdown any/all selected notebooks before deleting
594 // Shutdown any/all selected notebooks before deleting
579 // the files.
595 // the files.
580 that.shutdown_selected();
596 that.shutdown_selected();
581
597
582 // Delete selected.
598 // Delete selected.
583 that.selected.forEach(function(item) {
599 that.selected.forEach(function(item) {
584 that.contents.delete(item.path).then(function() {
600 that.contents.delete(item.path).then(function() {
585 that.notebook_deleted(item.path);
601 that.notebook_deleted(item.path);
586 }).catch(function(e) {
602 }).catch(function(e) {
587 dialog.modal({
603 dialog.modal({
588 title: "Delete Failed",
604 title: "Delete Failed",
589 body: $('<div/>')
605 body: $('<div/>')
590 .text("An error occurred while deleting \"" + item.path + "\".")
606 .text("An error occurred while deleting \"" + item.path + "\".")
591 .append($('<div/>')
607 .append($('<div/>')
592 .addClass('alert alert-danger')
608 .addClass('alert alert-danger')
593 .text(e.message || e)),
609 .text(e.message || e)),
594 buttons: {
610 buttons: {
595 OK: {'class': 'btn-primary'}
611 OK: {'class': 'btn-primary'}
596 }
612 }
597 });
613 });
598 });
614 });
599 });
615 });
600 }
616 }
601 },
617 },
602 Cancel : {}
618 Cancel : {}
603 }
619 }
604 });
620 });
605 };
621 };
606
622
607 NotebookList.prototype.duplicate_selected = function() {
623 NotebookList.prototype.duplicate_selected = function() {
608 var message;
624 var message;
609 if (this.selected.length == 1) {
625 if (this.selected.length == 1) {
610 message = 'Are you sure you want to duplicate: ' + this.selected[0].name + '?';
626 message = 'Are you sure you want to duplicate: ' + this.selected[0].name + '?';
611 } else {
627 } else {
612 message = 'Are you sure you want to duplicate the ' + this.selected.length + ' files selected?';
628 message = 'Are you sure you want to duplicate the ' + this.selected.length + ' files selected?';
613 }
629 }
614 var that = this;
630 var that = this;
615 dialog.modal({
631 dialog.modal({
616 title : "Delete",
632 title : "Delete",
617 body : message,
633 body : message,
618 buttons : {
634 buttons : {
619 Duplicate : {
635 Duplicate : {
620 class: "btn-primary",
636 class: "btn-primary",
621 click: function() {
637 click: function() {
622 that.selected.forEach(function(item) {
638 that.selected.forEach(function(item) {
623 that.contents.copy(item.path, that.notebook_path).then(function () {
639 that.contents.copy(item.path, that.notebook_path).then(function () {
624 that.load_list();
640 that.load_list();
625 }).catch(function(e) {
641 }).catch(function(e) {
626 dialog.modal({
642 dialog.modal({
627 title: "Delete Failed",
643 title: "Delete Failed",
628 body: $('<div/>')
644 body: $('<div/>')
629 .text("An error occurred while deleting \"" + item.path + "\".")
645 .text("An error occurred while deleting \"" + item.path + "\".")
630 .append($('<div/>')
646 .append($('<div/>')
631 .addClass('alert alert-danger')
647 .addClass('alert alert-danger')
632 .text(e.message || e)),
648 .text(e.message || e)),
633 buttons: {
649 buttons: {
634 OK: {'class': 'btn-primary'}
650 OK: {'class': 'btn-primary'}
635 }
651 }
636 });
652 });
637 });
653 });
638 });
654 });
639 }
655 }
640 },
656 },
641 Cancel : {}
657 Cancel : {}
642 }
658 }
643 });
659 });
644 };
660 };
645
661
646 NotebookList.prototype.notebook_deleted = function(path) {
662 NotebookList.prototype.notebook_deleted = function(path) {
647 /**
663 /**
648 * Remove the deleted notebook.
664 * Remove the deleted notebook.
649 */
665 */
650 var that = this;
666 var that = this;
651 $( ":data(path)" ).each(function() {
667 $( ":data(path)" ).each(function() {
652 var element = $(this);
668 var element = $(this);
653 if (element.data("path") === path) {
669 if (element.data("path") === path) {
654 element.remove();
670 element.remove();
655 events.trigger('notebook_deleted.NotebookList');
671 events.trigger('notebook_deleted.NotebookList');
656 that._selection_changed();
672 that._selection_changed();
657 }
673 }
658 });
674 });
659 };
675 };
660
676
661
677
662 NotebookList.prototype.add_upload_button = function (item) {
678 NotebookList.prototype.add_upload_button = function (item) {
663 var that = this;
679 var that = this;
664 var upload_button = $('<button/>').text("Upload")
680 var upload_button = $('<button/>').text("Upload")
665 .addClass('btn btn-primary btn-xs upload_button')
681 .addClass('btn btn-primary btn-xs upload_button')
666 .click(function (e) {
682 .click(function (e) {
667 var filename = item.find('.item_name > input').val();
683 var filename = item.find('.item_name > input').val();
668 var path = utils.url_path_join(that.notebook_path, filename);
684 var path = utils.url_path_join(that.notebook_path, filename);
669 var filedata = item.data('filedata');
685 var filedata = item.data('filedata');
670 var format = 'text';
686 var format = 'text';
671 if (filename.length === 0 || filename[0] === '.') {
687 if (filename.length === 0 || filename[0] === '.') {
672 dialog.modal({
688 dialog.modal({
673 title : 'Invalid file name',
689 title : 'Invalid file name',
674 body : "File names must be at least one character and not start with a dot",
690 body : "File names must be at least one character and not start with a dot",
675 buttons : {'OK' : { 'class' : 'btn-primary' }}
691 buttons : {'OK' : { 'class' : 'btn-primary' }}
676 });
692 });
677 return false;
693 return false;
678 }
694 }
679 if (filedata instanceof ArrayBuffer) {
695 if (filedata instanceof ArrayBuffer) {
680 // base64-encode binary file data
696 // base64-encode binary file data
681 var bytes = '';
697 var bytes = '';
682 var buf = new Uint8Array(filedata);
698 var buf = new Uint8Array(filedata);
683 var nbytes = buf.byteLength;
699 var nbytes = buf.byteLength;
684 for (var i=0; i<nbytes; i++) {
700 for (var i=0; i<nbytes; i++) {
685 bytes += String.fromCharCode(buf[i]);
701 bytes += String.fromCharCode(buf[i]);
686 }
702 }
687 filedata = btoa(bytes);
703 filedata = btoa(bytes);
688 format = 'base64';
704 format = 'base64';
689 }
705 }
690 var model = {};
706 var model = {};
691
707
692 var name_and_ext = utils.splitext(filename);
708 var name_and_ext = utils.splitext(filename);
693 var file_ext = name_and_ext[1];
709 var file_ext = name_and_ext[1];
694 var content_type;
710 var content_type;
695 if (file_ext === '.ipynb') {
711 if (file_ext === '.ipynb') {
696 model.type = 'notebook';
712 model.type = 'notebook';
697 model.format = 'json';
713 model.format = 'json';
698 try {
714 try {
699 model.content = JSON.parse(filedata);
715 model.content = JSON.parse(filedata);
700 } catch (e) {
716 } catch (e) {
701 dialog.modal({
717 dialog.modal({
702 title : 'Cannot upload invalid Notebook',
718 title : 'Cannot upload invalid Notebook',
703 body : "The error was: " + e,
719 body : "The error was: " + e,
704 buttons : {'OK' : {
720 buttons : {'OK' : {
705 'class' : 'btn-primary',
721 'class' : 'btn-primary',
706 click: function () {
722 click: function () {
707 item.remove();
723 item.remove();
708 }
724 }
709 }}
725 }}
710 });
726 });
711 return false;
727 return false;
712 }
728 }
713 content_type = 'application/json';
729 content_type = 'application/json';
714 } else {
730 } else {
715 model.type = 'file';
731 model.type = 'file';
716 model.format = format;
732 model.format = format;
717 model.content = filedata;
733 model.content = filedata;
718 content_type = 'application/octet-stream';
734 content_type = 'application/octet-stream';
719 }
735 }
720 filedata = item.data('filedata');
736 filedata = item.data('filedata');
721
737
722 var on_success = function () {
738 var on_success = function () {
723 item.removeClass('new-file');
739 item.removeClass('new-file');
724 that.add_link(model, item);
740 that.add_link(model, item);
725 that.session_list.load_sessions();
741 that.session_list.load_sessions();
726 };
742 };
727
743
728 var exists = false;
744 var exists = false;
729 $.each(that.element.find('.list_item:not(.new-file)'), function(k,v){
745 $.each(that.element.find('.list_item:not(.new-file)'), function(k,v){
730 if ($(v).data('name') === filename) { exists = true; return false; }
746 if ($(v).data('name') === filename) { exists = true; return false; }
731 });
747 });
732
748
733 if (exists) {
749 if (exists) {
734 dialog.modal({
750 dialog.modal({
735 title : "Replace file",
751 title : "Replace file",
736 body : 'There is already a file named ' + filename + ', do you want to replace it?',
752 body : 'There is already a file named ' + filename + ', do you want to replace it?',
737 buttons : {
753 buttons : {
738 Overwrite : {
754 Overwrite : {
739 class: "btn-danger",
755 class: "btn-danger",
740 click: function () {
756 click: function () {
741 that.contents.save(path, model).then(on_success);
757 that.contents.save(path, model).then(on_success);
742 }
758 }
743 },
759 },
744 Cancel : {
760 Cancel : {
745 click: function() { item.remove(); }
761 click: function() { item.remove(); }
746 }
762 }
747 }
763 }
748 });
764 });
749 } else {
765 } else {
750 that.contents.save(path, model).then(on_success);
766 that.contents.save(path, model).then(on_success);
751 }
767 }
752
768
753 return false;
769 return false;
754 });
770 });
755 var cancel_button = $('<button/>').text("Cancel")
771 var cancel_button = $('<button/>').text("Cancel")
756 .addClass("btn btn-default btn-xs")
772 .addClass("btn btn-default btn-xs")
757 .click(function (e) {
773 .click(function (e) {
758 item.remove();
774 item.remove();
759 return false;
775 return false;
760 });
776 });
761 item.find(".item_buttons").empty()
777 item.find(".item_buttons").empty()
762 .append(upload_button)
778 .append(upload_button)
763 .append(cancel_button);
779 .append(cancel_button);
764 };
780 };
765
781
766
782
767 // Backwards compatability.
783 // Backwards compatability.
768 IPython.NotebookList = NotebookList;
784 IPython.NotebookList = NotebookList;
769
785
770 return {'NotebookList': NotebookList};
786 return {'NotebookList': NotebookList};
771 });
787 });
General Comments 0
You need to be logged in to leave comments. Login now