jupyterpack 0.3.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +1 -1
  2. package/lib/document/commands.d.ts +8 -0
  3. package/lib/document/commands.js +76 -0
  4. package/lib/document/iframePanel.d.ts +4 -1
  5. package/lib/document/plugin.d.ts +2 -1
  6. package/lib/document/plugin.js +22 -4
  7. package/lib/document/toolbar.d.ts +9 -0
  8. package/lib/document/toolbar.js +20 -0
  9. package/lib/document/widgetFactory.d.ts +2 -1
  10. package/lib/document/widgetFactory.js +14 -6
  11. package/lib/index.d.ts +1 -1
  12. package/lib/pythonServer/baseServer.d.ts +82 -0
  13. package/lib/pythonServer/baseServer.js +141 -0
  14. package/lib/pythonServer/dash/dashServer.d.ts +5 -17
  15. package/lib/pythonServer/dash/dashServer.js +19 -24
  16. package/lib/pythonServer/index.d.ts +3 -3
  17. package/lib/pythonServer/index.js +5 -1
  18. package/lib/pythonServer/kernelExecutor.d.ts +2 -53
  19. package/lib/pythonServer/kernelExecutor.js +5 -70
  20. package/lib/pythonServer/shiny/shinyServer.d.ts +14 -0
  21. package/lib/pythonServer/shiny/shinyServer.js +49 -0
  22. package/lib/pythonServer/starlette/starletteServer.d.ts +13 -0
  23. package/lib/pythonServer/starlette/starletteServer.js +49 -0
  24. package/lib/pythonServer/streamlit/streamlitServer.d.ts +6 -26
  25. package/lib/pythonServer/streamlit/streamlitServer.js +33 -41
  26. package/lib/pythonServer/tornado/tornadoServer.d.ts +6 -26
  27. package/lib/pythonServer/tornado/tornadoServer.js +28 -36
  28. package/lib/pythonWidget/comm.d.ts +11 -0
  29. package/lib/pythonWidget/comm.js +52 -0
  30. package/lib/pythonWidget/pythonWidget.d.ts +5 -0
  31. package/lib/pythonWidget/pythonWidget.js +19 -0
  32. package/lib/pythonWidget/pythonWidgetModel.d.ts +16 -4
  33. package/lib/pythonWidget/pythonWidgetModel.js +77 -13
  34. package/lib/sandpackWidget/sandpackFilesModel.d.ts +6 -2
  35. package/lib/sandpackWidget/sandpackFilesModel.js +13 -2
  36. package/lib/sandpackWidget/sandpackPanel.d.ts +10 -1
  37. package/lib/sandpackWidget/sandpackPanel.js +38 -3
  38. package/lib/swConnection/mainConnectionManager.d.ts +4 -4
  39. package/lib/swConnection/mainConnectionManager.js +23 -12
  40. package/lib/token.d.ts +2 -1
  41. package/lib/token.js +1 -0
  42. package/lib/tools.d.ts +6 -0
  43. package/lib/tools.js +25 -0
  44. package/lib/type.d.ts +39 -4
  45. package/lib/type.js +2 -0
  46. package/lib/websocket/websocket.js +5 -1
  47. package/package.json +6 -6
  48. package/src/document/commands.ts +91 -0
  49. package/src/document/iframePanel.ts +4 -1
  50. package/src/document/plugin.ts +28 -7
  51. package/src/document/toolbar.ts +39 -0
  52. package/src/document/widgetFactory.ts +16 -6
  53. package/src/global.d.ts +5 -0
  54. package/src/pythonServer/baseServer.ts +245 -0
  55. package/src/pythonServer/dash/dashServer.ts +25 -35
  56. package/src/pythonServer/index.ts +9 -5
  57. package/src/pythonServer/kernelExecutor.ts +8 -147
  58. package/src/pythonServer/shiny/shinyServer.ts +62 -0
  59. package/src/pythonServer/starlette/starletteServer.ts +59 -0
  60. package/src/pythonServer/streamlit/streamlitServer.ts +40 -62
  61. package/src/pythonServer/tornado/tornadoServer.ts +33 -60
  62. package/src/pythonWidget/comm.ts +65 -0
  63. package/src/pythonWidget/pythonWidget.ts +19 -1
  64. package/src/pythonWidget/pythonWidgetModel.ts +107 -20
  65. package/src/sandpackWidget/sandpackFilesModel.ts +17 -3
  66. package/src/sandpackWidget/sandpackPanel.ts +45 -4
  67. package/src/swConnection/mainConnectionManager.ts +28 -20
  68. package/src/token.ts +5 -1
  69. package/src/tools.ts +31 -0
  70. package/src/type.ts +46 -7
  71. package/src/websocket/websocket.ts +9 -1
  72. package/style/base.css +7 -0
  73. package/style/icons/autoreload.svg +16 -0
  74. package/style/icons/box.svg +12 -0
  75. package/style/icons/externallink.svg +10 -0
  76. package/lib/pythonServer/common/generatedPythonFiles.d.ts +0 -2
  77. package/lib/pythonServer/common/generatedPythonFiles.js +0 -72
  78. package/lib/pythonServer/dash/generatedPythonFiles.d.ts +0 -2
  79. package/lib/pythonServer/dash/generatedPythonFiles.js +0 -31
  80. package/lib/pythonServer/streamlit/generatedPythonFiles.d.ts +0 -2
  81. package/lib/pythonServer/streamlit/generatedPythonFiles.js +0 -147
  82. package/lib/pythonServer/tornado/generatedPythonFiles.d.ts +0 -3
  83. package/lib/pythonServer/tornado/generatedPythonFiles.js +0 -456
  84. package/src/pythonServer/common/generatedPythonFiles.ts +0 -73
  85. package/src/pythonServer/dash/generatedPythonFiles.ts +0 -32
  86. package/src/pythonServer/streamlit/generatedPythonFiles.ts +0 -148
  87. package/src/pythonServer/tornado/generatedPythonFiles.ts +0 -457
