##// END OF EJS Templates
Reload list after creating a new folder in promises....
Matthias Bussonnier -
Show More
@@ -1,546 +1,548 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 that.contents.new_untitled(that.notebook_path || '', {type: 'directory'});
86 that.load_sessions();
85 that.contents.new_untitled(that.notebook_path || '', {type: 'directory'})
86 .then(function(){
87 that.load_list();
88 });
87 89 });
88 90 }
89 91 };
90 92
91 93 NotebookList.prototype.handleFilesUpload = function(event, dropOrForm) {
92 94 var that = this;
93 95 var files;
94 96 if(dropOrForm =='drop'){
95 97 files = event.originalEvent.dataTransfer.files;
96 98 } else
97 99 {
98 100 files = event.originalEvent.target.files;
99 101 }
100 102 for (var i = 0; i < files.length; i++) {
101 103 var f = files[i];
102 104 var name_and_ext = utils.splitext(f.name);
103 105 var file_ext = name_and_ext[1];
104 106
105 107 var reader = new FileReader();
106 108 if (file_ext === '.ipynb') {
107 109 reader.readAsText(f);
108 110 } else {
109 111 // read non-notebook files as binary
110 112 reader.readAsArrayBuffer(f);
111 113 }
112 114 var item = that.new_item(0);
113 115 item.addClass('new-file');
114 116 that.add_name_input(f.name, item, file_ext == '.ipynb' ? 'notebook' : 'file');
115 117 // Store the list item in the reader so we can use it later
116 118 // to know which item it belongs to.
117 119 $(reader).data('item', item);
118 120 reader.onload = function (event) {
119 121 var item = $(event.target).data('item');
120 122 that.add_file_data(event.target.result, item);
121 123 that.add_upload_button(item);
122 124 };
123 125 reader.onerror = function (event) {
124 126 var item = $(event.target).data('item');
125 127 var name = item.data('name');
126 128 item.remove();
127 129 dialog.modal({
128 130 title : 'Failed to read file',
129 131 body : "Failed to read file '" + name + "'",
130 132 buttons : {'OK' : { 'class' : 'btn-primary' }}
131 133 });
132 134 };
133 135 }
134 136 // Replace the file input form wth a clone of itself. This is required to
135 137 // reset the form. Otherwise, if you upload a file, delete it and try to
136 138 // upload it again, the changed event won't fire.
137 139 var form = $('input.fileinput');
138 140 form.replaceWith(form.clone(true));
139 141 return false;
140 142 };
141 143
142 144 NotebookList.prototype.clear_list = function (remove_uploads) {
143 145 /**
144 146 * Clears the navigation tree.
145 147 *
146 148 * Parameters
147 149 * remove_uploads: bool=False
148 150 * Should upload prompts also be removed from the tree.
149 151 */
150 152 if (remove_uploads) {
151 153 this.element.children('.list_item').remove();
152 154 } else {
153 155 this.element.children('.list_item:not(.new-file)').remove();
154 156 }
155 157 };
156 158
157 159 NotebookList.prototype.load_sessions = function(){
158 160 this.session_list.load_sessions();
159 161 };
160 162
161 163
162 164 NotebookList.prototype.sessions_loaded = function(data){
163 165 this.sessions = data;
164 166 this.load_list();
165 167 };
166 168
167 169 NotebookList.prototype.load_list = function () {
168 170 var that = this;
169 171 this.contents.list_contents(that.notebook_path).then(
170 172 $.proxy(this.draw_notebook_list, this),
171 173 function(error) {
172 174 that.draw_notebook_list({content: []}, "Server error: " + error.message);
173 175 }
174 176 );
175 177 };
176 178
177 179 /**
178 180 * Draw the list of notebooks
179 181 * @method draw_notebook_list
180 182 * @param {Array} list An array of dictionaries representing files or
181 183 * directories.
182 184 * @param {String} error_msg An error message
183 185 */
184 186
185 187
186 188 var type_order = {'directory':0,'notebook':1,'file':2};
187 189
188 190 NotebookList.prototype.draw_notebook_list = function (list, error_msg) {
189 191 list.content.sort(function(a, b) {
190 192 if (type_order[a['type']] < type_order[b['type']]) {
191 193 return -1;
192 194 }
193 195 if (type_order[a['type']] > type_order[b['type']]) {
194 196 return 1;
195 197 }
196 198 if (a['name'] < b['name']) {
197 199 return -1;
198 200 }
199 201 if (a['name'] > b['name']) {
200 202 return 1;
201 203 }
202 204 return 0;
203 205 });
204 206 var message = error_msg || 'Notebook list empty.';
205 207 var item = null;
206 208 var model = null;
207 209 var len = list.content.length;
208 210 this.clear_list();
209 211 var n_uploads = this.element.children('.list_item').length;
210 212 if (len === 0) {
211 213 item = this.new_item(0);
212 214 var span12 = item.children().first();
213 215 span12.empty();
214 216 span12.append($('<div style="margin:auto;text-align:center;color:grey"/>').text(message));
215 217 }
216 218 var path = this.notebook_path;
217 219 var offset = n_uploads;
218 220 if (path !== '') {
219 221 item = this.new_item(offset);
220 222 model = {
221 223 type: 'directory',
222 224 name: '..',
223 225 path: utils.url_path_split(path)[0],
224 226 };
225 227 this.add_link(model, item);
226 228 offset += 1;
227 229 }
228 230 for (var i=0; i<len; i++) {
229 231 model = list.content[i];
230 232 item = this.new_item(i+offset);
231 233 this.add_link(model, item);
232 234 }
233 235 // Trigger an event when we've finished drawing the notebook list.
234 236 events.trigger('draw_notebook_list.NotebookList');
235 237 };
236 238
237 239
238 240 NotebookList.prototype.new_item = function (index) {
239 241 var item = $('<div/>').addClass("list_item").addClass("row");
240 242 // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
241 243 // item.css('border-top-style','none');
242 244 item.append($("<div/>").addClass("col-md-12").append(
243 245 $('<i/>').addClass('item_icon')
244 246 ).append(
245 247 $("<a/>").addClass("item_link").append(
246 248 $("<span/>").addClass("item_name")
247 249 )
248 250 ).append(
249 251 $('<div/>').addClass("item_buttons pull-right")
250 252 ));
251 253
252 254 if (index === -1) {
253 255 this.element.append(item);
254 256 } else {
255 257 this.element.children().eq(index).after(item);
256 258 }
257 259 return item;
258 260 };
259 261
260 262
261 263 NotebookList.icons = {
262 264 directory: 'folder_icon',
263 265 notebook: 'notebook_icon',
264 266 file: 'file_icon',
265 267 };
266 268
267 269 NotebookList.uri_prefixes = {
268 270 directory: 'tree',
269 271 notebook: 'notebooks',
270 272 file: 'edit',
271 273 };
272 274
273 275
274 276 NotebookList.prototype.add_link = function (model, item) {
275 277 var path = model.path,
276 278 name = model.name;
277 279 item.data('name', name);
278 280 item.data('path', path);
279 281 item.find(".item_name").text(name);
280 282 var icon = NotebookList.icons[model.type];
281 283 var uri_prefix = NotebookList.uri_prefixes[model.type];
282 284 item.find(".item_icon").addClass(icon).addClass('icon-fixed-width');
283 285 var link = item.find("a.item_link")
284 286 .attr('href',
285 287 utils.url_join_encode(
286 288 this.base_url,
287 289 uri_prefix,
288 290 path
289 291 )
290 292 );
291 293 // directory nav doesn't open new tabs
292 294 // files, notebooks do
293 295 if (model.type !== "directory") {
294 296 link.attr('target','_blank');
295 297 }
296 298 if (model.type !== 'directory') {
297 299 this.add_duplicate_button(item);
298 300 }
299 301 if (model.type == 'file') {
300 302 this.add_delete_button(item);
301 303 } else if (model.type == 'notebook') {
302 304 if (this.sessions[path] === undefined){
303 305 this.add_delete_button(item);
304 306 } else {
305 307 this.add_shutdown_button(item, this.sessions[path]);
306 308 }
307 309 }
308 310 };
309 311
310 312
311 313 NotebookList.prototype.add_name_input = function (name, item, icon_type) {
312 314 item.data('name', name);
313 315 item.find(".item_icon").addClass(NotebookList.icons[icon_type]).addClass('icon-fixed-width');
314 316 item.find(".item_name").empty().append(
315 317 $('<input/>')
316 318 .addClass("filename_input")
317 319 .attr('value', name)
318 320 .attr('size', '30')
319 321 .attr('type', 'text')
320 322 .keyup(function(event){
321 323 if(event.keyCode == 13){item.find('.upload_button').click();}
322 324 else if(event.keyCode == 27){item.remove();}
323 325 })
324 326 );
325 327 };
326 328
327 329
328 330 NotebookList.prototype.add_file_data = function (data, item) {
329 331 item.data('filedata', data);
330 332 };
331 333
332 334
333 335 NotebookList.prototype.add_shutdown_button = function (item, session) {
334 336 var that = this;
335 337 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-xs btn-warning").
336 338 click(function (e) {
337 339 var settings = {
338 340 processData : false,
339 341 cache : false,
340 342 type : "DELETE",
341 343 dataType : "json",
342 344 success : function () {
343 345 that.load_sessions();
344 346 },
345 347 error : utils.log_ajax_error,
346 348 };
347 349 var url = utils.url_join_encode(
348 350 that.base_url,
349 351 'api/sessions',
350 352 session
351 353 );
352 354 $.ajax(url, settings);
353 355 return false;
354 356 });
355 357 item.find(".item_buttons").append(shutdown_button);
356 358 };
357 359
358 360 NotebookList.prototype.add_duplicate_button = function (item) {
359 361 var notebooklist = this;
360 362 var duplicate_button = $("<button/>").text("Duplicate").addClass("btn btn-default btn-xs").
361 363 click(function (e) {
362 364 // $(this) is the button that was clicked.
363 365 var that = $(this);
364 366 var name = item.data('name');
365 367 var path = item.data('path');
366 368 var message = 'Are you sure you want to duplicate ' + name + '?';
367 369 var copy_from = {copy_from : path};
368 370 IPython.dialog.modal({
369 371 title : "Duplicate " + name,
370 372 body : message,
371 373 buttons : {
372 374 Duplicate : {
373 375 class: "btn-primary",
374 376 click: function() {
375 377 notebooklist.contents.copy(path, notebooklist.notebook_path).then(function () {
376 378 notebooklist.load_list();
377 379 });
378 380 }
379 381 },
380 382 Cancel : {}
381 383 }
382 384 });
383 385 return false;
384 386 });
385 387 item.find(".item_buttons").append(duplicate_button);
386 388 };
387 389
388 390 NotebookList.prototype.add_delete_button = function (item) {
389 391 var notebooklist = this;
390 392 var delete_button = $("<button/>").text("Delete").addClass("btn btn-default btn-xs").
391 393 click(function (e) {
392 394 // $(this) is the button that was clicked.
393 395 var that = $(this);
394 396 // We use the filename from the parent list_item element's
395 397 // data because the outer scope's values change as we iterate through the loop.
396 398 var parent_item = that.parents('div.list_item');
397 399 var name = parent_item.data('name');
398 400 var path = parent_item.data('path');
399 401 var message = 'Are you sure you want to permanently delete the file: ' + name + '?';
400 402 dialog.modal({
401 403 title : "Delete file",
402 404 body : message,
403 405 buttons : {
404 406 Delete : {
405 407 class: "btn-danger",
406 408 click: function() {
407 409 notebooklist.contents.delete(path).then(
408 410 function() {
409 411 notebooklist.notebook_deleted(path);
410 412 }
411 413 );
412 414 }
413 415 },
414 416 Cancel : {}
415 417 }
416 418 });
417 419 return false;
418 420 });
419 421 item.find(".item_buttons").append(delete_button);
420 422 };
421 423
422 424 NotebookList.prototype.notebook_deleted = function(path) {
423 425 /**
424 426 * Remove the deleted notebook.
425 427 */
426 428 $( ":data(path)" ).each(function() {
427 429 var element = $(this);
428 430 if (element.data("path") == path) {
429 431 element.remove();
430 432 events.trigger('notebook_deleted.NotebookList');
431 433 }
432 434 });
433 435 };
434 436
435 437
436 438 NotebookList.prototype.add_upload_button = function (item) {
437 439 var that = this;
438 440 var upload_button = $('<button/>').text("Upload")
439 441 .addClass('btn btn-primary btn-xs upload_button')
440 442 .click(function (e) {
441 443 var filename = item.find('.item_name > input').val();
442 444 var path = utils.url_path_join(that.notebook_path, filename);
443 445 var filedata = item.data('filedata');
444 446 var format = 'text';
445 447 if (filename.length === 0 || filename[0] === '.') {
446 448 dialog.modal({
447 449 title : 'Invalid file name',
448 450 body : "File names must be at least one character and not start with a dot",
449 451 buttons : {'OK' : { 'class' : 'btn-primary' }}
450 452 });
451 453 return false;
452 454 }
453 455 if (filedata instanceof ArrayBuffer) {
454 456 // base64-encode binary file data
455 457 var bytes = '';
456 458 var buf = new Uint8Array(filedata);
457 459 var nbytes = buf.byteLength;
458 460 for (var i=0; i<nbytes; i++) {
459 461 bytes += String.fromCharCode(buf[i]);
460 462 }
461 463 filedata = btoa(bytes);
462 464 format = 'base64';
463 465 }
464 466 var model = {};
465 467
466 468 var name_and_ext = utils.splitext(filename);
467 469 var file_ext = name_and_ext[1];
468 470 var content_type;
469 471 if (file_ext === '.ipynb') {
470 472 model.type = 'notebook';
471 473 model.format = 'json';
472 474 try {
473 475 model.content = JSON.parse(filedata);
474 476 } catch (e) {
475 477 dialog.modal({
476 478 title : 'Cannot upload invalid Notebook',
477 479 body : "The error was: " + e,
478 480 buttons : {'OK' : {
479 481 'class' : 'btn-primary',
480 482 click: function () {
481 483 item.remove();
482 484 }
483 485 }}
484 486 });
485 487 return false;
486 488 }
487 489 content_type = 'application/json';
488 490 } else {
489 491 model.type = 'file';
490 492 model.format = format;
491 493 model.content = filedata;
492 494 content_type = 'application/octet-stream';
493 495 }
494 496 filedata = item.data('filedata');
495 497
496 498 var on_success = function () {
497 499 item.removeClass('new-file');
498 500 that.add_link(model, item);
499 501 that.add_delete_button(item);
500 502 that.session_list.load_sessions();
501 503 };
502 504
503 505 var exists = false;
504 506 $.each(that.element.find('.list_item:not(.new-file)'), function(k,v){
505 507 if ($(v).data('name') === filename) { exists = true; return false; }
506 508 });
507 509
508 510 if (exists) {
509 511 dialog.modal({
510 512 title : "Replace file",
511 513 body : 'There is already a file named ' + filename + ', do you want to replace it?',
512 514 buttons : {
513 515 Overwrite : {
514 516 class: "btn-danger",
515 517 click: function () {
516 518 that.contents.save(path, model).then(on_success);
517 519 }
518 520 },
519 521 Cancel : {
520 522 click: function() { item.remove(); }
521 523 }
522 524 }
523 525 });
524 526 } else {
525 527 that.contents.save(path, model).then(on_success);
526 528 }
527 529
528 530 return false;
529 531 });
530 532 var cancel_button = $('<button/>').text("Cancel")
531 533 .addClass("btn btn-default btn-xs")
532 534 .click(function (e) {
533 535 item.remove();
534 536 return false;
535 537 });
536 538 item.find(".item_buttons").empty()
537 539 .append(upload_button)
538 540 .append(cancel_button);
539 541 };
540 542
541 543
542 544 // Backwards compatability.
543 545 IPython.NotebookList = NotebookList;
544 546
545 547 return {'NotebookList': NotebookList};
546 548 });
General Comments 0
You need to be logged in to leave comments. Login now