jupyterpack 0.2.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.
Files changed (60) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +79 -0
  3. package/lib/document/iframePanel.d.ts +7 -0
  4. package/lib/document/iframePanel.js +20 -0
  5. package/lib/document/jupyterpackDocWidget.d.ts +9 -0
  6. package/lib/document/jupyterpackDocWidget.js +16 -0
  7. package/lib/document/plugin.d.ts +2 -0
  8. package/lib/document/plugin.js +31 -0
  9. package/lib/document/widgetFactory.d.ts +23 -0
  10. package/lib/document/widgetFactory.js +60 -0
  11. package/lib/index.d.ts +2 -0
  12. package/lib/index.js +3 -0
  13. package/lib/pythonWidget/connectionManager.d.ts +18 -0
  14. package/lib/pythonWidget/connectionManager.js +27 -0
  15. package/lib/pythonWidget/kernelExecutor.d.ts +27 -0
  16. package/lib/pythonWidget/kernelExecutor.js +104 -0
  17. package/lib/pythonWidget/pythonWidget.d.ts +13 -0
  18. package/lib/pythonWidget/pythonWidget.js +22 -0
  19. package/lib/pythonWidget/pythonWidgetModel.d.ts +29 -0
  20. package/lib/pythonWidget/pythonWidgetModel.js +75 -0
  21. package/lib/sandpackWidget/sandpackFilesModel.d.ts +27 -0
  22. package/lib/sandpackWidget/sandpackFilesModel.js +114 -0
  23. package/lib/sandpackWidget/sandpackPanel.d.ts +16 -0
  24. package/lib/sandpackWidget/sandpackPanel.js +52 -0
  25. package/lib/swConnection/comm_manager.d.ts +6 -0
  26. package/lib/swConnection/comm_manager.js +46 -0
  27. package/lib/swConnection/connection_manager.d.ts +18 -0
  28. package/lib/swConnection/connection_manager.js +27 -0
  29. package/lib/swConnection/index.d.ts +3 -0
  30. package/lib/swConnection/index.js +68 -0
  31. package/lib/swConnection/sw.d.ts +1 -0
  32. package/lib/swConnection/sw.js +49 -0
  33. package/lib/token.d.ts +3 -0
  34. package/lib/token.js +2 -0
  35. package/lib/tools.d.ts +2 -0
  36. package/lib/tools.js +17 -0
  37. package/lib/type.d.ts +38 -0
  38. package/lib/type.js +9 -0
  39. package/package.json +199 -0
  40. package/src/document/iframePanel.ts +25 -0
  41. package/src/document/jupyterpackDocWidget.ts +19 -0
  42. package/src/document/plugin.ts +44 -0
  43. package/src/document/widgetFactory.ts +79 -0
  44. package/src/index.ts +4 -0
  45. package/src/pythonWidget/connectionManager.ts +43 -0
  46. package/src/pythonWidget/kernelExecutor.ts +140 -0
  47. package/src/pythonWidget/pythonWidget.ts +34 -0
  48. package/src/pythonWidget/pythonWidgetModel.ts +107 -0
  49. package/src/sandpackWidget/sandpackFilesModel.ts +141 -0
  50. package/src/sandpackWidget/sandpackPanel.ts +82 -0
  51. package/src/swConnection/comm_manager.ts +53 -0
  52. package/src/swConnection/connection_manager.ts +43 -0
  53. package/src/swConnection/index.ts +93 -0
  54. package/src/swConnection/sw.ts +57 -0
  55. package/src/token.ts +6 -0
  56. package/src/tools.ts +18 -0
  57. package/src/type.ts +44 -0
  58. package/style/base.css +51 -0
  59. package/style/index.css +1 -0
  60. package/style/index.js +1 -0
