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