@fails-components/jupyter-filesystem-extension 0.0.1-alpha.9 → 0.0.2

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/drive.d.ts ADDED
@@ -0,0 +1,50 @@
1
+ import { Contents, Drive, ServerConnection } from '@jupyterlab/services';
2
+ import { ISignal } from '@lumino/signaling';
3
+ interface IContentEvent {
4
+ task: string;
5
+ }
6
+ export interface ILoadJupyterContentEvent extends IContentEvent {
7
+ task: 'loadFile';
8
+ fileData: object | undefined;
9
+ fileName: string;
10
+ }
11
+ export interface ISavedJupyterContentEvent extends IContentEvent {
12
+ task: 'savedFile';
13
+ fileName: string;
14
+ }
15
+ export type IContentEventType = ILoadJupyterContentEvent | ISavedJupyterContentEvent;
16
+ type IModel = Contents.IModel;
17
+ export declare class FailsDrive implements Contents.IDrive {
18
+ constructor(options: Drive.IOptions);
19
+ dispose(): void;
20
+ get isDisposed(): boolean;
21
+ get name(): string;
22
+ get serverSettings(): ServerConnection.ISettings;
23
+ get fileChanged(): ISignal<Contents.IDrive, Contents.IChangedArgs>;
24
+ getDownloadUrl(path: string): Promise<string>;
25
+ onMessage(event: IContentEventType): Promise<any>;
26
+ get(path: string, options?: Contents.IFetchOptions): Promise<IModel>;
27
+ save(path: string, options?: Partial<Contents.IModel>): Promise<Contents.IModel>;
28
+ newUntitled(options?: Contents.ICreateOptions): Promise<IModel>;
29
+ rename(oldLocalPath: string, newLocalPath: string): Promise<IModel>;
30
+ delete(path: string): Promise<void>;
31
+ copy(path: string, toDir: string): Promise<IModel>;
32
+ createCheckpoint(path: string): Promise<Contents.ICheckpointModel>;
33
+ listCheckpoints(path: string): Promise<Contents.ICheckpointModel[]>;
34
+ restoreCheckpoint(path: string, checkpointID: string): Promise<void>;
35
+ deleteCheckpoint(path: string, checkpointID: string): Promise<void>;
36
+ static EMPTY_NB: {
37
+ metadata: {
38
+ orig_nbformat: number;
39
+ };
40
+ nbformat_minor: number;
41
+ nbformat: number;
42
+ cells: never[];
43
+ };
44
+ private _fileContent;
45
+ private _isDisposed;
46
+ private _fileChanged;
47
+ private _fileName;
48
+ private _serverSettings;
49
+ }
50
+ export {};
@@ -1,21 +1,44 @@
1
- import { PromiseDelegate } from '@lumino/coreutils';
1
+ import { ServerConnection } from '@jupyterlab/services';
2
+ import { Signal } from '@lumino/signaling';
2
3
  // portions used from Jupyterlab:
3
4
  /* -----------------------------------------------------------------------------
4
5
  | Copyright (c) Jupyter Development Team.
5
6
  | Distributed under the terms of the Modified BSD License.
6
7
  |----------------------------------------------------------------------------*/
7
8
  // This code contains portions from or is inspired by Jupyter lab and lite
9
+ // especially the Drive implementation
8
10
  const jsonMime = 'application/json';
