2
2
// Distributed under the terms of the Modified BSD License.
3
3
4
4
import {
5
- app , BrowserWindow , ipcMain , shell
5
+ app , BrowserWindow , dialog , ipcMain , shell
6
6
} from 'electron' ;
7
7
8
8
import {
@@ -40,6 +40,8 @@ interface IApplication {
40
40
* Force the application service to write data to the disk.
41
41
*/
42
42
saveState : ( service : IStatefulService , data : JSONValue ) => Promise < void > ;
43
+
44
+ getPythonPath ( ) : Promise < string > ;
43
45
}
44
46
45
47
/**
@@ -97,6 +99,18 @@ namespace IAppRemoteInterface {
97
99
let openDevTools : AsyncRemote . IMethod < void , void > = {
98
100
id : 'JupyterLabDesktop-open-dev-tools'
99
101
} ;
102
+ export
103
+ let getCurrentPythonPath : AsyncRemote . IMethod < void , string > = {
104
+ id : 'JupyterLabDesktop-get-python-path'
105
+ } ;
106
+ export
107
+ let showPythonPathSelector : AsyncRemote . IMethod < void , void > = {
108
+ id : 'JupyterLabDesktop-select-python-path'
109
+ } ;
110
+ export
111
+ let pythonPathChangedEvent : AsyncRemote . IEvent < string > = {
112
+ id : 'JupyterLabDesktop-python-path-changed'
113
+ } ;
100
114
}
101
115
102
116
export
@@ -122,13 +136,17 @@ class JupyterApplication implements IApplication, IStatefulService {
122
136
} ) ;
123
137
124
138
this . _applicationState = {
125
- checkForUpdatesAutomatically : true
139
+ checkForUpdatesAutomatically : true ,
140
+ pythonPath : '' ,
126
141
} ;
127
142
128
143
this . registerStatefulService ( this )
129
144
. then ( ( state : JupyterApplication . IState ) => {
130
145
if ( state ) {
131
146
this . _applicationState = state ;
147
+ if ( this . _applicationState . pythonPath === undefined ) {
148
+ this . _applicationState . pythonPath = '' ;
149
+ }
132
150
}
133
151
134
152
if ( this . _applicationState . checkForUpdatesAutomatically ) {
@@ -139,6 +157,14 @@ class JupyterApplication implements IApplication, IStatefulService {
139
157
} ) ;
140
158
}
141
159
160
+ getPythonPath ( ) : Promise < string > {
161
+ return new Promise < string > ( ( resolve , _reject ) => {
162
+ this . _appState . then ( ( state : JSONObject ) => {
163
+ resolve ( this . _applicationState . pythonPath ) ;
164
+ } ) ;
165
+ } ) ;
166
+ }
167
+
142
168
registerStatefulService ( service : IStatefulService ) : Promise < JSONValue > {
143
169
this . _services . push ( service ) ;
144
170
@@ -266,6 +292,24 @@ class JupyterApplication implements IApplication, IStatefulService {
266
292
shell . openExternal ( 'https://github.com/jupyterlab/jupyterlab-desktop/releases' ) ;
267
293
} ) ;
268
294
295
+ ipcMain . on ( 'select-python-path' , ( event ) => {
296
+ dialog . showOpenDialog ( {
297
+ properties : [ 'openFile' , 'showHiddenFiles' , 'noResolveAliases' ] ,
298
+ buttonLabel : 'Use Path'
299
+ } ) . then ( ( { filePaths} ) => {
300
+ if ( filePaths ) {
301
+ event . sender . send ( 'custom-python-path-selected' , filePaths [ 0 ] ) ;
302
+ }
303
+ } ) ;
304
+ } ) ;
305
+
306
+ ipcMain . on ( 'set-python-path' , ( event , path ) => {
307
+ this . _applicationState . pythonPath = path ;
308
+ app . relaunch ( ) ;
309
+ app . quit ( ) ;
310
+ } ) ;
311
+
312
+
269
313
asyncRemoteMain . registerRemoteMethod ( IAppRemoteInterface . checkForUpdates ,
270
314
( ) : Promise < void > => {
271
315
this . _checkForUpdates ( 'always' ) ;
@@ -277,6 +321,18 @@ class JupyterApplication implements IApplication, IStatefulService {
277
321
this . _window . webContents . openDevTools ( ) ;
278
322
return Promise . resolve ( ) ;
279
323
} ) ;
324
+
325
+ asyncRemoteMain . registerRemoteMethod ( IAppRemoteInterface . getCurrentPythonPath ,
326
+ ( ) : Promise < string > => {
327
+ return this . getPythonPath ( ) ;
328
+ } ) ;
329
+
330
+ asyncRemoteMain . registerRemoteMethod ( IAppRemoteInterface . showPythonPathSelector ,
331
+ ( ) : Promise < void > => {
332
+ // asyncRemoteMain.emitRemoteEvent(IAppRemoteInterface.pythonPathChangedEvent, 'new-path', this._window.webContents);
333
+ this . _showPythonSelectorDialog ( ) ;
334
+ return Promise . resolve ( ) ;
335
+ } ) ;
280
336
}
281
337
282
338
private _showUpdateDialog ( type : 'updates-available' | 'error' | 'no-updates' ) {
@@ -326,6 +382,100 @@ class JupyterApplication implements IApplication, IStatefulService {
326
382
dialog . loadURL ( `data:text/html;charset=utf-8,${ pageSource } ` ) ;
327
383
}
328
384
385
+ private _showPythonSelectorDialog ( ) {
386
+ const dialog = new BrowserWindow ( {
387
+ title : 'Set Python Environment' ,
388
+ width : 600 ,
389
+ height : 200 ,
390
+ resizable : false ,
391
+ parent : this . _window ,
392
+ modal : true ,
393
+ webPreferences : {
394
+ nodeIntegration : true ,
395
+ enableRemoteModule : true ,
396
+ contextIsolation : false
397
+ }
398
+ } ) ;
399
+ dialog . setMenuBarVisibility ( false ) ;
400
+
401
+ const bundledPythonPath = '' ;
402
+ const pythonPath = this . _applicationState . pythonPath ;
403
+ let useBundledPythonPath = false ;
404
+ if ( pythonPath === '' || pythonPath === bundledPythonPath ) {
405
+ useBundledPythonPath = true ;
406
+ }
407
+
408
+ const pageSource = `
409
+ <body style="background: rgba(238,238,238,1); font-size: 13px; font-family: Helvetica, Arial, sans-serif; padding: 20px;">
410
+ <style>.row {display: flex; margin-bottom: 10px; }</style>
411
+ <div style="height: 100%; display: flex;flex-direction: column; justify-content: space-between;">
412
+ <div class="row">
413
+ <b>Set Python Environment</b>
414
+ </div>
415
+ <div>
416
+ <div class="row">
417
+ <input type="radio" id="bundled" name="env_type" value="bundled" ${ useBundledPythonPath ? 'checked' : '' } onchange="handleEnvTypeChange(this);">
418
+ <label for="bundled">Use bundled Python environment</label>
419
+ </div>
420
+ <div class="row">
421
+ <input type="radio" id="custom" name="env_type" value="custom" ${ ! useBundledPythonPath ? 'checked' : '' } onchange="handleEnvTypeChange(this);">
422
+ <label for="custom">Use custom Python environment</label>
423
+ </div>
424
+
425
+ <div class="row">
426
+ <div style="flex-grow: 1;">
427
+ <input type="text" id="python-path" value="${ pythonPath } " readonly style="width: 100%;"></input>
428
+ </div>
429
+ <div>
430
+ <button id='select-python-path' onclick='handleSelectPythonPath(this);'>Select Python path</button>
431
+ </div>
432
+ </div>
433
+ <div class="row" style="justify-content: flex-end;">
434
+ <button onclick='handleSave(this);' style='margin-right: 5px;'>Save and restart</button>
435
+ <button onclick='handleCancel(this);'>Cancel</button>
436
+ </div>
437
+ </div>
438
+ </div>
439
+
440
+ <script>
441
+ const ipcRenderer = require('electron').ipcRenderer;
442
+ let pythonPath = '';
443
+ const bundledRadio = document.getElementById('bundled');
444
+ const pythonPathInput = document.getElementById('python-path');
445
+ const selectPythonPathButton = document.getElementById('select-python-path');
446
+
447
+ function handleSelectPythonPath(el) {
448
+ ipcRenderer.send('select-python-path');
449
+ }
450
+
451
+ function handleReleasesLink(el) {
452
+ ipcRenderer.send('launch-installer-download-page');
453
+ }
454
+
455
+ function handleEnvTypeChange() {
456
+ pythonPathInput.disabled = bundledRadio.checked;
457
+ selectPythonPathButton.disabled = bundledRadio.checked;
458
+ }
459
+
460
+ function handleSave(el) {
461
+ ipcRenderer.send('set-python-path', bundledRadio.checked ? '' : pythonPathInput.value);
462
+ }
463
+
464
+ function handleCancel(el) {
465
+ window.close();
466
+ }
467
+
468
+ ipcRenderer.on('custom-python-path-selected', (event, path) => {
469
+ pythonPathInput.value = path;
470
+ });
471
+
472
+ handleEnvTypeChange();
473
+ </script>
474
+ </body>
475
+ ` ;
476
+ dialog . loadURL ( `data:text/html;charset=utf-8,${ pageSource } ` ) ;
477
+ }
478
+
329
479
private _checkForUpdates ( showDialog : 'on-new-version' | 'always' ) {
330
480
fetch ( 'https://github.com/jupyterlab/jupyterlab-desktop/releases/latest/download/latest.yml' ) . then ( async ( response ) => {
331
481
try {
@@ -389,6 +539,7 @@ namespace JupyterApplication {
389
539
export
390
540
interface IState extends JSONObject {
391
541
checkForUpdatesAutomatically ?: boolean ;
542
+ pythonPath ?: string ;
392
543
}
393
544
}
394
545
0 commit comments