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