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