##// END OF EJS Templates
Backport PR #8203: autosave & reload on kernelspec switch...
Min RK -
Show More
@@ -1,325 +1,335 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'jquery',
6 6 'base/js/namespace',
7 7 'base/js/dialog',
8 8 'base/js/utils',
9 9 ], function($, IPython, dialog, utils) {
10 10 "use strict";
11 11
12 12 var KernelSelector = function(selector, notebook) {
13 13 var that = this;
14 14 this.selector = selector;
15 15 this.notebook = notebook;
16 16 this.notebook.set_kernelselector(this);
17 17 this.events = notebook.events;
18 18 this.current_selection = null;
19 19 this.kernelspecs = {};
20 20 if (this.selector !== undefined) {
21 21 this.element = $(selector);
22 22 this.request_kernelspecs();
23 23 }
24 24 this.bind_events();
25 25 // Make the object globally available for user convenience & inspection
26 26 IPython.kernelselector = this;
27 27 this._finish_load = null;
28 28 this._loaded = false;
29 29 this.loaded = new Promise(function(resolve) {
30 30 that._finish_load = resolve;
31 31 });
32 32
33 33 Object.seal(this);
34 34 };
35 35
36 36 KernelSelector.prototype.request_kernelspecs = function() {
37 37 var url = utils.url_join_encode(this.notebook.base_url, 'api/kernelspecs');
38 38 utils.promising_ajax(url).then($.proxy(this._got_kernelspecs, this));
39 39 };
40 40
41 41 var _sorted_names = function(kernelspecs) {
42 42 // sort kernel names
43 43 return Object.keys(kernelspecs).sort(function (a, b) {
44 44 // sort by display_name
45 45 var da = kernelspecs[a].spec.display_name;
46 46 var db = kernelspecs[b].spec.display_name;
47 47 if (da === db) {
48 48 return 0;
49 49 } else if (da > db) {
50 50 return 1;
51 51 } else {
52 52 return -1;
53 53 }
54 54 });
55 55 };
56 56
57 57 KernelSelector.prototype._got_kernelspecs = function(data) {
58 58 var that = this;
59 59 this.kernelspecs = data.kernelspecs;
60 60 var change_kernel_submenu = $("#menu-change-kernel-submenu");
61 61 var new_notebook_submenu = $("#menu-new-notebook-submenu");
62 62 var keys = _sorted_names(data.kernelspecs);
63 63
64 64 keys.map(function (key) {
65 65 // Create the Kernel > Change kernel submenu
66 66 var ks = data.kernelspecs[key];
67 67 change_kernel_submenu.append(
68 68 $("<li>").attr("id", "kernel-submenu-"+ks.name).append(
69 69 $('<a>')
70 70 .attr('href', '#')
71 71 .click( function () {
72 72 that.set_kernel(ks.name);
73 73 })
74 74 .text(ks.spec.display_name)
75 75 )
76 76 );
77 77 // Create the File > New Notebook submenu
78 78 new_notebook_submenu.append(
79 79 $("<li>").attr("id", "new-notebook-submenu-"+ks.name).append(
80 80 $('<a>')
81 81 .attr('href', '#')
82 82 .click( function () {
83 83 that.new_notebook(ks.name);
84 84 })
85 85 .text(ks.spec.display_name)
86 86 )
87 87 );
88 88
89 89 });
90 90 // trigger loaded promise
91 91 this._loaded = true;
92 92 this._finish_load();
93 93 };
94 94
95 95 KernelSelector.prototype._spec_changed = function (event, ks) {
96 96 /** event handler for spec_changed */
97 var that = this;
97 98
98 99 // update selection
99 100 this.current_selection = ks.name;
100 101
101 102 // put the current kernel at the top of File > New Notebook
102 103 var cur_kernel_entry = $("#new-notebook-submenu-" + ks.name);
103 104 var parent = cur_kernel_entry.parent();
104 105 // do something only if there is more than one kernel
105 106 if (parent.children().length > 1) {
106 107 // first, sort back the submenu
107 108 parent.append(
108 109 parent.children("li[class!='divider']").sort(
109 110 function (a,b) {
110 111 var da = $("a",a).text();
111 112 var db = $("a",b).text();
112 113 if (da === db) {
113 114 return 0;
114 115 } else if (da > db) {
115 116 return 1;
116 117 } else {
117 118 return -1;
118 119 }}));
119 120 // then, if there is no divider yet, add one
120 121 if (!parent.children("li[class='divider']").length) {
121 122 parent.prepend($("<li>").attr("class","divider"));
122 123 }
123 124 // finally, put the current kernel at the top
124 125 parent.prepend(cur_kernel_entry);
125 126 }
126 127
127 128 // load logo
128 129 var logo_img = this.element.find("img.current_kernel_logo");
129 130 $("#kernel_indicator").find('.kernel_indicator_name').text(ks.spec.display_name);
130 131 if (ks.resources['logo-64x64']) {
131 132 logo_img.attr("src", ks.resources['logo-64x64']);
132 133 logo_img.show();
133 134 } else {
134 135 logo_img.hide();
135 136 }
136 137
137 138 // load kernel css
138 139 var css_url = ks.resources['kernel.css'];
139 140 if (css_url) {
140 141 $('#kernel-css').attr('href', css_url);
141 142 } else {
142 143 $('#kernel-css').attr('href', '');
143 144 }
144 145
145 146 // load kernel js
146 147 if (ks.resources['kernel.js']) {
147 148 require([ks.resources['kernel.js']],
148 149 function (kernel_mod) {
149 150 if (kernel_mod && kernel_mod.onload) {
150 151 kernel_mod.onload();
151 152 } else {
152 153 console.warn("Kernel " + ks.name + " has a kernel.js file that does not contain "+
153 154 "any asynchronous module definition. This is undefined behavior "+
154 155 "and not recommended.");
155 156 }
156 157 }, function (err) {
157 158 console.warn("Failed to load kernel.js from ", ks.resources['kernel.js'], err);
158 159 }
159 160 );
161 this.events.on('spec_changed.Kernel', function (evt, new_ks) {
162 if (ks.name != new_ks.name) {
163 console.warn("kernelspec %s had custom kernel.js. Forcing page reload for %s.",
164 ks.name, new_ks.name);
165 that.notebook.save_notebook().then(function () {
166 window.location.reload();
167 });
168 }
169 });
160 170 }
161 171 };
162 172
163 173 KernelSelector.prototype.set_kernel = function (selected) {
164 174 /** set the kernel by name, ensuring kernelspecs have been loaded, first
165 175
166 176 kernel can be just a kernel name, or a notebook kernelspec metadata
167 177 (name, language, display_name).
168 178 */
169 179 var that = this;
170 180 if (typeof selected === 'string') {
171 181 selected = {
172 182 name: selected
173 183 };
174 184 }
175 185 if (this._loaded) {
176 186 this._set_kernel(selected);
177 187 } else {
178 188 return this.loaded.then(function () {
179 189 that._set_kernel(selected);
180 190 });
181 191 }
182 192 };
183 193
184 194 KernelSelector.prototype._set_kernel = function (selected) {
185 195 /** Actually set the kernel (kernelspecs have been loaded) */
186 196 if (selected.name === this.current_selection) {
187 197 // only trigger event if value changed
188 198 return;
189 199 }
190 200 var kernelspecs = this.kernelspecs;
191 201 var ks = kernelspecs[selected.name];
192 202 if (ks === undefined) {
193 203 var available = _sorted_names(kernelspecs);
194 204 var matches = [];
195 205 if (selected.language && selected.language.length > 0) {
196 206 available.map(function (name) {
197 207 if (kernelspecs[name].spec.language.toLowerCase() === selected.language.toLowerCase()) {
198 208 matches.push(name);
199 209 }
200 210 });
201 211 }
202 212 if (matches.length === 1) {
203 213 ks = kernelspecs[matches[0]];
204 214 console.log("No exact match found for " + selected.name +
205 215 ", using only kernel that matches language=" + selected.language, ks);
206 216 this.events.trigger("spec_match_found.Kernel", {
207 217 selected: selected,
208 218 found: ks,
209 219 });
210 220 }
211 221 // if still undefined, trigger failure event
212 222 if (ks === undefined) {
213 223 this.events.trigger("spec_not_found.Kernel", {
214 224 selected: selected,
215 225 matches: matches,
216 226 available: available,
217 227 });
218 228 return;
219 229 }
220 230 }
221 231 if (this.notebook._session_starting &&
222 232 this.notebook.session.kernel.name !== ks.name) {
223 233 console.error("Cannot change kernel while waiting for pending session start.");
224 234 return;
225 235 }
226 236 this.current_selection = ks.name;
227 237 this.events.trigger('spec_changed.Kernel', ks);
228 238 };
229 239
230 240 KernelSelector.prototype._spec_not_found = function (event, data) {
231 241 var that = this;
232 242 var select = $("<select>").addClass('form-control');
233 243 console.warn("Kernelspec not found:", data);
234 244 var names;
235 245 if (data.matches.length > 1) {
236 246 names = data.matches;
237 247 } else {
238 248 names = data.available;
239 249 }
240 250 names.map(function (name) {
241 251 var ks = that.kernelspecs[name];
242 252 select.append(
243 253 $('<option/>').attr('value', ks.name).text(ks.spec.display_name || ks.name)
244 254 );
245 255 });
246 256
247 257 var body = $("<form>").addClass("form-inline").append(
248 258 $("<span>").text(
249 259 "I couldn't find a kernel matching " + (data.selected.display_name || data.selected.name) + "." +
250 260 " Please select a kernel:"
251 261 )
252 262 ).append(select);
253 263
254 264 dialog.modal({
255 265 title : 'Kernel not found',
256 266 body : body,
257 267 buttons : {
258 268 'Continue without kernel' : {
259 269 class : 'btn-danger',
260 270 click : function () {
261 271 that.events.trigger('no_kernel.Kernel');
262 272 }
263 273 },
264 274 OK : {
265 275 class : 'btn-primary',
266 276 click : function () {
267 277 that.set_kernel(select.val());
268 278 }
269 279 }
270 280 }
271 281 });
272 282 };
273 283
274 284 KernelSelector.prototype.new_notebook = function (kernel_name) {
275 285
276 286 var w = window.open('', IPython._target);
277 287 // Create a new notebook in the same path as the current
278 288 // notebook's path.
279 289 var that = this;
280 290 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
281 291 that.notebook.contents.new_untitled(parent, {type: "notebook"}).then(
282 292 function (data) {
283 293 var url = utils.url_join_encode(
284 294 that.notebook.base_url, 'notebooks', data.path
285 295 );
286 296 url += "?kernel_name=" + kernel_name;
287 297 w.location = url;
288 298 },
289 299 function(error) {
290 300 w.close();
291 301 dialog.modal({
292 302 title : 'Creating Notebook Failed',
293 303 body : "The error was: " + error.message,
294 304 buttons : {'OK' : {'class' : 'btn-primary'}}
295 305 });
296 306 }
297 307 );
298 308 };
299 309
300 310 KernelSelector.prototype.lock_switch = function() {
301 311 // should set a flag and display warning+reload if user want to
302 312 // re-change kernel. As UI discussion never finish
303 313 // making that a separate PR.
304 314 console.warn('switching kernel is not guaranteed to work !');
305 315 };
306 316
307 317 KernelSelector.prototype.bind_events = function() {
308 318 var that = this;
309 319 this.events.on('spec_changed.Kernel', $.proxy(this._spec_changed, this));
310 320 this.events.on('spec_not_found.Kernel', $.proxy(this._spec_not_found, this));
311 321 this.events.on('kernel_created.Session', function (event, data) {
312 322 that.set_kernel(data.kernel.name);
313 323 });
314 324
315 325 var logo_img = this.element.find("img.current_kernel_logo");
316 326 logo_img.on("load", function() {
317 327 logo_img.show();
318 328 });
319 329 logo_img.on("error", function() {
320 330 logo_img.hide();
321 331 });
322 332 };
323 333
324 334 return {'KernelSelector': KernelSelector};
325 335 });
General Comments 0
You need to be logged in to leave comments. Login now