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