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