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