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