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