@@ -7,31 +7,64 @@ import {
7
7
  Session
8
8
  } from '@jupyterlab/services';
9
9
  import { PromiseDelegate } from '@lumino/coreutils';
10
- import { IDisposable } from '@lumino/disposable';
11
10
 
12
11
  import { PYTHON_SERVER } from '../pythonServer';
12
+ import { Signal } from '@lumino/signaling';
13
13
  import {
14
+ IBasePythonServer,
14
15
  IConnectionManager,
15
16
  IJupyterPackFileFormat,
16
- IKernelExecutor,
17
+ IPythonWidgetModel,
17
18
  JupyterPackFramework
18
19
  } from '../type';
20
+ import { CommBroadcastManager } from './comm';
21
+ import { IS_LITE } from '../tools';
19
22
 
20
- export class PythonWidgetModel implements IDisposable {
23
+ export class PythonWidgetModel implements IPythonWidgetModel {
21
24
  constructor(options: PythonWidgetModel.IOptions) {
22
25
  this._context = options.context;
23
26
  this._manager = options.manager;
24
27
  this._connectionManager = options.connectionManager;
25
28
  this._contentsManager = options.contentsManager;
26
29
  this._jpackModel = options.jpackModel;
30
+ this._localPath = PathExt.dirname(this._context.localPath);
31
+ this._autoreload = Boolean(this._jpackModel?.metadata?.autoreload);
32
+
33
+ this._contentsManager.fileChanged.connect(this._onFileChanged, this);
34
+ }
35
+
36
+ get autoreload() {
37
+ return this._autoreload;
27
38
  }
28
39
 
40
+ set autoreload(val: boolean) {
41
+ this._autoreload = val;
42
+ }
29
43
  get isDisposed(): boolean {
30
44
  return this._isDisposed;
31
45
  }
32
46
  get connectionManager(): IConnectionManager {
33
47
  return this._connectionManager;
34
48
  }
49
+ get serverReloaded() {
50
+ return this._serverReloaded;
51
+ }
52
+ get kernelStatusChanged() {
53
+ return this._kernelStatusChanged;
54
+ }
55
+
56
+ async reload() {
57
+ if (!this._kernelStarted) {
58
+ return;
59
+ }
60
+ const { spkContent, entryContent } = await this._loadData();
61
+ await this._executor?.reloadPythonServer({
62
+ entryPath: spkContent.entry,
63
+ initCode: entryContent.content
64
+ });
65
+ this._serverReloaded.emit();
66
+ }
67
+
35
68
  async initialize(): Promise<
36
69
  | {
37
70
  success: true;
@@ -48,15 +81,9 @@ export class PythonWidgetModel implements IDisposable {
48
81
  error: 'Server is called twice'
49
82
  };
50
83
  }
51
- const filePath = this._context.localPath;
52
- const spkContent = this._jpackModel;
53
84
 
54
- const entryPath = PathExt.join(PathExt.dirname(filePath), spkContent.entry);
55
- const rootUrl = spkContent.rootUrl ?? '/';
56
- const entryContent = await this._contentsManager.get(entryPath, {
57
- content: true,
58
- format: 'text'
59
- });
85
+ const { filePath, spkContent, rootUrl, entryContent } =
86
+ await this._loadData();
60
87
  const sessionManager = this._manager.sessions;
61
88
  await sessionManager.ready;
62
89
  await this._manager.kernelspecs.ready;
@@ -73,14 +100,29 @@ export class PythonWidgetModel implements IDisposable {
73
100
  kernelName = specs.default;
74
101
  }
75
102
 
76
- this._sessionConnection = await sessionManager.startNew({
77
- name: filePath,
78
- path: filePath,
79
- kernel: {
80
- name: kernelName
103
+ this._sessionConnection = await sessionManager.startNew(
104
+ {
105
+ name: filePath,
106
+ path: filePath,
107
+ kernel: {
108
+ name: kernelName
109
+ },
110
+ type: 'notebook'
81
111
  },
82
- type: 'notebook'
83
- });
112
+ {
113
+ kernelConnectionOptions: { handleComms: true }
114
+ }
115
+ );
116
+ const kernel = this._sessionConnection.kernel;
117
+ if (kernel) {
118
+ this._kernelStatusChanged.emit('started');
119
+ this._commBroadcastManager.registerKernel(kernel);
120
+ kernel.disposed.connect(() => {
121
+ this._kernelStatusChanged.emit('stopped');
122
+ this._commBroadcastManager.unregisterKernel(kernel.id);
123
+ });
124
+ }
125
+
84
126
  const framework = spkContent.framework;
85
127
  const ServerClass = PYTHON_SERVER.get(framework);
86
128
  if (!ServerClass) {
@@ -111,14 +153,48 @@ export class PythonWidgetModel implements IDisposable {
111
153
  this._kernelStarted = true;
112
154
  return { ...data, rootUrl, framework, success: true };
113
155
  }
114
- dispose(): void {
156
+ async dispose(): Promise<void> {
115
157
  if (this._isDisposed) {
116
158
  return;
117
159
  }
160
+ if (!IS_LITE) {
161
+ this._sessionConnection?.kernel?.shutdown();
162
+ }
118
163
  void this._executor?.disposePythonServer();
164
+ this._contentsManager.fileChanged.disconnect(this._onFileChanged);
165
+ this._commBroadcastManager.dispose();
119
166
  this._isDisposed = true;
120
167
  }
121
168
 
169
+ private async _loadData() {
170
+ const filePath = this._context.localPath;
171
+ const spkContent = this._jpackModel;
172
+
173
+ const entryPath = PathExt.join(PathExt.dirname(filePath), spkContent.entry);
174
+ const rootUrl = spkContent.rootUrl ?? '/';
175
+ const entryContent = await this._contentsManager.get(entryPath, {
176
+ content: true,
177
+ format: 'text'
178
+ });
179
+ return { filePath, spkContent, rootUrl, entryContent };
180
+ }
181
+
182
+ private async _onFileChanged(
183
+ sender: Contents.IManager,
184
+ args: Contents.IChangedArgs
185
+ ) {
186
+ if (this._autoreload && args.type === 'save') {
187
+ if (
188
+ args.newValue?.path &&
189
+ args.newValue.path.startsWith(this._localPath)
190
+ ) {
191
+ await this.reload();
192
+ }
193
+ }
194
+ }
195
+
196
+ private _commBroadcastManager = new CommBroadcastManager();
197
+
122
198
  private _isDisposed = false;
123
199
  private _kernelStarted = false;
124
200
  private _sessionConnection: Session.ISessionConnection | undefined;
@@ -127,7 +203,18 @@ export class PythonWidgetModel implements IDisposable {
127
203
  private _connectionManager: IConnectionManager;
128
204
  private _contentsManager: Contents.IManager;
129
205
  private _jpackModel: IJupyterPackFileFormat;
130
- private _executor?: IKernelExecutor;
206
+ private _executor?: IBasePythonServer;
207
+ private _localPath: string;
208
+
209
+ private _serverReloaded: Signal<IPythonWidgetModel, void> = new Signal<
210
+ IPythonWidgetModel,
211
+ void
212
+ >(this);
213
+ private _kernelStatusChanged: Signal<
214
+ IPythonWidgetModel,
215
+ 'started' | 'stopped'
216
+ > = new Signal(this);
217
+ private _autoreload: boolean;
131
218
  }
132
219
 
133
220
  export namespace PythonWidgetModel {
@@ -3,16 +3,17 @@ import { ISignal, Signal } from '@lumino/signaling';
3
3
 
4
4
  import { IDict } from '../type';
5
5
  import { removePrefix } from '../tools';
6
+ import { IDisposable } from '@lumino/disposable';
6
7
 
7
- export class SandpackFilesModel {
8
+ export class SandpackFilesModel implements IDisposable {
8
9
  constructor(options: { contentsManager: Contents.IManager; path: string }) {
9
10
  this._contentManager = options.contentsManager;
10
11
  this._path = options.path;
11
12
  this._contentManager.fileChanged.connect(this._onFileChanged, this);
12
13
  }
13
14
 
14
- async getAllFiles(): Promise<IDict<{ code: string }>> {
15
- if (!this._allFiles) {
15
+ async getAllFiles(force = false): Promise<IDict<{ code: string }>> {
16
+ if (!this._allFiles || force) {
16
17
  const files = await this._contentManager.get(this._path, {
17
18
  content: true
18
19
  });
@@ -21,6 +22,9 @@ export class SandpackFilesModel {
21
22
 
22
23
  return this._allFiles;
23
24
  }
25
+ get isDisposed(): boolean {
26
+ return this._isDisposed;
27
+ }
24
28
 
25
29
  get fileChanged(): ISignal<
26
30
  SandpackFilesModel,
@@ -29,6 +33,14 @@ export class SandpackFilesModel {
29
33
  return this._fileChanged;
30
34
  }
31
35
 
36
+ dispose(): void {
37
+ if (this._isDisposed) {
38
+ return;
39
+ }
40
+ this._isDisposed = true;
41
+ this._contentManager.fileChanged.disconnect(this._onFileChanged);
42
+ }
43
+
32
44
  async flattenDirectory(
33
45
  dirContent: Contents.IModel
34
46
  ): Promise<IDict<{ code: string }>> {
@@ -138,4 +150,6 @@ export class SandpackFilesModel {
138
150
  private _contentManager: Contents.IManager;
139
151
 
140
152
  private _allFiles?: IDict<{ code: string }>;
153
+
154
+ private _isDisposed = false;
141
155
  }
@@ -7,8 +7,9 @@ import { DocumentRegistry } from '@jupyterlab/docregistry';
7
7
  import { Contents } from '@jupyterlab/services';
8
8
 
9
9
  import { IFramePanel } from '../document/iframePanel';
10
- import { IDict } from '../type';
10
+ import { IDict, IJupyterPackFileFormat } from '../type';
11
11
  import { SandpackFilesModel } from './sandpackFilesModel';
12
+ import { PromiseDelegate } from '@lumino/coreutils';
12
13
 
13
14
  export class SandpackPanel extends IFramePanel {
14
15
  constructor(options: {
@@ -17,17 +18,44 @@ export class SandpackPanel extends IFramePanel {
17
18
  }) {
18
19
  super();
19
20
  this._contentsManager = options.contentsManager;
21
+ const { context } = options;
20
22
  options.context.ready.then(async () => {
21
- await this.init(options.context.localPath);
23
+ const jpackModel =
24
+ context.model.toJSON() as any as IJupyterPackFileFormat;
25
+ await this.init(context.localPath, jpackModel);
22
26
  });
23
27
  }
24
28
 
25
- async init(localPath: string) {
29
+ dispose(): void {
30
+ this._fileModel?.fileChanged.disconnect(this._onFileChanged);
31
+ this._fileModel?.dispose();
32
+ this._spClient?.destroy();
33
+ super.dispose();
34
+ }
35
+
36
+ get isReady() {
37
+ return this._isReady.promise;
38
+ }
39
+
40
+ get autoreload(): boolean {
41
+ return this._autoreload;
42
+ }
43
+
44
+ set autoreload(val: boolean) {
45
+ this._autoreload = val;
46
+ }
47
+
48
+ async init(localPath: string, jpackModel: IJupyterPackFileFormat) {
49
+ if (jpackModel?.metadata?.autoreload === true) {
50
+ this._autoreload = true;
51
+ }
52
+
26
53
  const currentDir = localPath.split('/').slice(0, -1).join('/');
27
54
  const filesModel = new SandpackFilesModel({
28
55
  contentsManager: this._contentsManager,
29
56
  path: currentDir
30
57
  });
58
+ this._fileModel = filesModel;
31
59
  const allFiles = await filesModel.getAllFiles();
32
60
 
33
61
  const options: ClientOptions = {
@@ -43,6 +71,7 @@ export class SandpackPanel extends IFramePanel {
43
71
  options
44
72
  );
45
73
  await this.connectSignals(filesModel, this._spClient);
74
+ this._isReady.resolve();
46
75
  }
47
76
 
48
77
  async connectSignals(
@@ -66,11 +95,20 @@ export class SandpackPanel extends IFramePanel {
66
95
  });
67
96
  }
68
97
 
98
+ async reload(): Promise<void> {
99
+ if (this._spClient && this._fileModel) {
100
+ const allFiles = await this._fileModel.getAllFiles();
101
+ this._spClient.updateSandbox({
102
+ files: allFiles
103
+ });
104
+ }
105
+ }
106
+
69
107
  private _onFileChanged(
70
108
  sender: SandpackFilesModel,
71
109
  { allFiles }: { allFiles: IDict<{ code: string }> }
72
110
  ) {
73
- if (this._spClient) {
111
+ if (this._autoreload && this._spClient) {
74
112
  this._spClient.updateSandbox({
75
113
  files: allFiles
76
114
  });
@@ -79,4 +117,7 @@ export class SandpackPanel extends IFramePanel {
79
117
 
80
118
  private _spClient?: SandpackClient;
81
119
  private _contentsManager: Contents.IManager;
120
+ private _fileModel: SandpackFilesModel | undefined;
121
+ private _autoreload = false;
122
+ private _isReady = new PromiseDelegate<void>();
82
123
  }
@@ -1,9 +1,9 @@
1
- import { arrayBufferToBase64 } from '../tools';
1
+ import { arrayBufferToBase64, stringToBase64 } from '../tools';
2
2
  import {
3
+ IBasePythonServer,
3
4
  IBroadcastMessage,
4
5
  IConnectionManager,
5
- IDict,
6
- IKernelExecutor
6
+ IDict
7
7
  } from '../type';
8
8
  import { UUID } from '@lumino/coreutils';
9
9
 
@@ -16,20 +16,19 @@ import { UUID } from '@lumino/coreutils';
16
16
  * It's running on the main thread
17
17
  */
18
18
  export class ConnectionManager implements IConnectionManager {
19
- constructor(public instanceId: string) {
20
- this._wsBroadcastChannel = new BroadcastChannel(
21
- `/jupyterpack/ws/${instanceId}`
22
- );
23
- this._initWsChannel();
24
- }
19
+ constructor(public instanceId: string) {}
25
20
 
26
21
  async registerConnection(
27
- kernelExecutor: IKernelExecutor
22
+ pythonServer: IBasePythonServer
28
23
  ): Promise<{ instanceId: string; kernelClientId: string }> {
29
24
  const uuid = UUID.uuid4();
30
25
 
31
- this._kernelExecutors.set(uuid, kernelExecutor);
32
-
26
+ this._pythonServers.set(uuid, pythonServer);
27
+ const wsbc = new BroadcastChannel(
28
+ `/jupyterpack/ws/${this.instanceId}/${uuid}`
29
+ );
30
+ this._initWsChannel(wsbc);
31
+ this._wsBroadcastChannelMap.set(`${this.instanceId}/${uuid}`, wsbc);
33
32
  return { instanceId: this.instanceId, kernelClientId: uuid };
34
33
  }
35
34
 
@@ -43,7 +42,7 @@ export class ConnectionManager implements IConnectionManager {
43
42
  }): Promise<IDict | null> {
44
43
  const { urlPath, kernelClientId, method, params, requestBody, headers } =
45
44
  options;
46
- const executor = this._kernelExecutors.get(kernelClientId);
45
+ const executor = this._pythonServers.get(kernelClientId);
47
46
  if (!executor) {
48
47
  return null;
49
48
  }
@@ -57,8 +56,8 @@ export class ConnectionManager implements IConnectionManager {
57
56
  });
58
57
  return response;
59
58
  }
60
- private _initWsChannel() {
61
- this._wsBroadcastChannel.onmessage = event => {
59
+ private _initWsChannel(broadcastChannel: BroadcastChannel) {
60
+ broadcastChannel.onmessage = event => {
62
61
  const rawData = event.data;
63
62
  let data: IBroadcastMessage;
64
63
  if (typeof rawData === 'string') {
@@ -68,13 +67,13 @@ export class ConnectionManager implements IConnectionManager {
68
67
  }
69
68
 
70
69
  const { action, dest, wsUrl, payload } = data;
71
- const executor = this._kernelExecutors.get(dest);
70
+ const executor = this._pythonServers.get(dest);
72
71
  if (!executor) {
73
72
  console.error(
74
73
  'Missing kernel handle for message',
75
74
  data,
76
75
  dest,
77
- this._kernelExecutors
76
+ this._pythonServers
78
77
  );
79
78
  return;
80
79
  }
@@ -89,6 +88,14 @@ export class ConnectionManager implements IConnectionManager {
89
88
  });
90
89
  break;
91
90
  }
91
+ case 'close': {
92
+ executor.closeWebsocket({
93
+ instanceId: this.instanceId,
94
+ kernelId: dest,
95
+ wsUrl
96
+ });
97
+ break;
98
+ }
92
99
  case 'send': {
93
100
  let serializedData: string;
94
101
  let isBinary: boolean;
@@ -97,7 +104,8 @@ export class ConnectionManager implements IConnectionManager {
97
104
  serializedData = arrayBufferToBase64(payload as any);
98
105
  isBinary = true;
99
106
  } else if (typeof payload === 'string') {
100
- serializedData = payload;
107
+ // convert string to base64 string to avoid encoding problem
108
+ serializedData = stringToBase64(payload);
101
109
  isBinary = false;
102
110
  } else {
103
111
  console.error('Unknown message type', payload);
@@ -116,6 +124,6 @@ export class ConnectionManager implements IConnectionManager {
116
124
  }
117
125
  };
118
126
  }
119
- private _kernelExecutors = new Map<string, IKernelExecutor>();
120
- private _wsBroadcastChannel: BroadcastChannel;
127
+ private _pythonServers = new Map<string, IBasePythonServer>();
128
+ private _wsBroadcastChannelMap: Map<string, BroadcastChannel> = new Map();
121
129
  }
package/src/token.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  import { Token } from '@lumino/coreutils';
2
- import { IConnectionManager } from './type';
2
+ import { IConnectionManager, IJupyterpackDocTracker } from './type';
3
3
 
4
4
  export const IConnectionManagerToken = new Token<IConnectionManager>(
5
5
  'jupyterpack:connection-manager'
6
6
  );
7
+
8
+ export const IJupyterpackDocTrackerToken = new Token<IJupyterpackDocTracker>(
9
+ 'jupyterpack:dock-tracker'
10
+ );
package/src/tools.ts CHANGED
@@ -1,3 +1,25 @@
1
+ import logoStr from '../style/icons/box.svg';
2
+ import autoReloadStr from '../style/icons/autoreload.svg';
3
+ import linkStr from '../style/icons/externallink.svg';
4
+ import { LabIcon } from '@jupyterlab/ui-components';
5
+
6
+ export const IS_LITE = !!document.getElementById('jupyter-lite-main');
7
+
8
+ export const logoIcon = new LabIcon({
9
+ name: 'jupyterpack:logo',
10
+ svgstr: logoStr
11
+ });
12
+
13
+ export const autoReloadIcon = new LabIcon({
14
+ name: 'jupyterpack:autoReload',
15
+ svgstr: autoReloadStr
16
+ });
17
+
18
+ export const linkIcon = new LabIcon({
19
+ name: 'jupyterpack:externalLink',
20
+ svgstr: linkStr
21
+ });
22
+
1
23
  export function removePrefix(path: string, prefix: string): string {
2
24
  if (path.startsWith(prefix)) {
3
25
  return path.slice(prefix.length);
@@ -31,6 +53,15 @@ export function base64ToString(base64: string): string {
31
53
  return new TextDecoder('utf-8').decode(bytes);
32
54
  }
33
55
 
56
+ export function stringToBase64(str: string): string {
57
+ const bytes = new TextEncoder().encode(str);
58
+ let binary = '';
59
+ for (let i = 0; i < bytes.length; i++) {
60
+ binary += String.fromCharCode(bytes[i]);
61
+ }
62
+ return window.btoa(binary);
63
+ }
64
+
34
65
  export function stringOrNone(content?: string) {
35
66
  return content ? `"${content}"` : 'None';
36
67
  }
package/src/type.ts CHANGED
@@ -1,5 +1,8 @@
1
+ import { DocumentWidget } from '@jupyterlab/docregistry';
1
2
  import { KernelMessage } from '@jupyterlab/services';
2
3
  import { IDisposable } from '@lumino/disposable';
4
+ import { IWidgetTracker } from '@jupyterlab/apputils';
5
+ import { ISignal } from '@lumino/signaling';
3
6
 
4
7
  export interface IDict<T = any> {
5
8
  [key: string]: T;
@@ -23,13 +26,17 @@ export enum JupyterPackFramework {
23
26
  REACT = 'react',
24
27
  DASH = 'dash',
25
28
  STREAMLIT = 'streamlit',
26
- TORNADO = 'tornado'
29
+ TORNADO = 'tornado',
30
+ SHINY = 'shiny',
31
+ STARLETTE = 'starlette'
27
32
  }
28
33
  export interface IJupyterPackFileFormat {
29
34
  entry: string;
30
35
  framework: JupyterPackFramework;
31
36
  name?: string;
32
- metadata?: IDict;
37
+ metadata?: {
38
+ autoreload?: boolean;
39
+ };
33
40
  rootUrl?: string;
34
41
  }
35
42
 
@@ -44,7 +51,15 @@ export interface IKernelExecutorParams {
44
51
  params?: string;
45
52
  requestBody?: ArrayBuffer;
46
53
  }
54
+
47
55
  export interface IKernelExecutor extends IDisposable {
56
+ executeCode(
57
+ code: KernelMessage.IExecuteRequestMsg['content'],
58
+ waitForResult?: boolean
59
+ ): Promise<string | null>;
60
+ }
61
+
62
+ export interface IBasePythonServer extends IDisposable {
48
63
  getResponse(options: IKernelExecutorParams): Promise<IDict>;
49
64
  openWebsocket(options: {
50
65
  instanceId: string;
@@ -52,16 +67,17 @@ export interface IKernelExecutor extends IDisposable {
52
67
  wsUrl: string;
53
68
  protocol?: string;
54
69
  }): Promise<void>;
70
+ closeWebsocket(options: {
71
+ instanceId: string;
72
+ kernelId: string;
73
+ wsUrl: string;
74
+ }): Promise<void>;
55
75
  sendWebsocketMessage(options: {
56
76
  instanceId: string;
57
77
  kernelId: string;
58
78
  wsUrl: string;
59
79
  message: string;
60
80
  }): Promise<void>;
61
- executeCode(
62
- code: KernelMessage.IExecuteRequestMsg['content'],
63
- waitForResult?: boolean
64
- ): Promise<string | null>;
65
81
  init(options: {
66
82
  entryPath?: string;
67
83
  initCode?: string;
@@ -69,6 +85,10 @@ export interface IKernelExecutor extends IDisposable {
69
85
  kernelClientId: string;
70
86
  }): Promise<void>;
71
87
  disposePythonServer(): Promise<void>;
88
+ reloadPythonServer(options: {
89
+ entryPath?: string;
90
+ initCode?: string;
91
+ }): Promise<void>;
72
92
  getResponseFunctionFactory(options: {
73
93
  urlPath: string;
74
94
  method: string;
@@ -80,9 +100,28 @@ export interface IKernelExecutor extends IDisposable {
80
100
 
81
101
  export interface IConnectionManager {
82
102
  registerConnection(
83
- kernelExecutor: IKernelExecutor
103
+ kernelExecutor: IBasePythonServer
84
104
  ): Promise<{ instanceId: string; kernelClientId: string }>;
85
105
  generateResponse(
86
106
  option: { kernelClientId: string } & IKernelExecutorParams
87
107
  ): Promise<IDict | null>;
88
108
  }
109
+
110
+ export type IJupyterpackDocTracker = IWidgetTracker<DocumentWidget>;
111
+
112
+ export interface IPythonWidgetModel extends IDisposable {
113
+ connectionManager: IConnectionManager;
114
+ serverReloaded: ISignal<IPythonWidgetModel, void>;
115
+ kernelStatusChanged: ISignal<IPythonWidgetModel, 'started' | 'stopped'>;
116
+ reload(): Promise<void>;
117
+ initialize(): Promise<
118
+ | {
119
+ success: true;
120
+ instanceId: string;
121
+ kernelClientId: string;
122
+ rootUrl: string;
123
+ framework: JupyterPackFramework;
124
+ }
125
+ | { success: false; error: string }
126
+ >;
127
+ }
@@ -59,7 +59,9 @@
59
59
  }
60
60
  return data;
61
61
  };
62
- const bcWsChannel = new BroadcastChannel(`/jupyterpack/ws/${instanceId}`);
62
+ const bcWsChannel = new BroadcastChannel(
63
+ `/jupyterpack/ws/${instanceId}/${kernelClientId}`
64
+ );
63
65
 
64
66
  class BroadcastChannelWebSocket implements WebSocket {
65
67
  constructor(url: string | URL, protocols?: string | string[]) {
@@ -109,6 +111,10 @@
109
111
  cb();
110
112
  }
111
113
  this._eventHandlers['close'] = [];
114
+ sendTypedMessage({
115
+ action: 'close',
116
+ wsUrl: this.url
117
+ });
112
118
  bcWsChannel.removeEventListener('message', this._bcMessageHandler);
113
119
 
114
120
  this.readyState = this.CLOSED;
@@ -168,10 +174,12 @@
168
174
  } else {
169
175
  data = rawData;
170
176
  }
177
+
171
178
  const { action, dest, wsUrl, payload } = data;
172
179
  if (dest !== kernelClientId || wsUrl !== this.url) {
173
180
  return;
174
181
  }
182
+
175
183
  switch (action) {
176
184
  case 'connected': {
177
185
  this.readyState = this.OPEN;
package/style/base.css CHANGED
@@ -49,3 +49,10 @@
49
49
  0 0 0 0 rgb(212 72 13 / 100%);
50
50
  }
51
51
  }
52
+
53
+ .specta-main-content-panel .jupyterpack-toolbar {
54
+ display: none;
55
+ height: 0 !important;
56
+ width: 0 !important;
57
+ min-height: 0 !important;
58
+ }
@@ -0,0 +1,16 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+
3
+ <svg height="800px" width="800px" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
4
+ <g>
5
+ <path d="M403.925,108.102c-27.595-27.595-62.899-47.558-102.459-56.29L304.182,0L201.946,53.867l-27.306,14.454
6
+ l-5.066,2.654l8.076,4.331l38.16,20.542l81.029,43.602l2.277-42.859c28.265,7.546,53.438,22.53,73.623,42.638
7
+ c29.94,29.939,48.358,71.119,48.358,116.776c0,23.407-4.843,45.58-13.575,65.687l40.37,17.532
8
+ c11.076-25.463,17.242-53.637,17.242-83.219C465.212,198.306,441.727,145.904,403.925,108.102z"
9
+ fill="var(--jp-inverse-layout-color3)" />
10
+ <path d="M296.256,416.151l-81.101-43.612l-2.272,42.869c-28.26-7.555-53.51-22.53-73.618-42.636
11
+ c-29.945-29.95-48.364-71.12-48.364-116.767c0-23.427,4.844-45.522,13.576-65.697l-40.37-17.531
12
+ c-11.076,25.53-17.242,53.723-17.242,83.228c0,57.679,23.407,110.157,61.21,147.893c27.595,27.594,62.899,47.548,102.453,56.202
13
+ l-2.716,51.9l102.169-53.878l27.455-14.454l4.988-2.643l-7.999-4.332L296.256,416.151z"
14
+ fill="var(--jp-inverse-layout-color3)" />
15
+ </g>
16
+ </svg>
@@ -0,0 +1,12 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
3
+ <path
4
+ d="M17.5777 4.43152L15.5777 3.38197C13.8221 2.46066 12.9443 2 12 2C11.0557 2 10.1779 2.46066 8.42229 3.38197L6.42229 4.43152C4.64855 5.36234 3.6059 5.9095 2.95969 6.64132L12 11.1615L21.0403 6.64132C20.3941 5.9095 19.3515 5.36234 17.5777 4.43152Z"
5
+ fill="var(--jp-inverse-layout-color3)" />
6
+ <path
7
+ d="M21.7484 7.96435L12.75 12.4635V21.904C13.4679 21.7252 14.2848 21.2965 15.5777 20.618L17.5777 19.5685C19.7294 18.4393 20.8052 17.8748 21.4026 16.8603C22 15.8458 22 14.5833 22 12.0585V11.9415C22 10.0489 22 8.86558 21.7484 7.96435Z"
8
+ fill="var(--jp-inverse-layout-color3)" />
9
+ <path
10
+ d="M11.25 21.904V12.4635L2.25164 7.96434C2 8.86557 2 10.0489 2 11.9415V12.0585C2 14.5833 2 15.8458 2.5974 16.8603C3.19479 17.8748 4.27063 18.4393 6.42229 19.5685L8.42229 20.618C9.71524 21.2965 10.5321 21.7252 11.25 21.904Z"
11
+ fill="var(--jp-inverse-layout-color3)" />
12
+ </svg>