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