@@ -0,0 +1,107 @@
1
+ import { DocumentRegistry } from '@jupyterlab/docregistry';
2
+ import { IDisposable } from '@lumino/disposable';
3
+ import {
4
+ ServiceManager,
5
+ Session,
6
+ Kernel,
7
+ Contents
8
+ } from '@jupyterlab/services';
9
+ import { PromiseDelegate } from '@lumino/coreutils';
10
+ import { IConnectionManager, IJupyterPackFileFormat } from '../type';
11
+ import { KernelExecutor } from './kernelExecutor';
12
+ import { PathExt } from '@jupyterlab/coreutils';
13
+
14
+ export class PythonWidgetModel implements IDisposable {
15
+ constructor(options: PythonWidgetModel.IOptions) {
16
+ this._context = options.context;
17
+ this._manager = options.manager;
18
+ this._connectionManager = options.connectionManager;
19
+ this._contentsManager = options.contentsManager;
20
+ }
21
+
22
+ get isDisposed(): boolean {
23
+ return this._isDisposed;
24
+ }
25
+ get connectionManager(): IConnectionManager {
26
+ return this._connectionManager;
27
+ }
28
+ async initialize(): Promise<{
29
+ instanceId: string;
30
+ kernelClientId: string;
31
+ } | null> {
32
+ if (this._kernelStarted) {
33
+ return null;
34
+ }
35
+ const filePath = this._context.localPath;
36
+ const spkContent =
37
+ this._context.model.toJSON() as any as IJupyterPackFileFormat;
38
+
39
+ const entryPath = PathExt.join(PathExt.dirname(filePath), spkContent.entry);
40
+
41
+ const entryContent = await this._contentsManager.get(entryPath, {
42
+ content: true,
43
+ format: 'text'
44
+ });
45
+ const sessionManager = this._manager.sessions;
46
+ await sessionManager.ready;
47
+ await this._manager.kernelspecs.ready;
48
+ const specs = this._manager.kernelspecs.specs;
49
+ if (!specs) {
50
+ return null;
51
+ }
52
+ const { kernelspecs } = specs;
53
+ let kernelName = Object.keys(kernelspecs)[0];
54
+ if (kernelspecs[specs.default]) {
55
+ kernelName = specs.default;
56
+ }
57
+
58
+ this._sessionConnection = await sessionManager.startNew({
59
+ name: filePath,
60
+ path: filePath,
61
+ kernel: {
62
+ name: kernelName
63
+ },
64
+ type: 'notebook'
65
+ });
66
+ const executor = new KernelExecutor({
67
+ sessionConnection: this._sessionConnection
68
+ });
69
+ const data = await this._connectionManager.registerConnection(executor);
70
+ await executor.init({
71
+ initCode: entryContent.content,
72
+ ...data
73
+ });
74
+ const finish = new PromiseDelegate<void>();
75
+ const cb = (_: Kernel.IKernelConnection, status: Kernel.Status) => {
76
+ if (status === 'idle') {
77
+ this._sessionConnection!.kernel?.statusChanged.disconnect(cb);
78
+ finish.resolve();
79
+ }
80
+ };
81
+ this._sessionConnection.kernel?.statusChanged.connect(cb);
82
+
83
+ await finish.promise;
84
+ this._kernelStarted = true;
85
+ return data;
86
+ }
87
+ dispose(): void {
88
+ this._isDisposed = true;
89
+ }
90
+
91
+ private _isDisposed = false;
92
+ private _kernelStarted = false;
93
+ private _sessionConnection: Session.ISessionConnection | undefined;
94
+ private _manager: ServiceManager.IManager;
95
+ private _context: DocumentRegistry.IContext<DocumentRegistry.IModel>;
96
+ private _connectionManager: IConnectionManager;
97
+ private _contentsManager: Contents.IManager;
98
+ }
99
+
100
+ export namespace PythonWidgetModel {
101
+ export interface IOptions {
102
+ context: DocumentRegistry.IContext<DocumentRegistry.IModel>;
103
+ manager: ServiceManager.IManager;
104
+ connectionManager: IConnectionManager;
105
+ contentsManager: Contents.IManager;
106
+ }
107
+ }
@@ -0,0 +1,141 @@
1
+ import { Contents } from '@jupyterlab/services';
2
+ import { ISignal, Signal } from '@lumino/signaling';
3
+
4
+ import { IDict } from '../type';
5
+ import { removePrefix } from '../tools';
6
+
7
+ export class SandpackFilesModel {
8
+ constructor(options: { contentsManager: Contents.IManager; path: string }) {
9
+ this._contentManager = options.contentsManager;
10
+ this._path = options.path;
11
+ this._contentManager.fileChanged.connect(this._onFileChanged, this);
12
+ }
13
+
14
+ async getAllFiles(): Promise<IDict<{ code: string }>> {
15
+ if (!this._allFiles) {
16
+ const files = await this._contentManager.get(this._path, {
17
+ content: true
18
+ });
19
+ this._allFiles = await this.flattenDirectory(files);
20
+ }
21
+
22
+ return this._allFiles;
23
+ }
24
+
25
+ get fileChanged(): ISignal<
26
+ SandpackFilesModel,
27
+ { allFiles: IDict<{ code: string }> }
28
+ > {
29
+ return this._fileChanged;
30
+ }
31
+
32
+ async flattenDirectory(
33
+ dirContent: Contents.IModel
34
+ ): Promise<IDict<{ code: string }>> {
35
+ const flatDict: IDict<{ code: string }> = {};
36
+ const content = dirContent.content as Contents.IModel[];
37
+
38
+ for (const item of content) {
39
+ if (item.type === 'file') {
40
+ const pathWithoutRoot = this._removeRoot(item.path);
41
+ let itemContent = item;
42
+ if (!itemContent.content) {
43
+ itemContent = await this._getContent(item.path);
44
+ }
45
+ if (
46
+ itemContent.mimetype === 'application/json' &&
47
+ typeof itemContent.content !== 'string'
48
+ ) {
49
+ flatDict[pathWithoutRoot] = {
50
+ code: JSON.stringify(itemContent.content)
51
+ };
52
+ } else {
53
+ flatDict[pathWithoutRoot] = { code: itemContent.content };
54
+ }
55
+ } else if (item.type === 'directory') {
56
+ // If it's a directory, recursively flatten its content
57
+ const nestedContent = await this._getContent(item.path);
58
+ const nestedDict = await this.flattenDirectory(nestedContent);
59
+ Object.assign(flatDict, nestedDict);
60
+ }
61
+ }
62
+ return flatDict;
63
+ }
64
+
65
+ private async _onFileChanged(
66
+ sender: Contents.IManager,
67
+ args: Contents.IChangedArgs
68
+ ) {
69
+ switch (args.type) {
70
+ case 'save': {
71
+ if (args.newValue?.path) {
72
+ const newContent = await this._getContent(args.newValue.path);
73
+ const pathWithoutRoot = this._removeRoot(args.newValue.path);
74
+ if (this._allFiles) {
75
+ this._allFiles[pathWithoutRoot] = {
76
+ code: newContent.content
77
+ };
78
+ }
79
+ }
80
+ break;
81
+ }
82
+ case 'delete': {
83
+ if (args.oldValue?.path) {
84
+ const pathWithoutRoot = this._removeRoot(args.oldValue.path);
85
+ if (this._allFiles) {
86
+ delete this._allFiles[pathWithoutRoot];
87
+ }
88
+ }
89
+ break;
90
+ }
91
+ case 'rename': {
92
+ if (args.oldValue?.path && args.newValue?.path) {
93
+ const oldPathWithoutRoot = this._removeRoot(args.oldValue.path);
94
+ const newPathWithoutRoot = this._removeRoot(args.newValue.path);
95
+ if (this._allFiles) {
96
+ this._allFiles[newPathWithoutRoot] =
97
+ this._allFiles[oldPathWithoutRoot];
98
+ delete this._allFiles[newPathWithoutRoot];
99
+ }
100
+ }
101
+ break;
102
+ }
103
+ case 'new': {
104
+ if (args.newValue?.path) {
105
+ const newContent = await this._getContent(args.newValue.path);
106
+ const pathWithoutRoot = this._removeRoot(args.newValue.path);
107
+ if (this._allFiles) {
108
+ this._allFiles[pathWithoutRoot] = {
109
+ code: newContent.content
110
+ };
111
+ }
112
+ }
113
+ break;
114
+ }
115
+
116
+ default:
117
+ break;
118
+ }
119
+ if (this._allFiles) {
120
+ this._fileChanged.emit({
121
+ allFiles: this._allFiles
122
+ });
123
+ }
124
+ }
125
+
126
+ private _removeRoot(path: string): string {
127
+ return removePrefix(path, this._path);
128
+ }
129
+ private _getContent(path: string): Promise<Contents.IModel> {
130
+ return this._contentManager.get(path, { content: true });
131
+ }
132
+ private _path: string;
133
+ private _fileChanged = new Signal<
134
+ this,
135
+ { allFiles: IDict<{ code: string }> }
136
+ >(this);
137
+
138
+ private _contentManager: Contents.IManager;
139
+
140
+ private _allFiles?: IDict<{ code: string }>;
141
+ }
@@ -0,0 +1,82 @@
1
+ import {
2
+ ClientOptions,
3
+ loadSandpackClient,
4
+ SandpackClient
5
+ } from '@codesandbox/sandpack-client';
6
+ import { DocumentRegistry } from '@jupyterlab/docregistry';
7
+ import { Contents } from '@jupyterlab/services';
8
+
9
+ import { IFramePanel } from '../document/iframePanel';
10
+ import { IDict } from '../type';
11
+ import { SandpackFilesModel } from './sandpackFilesModel';
12
+
13
+ export class SandpackPanel extends IFramePanel {
14
+ constructor(options: {
15
+ context: DocumentRegistry.IContext<DocumentRegistry.IModel>;
16
+ contentsManager: Contents.IManager;
17
+ }) {
18
+ super();
19
+ this._contentsManager = options.contentsManager;
20
+ options.context.ready.then(async () => {
21
+ await this.init(options.context.localPath);
22
+ });
23
+ }
24
+
25
+ async init(localPath: string) {
26
+ const currentDir = localPath.split('/').slice(0, -1).join('/');
27
+ const filesModel = new SandpackFilesModel({
28
+ contentsManager: this._contentsManager,
29
+ path: currentDir
30
+ });
31
+ const allFiles = await filesModel.getAllFiles();
32
+
33
+ const options: ClientOptions = {
34
+ showLoadingScreen: true,
35
+ showOpenInCodeSandbox: false
36
+ };
37
+
38
+ this._spClient = await loadSandpackClient(
39
+ this._iframe,
40
+ {
41
+ files: allFiles
42
+ },
43
+ options
44
+ );
45
+ await this.connectSignals(filesModel, this._spClient);
46
+ }
47
+
48
+ async connectSignals(
49
+ filesModel: SandpackFilesModel,
50
+ sandpackClient: SandpackClient
51
+ ) {
52
+ filesModel.fileChanged.connect(this._onFileChanged, this);
53
+ sandpackClient.listen(msg => {
54
+ switch (msg.type) {
55
+ case 'start': {
56
+ this.toggleSpinner(true);
57
+ break;
58
+ }
59
+ case 'success': {
60
+ this.toggleSpinner(false);
61
+ break;
62
+ }
63
+ default:
64
+ break;
65
+ }
66
+ });
67
+ }
68
+
69
+ private _onFileChanged(
70
+ sender: SandpackFilesModel,
71
+ { allFiles }: { allFiles: IDict<{ code: string }> }
72
+ ) {
73
+ if (this._spClient) {
74
+ this._spClient.updateSandbox({
75
+ files: allFiles
76
+ });
77
+ }
78
+ }
79
+
80
+ private _spClient?: SandpackClient;
81
+ private _contentsManager: Contents.IManager;
82
+ }
@@ -0,0 +1,53 @@
1
+ import { IConnectionManager, IDict } from '../type';
2
+ import { wrap, transfer } from 'comlink';
3
+ export class CommManager {
4
+ constructor() {}
5
+ registerComm(instanceId: string, port: MessagePort): void {
6
+ const comm = wrap<Omit<IConnectionManager, 'registerConnection'>>(port);
7
+
8
+ this._commIds.set(instanceId, comm);
9
+ }
10
+ async generateResponse(request: Request): Promise<Response> {
11
+ const url = new URL(request.url);
12
+ const urlPath = url.pathname;
13
+ const method = request.method;
14
+ const requestHeaders: IDict = {};
15
+
16
+ for (const pair of request.headers.entries()) {
17
+ if (!pair[0].startsWith('sec-ch-ua')) {
18
+ requestHeaders[pair[0]] = pair[1];
19
+ }
20
+ }
21
+ const params = url.searchParams.toString();
22
+ const pathAfterExtensionName = urlPath.split('/jupyterpack/static')[1];
23
+ const pathList = pathAfterExtensionName.split('/').filter(Boolean);
24
+ const instanceId = pathList[0];
25
+ const kernelClientId = pathList[2];
26
+
27
+ const comm = this._commIds.get(instanceId);
28
+ if (!comm) {
29
+ throw new Error('Missing comm');
30
+ }
31
+ const requestBody = request.body ? await request.arrayBuffer() : undefined;
32
+ const data = await comm.generateResponse({
33
+ kernelClientId,
34
+ urlPath,
35
+ method,
36
+ headers: requestHeaders,
37
+ requestBody: requestBody
38
+ ? transfer(requestBody, [requestBody])
39
+ : undefined,
40
+ params
41
+ });
42
+ if (data) {
43
+ const { headers, content, status_code } = data;
44
+ return new Response(content, { status: status_code, headers });
45
+ }
46
+ return await fetch(url, { method });
47
+ }
48
+
49
+ private _commIds: Map<
50
+ string,
51
+ Omit<IConnectionManager, 'registerConnection'>
52
+ > = new Map();
53
+ }
@@ -0,0 +1,43 @@
1
+ import { IConnectionManager, IDict, IKernelExecutor } from '../type';
2
+ import { UUID } from '@lumino/coreutils';
3
+
4
+ export class ConnectionManager implements IConnectionManager {
5
+ constructor(public instanceId: string) {}
6
+
7
+ async registerConnection(
8
+ kernelExecutor: IKernelExecutor
9
+ ): Promise<{ instanceId: string; kernelClientId: string }> {
10
+ const uuid = UUID.uuid4();
11
+
12
+ this._kernelExecutors.set(uuid, kernelExecutor);
13
+
14
+ return { instanceId: this.instanceId, kernelClientId: uuid };
15
+ }
16
+
17
+ async generateResponse(options: {
18
+ kernelClientId: string;
19
+ urlPath: string;
20
+ method: string;
21
+ headers: IDict;
22
+ requestBody?: ArrayBuffer;
23
+ params?: string;
24
+ }): Promise<IDict | null> {
25
+ const { urlPath, kernelClientId, method, params, requestBody, headers } =
26
+ options;
27
+ const executor = this._kernelExecutors.get(kernelClientId);
28
+ if (!executor) {
29
+ return null;
30
+ }
31
+
32
+ const response = await executor.getResponse({
33
+ urlPath,
34
+ method,
35
+ params,
36
+ headers,
37
+ requestBody
38
+ });
39
+ return response;
40
+ }
41
+
42
+ private _kernelExecutors = new Map<string, IKernelExecutor>();
43
+ }
@@ -0,0 +1,93 @@
1
+ import {
2
+ JupyterFrontEnd,
3
+ JupyterFrontEndPlugin
4
+ } from '@jupyterlab/application';
5
+ import { PageConfig, URLExt } from '@jupyterlab/coreutils';
6
+ import { UUID } from '@lumino/coreutils';
7
+ import { expose } from 'comlink';
8
+
9
+ import { IConnectionManagerToken } from '../token';
10
+ import { IConnectionManager, MessageAction } from '../type';
11
+ import { ConnectionManager } from './connection_manager';
12
+
13
+ const fullLabextensionsUrl = PageConfig.getOption('fullLabextensionsUrl');
14
+ const SCOPE = `${fullLabextensionsUrl}/jupyterpack/static`;
15
+ async function initServiceWorker(): Promise<ServiceWorker | undefined | null> {
16
+ if (!('serviceWorker' in navigator)) {
17
+ console.error('Cannot start extension without service worker');
18
+
19
+ return;
20
+ }
21
+
22
+ const fullWorkerUrl = `${SCOPE}/service-worker.js`;
23
+
24
+ try {
25
+ const reg = await navigator.serviceWorker.register(fullWorkerUrl);
26
+
27
+ if (!reg) {
28
+ console.error('Missing service worker registration');
29
+ return;
30
+ }
31
+ await reg.update();
32
+ if (reg.installing) {
33
+ const sw = reg.installing || reg.waiting;
34
+ sw.onstatechange = () => {
35
+ if (sw.state === 'installed') {
36
+ window.location.reload();
37
+ }
38
+ };
39
+ }
40
+ if (reg.active) {
41
+ return reg.active;
42
+ }
43
+
44
+ console.log(
45
+ 'Service worker newly registered',
46
+ await navigator.serviceWorker.getRegistration(fullWorkerUrl)
47
+ );
48
+ return reg.active;
49
+ } catch (e) {
50
+ console.error('Failed to register service worker', e);
51
+
52
+ return;
53
+ }
54
+ }
55
+
56
+ function createPingFrame() {
57
+ const iframe = document.createElement('iframe');
58
+ iframe.style.display = 'none';
59
+ iframe.src = URLExt.join(SCOPE, '__jupyterpack__', 'ping.html');
60
+ document.body.appendChild(iframe);
61
+ }
62
+
63
+ export const swPlugin: JupyterFrontEndPlugin<IConnectionManager> = {
64
+ id: 'jupyterpack:service-worker-plugin',
65
+ description: 'jupyterpack service worker plugin',
66
+ autoStart: true,
67
+ provides: IConnectionManagerToken,
68
+ activate: async (app: JupyterFrontEnd): Promise<IConnectionManager> => {
69
+ console.log('Activating jupyterpack service worker');
70
+ const serviceWorker = await initServiceWorker();
71
+ if (!serviceWorker) {
72
+ throw new Error(
73
+ 'Failed to register the Service Worker, please make sure to use a browser that supports this feature.'
74
+ );
75
+ }
76
+
77
+ const instanceId = UUID.uuid4();
78
+ const { port1: mainToServiceWorker, port2: serviceWorkerToMain } =
79
+ new MessageChannel();
80
+
81
+ const connectionManager = new ConnectionManager(instanceId);
82
+ expose(connectionManager, mainToServiceWorker);
83
+ serviceWorker.postMessage(
84
+ { type: MessageAction.INIT, data: { instanceId } },
85
+ [serviceWorkerToMain]
86
+ );
87
+ setTimeout(() => {
88
+ createPingFrame();
89
+ }, 10000);
90
+
91
+ return connectionManager;
92
+ }
93
+ };
@@ -0,0 +1,57 @@
1
+ // import { expose } from 'comlink';
2
+ import { MessageAction } from '../type';
3
+ import { CommManager } from './comm_manager';
4
+
5
+ const COMM_MANAGER = new CommManager();
6
+ /**
7
+ * Install event listeners
8
+ */
9
+ self.addEventListener('install', onInstall);
10
+ self.addEventListener('activate', onActivate);
11
+ self.addEventListener('fetch', onFetch);
12
+ self.addEventListener('message', onMessage);
13
+
14
+ /**
15
+ * Handle installation
16
+ */
17
+ async function onInstall(event: ExtendableEvent): Promise<void> {
18
+ await self.skipWaiting();
19
+ }
20
+
21
+ /**
22
+ * Handle activation.
23
+ */
24
+ async function onActivate(event: ExtendableEvent): Promise<void> {
25
+ event.waitUntil(self.clients.claim());
26
+ }
27
+
28
+ /**
29
+ * Handle fetching a single resource.
30
+ */
31
+ async function onFetch(event: FetchEvent): Promise<void> {
32
+ const url = event.request.url;
33
+ if (url.endsWith('__jupyterpack__/ping')) {
34
+ return event.respondWith(new Response('pong'));
35
+ }
36
+ if (url.endsWith('__jupyterpack__/ping.html')) {
37
+ return;
38
+ }
39
+ event.respondWith(COMM_MANAGER.generateResponse(event.request));
40
+ }
41
+
42
+ function onMessage(
43
+ msg: MessageEvent<{ type: MessageAction; data: any }>
44
+ ): void {
45
+ const { type, data } = msg.data;
46
+
47
+ switch (type) {
48
+ case MessageAction.INIT: {
49
+ const { instanceId } = data;
50
+ const serviceWorkerToMain = msg.ports[0];
51
+ COMM_MANAGER.registerComm(instanceId, serviceWorkerToMain);
52
+ break;
53
+ }
54
+ default:
55
+ break;
56
+ }
57
+ }
package/src/token.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { Token } from '@lumino/coreutils';
2
+ import { IConnectionManager } from './type';
3
+
4
+ export const IConnectionManagerToken = new Token<IConnectionManager>(
5
+ 'jupyterpack:connection-manager'
6
+ );
package/src/tools.ts ADDED
@@ -0,0 +1,18 @@
1
+ export function removePrefix(path: string, prefix: string): string {
2
+ if (path.startsWith(prefix)) {
3
+ return path.slice(prefix.length);
4
+ }
5
+ // If the prefix doesn't match, return the original path
6
+ return path;
7
+ }
8
+
9
+ export function arrayBufferToBase64(buffer: ArrayBuffer) {
10
+ let binary = '';
11
+ const bytes = new Uint8Array(buffer);
12
+ const chunkSize = 32768; // process in chunks for large buffers
13
+ for (let i = 0; i < bytes.length; i += chunkSize) {
14
+ const chunk = bytes.subarray(i, i + chunkSize);
15
+ binary += String.fromCharCode(...chunk);
16
+ }
17
+ return btoa(binary);
18
+ }
package/src/type.ts ADDED
@@ -0,0 +1,44 @@
1
+ import { KernelMessage } from '@jupyterlab/services';
2
+ import { IDisposable } from '@lumino/disposable';
3
+
4
+ export interface IDict<T = any> {
5
+ [key: string]: T;
6
+ }
7
+
8
+ export enum JupyterPackFramework {
9
+ REACT = 'react',
10
+ DASH = 'dash'
11
+ }
12
+ export interface IJupyterPackFileFormat {
13
+ entry: string;
14
+ framework: JupyterPackFramework;
15
+ name?: string;
16
+ metadata?: IDict;
17
+ }
18
+
19
+ export enum MessageAction {
20
+ INIT = 'INIT'
21
+ }
22
+
23
+ export interface IKernelExecutorParams {
24
+ method: string;
25
+ urlPath: string;
26
+ headers: IDict;
27
+ params?: string;
28
+ requestBody?: ArrayBuffer;
29
+ }
30
+ export interface IKernelExecutor extends IDisposable {
31
+ getResponse(options: IKernelExecutorParams): Promise<IDict>;
32
+ executeCode(
33
+ code: KernelMessage.IExecuteRequestMsg['content']
34
+ ): Promise<string>;
35
+ }
36
+
37
+ export interface IConnectionManager {
38
+ registerConnection(
39
+ kernelExecutor: IKernelExecutor
40
+ ): Promise<{ instanceId: string; kernelClientId: string }>;
41
+ generateResponse(
42
+ option: { kernelClientId: string } & IKernelExecutorParams
43
+ ): Promise<IDict | null>;
44
+ }
package/style/base.css ADDED
@@ -0,0 +1,51 @@
1
+ /*
2
+ See the JupyterLab Developer Guide for useful CSS Patterns:
3
+
4
+ https://jupyterlab.readthedocs.io/en/stable/developer/css.html
5
+ */
6
+
7
+ .jp-jupyterpack-document-panel {
8
+ display: flex;
9
+ flex-direction: column;
10
+ }
11
+
12
+ .jupyterpack-iframe-panel {
13
+ display: flex;
14
+ flex-grow: 1;
15
+ }
16
+
17
+ .jupyterpack-iframe-panel iframe {
18
+ flex-grow: 1;
19
+ border: 0;
20
+ }
21
+
22
+ .jupyterpack-spinner {
23
+ top: calc(50% - 4px);
24
+ left: calc(50% - 4px);
25
+ width: 8px;
26
+ height: 8px;
27
+ position: relative;
28
+ border-radius: 50%;
29
+ background: rgb(212 72 13 / 100%);
30
+ animation: wave 1.5s ease-in infinite;
31
+ }
32
+
33
+ @keyframes wave {
34
+ 0% {
35
+ box-shadow:
36
+ 0 0 0 0 rgb(212 72 13 / 0%),
37
+ 0 0 0 10px rgb(212 72 13 / 20%),
38
+ 0 0 0 20px rgb(212 72 13 / 60%),
39
+ 0 0 0 30px rgb(212 72 13 / 40%),
40
+ 0 0 0 40px rgb(212 72 13 / 20%);
41
+ }
42
+
43
+ 100% {
44
+ box-shadow:
45
+ 0 0 0 40px rgb(212 72 13 / 0%),
46
+ 0 0 0 30px rgb(212 72 13 / 20%),
47
+ 0 0 0 20px rgb(212 72 13 / 40%),
48
+ 0 0 0 10px rgb(212 72 13 / 60%),
49
+ 0 0 0 0 rgb(212 72 13 / 100%);
50
+ }
51
+ }
@@ -0,0 +1 @@
1
+ @import url('base.css');