Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@ export * from './notebookrenderer';
export * from './notebookrenderer/types';

import { notebookRenderer, yWidgetManager } from './notebookrenderer';
import { yOutputHandler } from './youtputhandler';
import { yInputWidget } from './yinputwidget';

export default [notebookRenderer, yWidgetManager];
export default [notebookRenderer, yWidgetManager, yOutputHandler, yInputWidget];
27 changes: 14 additions & 13 deletions src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import * as Y from 'yjs';
import { IJupyterYDoc, IJupyterYModel } from './types';

export class JupyterYModel implements IJupyterYModel {
constructor(commMetadata: {[key: string]: any}) {
this._yModelName = commMetadata.ymodel_name;
const ydoc = this.ydocFactory(commMetadata);
this._sharedModel = new JupyterYDoc(commMetadata, ydoc);
constructor(options: {[key: string]: any}) {
this._yModelName = options.ymodel_name;
const ydoc = this.ydocFactory(options);
this._sharedModel = new JupyterYDoc(options, ydoc);
this.roomId = options.room_id;
}

get yModelName(): string {
Expand All @@ -32,7 +33,7 @@ export class JupyterYModel implements IJupyterYModel {
return this._isDisposed;
}

ydocFactory(commMetadata: {[key: string]: any}): Y.Doc {
ydocFactory(options: {[key: string]: any}): Y.Doc {
return new Y.Doc();
}

Expand All @@ -56,24 +57,24 @@ export class JupyterYModel implements IJupyterYModel {

private _yModelName: string;
private _sharedModel: IJupyterYDoc;

private _isDisposed = false;

private _disposed = new Signal<this, void>(this);

roomId?: string;
}

export class JupyterYDoc implements IJupyterYDoc {
constructor(commMetadata: {[key: string]: any}, ydoc: Y.Doc) {
this._commMetadata = commMetadata;
constructor(options: {[key: string]: any}, ydoc: Y.Doc) {
this._options = options;
this._ydoc = ydoc;
if (commMetadata.create_ydoc) {
if (options.create_ydoc) {
this._attrs = this._ydoc.getMap<string>('_attrs');
this._attrs.observe(this._attrsObserver);
}
}

get commMetadata(): {[key: string]: any} {
return this._commMetadata;
get options(): {[key: string]: any} {
return this._options;
}

get ydoc(): Y.Doc {
Expand Down Expand Up @@ -130,5 +131,5 @@ export class JupyterYDoc implements IJupyterYDoc {

private _disposed = new Signal<this, void>(this);
private _ydoc: Y.Doc;
private _commMetadata: {[key: string]: any};
private _options: {[key: string]: any};
}
2 changes: 1 addition & 1 deletion src/notebookrenderer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const notebookRenderer: JupyterFrontEndPlugin<void> = {
nbTracker.currentWidget?.sessionContext.session?.kernel?.id;
const mimeType = options.mimeType;
const modelFactory = new NotebookRendererModel({
kernelId,
kernelOrNotebookId: kernelId,
widgetManager: wmManager
});
return new JupyterYWidget({ mimeType, modelFactory });
Expand Down
18 changes: 9 additions & 9 deletions src/notebookrenderer/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { IJupyterYWidgetManager } from './types';
export class NotebookRendererModel implements IDisposable {
constructor(options: NotebookRendererModel.IOptions) {
this._widgetManager = options.widgetManager;
this._kernelId = options.kernelId;
this._kernelOrNotebookId = options.kernelOrNotebookId;
}

get isDisposed(): boolean {
Expand All @@ -20,15 +20,15 @@ export class NotebookRendererModel implements IDisposable {
this._isDisposed = true;
}

getYModel(commId: string): IJupyterYModel | undefined {
if (this._kernelId) {
return this._widgetManager.getWidgetModel(this._kernelId, commId);
getYModel(commOrRoomId: string): IJupyterYModel | undefined {
if (this._kernelOrNotebookId) {
return this._widgetManager.getWidgetModel(this._kernelOrNotebookId, commOrRoomId);
}
}

createYWidget(commId: string, node: HTMLElement): void {
if (this._kernelId) {
const yModel = this._widgetManager.getWidgetModel(this._kernelId, commId);
createYWidget(commOrRoomId: string, node: HTMLElement): void {
if (this._kernelOrNotebookId) {
const yModel = this._widgetManager.getWidgetModel(this._kernelOrNotebookId, commOrRoomId);
if (yModel) {
const widgetFactory = this._widgetManager.getWidgetFactory(
yModel.yModelName
Expand All @@ -39,13 +39,13 @@ export class NotebookRendererModel implements IDisposable {
}

private _isDisposed = false;
private _kernelId?: string;
private _kernelOrNotebookId?: string;
private _widgetManager: IJupyterYWidgetManager;
}

export namespace NotebookRendererModel {
export interface IOptions {
kernelId?: string;
kernelOrNotebookId?: string;
widgetManager: IJupyterYWidgetManager;
}
}
3 changes: 3 additions & 0 deletions src/notebookrenderer/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { IJupyterYModel } from '../types';

export interface IJupyterYWidgetModelRegistry {
getModel(id: string): IJupyterYModel | undefined;
setModel(id: string, model: IJupyterYModel): void;
}

export interface IJupyterYModelFactory {
Expand All @@ -15,6 +16,7 @@ export interface IJupyterYWidgetFactory {
}

export interface IJupyterYWidgetManager {
registerNotebook(notebookId: string): IJupyterYWidgetModelRegistry;
registerKernel(kernel: Kernel.IKernelConnection): void;
registerWidget(
name: string,
Expand All @@ -23,6 +25,7 @@ export interface IJupyterYWidgetManager {
): void;
getWidgetModel(kernelId: string, commId: string): IJupyterYModel | undefined;
getWidgetFactory(modelName: string): any | undefined;
yModelFactories: Map<string, IJupyterYModelFactory>;
}

export const IJupyterYWidgetManager = new Token<IJupyterYWidgetManager>(
Expand Down
9 changes: 9 additions & 0 deletions src/notebookrenderer/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export class JupyterYWidget extends Widget implements IRenderMime.IRenderer {
this._yModel?.dispose();
super.dispose();
}

async renderModel(mimeModel: IRenderMime.IMimeModel): Promise<void> {
const modelId = mimeModel.data[this._mimeType]!['model_id'];

Expand All @@ -38,6 +39,14 @@ export class JupyterYWidget extends Widget implements IRenderMime.IRenderer {
this._modelFactory.createYWidget(modelId, this.node);
}

render(roomId: string): void {
this._yModel = this._modelFactory.getYModel(roomId);
if (!this._yModel) {
return;
}
this._modelFactory.createYWidget(roomId, this.node);
}

private _modelFactory: NotebookRendererModel;
private _mimeType: string;
private _yModel?: IJupyterYModel;
Expand Down
28 changes: 21 additions & 7 deletions src/notebookrenderer/widgetManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,16 @@ import { YCommProvider } from './yCommProvider';
import { IJupyterYModel } from '../types';

export class JupyterYWidgetManager implements IJupyterYWidgetManager {

registerNotebook(notebookId: string): IJupyterYWidgetModelRegistry {
const yModelFactories = this.yModelFactories;
const wm = new WidgetModelRegistry({ yModelFactories });
this._registry.set(notebookId, wm);
return wm;
}

registerKernel(kernel: Kernel.IKernelConnection): void {
const yModelFactories = this._yModelFactories;
const yModelFactories = this.yModelFactories;
const wm = new WidgetModelRegistry({ kernel, yModelFactories });
this._registry.set(kernel.id, wm);
}
Expand All @@ -26,37 +34,43 @@ export class JupyterYWidgetManager implements IJupyterYWidgetManager {
yModelFactory: IJupyterYModelFactory,
yWidgetFactory: IJupyterYWidgetFactory
): void {
this._yModelFactories.set(name, yModelFactory);
this.yModelFactories.set(name, yModelFactory);
this._yWidgetFactories.set(name, yWidgetFactory);
}

getWidgetModel(kernelId: string, commId: string): IJupyterYModel | undefined {
return this._registry.get(kernelId)?.getModel(commId);
getWidgetModel(kernelOrNotebookId: string, commOrRoomId: string): IJupyterYModel | undefined {
return this._registry.get(kernelOrNotebookId)?.getModel(commOrRoomId);
}

getWidgetFactory(modelName: string) {
return this._yWidgetFactories.get(modelName);
}

yModelFactories = new Map<string, IJupyterYModelFactory>();
private _registry = new Map<string, IJupyterYWidgetModelRegistry>();
private _yModelFactories = new Map<string, IJupyterYModelFactory>();
private _yWidgetFactories = new Map<string, IJupyterYWidgetFactory>();
}

export class WidgetModelRegistry implements IJupyterYWidgetModelRegistry {
constructor(options: {
kernel: Kernel.IKernelConnection;
kernel?: Kernel.IKernelConnection;
yModelFactories: any;
}) {
const { kernel, yModelFactories } = options;
this._yModelFactories = yModelFactories;
kernel.registerCommTarget('ywidget', this._handle_comm_open);
if (kernel !== undefined) {
kernel.registerCommTarget('ywidget', this._handle_comm_open);
}
}

getModel(id: string): IJupyterYModel | undefined {
return this._yModels.get(id);
}

setModel(id: string, model: IJupyterYModel): void {
this._yModels.set(id, model);
}

/**
* Handle when a comm is opened.
*/
Expand Down
3 changes: 2 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ export interface IJupyterYDoc extends IDisposable {

attrsChanged: ISignal<IJupyterYDoc, MapChange>;
ydoc: Y.Doc;
commMetadata: {[key: string]: any};
options: {[key: string]: any};
disposed: ISignal<any, void>;
}

export interface IJupyterYModel extends IDisposable {
yModelName: string;
isDisposed: boolean;
sharedModel: IJupyterYDoc;
roomId?: string;

sharedAttrsChanged: ISignal<IJupyterYDoc, MapChange>;

Expand Down
67 changes: 67 additions & 0 deletions src/yinputwidget/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { IJupyterYModel } from '../types';
import { JupyterYModel } from '../model';
import { IJupyterYWidgetManager } from '../notebookrenderer/types';
import {
JupyterFrontEnd,
JupyterFrontEndPlugin
} from '@jupyterlab/application';
import { ybinding } from '@jupyterlab/codemirror';
import { StateCommand } from '@codemirror/state';
import { EditorView, KeyBinding, keymap } from '@codemirror/view';
import { WebsocketProvider } from 'y-websocket';

class InputWidget {
constructor(yModel: IJupyterYModel, node: HTMLElement) {
this.yModel = yModel;
this.node = node;

const wsProvider = new WebsocketProvider(
'ws://127.0.0.1:8000', `api/collaboration/room/${yModel.roomId}`,
yModel.sharedModel.ydoc
);

wsProvider.on('sync', (isSynced) => {
const prompt: string = this.yModel.sharedModel.getAttr('prompt');
const password: boolean = this.yModel.sharedModel.getAttr('password');
const promptNode = document.createElement('pre');
promptNode.textContent = prompt;
const input1 = document.createElement('div');
input1.style.border = 'thin solid';
const input2 = document.createElement('div');
if (password === true) {
(input2.style as any).webkitTextSecurity = 'disc';
}
input1.appendChild(input2);
this.node.appendChild(promptNode);
promptNode.appendChild(input1);

const stdin = this.yModel.sharedModel.getAttr('value');
const ybind = ybinding({ ytext: stdin });
const submit: StateCommand = ({ state, dispatch }) => {
this.yModel.sharedModel.setAttr('submitted', true);
return true;
};
const submitWithEnter: KeyBinding = {
key: 'Enter',
run: submit
};
new EditorView({
doc: stdin.toString(),
extensions: [keymap.of([submitWithEnter]), ybind],
parent: input2
});
});
}

yModel: IJupyterYModel;
node: HTMLElement;
}

export const yInputWidget: JupyterFrontEndPlugin<void> = {
id: 'jupyterywidget:yInputWidget',
autoStart: true,
requires: [IJupyterYWidgetManager],
activate: (app: JupyterFrontEnd, wm: IJupyterYWidgetManager): void => {
wm.registerWidget('Input', JupyterYModel, InputWidget);
}
};
50 changes: 50 additions & 0 deletions src/youtputhandler/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import {
JupyterFrontEnd,
JupyterFrontEndPlugin,
} from '@jupyterlab/application';
import {
INotebookTracker,
INotebookModel,
NotebookPanel,
} from '@jupyterlab/notebook';
import { ISettingRegistry } from '@jupyterlab/settingregistry';
import { DocumentRegistry } from '@jupyterlab/docregistry';
import YWidget, { PLUGIN_NAME } from './yWidget';
import { IJupyterYWidgetManager } from '../notebookrenderer/types';

class yWidgetExtension implements DocumentRegistry.WidgetExtension {
constructor(tracker: INotebookTracker, wmManager: IJupyterYWidgetManager) {
this._tracker = tracker;
this._wmManager = wmManager;
}

createNew(
panel: NotebookPanel,
context: DocumentRegistry.IContext<INotebookModel>
) {
return new YWidget(panel, this._tracker, this._wmManager);
}

private _tracker: INotebookTracker;
private _wmManager: IJupyterYWidgetManager;
}

export const yOutputHandler: JupyterFrontEndPlugin<void> = {
id: PLUGIN_NAME,
autoStart: true,
requires: [INotebookTracker, ISettingRegistry, IJupyterYWidgetManager],
activate: async (
app: JupyterFrontEnd,
tracker: INotebookTracker,
settingRegistry: ISettingRegistry,
wmManager: IJupyterYWidgetManager
) => {
app.docRegistry.addWidgetExtension(
'Notebook',
new yWidgetExtension(tracker, wmManager)
);

// eslint-disable-next-line no-console
console.log(`JupyterLab extension ${PLUGIN_NAME} is activated!`);
},
};
Loading