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