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