##// END OF EJS Templates
refresh of Notebook list should reload sessions
Paul Ivanov -
Show More
@@ -1,433 +1,433 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
2 // Copyright (C) 2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // NotebookList
9 // NotebookList
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 var utils = IPython.utils;
15 var utils = IPython.utils;
16
16
17 var NotebookList = function (selector, options, element_name) {
17 var NotebookList = function (selector, options, element_name) {
18 // allow code re-use by just changing element_name in kernellist.js
18 // allow code re-use by just changing element_name in kernellist.js
19 this.element_name = element_name || 'notebook';
19 this.element_name = element_name || 'notebook';
20 this.selector = selector;
20 this.selector = selector;
21 if (this.selector !== undefined) {
21 if (this.selector !== undefined) {
22 this.element = $(selector);
22 this.element = $(selector);
23 this.style();
23 this.style();
24 this.bind_events();
24 this.bind_events();
25 }
25 }
26 this.notebooks_list = [];
26 this.notebooks_list = [];
27 this.sessions = {};
27 this.sessions = {};
28 this.base_url = options.base_url || utils.get_body_data("baseUrl");
28 this.base_url = options.base_url || utils.get_body_data("baseUrl");
29 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
29 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
30 };
30 };
31
31
32 NotebookList.prototype.style = function () {
32 NotebookList.prototype.style = function () {
33 $('#' + this.element_name + '_toolbar').addClass('list_toolbar');
33 $('#' + this.element_name + '_toolbar').addClass('list_toolbar');
34 $('#drag_info').addClass('toolbar_info');
34 $('#drag_info').addClass('toolbar_info');
35 $('#' + this.element_name + '_buttons').addClass('toolbar_buttons');
35 $('#' + this.element_name + '_buttons').addClass('toolbar_buttons');
36 $('#' + this.element_name + '_list_header').addClass('list_header');
36 $('#' + this.element_name + '_list_header').addClass('list_header');
37 this.element.addClass("list_container");
37 this.element.addClass("list_container");
38 };
38 };
39
39
40
40
41 NotebookList.prototype.bind_events = function () {
41 NotebookList.prototype.bind_events = function () {
42 var that = this;
42 var that = this;
43 $('#refresh_notebook_list').click(function () {
43 $('#refresh_' + this.element_name + '_list').click(function () {
44 that.load_list();
44 that.load_sessions();
45 });
45 });
46 this.element.bind('dragover', function () {
46 this.element.bind('dragover', function () {
47 return false;
47 return false;
48 });
48 });
49 this.element.bind('drop', function(event){
49 this.element.bind('drop', function(event){
50 that.handleFilesUpload(event,'drop');
50 that.handleFilesUpload(event,'drop');
51 return false;
51 return false;
52 });
52 });
53 };
53 };
54
54
55 NotebookList.prototype.handleFilesUpload = function(event, dropOrForm) {
55 NotebookList.prototype.handleFilesUpload = function(event, dropOrForm) {
56 var that = this;
56 var that = this;
57 var files;
57 var files;
58 if(dropOrForm =='drop'){
58 if(dropOrForm =='drop'){
59 files = event.originalEvent.dataTransfer.files;
59 files = event.originalEvent.dataTransfer.files;
60 } else
60 } else
61 {
61 {
62 files = event.originalEvent.target.files;
62 files = event.originalEvent.target.files;
63 }
63 }
64 for (var i = 0; i < files.length; i++) {
64 for (var i = 0; i < files.length; i++) {
65 var f = files[i];
65 var f = files[i];
66 var reader = new FileReader();
66 var reader = new FileReader();
67 reader.readAsText(f);
67 reader.readAsText(f);
68 var name_and_ext = utils.splitext(f.name);
68 var name_and_ext = utils.splitext(f.name);
69 var file_ext = name_and_ext[1];
69 var file_ext = name_and_ext[1];
70 if (file_ext === '.ipynb') {
70 if (file_ext === '.ipynb') {
71 var item = that.new_notebook_item(0);
71 var item = that.new_notebook_item(0);
72 that.add_name_input(f.name, item);
72 that.add_name_input(f.name, item);
73 // Store the notebook item in the reader so we can use it later
73 // Store the notebook item in the reader so we can use it later
74 // to know which item it belongs to.
74 // to know which item it belongs to.
75 $(reader).data('item', item);
75 $(reader).data('item', item);
76 reader.onload = function (event) {
76 reader.onload = function (event) {
77 var nbitem = $(event.target).data('item');
77 var nbitem = $(event.target).data('item');
78 that.add_notebook_data(event.target.result, nbitem);
78 that.add_notebook_data(event.target.result, nbitem);
79 that.add_upload_button(nbitem);
79 that.add_upload_button(nbitem);
80 };
80 };
81 } else {
81 } else {
82 var dialog = 'Uploaded notebooks must be .ipynb files';
82 var dialog = 'Uploaded notebooks must be .ipynb files';
83 IPython.dialog.modal({
83 IPython.dialog.modal({
84 title : 'Invalid file type',
84 title : 'Invalid file type',
85 body : dialog,
85 body : dialog,
86 buttons : {'OK' : {'class' : 'btn-primary'}}
86 buttons : {'OK' : {'class' : 'btn-primary'}}
87 });
87 });
88 }
88 }
89 }
89 }
90 // Replace the file input form wth a clone of itself. This is required to
90 // Replace the file input form wth a clone of itself. This is required to
91 // reset the form. Otherwise, if you upload a file, delete it and try to
91 // reset the form. Otherwise, if you upload a file, delete it and try to
92 // upload it again, the changed event won't fire.
92 // upload it again, the changed event won't fire.
93 var form = $('input.fileinput');
93 var form = $('input.fileinput');
94 form.replaceWith(form.clone(true));
94 form.replaceWith(form.clone(true));
95 return false;
95 return false;
96 };
96 };
97
97
98 NotebookList.prototype.clear_list = function () {
98 NotebookList.prototype.clear_list = function () {
99 this.element.children('.list_item').remove();
99 this.element.children('.list_item').remove();
100 };
100 };
101
101
102 NotebookList.prototype.load_sessions = function(){
102 NotebookList.prototype.load_sessions = function(){
103 var that = this;
103 var that = this;
104 var settings = {
104 var settings = {
105 processData : false,
105 processData : false,
106 cache : false,
106 cache : false,
107 type : "GET",
107 type : "GET",
108 dataType : "json",
108 dataType : "json",
109 success : $.proxy(that.sessions_loaded, this)
109 success : $.proxy(that.sessions_loaded, this)
110 };
110 };
111 var url = utils.url_join_encode(this.base_url, 'api/sessions');
111 var url = utils.url_join_encode(this.base_url, 'api/sessions');
112 $.ajax(url,settings);
112 $.ajax(url,settings);
113 };
113 };
114
114
115
115
116 NotebookList.prototype.sessions_loaded = function(data){
116 NotebookList.prototype.sessions_loaded = function(data){
117 this.sessions = {};
117 this.sessions = {};
118 var len = data.length;
118 var len = data.length;
119 if (len > 0) {
119 if (len > 0) {
120 for (var i=0; i<len; i++) {
120 for (var i=0; i<len; i++) {
121 var nb_path;
121 var nb_path;
122 if (!data[i].notebook.path) {
122 if (!data[i].notebook.path) {
123 nb_path = data[i].notebook.name;
123 nb_path = data[i].notebook.name;
124 }
124 }
125 else {
125 else {
126 nb_path = utils.url_path_join(
126 nb_path = utils.url_path_join(
127 data[i].notebook.path,
127 data[i].notebook.path,
128 data[i].notebook.name
128 data[i].notebook.name
129 );
129 );
130 }
130 }
131 this.sessions[nb_path] = data[i].id;
131 this.sessions[nb_path] = data[i].id;
132 }
132 }
133 }
133 }
134 this.load_list();
134 this.load_list();
135 };
135 };
136
136
137 NotebookList.prototype.load_list = function () {
137 NotebookList.prototype.load_list = function () {
138 var that = this;
138 var that = this;
139 var settings = {
139 var settings = {
140 processData : false,
140 processData : false,
141 cache : false,
141 cache : false,
142 type : "GET",
142 type : "GET",
143 dataType : "json",
143 dataType : "json",
144 success : $.proxy(this.list_loaded, this),
144 success : $.proxy(this.list_loaded, this),
145 error : $.proxy( function(){
145 error : $.proxy( function(){
146 that.list_loaded([], null, null, {msg:"Error connecting to server."});
146 that.list_loaded([], null, null, {msg:"Error connecting to server."});
147 },this)
147 },this)
148 };
148 };
149
149
150 var url = utils.url_join_encode(
150 var url = utils.url_join_encode(
151 this.base_url,
151 this.base_url,
152 'api',
152 'api',
153 'notebooks',
153 'notebooks',
154 this.notebook_path
154 this.notebook_path
155 );
155 );
156 $.ajax(url, settings);
156 $.ajax(url, settings);
157 };
157 };
158
158
159
159
160 NotebookList.prototype.list_loaded = function (data, status, xhr, param) {
160 NotebookList.prototype.list_loaded = function (data, status, xhr, param) {
161 var message = 'Notebook list empty.';
161 var message = 'Notebook list empty.';
162 if (param !== undefined && param.msg) {
162 if (param !== undefined && param.msg) {
163 message = param.msg;
163 message = param.msg;
164 }
164 }
165 var item = null;
165 var item = null;
166 var len = data.length;
166 var len = data.length;
167 this.clear_list();
167 this.clear_list();
168 if (len === 0) {
168 if (len === 0) {
169 item = this.new_notebook_item(0);
169 item = this.new_notebook_item(0);
170 var span12 = item.children().first();
170 var span12 = item.children().first();
171 span12.empty();
171 span12.empty();
172 span12.append($('<div style="margin:auto;text-align:center;color:grey"/>').text(message));
172 span12.append($('<div style="margin:auto;text-align:center;color:grey"/>').text(message));
173 }
173 }
174 var path = this.notebook_path;
174 var path = this.notebook_path;
175 var offset = 0;
175 var offset = 0;
176 if (path !== '') {
176 if (path !== '') {
177 item = this.new_notebook_item(0);
177 item = this.new_notebook_item(0);
178 this.add_dir(path, '..', item);
178 this.add_dir(path, '..', item);
179 offset = 1;
179 offset = 1;
180 }
180 }
181 for (var i=0; i<len; i++) {
181 for (var i=0; i<len; i++) {
182 if (data[i].type === 'directory') {
182 if (data[i].type === 'directory') {
183 var name = data[i].name;
183 var name = data[i].name;
184 item = this.new_notebook_item(i+offset);
184 item = this.new_notebook_item(i+offset);
185 this.add_dir(path, name, item);
185 this.add_dir(path, name, item);
186 } else {
186 } else {
187 var name = data[i].name;
187 var name = data[i].name;
188 item = this.new_notebook_item(i+offset);
188 item = this.new_notebook_item(i+offset);
189 this.add_link(path, name, item);
189 this.add_link(path, name, item);
190 name = utils.url_path_join(path, name);
190 name = utils.url_path_join(path, name);
191 if(this.sessions[name] === undefined){
191 if(this.sessions[name] === undefined){
192 this.add_delete_button(item);
192 this.add_delete_button(item);
193 } else {
193 } else {
194 this.add_shutdown_button(item,this.sessions[name]);
194 this.add_shutdown_button(item,this.sessions[name]);
195 }
195 }
196 }
196 }
197 }
197 }
198 };
198 };
199
199
200
200
201 NotebookList.prototype.new_notebook_item = function (index) {
201 NotebookList.prototype.new_notebook_item = function (index) {
202 var item = $('<div/>').addClass("list_item").addClass("row-fluid");
202 var item = $('<div/>').addClass("list_item").addClass("row-fluid");
203 // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
203 // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
204 // item.css('border-top-style','none');
204 // item.css('border-top-style','none');
205 item.append($("<div/>").addClass("span12").append(
205 item.append($("<div/>").addClass("span12").append(
206 $('<i/>').addClass('item_icon')
206 $('<i/>').addClass('item_icon')
207 ).append(
207 ).append(
208 $("<a/>").addClass("item_link").append(
208 $("<a/>").addClass("item_link").append(
209 $("<span/>").addClass("item_name")
209 $("<span/>").addClass("item_name")
210 )
210 )
211 ).append(
211 ).append(
212 $('<div/>').addClass("item_buttons btn-group pull-right")
212 $('<div/>').addClass("item_buttons btn-group pull-right")
213 ));
213 ));
214
214
215 if (index === -1) {
215 if (index === -1) {
216 this.element.append(item);
216 this.element.append(item);
217 } else {
217 } else {
218 this.element.children().eq(index).after(item);
218 this.element.children().eq(index).after(item);
219 }
219 }
220 return item;
220 return item;
221 };
221 };
222
222
223
223
224 NotebookList.prototype.add_dir = function (path, name, item) {
224 NotebookList.prototype.add_dir = function (path, name, item) {
225 item.data('name', name);
225 item.data('name', name);
226 item.data('path', path);
226 item.data('path', path);
227 item.find(".item_name").text(name);
227 item.find(".item_name").text(name);
228 item.find(".item_icon").addClass('icon-folder-open');
228 item.find(".item_icon").addClass('icon-folder-open');
229 item.find("a.item_link")
229 item.find("a.item_link")
230 .attr('href',
230 .attr('href',
231 utils.url_join_encode(
231 utils.url_join_encode(
232 this.base_url,
232 this.base_url,
233 "tree",
233 "tree",
234 path,
234 path,
235 name
235 name
236 )
236 )
237 );
237 );
238 };
238 };
239
239
240
240
241 NotebookList.prototype.add_link = function (path, nbname, item) {
241 NotebookList.prototype.add_link = function (path, nbname, item) {
242 item.data('nbname', nbname);
242 item.data('nbname', nbname);
243 item.data('path', path);
243 item.data('path', path);
244 item.find(".item_name").text(nbname);
244 item.find(".item_name").text(nbname);
245 item.find(".item_icon").addClass('icon-book');
245 item.find(".item_icon").addClass('icon-book');
246 item.find("a.item_link")
246 item.find("a.item_link")
247 .attr('href',
247 .attr('href',
248 utils.url_join_encode(
248 utils.url_join_encode(
249 this.base_url,
249 this.base_url,
250 "notebooks",
250 "notebooks",
251 path,
251 path,
252 nbname
252 nbname
253 )
253 )
254 ).attr('target','_blank');
254 ).attr('target','_blank');
255 };
255 };
256
256
257
257
258 NotebookList.prototype.add_name_input = function (nbname, item) {
258 NotebookList.prototype.add_name_input = function (nbname, item) {
259 item.data('nbname', nbname);
259 item.data('nbname', nbname);
260 item.find(".item_icon").addClass('icon-book');
260 item.find(".item_icon").addClass('icon-book');
261 item.find(".item_name").empty().append(
261 item.find(".item_name").empty().append(
262 $('<input/>')
262 $('<input/>')
263 .addClass("nbname_input")
263 .addClass("nbname_input")
264 .attr('value', utils.splitext(nbname)[0])
264 .attr('value', utils.splitext(nbname)[0])
265 .attr('size', '30')
265 .attr('size', '30')
266 .attr('type', 'text')
266 .attr('type', 'text')
267 );
267 );
268 };
268 };
269
269
270
270
271 NotebookList.prototype.add_notebook_data = function (data, item) {
271 NotebookList.prototype.add_notebook_data = function (data, item) {
272 item.data('nbdata', data);
272 item.data('nbdata', data);
273 };
273 };
274
274
275
275
276 NotebookList.prototype.add_shutdown_button = function (item, session) {
276 NotebookList.prototype.add_shutdown_button = function (item, session) {
277 var that = this;
277 var that = this;
278 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-mini btn-danger").
278 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-mini btn-danger").
279 click(function (e) {
279 click(function (e) {
280 var settings = {
280 var settings = {
281 processData : false,
281 processData : false,
282 cache : false,
282 cache : false,
283 type : "DELETE",
283 type : "DELETE",
284 dataType : "json",
284 dataType : "json",
285 success : function () {
285 success : function () {
286 that.load_sessions();
286 that.load_sessions();
287 }
287 }
288 };
288 };
289 var url = utils.url_join_encode(
289 var url = utils.url_join_encode(
290 that.base_url,
290 that.base_url,
291 'api/sessions',
291 'api/sessions',
292 session
292 session
293 );
293 );
294 $.ajax(url, settings);
294 $.ajax(url, settings);
295 return false;
295 return false;
296 });
296 });
297 // var new_buttons = item.find('a'); // shutdown_button;
297 // var new_buttons = item.find('a'); // shutdown_button;
298 item.find(".item_buttons").text("").append(shutdown_button);
298 item.find(".item_buttons").text("").append(shutdown_button);
299 };
299 };
300
300
301 NotebookList.prototype.add_delete_button = function (item) {
301 NotebookList.prototype.add_delete_button = function (item) {
302 var new_buttons = $('<span/>').addClass("btn-group pull-right");
302 var new_buttons = $('<span/>').addClass("btn-group pull-right");
303 var notebooklist = this;
303 var notebooklist = this;
304 var delete_button = $("<button/>").text("Delete").addClass("btn btn-mini").
304 var delete_button = $("<button/>").text("Delete").addClass("btn btn-mini").
305 click(function (e) {
305 click(function (e) {
306 // $(this) is the button that was clicked.
306 // $(this) is the button that was clicked.
307 var that = $(this);
307 var that = $(this);
308 // We use the nbname and notebook_id from the parent notebook_item element's
308 // We use the nbname and notebook_id from the parent notebook_item element's
309 // data because the outer scopes values change as we iterate through the loop.
309 // data because the outer scopes values change as we iterate through the loop.
310 var parent_item = that.parents('div.list_item');
310 var parent_item = that.parents('div.list_item');
311 var nbname = parent_item.data('nbname');
311 var nbname = parent_item.data('nbname');
312 var message = 'Are you sure you want to permanently delete the notebook: ' + nbname + '?';
312 var message = 'Are you sure you want to permanently delete the notebook: ' + nbname + '?';
313 IPython.dialog.modal({
313 IPython.dialog.modal({
314 title : "Delete notebook",
314 title : "Delete notebook",
315 body : message,
315 body : message,
316 buttons : {
316 buttons : {
317 Delete : {
317 Delete : {
318 class: "btn-danger",
318 class: "btn-danger",
319 click: function() {
319 click: function() {
320 var settings = {
320 var settings = {
321 processData : false,
321 processData : false,
322 cache : false,
322 cache : false,
323 type : "DELETE",
323 type : "DELETE",
324 dataType : "json",
324 dataType : "json",
325 success : function (data, status, xhr) {
325 success : function (data, status, xhr) {
326 parent_item.remove();
326 parent_item.remove();
327 }
327 }
328 };
328 };
329 var url = utils.url_join_encode(
329 var url = utils.url_join_encode(
330 notebooklist.base_url,
330 notebooklist.base_url,
331 'api/notebooks',
331 'api/notebooks',
332 notebooklist.notebook_path,
332 notebooklist.notebook_path,
333 nbname
333 nbname
334 );
334 );
335 $.ajax(url, settings);
335 $.ajax(url, settings);
336 }
336 }
337 },
337 },
338 Cancel : {}
338 Cancel : {}
339 }
339 }
340 });
340 });
341 return false;
341 return false;
342 });
342 });
343 item.find(".item_buttons").text("").append(delete_button);
343 item.find(".item_buttons").text("").append(delete_button);
344 };
344 };
345
345
346
346
347 NotebookList.prototype.add_upload_button = function (item) {
347 NotebookList.prototype.add_upload_button = function (item) {
348 var that = this;
348 var that = this;
349 var upload_button = $('<button/>').text("Upload")
349 var upload_button = $('<button/>').text("Upload")
350 .addClass('btn btn-primary btn-mini upload_button')
350 .addClass('btn btn-primary btn-mini upload_button')
351 .click(function (e) {
351 .click(function (e) {
352 var nbname = item.find('.item_name > input').val();
352 var nbname = item.find('.item_name > input').val();
353 if (nbname.slice(nbname.length-6, nbname.length) != ".ipynb") {
353 if (nbname.slice(nbname.length-6, nbname.length) != ".ipynb") {
354 nbname = nbname + ".ipynb";
354 nbname = nbname + ".ipynb";
355 }
355 }
356 var path = that.notebook_path;
356 var path = that.notebook_path;
357 var nbdata = item.data('nbdata');
357 var nbdata = item.data('nbdata');
358 var content_type = 'application/json';
358 var content_type = 'application/json';
359 var model = {
359 var model = {
360 content : JSON.parse(nbdata),
360 content : JSON.parse(nbdata),
361 };
361 };
362 var settings = {
362 var settings = {
363 processData : false,
363 processData : false,
364 cache : false,
364 cache : false,
365 type : 'PUT',
365 type : 'PUT',
366 dataType : 'json',
366 dataType : 'json',
367 data : JSON.stringify(model),
367 data : JSON.stringify(model),
368 headers : {'Content-Type': content_type},
368 headers : {'Content-Type': content_type},
369 success : function (data, status, xhr) {
369 success : function (data, status, xhr) {
370 that.add_link(path, nbname, item);
370 that.add_link(path, nbname, item);
371 that.add_delete_button(item);
371 that.add_delete_button(item);
372 },
372 },
373 error : function (data, status, xhr) {
373 error : function (data, status, xhr) {
374 console.log(data, status);
374 console.log(data, status);
375 }
375 }
376 };
376 };
377
377
378 var url = utils.url_join_encode(
378 var url = utils.url_join_encode(
379 that.base_url,
379 that.base_url,
380 'api/notebooks',
380 'api/notebooks',
381 that.notebook_path,
381 that.notebook_path,
382 nbname
382 nbname
383 );
383 );
384 $.ajax(url, settings);
384 $.ajax(url, settings);
385 return false;
385 return false;
386 });
386 });
387 var cancel_button = $('<button/>').text("Cancel")
387 var cancel_button = $('<button/>').text("Cancel")
388 .addClass("btn btn-mini")
388 .addClass("btn btn-mini")
389 .click(function (e) {
389 .click(function (e) {
390 console.log('cancel click');
390 console.log('cancel click');
391 item.remove();
391 item.remove();
392 return false;
392 return false;
393 });
393 });
394 item.find(".item_buttons").empty()
394 item.find(".item_buttons").empty()
395 .append(upload_button)
395 .append(upload_button)
396 .append(cancel_button);
396 .append(cancel_button);
397 };
397 };
398
398
399
399
400 NotebookList.prototype.new_notebook = function(){
400 NotebookList.prototype.new_notebook = function(){
401 var path = this.notebook_path;
401 var path = this.notebook_path;
402 var base_url = this.base_url;
402 var base_url = this.base_url;
403 var settings = {
403 var settings = {
404 processData : false,
404 processData : false,
405 cache : false,
405 cache : false,
406 type : "POST",
406 type : "POST",
407 dataType : "json",
407 dataType : "json",
408 async : false,
408 async : false,
409 success : function (data, status, xhr) {
409 success : function (data, status, xhr) {
410 var notebook_name = data.name;
410 var notebook_name = data.name;
411 window.open(
411 window.open(
412 utils.url_join_encode(
412 utils.url_join_encode(
413 base_url,
413 base_url,
414 'notebooks',
414 'notebooks',
415 path,
415 path,
416 notebook_name),
416 notebook_name),
417 '_blank'
417 '_blank'
418 );
418 );
419 }
419 }
420 };
420 };
421 var url = utils.url_join_encode(
421 var url = utils.url_join_encode(
422 base_url,
422 base_url,
423 'api/notebooks',
423 'api/notebooks',
424 path
424 path
425 );
425 );
426 $.ajax(url, settings);
426 $.ajax(url, settings);
427 };
427 };
428
428
429 IPython.NotebookList = NotebookList;
429 IPython.NotebookList = NotebookList;
430
430
431 return IPython;
431 return IPython;
432
432
433 }(IPython));
433 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now