9
- class FailsContents {
10
- constructor() {
11
- this._fileContent = JSON.stringify(FailsContents.EMPTY_NB);
11
+ class FailsDrive {
12
+ constructor(options) {
13
+ var _a;
14
+ this._fileContent = JSON.stringify(FailsDrive.EMPTY_NB);
15
+ this._isDisposed = false;
16
+ this._fileChanged = new Signal(this);
12
17
  this._fileName = 'unloaded.ipynb';
13
- this._ready = new PromiseDelegate();
14
- if (!window.failsCallbacks) {
15
- window.failsCallbacks = {};
18
+ this._serverSettings =
19
+ (_a = options.serverSettings) !== null && _a !== void 0 ? _a : ServerConnection.makeSettings();
20
+ }
21
+ dispose() {
22
+ if (this.isDisposed) {
23
+ return;
16
24
  }
17
- this._failsCallbacks = window.failsCallbacks;
18
- this._failsCallbacks.callContents = this.onMessage.bind(this);
25
+ this._isDisposed = true;
26
+ Signal.clearData(this);
27
+ }
28
+ get isDisposed() {
29
+ return this._isDisposed;
30
+ }
31
+ get name() {
32
+ return 'JupyterFailsSingleFileDrive';
33
+ }
34
+ get serverSettings() {
35
+ return this._serverSettings;
36
+ }
37
+ get fileChanged() {
38
+ return this._fileChanged;
39
+ }
40
+ async getDownloadUrl(path) {
41
+ throw new Error('Method not implemented.');
19
42
  }
20
43
  async onMessage(event) {
21
44
  // todo handle events
@@ -23,8 +46,24 @@ class FailsContents {
23
46
  case 'loadFile':
24
47
  {
25
48
  const loadevent = event;
26
- this._fileContent = JSON.stringify(loadevent.fileData || FailsContents.EMPTY_NB);
49
+ this._fileContent = JSON.stringify(loadevent.fileData || FailsDrive.EMPTY_NB);
27
50
  this._fileName = loadevent.fileName;
51
+ this._fileChanged.emit({
52
+ type: 'save',
53
+ oldValue: null,
54
+ newValue: {
55
+ name: this._fileName,
56
+ path: this._fileName,
57
+ last_modified: new Date(0).toISOString(),
58
+ created: new Date(0).toISOString(),
59
+ format: 'json',
60
+ mimetype: jsonMime,
61
+ content: JSON.parse(this._fileContent),
62
+ size: 0,
63
+ writable: true,
64
+ type: 'notebook'
65
+ }
66
+ });
28
67
  }
29
68
  break;
30
69
  case 'savedFile':
@@ -40,12 +79,6 @@ class FailsContents {
40
79
  break;
41
80
  }
42
81
  }
43
- get ready() {
44
- return this._ready.promise;
45
- }
46
- async initialize() {
47
- this._ready.resolve(void 0);
48
- }
49
82
  async get(path, options) {
50
83
  // remove leading slash
51
84
  path = decodeURIComponent(path.replace(/^\//, ''));
@@ -79,13 +112,13 @@ class FailsContents {
79
112
  if (path === this._fileName) {
80
113
  return serverFile;
81
114
  }
82
- return null; // not found
115
+ throw Error(`Could not find content with path ${path}`);
83
116
  }
84
117
  async save(path, options = {}) {
85
118
  path = decodeURIComponent(path);
86
119
  if (path !== this._fileName) {
87
120
  // we only allow the proxy object
88
- return null;
121
+ throw Error(`File ${path} is not the proxy file`);
89
122
  }
90
123
  const chunk = options.chunk;
91
124
  const chunked = chunk ? chunk > 1 || chunk === -1 : false;
@@ -93,7 +126,7 @@ class FailsContents {
93
126
  content: chunked
94
127
  });
95
128
  if (!item) {
96
- return null;
129
+ throw Error(`Could not find file with path ${path}`);
97
130
  }
98
131
  const modified = new Date().toISOString();
99
132
  // override with the new values
@@ -122,9 +155,19 @@ class FailsContents {
122
155
  size: newcontent.length
123
156
  };
124
157
  this._fileContent = JSON.stringify(newcontent); // no parsing
158
+ this._fileChanged.emit({
159
+ type: 'save',
160
+ oldValue: null,
161
+ newValue: item
162
+ });
125
163
  return item;
126
164
  }
127
165
  this._fileContent = JSON.stringify(item.content); // no parsing
166
+ this._fileChanged.emit({
167
+ type: 'save',
168
+ oldValue: null,
169
+ newValue: item
170
+ });
128
171
  return item;
129
172
  }
130
173
  // For fails creating a new file is not allowed, so no need to implment it
@@ -154,7 +197,7 @@ class FailsContents {
154
197
  throw new Error('deleteCheckpoint not (yet?) implemented');
155
198
  }
156
199
  }
157
- FailsContents.EMPTY_NB = {
200
+ FailsDrive.EMPTY_NB = {
158
201
  metadata: {
159
202
  orig_nbformat: 4
160
203
  },
@@ -162,4 +205,4 @@ FailsContents.EMPTY_NB = {
162
205
  nbformat: 4,
163
206
  cells: []
164
207
  };
165
- export { FailsContents };
208
+ export { FailsDrive };
package/lib/index.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- import { IContents } from '@jupyterlite/contents';
2
- import { JupyterLiteServerPlugin } from '@jupyterlite/server';
3
- export declare const failsContentsPlugin: JupyterLiteServerPlugin<IContents>;
4
- declare const plugins: JupyterLiteServerPlugin<any>[];
1
+ import { ServiceManagerPlugin } from '@jupyterlab/services';
2
+ export * from './token';
3
+ declare const plugins: ServiceManagerPlugin<any>[];
5
4
  export default plugins;
package/lib/index.js CHANGED
@@ -1,37 +1,60 @@
1
- import { IContents } from '@jupyterlite/contents';
2
- import { ISettings } from '@jupyterlite/settings';
3
- import { FailsContents } from './contents';
1
+ import { IDefaultDrive, ISettingManager, IServerSettings } from '@jupyterlab/services';
2
+ import { FailsDrive } from './drive';
4
3
  import { FailsSettings } from './settings';
5
- export const failsContentsPlugin = {
6
- id: '@fails-components/jupyter-fails-server:contents',
4
+ import { IFailsDriveMessages } from './token';
5
+ export * from './token';
6
+ const failsDriveMessages = {
7
+ id: '@fails-components/jupyter-applet-widget:drivemessages',
7
8
  requires: [],
8
9
  autoStart: true,
9
- provides: IContents,
10
- activate: (app) => {
11
- if (app.namespace !== 'JupyterLite Server') {
12
- console.log('Not on server');
13
- }
14
- const contents = new FailsContents();
15
- app.started.then(() => contents.initialize().catch(console.warn));
16
- return contents;
10
+ provides: IFailsDriveMessages,
11
+ activate: (_) => {
12
+ let initialWaitRes;
13
+ const initialWait = new Promise(resolve => (initialWaitRes = resolve));
14
+ let messageHandler;
15
+ const driveMessages = {
16
+ registerMessageHandler: (handler) => {
17
+ messageHandler = handler;
18
+ if (initialWaitRes) {
19
+ initialWaitRes(undefined);
20
+ }
21
+ initialWaitRes = undefined;
22
+ },
23
+ sendMessage: async (msg) => {
24
+ await initialWait;
25
+ return messageHandler(msg);
26
+ }
27
+ };
28
+ return driveMessages;
29
+ }
30
+ };
31
+ const failsDrivePlugin = {
32
+ id: '@fails-components/jupyter-applet-widget:drive',
33
+ requires: [IFailsDriveMessages],
34
+ autoStart: true,
35
+ provides: IDefaultDrive,
36
+ activate: (_, driveMessages) => {
37
+ const drive = new FailsDrive({});
38
+ driveMessages.registerMessageHandler(msg => drive.onMessage(msg));
39
+ return drive;
17
40
  }
18
41
  };
19
42
  const failsSettingsPlugin = {
20
- id: '@fails-components/jupyter-fails-server:settings',
43
+ id: '@fails-components/jupyter-applet-widget:settings',
21
44
  requires: [],
22
45
  autoStart: true,
23
- provides: ISettings,
24
- activate: (app) => {
25
- if (app.namespace !== 'JupyterLite Server') {
26
- console.log('Not on server');
27
- }
28
- const settings = new FailsSettings();
29
- app.started.then(() => settings.initialize().catch(console.warn));
46
+ provides: ISettingManager,
47
+ optional: [IServerSettings],
48
+ activate: (_, serverSettings) => {
49
+ const settings = new FailsSettings({
50
+ serverSettings: serverSettings !== null && serverSettings !== void 0 ? serverSettings : undefined
51
+ });
30
52
  return settings;
31
53
  }
32
54
  };
33
55
  const plugins = [
34
- failsContentsPlugin,
56
+ failsDriveMessages,
57
+ failsDrivePlugin,
35
58
  failsSettingsPlugin
36
59
  ];
37
60
  export default plugins;
package/lib/settings.d.ts CHANGED
@@ -1,15 +1,15 @@
1
- import { ISettings, IPlugin as ISettingsPlugin } from '@jupyterlite/settings';
2
- export declare class FailsSettings implements ISettings {
3
- static _overrides: Record<string, ISettingsPlugin['schema']['default']>;
4
- static override(plugin: ISettingsPlugin): ISettingsPlugin;
5
- constructor();
6
- get ready(): Promise<void>;
7
- initialize(): Promise<void>;
8
- get(pluginId: string): Promise<ISettingsPlugin | undefined>;
9
- getAll(): Promise<{
10
- settings: ISettingsPlugin[];
1
+ import { Setting, SettingManager } from '@jupyterlab/services';
2
+ import { ISettingRegistry } from '@jupyterlab/settingregistry';
3
+ export type SettingsFile = 'all.json' | 'all_federated.json';
4
+ export declare class FailsSettings extends SettingManager implements Setting.IManager {
5
+ static _overrides: Record<string, ISettingRegistry.IPlugin['schema']['default']>;
6
+ static override(plugin: ISettingRegistry.IPlugin): ISettingRegistry.IPlugin;
7
+ constructor(options: SettingManager.IOptions);
8
+ fetch(pluginId: string): Promise<ISettingRegistry.IPlugin>;
9
+ list(query?: 'ids'): Promise<{
10
+ ids: string[];
11
+ values: ISettingRegistry.IPlugin[];
11
12
  }>;
12
13
  private _getAll;
13
- save(pluginId: string, raw: string): Promise<void>;
14
- private _ready;
14
+ save(id: string, raw: string): Promise<void>;
15
15
  }
package/lib/settings.js CHANGED
@@ -1,13 +1,7 @@
1
1
  import { PageConfig, URLExt } from '@jupyterlab/coreutils';
2
- import { PromiseDelegate } from '@lumino/coreutils';
2
+ import { SettingManager } from '@jupyterlab/services';
3
3
  import * as json5 from 'json5';
4
- // portions used from Jupyterlab:
5
- /* -----------------------------------------------------------------------------
6
- | Copyright (c) Jupyter Development Team.
7
- | Distributed under the terms of the Modified BSD License.
8
- |----------------------------------------------------------------------------*/
9
- // This code contains portions from or is inspired by Jupyter lab and lite
10
- class FailsSettings {
4
+ class FailsSettings extends SettingManager {
11
5
  static override(plugin) {
12
6
  if (FailsSettings._overrides[plugin.id]) {
13
7
  if (!plugin.schema.properties) {
@@ -20,32 +14,32 @@ class FailsSettings {
20
14
  }
21
15
  return plugin;
22
16
  }
23
- constructor() {
24
- this._ready = new PromiseDelegate();
25
- }
26
- get ready() {
27
- return this._ready.promise;
28
- }
29
- async initialize() {
30
- this._ready.resolve(void 0);
17
+ constructor(options) {
18
+ super({
19
+ serverSettings: options.serverSettings
20
+ });
31
21
  }
32
- // copied from the original settings
33
- async get(pluginId) {
34
- const all = await this.getAll();
35
- const settings = all.settings;
22
+ // copied from the original settings (updated)
23
+ async fetch(pluginId) {
24
+ const all = await this.list();
25
+ const settings = all.values;
36
26
  const setting = settings.find((setting) => {
37
27
  return setting.id === pluginId;
38
28
  });
29
+ if (!setting) {
30
+ throw new Error(`Setting ${pluginId} not found`);
31
+ }
39
32
  return setting;
40
33
  }
41
- // copied from the original settings
42
- async getAll() {
34
+ // copied from the original settings (updated)
35
+ async list(query) {
36
+ var _a, _b;
43
37
  const allCore = await this._getAll('all.json');
44
38
  let allFederated = [];
45
39
  try {
46
40
  allFederated = await this._getAll('all_federated.json');
47
41
  }
48
- catch (_a) {
42
+ catch (_c) {
49
43
  // handle the case where there is no federated extension
50
44
  }
51
45
  // JupyterLab 4 expects all settings to be returned in one go
@@ -61,7 +55,17 @@ class FailsSettings {
61
55
  settings: json5.parse(raw)
62
56
  };
63
57
  }));
64
- return { settings };
58
+ // format the settings
59
+ const ids = (_a = settings.map((plugin) => plugin.id)) !== null && _a !== void 0 ? _a : [];
60
+ let values = [];
61
+ if (!query) {
62
+ values =
63
+ (_b = settings.map((plugin) => {
64
+ plugin.data = { composite: {}, user: {} };
65
+ return plugin;
66
+ })) !== null && _b !== void 0 ? _b : [];
67
+ }
68
+ return { ids, values };
65
69
  }
66
70
  // one to one copy from settings of the original JupyterLite
67
71
  async _getAll(file) {
@@ -70,7 +74,7 @@ class FailsSettings {
70
74
  const all = (await (await fetch(URLExt.join(settingsUrl, file))).json());
71
75
  return all;
72
76
  }
73
- async save(pluginId, raw) {
77
+ async save(id, raw) {
74
78
  // we do nothing
75
79
  }
76
80
  }
package/lib/token.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { Token } from '@lumino/coreutils';
2
+ import { IContentEventType } from './drive';
3
+ export type IFailsDriveMessageHandler = (msg: IContentEventType) => Promise<void> | void;
4
+ export interface IFailsDriveMessages {
5
+ registerMessageHandler: (handler: IFailsDriveMessageHandler) => void;
6
+ sendMessage: (msg: IContentEventType) => Promise<any>;
7
+ }
8
+ export declare const IFailsDriveMessages: Token<IFailsDriveMessages>;
package/lib/token.js ADDED
@@ -0,0 +1,2 @@
1
+ import { Token } from '@lumino/coreutils';
2
+ export const IFailsDriveMessages = new Token('@fails-components/jupyter-fails:IFailsDriveMessages', 'A service to communicate to the FAILS single file drive.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fails-components/jupyter-filesystem-extension",
3
- "version": "0.0.1-alpha.9",
3
+ "version": "0.0.2",
4
4
  "description": "A collection of extensions, that redirect's filesystems access to fails and let fails puppeteer Jupyter lite. ",
5
5
  "keywords": [
6
6
  "jupyter",
@@ -60,15 +60,11 @@
60
60
  "watch:labextension": "jupyter labextension watch ."
61
61
  },
62
62
  "dependencies": {
63
- "@fails-components/jupyter-launcher": "^0.0.1-alpha.9",
64
- "@jupyter-widgets/jupyterlab-manager": "^5.0.11",
65
- "@jupyterlab/application": "^4.3.4",
66
- "@jupyterlab/services": "^7.3.4",
67
- "@jupyterlite/application-extension": "^0.5.0",
68
- "@jupyterlite/contents": "^0.5.0",
69
- "@jupyterlite/server": "^0.5.0",
70
- "@jupyterlite/settings": "^0.5.0",
71
- "@lumino/coreutils": "^2.2.0",
63
+ "@jupyterlab/coreutils": "^6.4.3",
64
+ "@jupyterlab/services": "^7.4.3",
65
+ "@jupyterlab/settingregistry": "^4.4.3",
66
+ "@lumino/coreutils": "^2.2.1",
67
+ "@lumino/signaling": "^2.1.4",
72
68
  "json5": "^2.2.3"
73
69
  },
74
70
  "devDependencies": {
@@ -111,9 +107,6 @@
111
107
  "outputDir": "fails_components_jupyter_filesystem_extension/labextension",
112
108
  "schemaDir": "schema"
113
109
  },
114
- "jupyterlite": {
115
- "liteExtension": true
116
- },
117
110
  "eslintIgnore": [
118
111
  "node_modules",
119
112
  "dist",
@@ -1,12 +1,24 @@
1
- import { Contents as ServerContents } from '@jupyterlab/services';
2
- import { IContents } from '@jupyterlite/contents';
3
- import { PromiseDelegate } from '@lumino/coreutils';
4
- import {
5
- IFailsCallbacks,
6
- IContentEventType,
7
- ILoadJupyterContentEvent,
8
- ISavedJupyterContentEvent
9
- } from '@fails-components/jupyter-launcher';
1
+ import { Contents, Drive, ServerConnection } from '@jupyterlab/services';
2
+ import { ISignal, Signal } from '@lumino/signaling';
3
+
4
+ interface IContentEvent {
5
+ task: string;
6
+ }
7
+
8
+ export interface ILoadJupyterContentEvent extends IContentEvent {
9
+ task: 'loadFile';
10
+ fileData: object | undefined;
11
+ fileName: string;
12
+ }
13
+
14
+ export interface ISavedJupyterContentEvent extends IContentEvent {
15
+ task: 'savedFile';
16
+ fileName: string;
17
+ }
18
+
19
+ export type IContentEventType =
20
+ | ILoadJupyterContentEvent
21
+ | ISavedJupyterContentEvent; // use union
10
22
 
11
23
  // portions used from Jupyterlab:
12
24
  /* -----------------------------------------------------------------------------
@@ -14,17 +26,43 @@ import {
14
26
  | Distributed under the terms of the Modified BSD License.
15
27
  |----------------------------------------------------------------------------*/
16
28
  // This code contains portions from or is inspired by Jupyter lab and lite
29
+ // especially the Drive implementation
17
30
 
18
31
  const jsonMime = 'application/json';
32
+ type IModel = Contents.IModel;
19
33
 
20
- export class FailsContents implements IContents {
21
- constructor() {
22
- this._ready = new PromiseDelegate();
23
- if (!(window as any).failsCallbacks) {
24
- (window as any).failsCallbacks = {};
34
+ export class FailsDrive implements Contents.IDrive {
35
+ constructor(options: Drive.IOptions) {
36
+ this._serverSettings =
37
+ options.serverSettings ?? ServerConnection.makeSettings();
38
+ }
39
+
40
+ dispose(): void {
41
+ if (this.isDisposed) {
42
+ return;
25
43
  }
26
- this._failsCallbacks = (window as any).failsCallbacks;
27
- this._failsCallbacks.callContents = this.onMessage.bind(this);
44
+ this._isDisposed = true;
45
+ Signal.clearData(this);
46
+ }
47
+
48
+ get isDisposed(): boolean {
49
+ return this._isDisposed;
50
+ }
51
+
52
+ get name(): string {
53
+ return 'JupyterFailsSingleFileDrive';
54
+ }
55
+
56
+ get serverSettings(): ServerConnection.ISettings {
57
+ return this._serverSettings;
58
+ }
59
+
60
+ get fileChanged(): ISignal<Contents.IDrive, Contents.IChangedArgs> {
61
+ return this._fileChanged;
62
+ }
63
+
64
+ async getDownloadUrl(path: string): Promise<string> {
65
+ throw new Error('Method not implemented.');
28
66
  }
29
67
 
30
68
  async onMessage(event: IContentEventType): Promise<any> {
@@ -34,9 +72,25 @@ export class FailsContents implements IContents {
34
72
  {
35
73
  const loadevent = event as ILoadJupyterContentEvent;
36
74
  this._fileContent = JSON.stringify(
37
- loadevent.fileData || FailsContents.EMPTY_NB
75
+ loadevent.fileData || FailsDrive.EMPTY_NB
38
76
  );
39
77
  this._fileName = loadevent.fileName;
78
+ this._fileChanged.emit({
79
+ type: 'save',
80
+ oldValue: null,
81
+ newValue: {
82
+ name: this._fileName,
83
+ path: this._fileName,
84
+ last_modified: new Date(0).toISOString(),
85
+ created: new Date(0).toISOString(),
86
+ format: 'json' as Contents.FileFormat,
87
+ mimetype: jsonMime,
88
+ content: JSON.parse(this._fileContent),
89
+ size: 0,
90
+ writable: true,
91
+ type: 'notebook'
92
+ }
93
+ });
40
94
  }
41
95
  break;
42
96
  case 'savedFile':
@@ -53,18 +107,7 @@ export class FailsContents implements IContents {
53
107
  }
54
108
  }
55
109
 
56
- get ready(): Promise<void> {
57
- return this._ready.promise;
58
- }
59
-
60
- async initialize() {
61
- this._ready.resolve(void 0);
62
- }
63
-
64
- async get(
65
- path: string,
66
- options?: ServerContents.IFetchOptions
67
- ): Promise<ServerContents.IModel | null> {
110
+ async get(path: string, options?: Contents.IFetchOptions): Promise<IModel> {
68
111
  // remove leading slash
69
112
  path = decodeURIComponent(path.replace(/^\//, ''));
70
113
 
@@ -73,7 +116,7 @@ export class FailsContents implements IContents {
73
116
  path: this._fileName,
74
117
  last_modified: new Date(0).toISOString(),
75
118
  created: new Date(0).toISOString(),
76
- format: 'json' as ServerContents.FileFormat,
119
+ format: 'json' as Contents.FileFormat,
77
120
  mimetype: jsonMime,
78
121
  content: JSON.parse(this._fileContent),
79
122
  size: 0,
@@ -99,27 +142,27 @@ export class FailsContents implements IContents {
99
142
  if (path === this._fileName) {
100
143
  return serverFile;
101
144
  }
102
- return null; // not found
145
+ throw Error(`Could not find content with path ${path}`);
103
146
  }
104
147
 
105
148
  async save(
106
149
  path: string,
107
- options: Partial<ServerContents.IModel> = {}
108
- ): Promise<ServerContents.IModel | null> {
150
+ options: Partial<Contents.IModel> = {}
151
+ ): Promise<Contents.IModel> {
109
152
  path = decodeURIComponent(path);
110
153
  if (path !== this._fileName) {
111
154
  // we only allow the proxy object
112
- return null;
155
+ throw Error(`File ${path} is not the proxy file`);
113
156
  }
114
157
  const chunk = options.chunk;
115
158
  const chunked = chunk ? chunk > 1 || chunk === -1 : false;
116
159
 
117
- let item: ServerContents.IModel | null = await this.get(path, {
160
+ let item: Contents.IModel | null = await this.get(path, {
118
161
  content: chunked
119
162
  });
120
163
 
121
164
  if (!item) {
122
- return null;
165
+ throw Error(`Could not find file with path ${path}`);
123
166
  }
124
167
 
125
168
  const modified = new Date().toISOString();
@@ -152,24 +195,29 @@ export class FailsContents implements IContents {
152
195
  size: newcontent.length
153
196
  };
154
197
  this._fileContent = JSON.stringify(newcontent); // no parsing
198
+ this._fileChanged.emit({
199
+ type: 'save',
200
+ oldValue: null,
201
+ newValue: item
202
+ });
155
203
  return item;
156
204
  }
157
205
 
158
206
  this._fileContent = JSON.stringify(item.content); // no parsing
207
+ this._fileChanged.emit({
208
+ type: 'save',
209
+ oldValue: null,
210
+ newValue: item
211
+ });
159
212
  return item;
160
213
  }
161
214
 
162
215
  // For fails creating a new file is not allowed, so no need to implment it
163
- async newUntitled(
164
- options?: ServerContents.ICreateOptions
165
- ): Promise<ServerContents.IModel | null> {
216
+ async newUntitled(options?: Contents.ICreateOptions): Promise<IModel> {
166
217
  throw new Error('NewUntitled not implemented');
167
218
  }
168
219
 
169
- async rename(
170
- oldLocalPath: string,
171
- newLocalPath: string
172
- ): Promise<ServerContents.IModel> {
220
+ async rename(oldLocalPath: string, newLocalPath: string): Promise<IModel> {
173
221
  throw new Error('rename not implemented');
174
222
  }
175
223
 
@@ -177,19 +225,15 @@ export class FailsContents implements IContents {
177
225
  throw new Error('delete not implemented');
178
226
  }
179
227
 
180
- async copy(path: string, toDir: string): Promise<ServerContents.IModel> {
228
+ async copy(path: string, toDir: string): Promise<IModel> {
181
229
  throw new Error('copy not implemented');
182
230
  }
183
231
 
184
- async createCheckpoint(
185
- path: string
186
- ): Promise<ServerContents.ICheckpointModel> {
232
+ async createCheckpoint(path: string): Promise<Contents.ICheckpointModel> {
187
233
  throw new Error('createCheckpoint not (yet?) implemented');
188
234
  }
189
235
 
190
- async listCheckpoints(
191
- path: string
192
- ): Promise<ServerContents.ICheckpointModel[]> {
236
+ async listCheckpoints(path: string): Promise<Contents.ICheckpointModel[]> {
193
237
  // throw new Error('listCheckpoints not (yet?) implemented');
194
238
  return [{ id: 'fakeCheckpoint', last_modified: new Date().toISOString() }];
195
239
  }
@@ -211,8 +255,11 @@ export class FailsContents implements IContents {
211
255
  cells: []
212
256
  };
213
257
 
214
- private _ready: PromiseDelegate<void>;
215
- private _fileContent: string = JSON.stringify(FailsContents.EMPTY_NB);
258
+ private _fileContent: string = JSON.stringify(FailsDrive.EMPTY_NB);
259
+ private _isDisposed = false;
260
+ private _fileChanged = new Signal<Contents.IDrive, Contents.IChangedArgs>(
261
+ this
262
+ );
216
263
  private _fileName: string = 'unloaded.ipynb';
217
- private _failsCallbacks: IFailsCallbacks;
264
+ private _serverSettings: ServerConnection.ISettings;
218
265
  }
package/src/index.ts CHANGED
@@ -1,44 +1,73 @@
1
- import { IContents } from '@jupyterlite/contents';
2
1
  import {
3
- JupyterLiteServerPlugin,
4
- JupyterLiteServer
5
- } from '@jupyterlite/server';
6
- import { ISettings } from '@jupyterlite/settings';
7
- import { FailsContents } from './contents';
2
+ IDefaultDrive,
3
+ Contents,
4
+ ServerConnection,
5
+ Setting,
6
+ ISettingManager,
7
+ IServerSettings,
8
+ ServiceManagerPlugin
9
+ } from '@jupyterlab/services';
10
+ import { FailsDrive, IContentEventType } from './drive';
8
11
  import { FailsSettings } from './settings';
12
+ import { IFailsDriveMessageHandler, IFailsDriveMessages } from './token';
9
13
 
10
- export const failsContentsPlugin: JupyterLiteServerPlugin<IContents> = {
11
- id: '@fails-components/jupyter-fails-server:contents',
14
+ export * from './token';
15
+
16
+ const failsDriveMessages: ServiceManagerPlugin<IFailsDriveMessages> = {
17
+ id: '@fails-components/jupyter-applet-widget:drivemessages',
12
18
  requires: [],
13
19
  autoStart: true,
14
- provides: IContents,
15
- activate: (app: JupyterLiteServer) => {
16
- if (app.namespace !== 'JupyterLite Server') {
17
- console.log('Not on server');
18
- }
19
- const contents = new FailsContents();
20
- app.started.then(() => contents.initialize().catch(console.warn));
21
- return contents;
20
+ provides: IFailsDriveMessages,
21
+ activate: (_: null) => {
22
+ let initialWaitRes: ((val: unknown) => void) | undefined;
23
+ const initialWait = new Promise(resolve => (initialWaitRes = resolve));
24
+ let messageHandler: IFailsDriveMessageHandler;
25
+ const driveMessages = {
26
+ registerMessageHandler: (handler: IFailsDriveMessageHandler) => {
27
+ messageHandler = handler;
28
+ if (initialWaitRes) {
29
+ initialWaitRes(undefined);
30
+ }
31
+ initialWaitRes = undefined;
32
+ },
33
+ sendMessage: async (msg: IContentEventType) => {
34
+ await initialWait;
35
+ return messageHandler(msg);
36
+ }
37
+ };
38
+ return driveMessages;
39
+ }
40
+ };
41
+
42
+ const failsDrivePlugin: ServiceManagerPlugin<Contents.IDrive> = {
43
+ id: '@fails-components/jupyter-applet-widget:drive',
44
+ requires: [IFailsDriveMessages],
45
+ autoStart: true,
46
+ provides: IDefaultDrive,
47
+ activate: (_: null, driveMessages: IFailsDriveMessages) => {
48
+ const drive = new FailsDrive({});
49
+ driveMessages.registerMessageHandler(msg => drive.onMessage(msg));
50
+ return drive;
22
51
  }
23
52
  };
24
53
 
25
- const failsSettingsPlugin: JupyterLiteServerPlugin<ISettings> = {
26
- id: '@fails-components/jupyter-fails-server:settings',
54
+ const failsSettingsPlugin: ServiceManagerPlugin<Setting.IManager> = {
55
+ id: '@fails-components/jupyter-applet-widget:settings',
27
56
  requires: [],
28
57
  autoStart: true,
29
- provides: ISettings,
30
- activate: (app: JupyterLiteServer) => {
31
- if (app.namespace !== 'JupyterLite Server') {
32
- console.log('Not on server');
33
- }
34
- const settings = new FailsSettings();
35
- app.started.then(() => settings.initialize().catch(console.warn));
58
+ provides: ISettingManager,
59
+ optional: [IServerSettings],
60
+ activate: (_: null, serverSettings: ServerConnection.ISettings | null) => {
61
+ const settings = new FailsSettings({
62
+ serverSettings: serverSettings ?? undefined
63
+ });
36
64
  return settings;
37
65
  }
38
66
  };
39
67
 
40
- const plugins: JupyterLiteServerPlugin<any>[] = [
41
- failsContentsPlugin,
68
+ const plugins: ServiceManagerPlugin<any>[] = [
69
+ failsDriveMessages,
70
+ failsDrivePlugin,
42
71
  failsSettingsPlugin
43
72
  ];
44
73
 
package/src/settings.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { PageConfig, URLExt } from '@jupyterlab/coreutils';
2
- import { ISettings, IPlugin as ISettingsPlugin } from '@jupyterlite/settings';
3
- import { PromiseDelegate } from '@lumino/coreutils';
2
+ import { Setting, SettingManager } from '@jupyterlab/services';
3
+ import { ISettingRegistry } from '@jupyterlab/settingregistry';
4
4
  import * as json5 from 'json5';
5
5
 
6
6
  // portions used from Jupyterlab:
@@ -9,13 +9,16 @@ import * as json5 from 'json5';
9
9
  | Distributed under the terms of the Modified BSD License.
10
10
  |----------------------------------------------------------------------------*/
11
11
  // This code contains portions from or is inspired by Jupyter lab and lite
12
+ export type SettingsFile = 'all.json' | 'all_federated.json';
12
13
 
13
- export class FailsSettings implements ISettings {
14
+ export class FailsSettings extends SettingManager implements Setting.IManager {
14
15
  // the following is copied from the original Jupyter Lite Settings Object
15
- static _overrides: Record<string, ISettingsPlugin['schema']['default']> =
16
- JSON.parse(PageConfig.getOption('settingsOverrides') || '{}');
16
+ static _overrides: Record<
17
+ string,
18
+ ISettingRegistry.IPlugin['schema']['default']
19
+ > = JSON.parse(PageConfig.getOption('settingsOverrides') || '{}');
17
20
 
18
- static override(plugin: ISettingsPlugin): ISettingsPlugin {
21
+ static override(plugin: ISettingRegistry.IPlugin): ISettingRegistry.IPlugin {
19
22
  if (FailsSettings._overrides[plugin.id]) {
20
23
  if (!plugin.schema.properties) {
21
24
  // probably malformed, or only provides keyboard shortcuts, etc.
@@ -30,32 +33,31 @@ export class FailsSettings implements ISettings {
30
33
  return plugin;
31
34
  }
32
35
 
33
- constructor() {
34
- this._ready = new PromiseDelegate();
35
- }
36
-
37
- get ready(): Promise<void> {
38
- return this._ready.promise;
39
- }
40
-
41
- async initialize() {
42
- this._ready.resolve(void 0);
36
+ constructor(options: SettingManager.IOptions) {
37
+ super({
38
+ serverSettings: options.serverSettings
39
+ });
43
40
  }
44
41
 
45
- // copied from the original settings
46
- async get(pluginId: string): Promise<ISettingsPlugin | undefined> {
47
- const all = await this.getAll();
48
- const settings = all.settings as ISettingsPlugin[];
49
- const setting = settings.find((setting: ISettingsPlugin) => {
42
+ // copied from the original settings (updated)
43
+ async fetch(pluginId: string): Promise<ISettingRegistry.IPlugin> {
44
+ const all = await this.list();
45
+ const settings = all.values as ISettingRegistry.IPlugin[];
46
+ const setting = settings.find((setting: ISettingRegistry.IPlugin) => {
50
47
  return setting.id === pluginId;
51
48
  });
49
+ if (!setting) {
50
+ throw new Error(`Setting ${pluginId} not found`);
51
+ }
52
52
  return setting;
53
53
  }
54
54
 
55
- // copied from the original settings
56
- async getAll(): Promise<{ settings: ISettingsPlugin[] }> {
55
+ // copied from the original settings (updated)
56
+ async list(
57
+ query?: 'ids'
58
+ ): Promise<{ ids: string[]; values: ISettingRegistry.IPlugin[] }> {
57
59
  const allCore = await this._getAll('all.json');
58
- let allFederated: ISettingsPlugin[] = [];
60
+ let allFederated: ISettingRegistry.IPlugin[] = [];
59
61
  try {
60
62
  allFederated = await this._getAll('all_federated.json');
61
63
  } catch {
@@ -78,23 +80,35 @@ export class FailsSettings implements ISettings {
78
80
  };
79
81
  })
80
82
  );
81
- return { settings };
83
+
84
+ // format the settings
85
+ const ids =
86
+ settings.map((plugin: ISettingRegistry.IPlugin) => plugin.id) ?? [];
87
+
88
+ let values: ISettingRegistry.IPlugin[] = [];
89
+ if (!query) {
90
+ values =
91
+ settings.map((plugin: ISettingRegistry.IPlugin) => {
92
+ plugin.data = { composite: {}, user: {} };
93
+ return plugin;
94
+ }) ?? [];
95
+ }
96
+
97
+ return { ids, values };
82
98
  }
83
99
 
84
100
  // one to one copy from settings of the original JupyterLite
85
101
  private async _getAll(
86
- file: 'all.json' | 'all_federated.json'
87
- ): Promise<ISettingsPlugin[]> {
102
+ file: SettingsFile
103
+ ): Promise<ISettingRegistry.IPlugin[]> {
88
104
  const settingsUrl = PageConfig.getOption('settingsUrl') ?? '/';
89
105
  const all = (await (
90
106
  await fetch(URLExt.join(settingsUrl, file))
91
- ).json()) as ISettingsPlugin[];
107
+ ).json()) as ISettingRegistry.IPlugin[];
92
108
  return all;
93
109
  }
94
110
 
95
- async save(pluginId: string, raw: string): Promise<void> {
111
+ async save(id: string, raw: string): Promise<void> {
96
112
  // we do nothing
97
113
  }
98
-
99
- private _ready: PromiseDelegate<void>;
100
114
  }
package/src/token.ts ADDED
@@ -0,0 +1,15 @@
1
+ import { Token } from '@lumino/coreutils';
2
+ import { IContentEventType } from './drive';
3
+
4
+ export type IFailsDriveMessageHandler = (
5
+ msg: IContentEventType
6
+ ) => Promise<void> | void;
7
+ export interface IFailsDriveMessages {
8
+ registerMessageHandler: (handler: IFailsDriveMessageHandler) => void;
9
+ sendMessage: (msg: IContentEventType) => Promise<any>;
10
+ }
11
+
12
+ export const IFailsDriveMessages = new Token<IFailsDriveMessages>(
13
+ '@fails-components/jupyter-fails:IFailsDriveMessages',
14
+ 'A service to communicate to the FAILS single file drive.'
15
+ );
package/lib/contents.d.ts DELETED
@@ -1,31 +0,0 @@
1
- import { Contents as ServerContents } from '@jupyterlab/services';
2
- import { IContents } from '@jupyterlite/contents';
3
- import { IContentEventType } from '@fails-components/jupyter-launcher';
4
- export declare class FailsContents implements IContents {
5
- constructor();
6
- onMessage(event: IContentEventType): Promise<any>;
7
- get ready(): Promise<void>;
8
- initialize(): Promise<void>;
9
- get(path: string, options?: ServerContents.IFetchOptions): Promise<ServerContents.IModel | null>;
10
- save(path: string, options?: Partial<ServerContents.IModel>): Promise<ServerContents.IModel | null>;
11
- newUntitled(options?: ServerContents.ICreateOptions): Promise<ServerContents.IModel | null>;
12
- rename(oldLocalPath: string, newLocalPath: string): Promise<ServerContents.IModel>;
13
- delete(path: string): Promise<void>;
14
- copy(path: string, toDir: string): Promise<ServerContents.IModel>;
15
- createCheckpoint(path: string): Promise<ServerContents.ICheckpointModel>;
16
- listCheckpoints(path: string): Promise<ServerContents.ICheckpointModel[]>;
17
- restoreCheckpoint(path: string, checkpointID: string): Promise<void>;
18
- deleteCheckpoint(path: string, checkpointID: string): Promise<void>;
19
- static EMPTY_NB: {
20
- metadata: {
21
- orig_nbformat: number;
22
- };
23
- nbformat_minor: number;
24
- nbformat: number;
25
- cells: never[];
26
- };
27
- private _ready;
28
- private _fileContent;
29
- private _fileName;
30
- private _failsCallbacks;
31
- }