jupyterpack 0.3.0 → 0.4.0
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.
- package/lib/document/commands.d.ts +8 -0
- package/lib/document/commands.js +76 -0
- package/lib/document/iframePanel.d.ts +4 -1
- package/lib/document/plugin.d.ts +2 -1
- package/lib/document/plugin.js +22 -4
- package/lib/document/toolbar.d.ts +9 -0
- package/lib/document/toolbar.js +20 -0
- package/lib/document/widgetFactory.d.ts +2 -1
- package/lib/document/widgetFactory.js +11 -5
- package/lib/index.d.ts +1 -1
- package/lib/pythonServer/dash/dashServer.d.ts +4 -6
- package/lib/pythonServer/dash/dashServer.js +18 -10
- package/lib/pythonServer/kernelExecutor.d.ts +5 -0
- package/lib/pythonServer/kernelExecutor.js +13 -6
- package/lib/pythonServer/streamlit/streamlitServer.d.ts +7 -26
- package/lib/pythonServer/streamlit/streamlitServer.js +32 -36
- package/lib/pythonServer/tornado/tornadoServer.d.ts +5 -3
- package/lib/pythonServer/tornado/tornadoServer.js +31 -14
- package/lib/pythonWidget/comm.d.ts +11 -0
- package/lib/pythonWidget/comm.js +52 -0
- package/lib/pythonWidget/pythonWidget.d.ts +5 -0
- package/lib/pythonWidget/pythonWidget.js +19 -0
- package/lib/pythonWidget/pythonWidgetModel.d.ts +16 -4
- package/lib/pythonWidget/pythonWidgetModel.js +77 -13
- package/lib/sandpackWidget/sandpackFilesModel.d.ts +6 -2
- package/lib/sandpackWidget/sandpackFilesModel.js +13 -2
- package/lib/sandpackWidget/sandpackPanel.d.ts +10 -1
- package/lib/sandpackWidget/sandpackPanel.js +38 -3
- package/lib/token.d.ts +2 -1
- package/lib/token.js +1 -0
- package/lib/tools.d.ts +5 -0
- package/lib/tools.js +17 -0
- package/lib/type.d.ts +27 -1
- package/package.json +6 -6
- package/src/document/commands.ts +91 -0
- package/src/document/iframePanel.ts +4 -1
- package/src/document/plugin.ts +28 -7
- package/src/document/toolbar.ts +39 -0
- package/src/document/widgetFactory.ts +13 -5
- package/src/global.d.ts +5 -0
- package/src/pythonServer/dash/dashServer.ts +23 -14
- package/src/pythonServer/kernelExecutor.ts +21 -7
- package/src/pythonServer/streamlit/streamlitServer.ts +41 -61
- package/src/pythonServer/tornado/tornadoServer.ts +35 -18
- package/src/pythonWidget/comm.ts +65 -0
- package/src/pythonWidget/pythonWidget.ts +19 -1
- package/src/pythonWidget/pythonWidgetModel.ts +105 -18
- package/src/sandpackWidget/sandpackFilesModel.ts +17 -3
- package/src/sandpackWidget/sandpackPanel.ts +45 -4
- package/src/token.ts +5 -1
- package/src/tools.ts +22 -0
- package/src/type.ts +29 -1
- package/style/base.css +7 -0
- package/style/icons/autoreload.svg +16 -0
- package/style/icons/box.svg +12 -0
- package/style/icons/externallink.svg +10 -0
- package/lib/pythonServer/common/generatedPythonFiles.d.ts +0 -2
- package/lib/pythonServer/common/generatedPythonFiles.js +0 -72
- package/lib/pythonServer/dash/generatedPythonFiles.d.ts +0 -2
- package/lib/pythonServer/dash/generatedPythonFiles.js +0 -31
- package/lib/pythonServer/streamlit/generatedPythonFiles.d.ts +0 -2
- package/lib/pythonServer/streamlit/generatedPythonFiles.js +0 -147
- package/lib/pythonServer/tornado/generatedPythonFiles.d.ts +0 -3
- package/lib/pythonServer/tornado/generatedPythonFiles.js +0 -456
- package/src/pythonServer/common/generatedPythonFiles.ts +0 -73
- package/src/pythonServer/dash/generatedPythonFiles.ts +0 -32
- package/src/pythonServer/streamlit/generatedPythonFiles.ts +0 -148
- package/src/pythonServer/tornado/generatedPythonFiles.ts +0 -457
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Kernel, KernelMessage } from '@jupyterlab/services';
|
|
2
|
+
|
|
3
|
+
const COMM_NAME = 'jupyterpack:broadcast:comm';
|
|
4
|
+
|
|
5
|
+
export class CommBroadcastManager {
|
|
6
|
+
constructor() {
|
|
7
|
+
this._kernels = new Map();
|
|
8
|
+
this._comms = new Map();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
registerKernel(kernel: Kernel.IKernelConnection) {
|
|
12
|
+
this._kernels.set(kernel.id, kernel);
|
|
13
|
+
kernel.registerCommTarget(COMM_NAME, (comm, msg) =>
|
|
14
|
+
this._handle_comm_open(comm, msg, kernel.id)
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
unregisterKernel(kernelId?: string) {
|
|
18
|
+
if (kernelId) {
|
|
19
|
+
this._kernels.delete(kernelId);
|
|
20
|
+
const comms = this._comms.get(kernelId) ?? [];
|
|
21
|
+
comms.forEach(comm => comm.dispose());
|
|
22
|
+
this._comms.delete(kernelId);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private _handle_comm_open = async (
|
|
27
|
+
comm: Kernel.IComm,
|
|
28
|
+
msg: KernelMessage.ICommOpenMsg,
|
|
29
|
+
kernelId: string
|
|
30
|
+
): Promise<void> => {
|
|
31
|
+
if (this._comms.has(kernelId)) {
|
|
32
|
+
this._comms.get(kernelId)?.push(comm);
|
|
33
|
+
} else {
|
|
34
|
+
this._comms.set(kernelId, [comm]);
|
|
35
|
+
}
|
|
36
|
+
const channelName = msg.metadata.channel_name as string | undefined;
|
|
37
|
+
if (!channelName) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (!this._broadcastChannels.has(channelName)) {
|
|
41
|
+
this._broadcastChannels.set(
|
|
42
|
+
channelName,
|
|
43
|
+
new BroadcastChannel(channelName)
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
const broadcastChannel = this._broadcastChannels.get(channelName)!;
|
|
47
|
+
comm.onMsg = commMsg => {
|
|
48
|
+
const { data } = commMsg.content;
|
|
49
|
+
broadcastChannel.postMessage(data);
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
dispose() {
|
|
54
|
+
this._kernels.clear();
|
|
55
|
+
this._comms.clear();
|
|
56
|
+
this._broadcastChannels.forEach(it => {
|
|
57
|
+
it.close();
|
|
58
|
+
});
|
|
59
|
+
this._broadcastChannels.clear();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private _kernels: Map<string, Kernel.IKernelConnection> = new Map();
|
|
63
|
+
private _comms: Map<string, Kernel.IComm[]> = new Map();
|
|
64
|
+
private _broadcastChannels: Map<string, BroadcastChannel> = new Map();
|
|
65
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { PythonWidgetModel } from './pythonWidgetModel';
|
|
2
2
|
import { PageConfig, URLExt } from '@jupyterlab/coreutils';
|
|
3
3
|
import { IFramePanel } from '../document/iframePanel';
|
|
4
|
+
import { PromiseDelegate } from '@lumino/coreutils';
|
|
4
5
|
|
|
5
6
|
export class PythonWidget extends IFramePanel {
|
|
6
7
|
constructor(options: PythonWidget.IOptions) {
|
|
@@ -12,6 +13,7 @@ export class PythonWidget extends IFramePanel {
|
|
|
12
13
|
this._iframe.contentDocument!.body.innerText = `Failed to start server: ${connectionData.error}`;
|
|
13
14
|
return;
|
|
14
15
|
}
|
|
16
|
+
this._isReady.resolve();
|
|
15
17
|
const iframe = this._iframe;
|
|
16
18
|
const fullLabextensionsUrl = PageConfig.getOption('fullLabextensionsUrl');
|
|
17
19
|
|
|
@@ -23,24 +25,40 @@ export class PythonWidget extends IFramePanel {
|
|
|
23
25
|
connectionData.kernelClientId,
|
|
24
26
|
connectionData.rootUrl
|
|
25
27
|
);
|
|
26
|
-
|
|
27
28
|
iframe.src = iframeUrl;
|
|
28
29
|
|
|
29
30
|
iframe.addEventListener('load', () => {
|
|
30
31
|
this.toggleSpinner(false);
|
|
31
32
|
});
|
|
33
|
+
this._model.serverReloaded.connect(() => {
|
|
34
|
+
this._iframe?.contentWindow?.location?.reload();
|
|
35
|
+
});
|
|
32
36
|
});
|
|
33
37
|
}
|
|
34
38
|
|
|
39
|
+
get autoreload() {
|
|
40
|
+
return this._model.autoreload;
|
|
41
|
+
}
|
|
42
|
+
set autoreload(value: boolean) {
|
|
43
|
+
this._model.autoreload = value;
|
|
44
|
+
}
|
|
45
|
+
get isReady(): Promise<void> {
|
|
46
|
+
return this._isReady.promise;
|
|
47
|
+
}
|
|
35
48
|
get model(): PythonWidgetModel {
|
|
36
49
|
return this._model;
|
|
37
50
|
}
|
|
38
51
|
|
|
52
|
+
async reload(): Promise<void> {
|
|
53
|
+
await this._model.reload();
|
|
54
|
+
}
|
|
55
|
+
|
|
39
56
|
dispose(): void {
|
|
40
57
|
this._model.dispose();
|
|
41
58
|
}
|
|
42
59
|
|
|
43
60
|
private _model: PythonWidgetModel;
|
|
61
|
+
private _isReady = new PromiseDelegate<void>();
|
|
44
62
|
}
|
|
45
63
|
|
|
46
64
|
export namespace PythonWidget {
|
|
@@ -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
14
|
IConnectionManager,
|
|
15
15
|
IJupyterPackFileFormat,
|
|
16
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
|
|
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
|
|
55
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
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;
|
|
@@ -128,6 +204,17 @@ export class PythonWidgetModel implements IDisposable {
|
|
|
128
204
|
private _contentsManager: Contents.IManager;
|
|
129
205
|
private _jpackModel: IJupyterPackFileFormat;
|
|
130
206
|
private _executor?: IKernelExecutor;
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
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);
|
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;
|
|
@@ -29,7 +32,9 @@ export interface IJupyterPackFileFormat {
|
|
|
29
32
|
entry: string;
|
|
30
33
|
framework: JupyterPackFramework;
|
|
31
34
|
name?: string;
|
|
32
|
-
metadata?:
|
|
35
|
+
metadata?: {
|
|
36
|
+
autoreload?: boolean;
|
|
37
|
+
};
|
|
33
38
|
rootUrl?: string;
|
|
34
39
|
}
|
|
35
40
|
|
|
@@ -69,6 +74,10 @@ export interface IKernelExecutor extends IDisposable {
|
|
|
69
74
|
kernelClientId: string;
|
|
70
75
|
}): Promise<void>;
|
|
71
76
|
disposePythonServer(): Promise<void>;
|
|
77
|
+
reloadPythonServer(options: {
|
|
78
|
+
entryPath?: string;
|
|
79
|
+
initCode?: string;
|
|
80
|
+
}): Promise<void>;
|
|
72
81
|
getResponseFunctionFactory(options: {
|
|
73
82
|
urlPath: string;
|
|
74
83
|
method: string;
|
|
@@ -86,3 +95,22 @@ export interface IConnectionManager {
|
|
|
86
95
|
option: { kernelClientId: string } & IKernelExecutorParams
|
|
87
96
|
): Promise<IDict | null>;
|
|
88
97
|
}
|
|
98
|
+
|
|
99
|
+
export type IJupyterpackDocTracker = IWidgetTracker<DocumentWidget>;
|
|
100
|
+
|
|
101
|
+
export interface IPythonWidgetModel extends IDisposable {
|
|
102
|
+
connectionManager: IConnectionManager;
|
|
103
|
+
serverReloaded: ISignal<IPythonWidgetModel, void>;
|
|
104
|
+
kernelStatusChanged: ISignal<IPythonWidgetModel, 'started' | 'stopped'>;
|
|
105
|
+
reload(): Promise<void>;
|
|
106
|
+
initialize(): Promise<
|
|
107
|
+
| {
|
|
108
|
+
success: true;
|
|
109
|
+
instanceId: string;
|
|
110
|
+
kernelClientId: string;
|
|
111
|
+
rootUrl: string;
|
|
112
|
+
framework: JupyterPackFramework;
|
|
113
|
+
}
|
|
114
|
+
| { success: false; error: string }
|
|
115
|
+
>;
|
|
116
|
+
}
|
package/style/base.css
CHANGED
|
@@ -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>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
5
|
+
<g>
|
|
6
|
+
<path id="Vector"
|
|
7
|
+
d="M10.0002 5H8.2002C7.08009 5 6.51962 5 6.0918 5.21799C5.71547 5.40973 5.40973 5.71547 5.21799 6.0918C5 6.51962 5 7.08009 5 8.2002V15.8002C5 16.9203 5 17.4801 5.21799 17.9079C5.40973 18.2842 5.71547 18.5905 6.0918 18.7822C6.5192 19 7.07899 19 8.19691 19H15.8031C16.921 19 17.48 19 17.9074 18.7822C18.2837 18.5905 18.5905 18.2839 18.7822 17.9076C19 17.4802 19 16.921 19 15.8031V14M20 9V4M20 4H15M20 4L13 11"
|
|
8
|
+
stroke="var(--jp-inverse-layout-color3)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
|
9
|
+
</g>
|
|
10
|
+
</svg>
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
export declare const patch = "\nimport pyodide_http\nimport collections\n\nif not hasattr(collections, \"MutableSet\"):\n import collections.abc\n\n collections.MutableSet = collections.abc.MutableSet\n\npyodide_http.patch_all()";
|
|
2
|
-
export declare const tools = "\nimport importlib.util\nimport sys\nfrom types import ModuleType\n\nimport tempfile\nfrom pathlib import Path\nfrom typing import List\nimport os\n\nos.environ.setdefault(\"JUPYTERPACK_BASE_URL\", \"{{base_url}}\")\n\n\ndef __jupyterpack_import_from_path(module_name: str, path: str) -> ModuleType:\n \"\"\"\n Import a Python module from a given file path.\n Always reloads (does not use sys.modules cache).\n \"\"\"\n # Remove from sys.modules if already loaded\n if module_name in sys.modules:\n del sys.modules[module_name]\n\n spec = importlib.util.spec_from_file_location(module_name, path)\n if spec is None or spec.loader is None:\n raise ImportError(f\"Cannot import module {module_name} from {path}\")\n\n module = importlib.util.module_from_spec(spec)\n sys.modules[module_name] = module\n spec.loader.exec_module(module)\n return module\n\n\ndef __jupyterpack_create_mock_module(\n module_names: List[str], mock_content: str, patch_parent=True\n):\n tmpdir = tempfile.TemporaryDirectory()\n package_dir = Path(tmpdir.name) / \"__jupyterpack_mock_module\"\n package_dir.mkdir()\n (package_dir / \"__init__.py\").write_text(mock_content)\n\n sys.path.insert(0, tmpdir.name)\n mock_module = importlib.import_module(\"__jupyterpack_mock_module\")\n sys.path.pop(0)\n for module_name in module_names:\n if patch_parent:\n parts = module_name.split(\".\")\n for i in range(1, len(parts) + 1):\n subpath = \".\".join(parts[:i])\n sys.modules[subpath] = mock_module\n\n for i in range(1, len(parts)):\n parent_name = \".\".join(parts[:i])\n child_name = \".\".join(parts[: i + 1])\n parent_mod = sys.modules[parent_name]\n child_mod = sys.modules[child_name]\n setattr(parent_mod, parts[i], child_mod)\n else:\n sys.modules[module_name] = mock_module\n\n return tmpdir\n";
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
// Auto-generated TypeScript file from Python files
|
|
2
|
-
export const patch = `
|
|
3
|
-
import pyodide_http
|
|
4
|
-
import collections
|
|
5
|
-
|
|
6
|
-
if not hasattr(collections, "MutableSet"):
|
|
7
|
-
import collections.abc
|
|
8
|
-
|
|
9
|
-
collections.MutableSet = collections.abc.MutableSet
|
|
10
|
-
|
|
11
|
-
pyodide_http.patch_all()`;
|
|
12
|
-
export const tools = `
|
|
13
|
-
import importlib.util
|
|
14
|
-
import sys
|
|
15
|
-
from types import ModuleType
|
|
16
|
-
|
|
17
|
-
import tempfile
|
|
18
|
-
from pathlib import Path
|
|
19
|
-
from typing import List
|
|
20
|
-
import os
|
|
21
|
-
|
|
22
|
-
os.environ.setdefault("JUPYTERPACK_BASE_URL", "{{base_url}}")
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def __jupyterpack_import_from_path(module_name: str, path: str) -> ModuleType:
|
|
26
|
-
"""
|
|
27
|
-
Import a Python module from a given file path.
|
|
28
|
-
Always reloads (does not use sys.modules cache).
|
|
29
|
-
"""
|
|
30
|
-
# Remove from sys.modules if already loaded
|
|
31
|
-
if module_name in sys.modules:
|
|
32
|
-
del sys.modules[module_name]
|
|
33
|
-
|
|
34
|
-
spec = importlib.util.spec_from_file_location(module_name, path)
|
|
35
|
-
if spec is None or spec.loader is None:
|
|
36
|
-
raise ImportError(f"Cannot import module {module_name} from {path}")
|
|
37
|
-
|
|
38
|
-
module = importlib.util.module_from_spec(spec)
|
|
39
|
-
sys.modules[module_name] = module
|
|
40
|
-
spec.loader.exec_module(module)
|
|
41
|
-
return module
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def __jupyterpack_create_mock_module(
|
|
45
|
-
module_names: List[str], mock_content: str, patch_parent=True
|
|
46
|
-
):
|
|
47
|
-
tmpdir = tempfile.TemporaryDirectory()
|
|
48
|
-
package_dir = Path(tmpdir.name) / "__jupyterpack_mock_module"
|
|
49
|
-
package_dir.mkdir()
|
|
50
|
-
(package_dir / "__init__.py").write_text(mock_content)
|
|
51
|
-
|
|
52
|
-
sys.path.insert(0, tmpdir.name)
|
|
53
|
-
mock_module = importlib.import_module("__jupyterpack_mock_module")
|
|
54
|
-
sys.path.pop(0)
|
|
55
|
-
for module_name in module_names:
|
|
56
|
-
if patch_parent:
|
|
57
|
-
parts = module_name.split(".")
|
|
58
|
-
for i in range(1, len(parts) + 1):
|
|
59
|
-
subpath = ".".join(parts[:i])
|
|
60
|
-
sys.modules[subpath] = mock_module
|
|
61
|
-
|
|
62
|
-
for i in range(1, len(parts)):
|
|
63
|
-
parent_name = ".".join(parts[:i])
|
|
64
|
-
child_name = ".".join(parts[: i + 1])
|
|
65
|
-
parent_mod = sys.modules[parent_name]
|
|
66
|
-
child_mod = sys.modules[child_name]
|
|
67
|
-
setattr(parent_mod, parts[i], child_mod)
|
|
68
|
-
else:
|
|
69
|
-
sys.modules[module_name] = mock_module
|
|
70
|
-
|
|
71
|
-
return tmpdir
|
|
72
|
-
`;
|