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