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