From e0405a7e7afc55bbe16de62b00834464c0c368d5 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Thu, 25 Jul 2024 10:24:26 +0200 Subject: [PATCH 01/15] chore: use id instead of uid prop --- packages/react/jupyter_react/static/README.md | 1 - packages/react/package.json | 2 +- .../app/tabs/components/NotebookComponent.tsx | 2 +- .../codemirror/CodeMirrorEditor.tsx | 2 +- .../components/kernel/inspector/widget.tsx | 4 +- .../src/components/notebook/Notebook.tsx | 40 ++-- .../components/notebook/NotebookAdapter.ts | 16 +- .../src/components/notebook/NotebookState.ts | 178 +++++++++--------- .../notebook/cell/sidebar/CellSidebar.tsx | 12 +- .../cell/sidebar/CellSidebarButton.tsx | 12 +- .../cell/sidebar/CellSidebarWidget.tsx | 2 +- .../react/src/components/output/Output.tsx | 2 +- .../src/components/output/OutputState.ts | 22 ++- packages/react/src/examples/All.tsx | 18 +- packages/react/src/examples/Bokeh.tsx | 2 +- packages/react/src/examples/Bqplot.tsx | 2 +- packages/react/src/examples/Dashboard.tsx | 2 +- packages/react/src/examples/GeoJson.tsx | 2 +- packages/react/src/examples/IPyLeaflet.tsx | 2 +- packages/react/src/examples/IPyReact.tsx | 2 +- packages/react/src/examples/IPyWidgets.tsx | 2 +- .../src/examples/IPyWidgetsWithState.tsx | 2 +- packages/react/src/examples/Matplotlib.tsx | 2 +- packages/react/src/examples/Notebook.tsx | 4 +- .../src/examples/NotebookCellSidebar.tsx | 2 +- .../react/src/examples/NotebookColorMode.tsx | 2 +- .../src/examples/NotebookExternalContent.tsx | 6 +- packages/react/src/examples/NotebookInit.tsx | 4 +- .../src/examples/NotebookKernelChange.tsx | 6 +- packages/react/src/examples/NotebookLite.tsx | 2 +- .../react/src/examples/NotebookNbFormat.tsx | 2 +- .../src/examples/NotebookNbFormatChange.tsx | 6 +- .../react/src/examples/NotebookNoContext.tsx | 4 +- .../examples/NotebookNoContextNoPrimer.tsx | 4 +- packages/react/src/examples/NotebookPath.tsx | 4 +- .../react/src/examples/NotebookPathChange.tsx | 2 +- .../react/src/examples/NotebookSkeleton.tsx | 4 +- packages/react/src/examples/NotebookTheme.tsx | 2 +- .../src/examples/NotebookThemeColorMode.tsx | 2 +- .../react/src/examples/NotebookUnmount.tsx | 2 +- packages/react/src/examples/NotebookUrl.tsx | 4 +- packages/react/src/examples/Output.tsx | 13 +- packages/react/src/examples/Panel.tsx | 2 +- packages/react/src/examples/Plotly.tsx | 2 +- packages/react/src/examples/Vega.tsx | 2 +- .../examples/notebooks/Matplotlib.ipynb.json | 2 +- .../examples/sidebars/CellSidebarSource.tsx | 12 +- .../src/examples/toolbars/NotebookToolbar.tsx | 8 +- .../toolbars/NotebookToolbarAutoSave.tsx | 4 +- packages/react/src/index.ts | 2 +- packages/react/src/jupyter/kernel/Kernel.ts | 2 +- packages/react/src/utils/Utils.ts | 14 +- 52 files changed, 228 insertions(+), 228 deletions(-) delete mode 100644 packages/react/jupyter_react/static/README.md diff --git a/packages/react/jupyter_react/static/README.md b/packages/react/jupyter_react/static/README.md deleted file mode 100644 index 5cde08ff..00000000 --- a/packages/react/jupyter_react/static/README.md +++ /dev/null @@ -1 +0,0 @@ -[![Datalayer](https://assets.datalayer.tech/datalayer-25.svg)](https://datalayer.io) diff --git a/packages/react/package.json b/packages/react/package.json index 709bcadc..0ce06d97 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -73,7 +73,7 @@ "@jupyter-widgets/output": "^6.0.0", "@jupyter/collaboration-extension": "^1.0.0", "@jupyter/web-components": "^0.15.3", - "@jupyter/ydoc": "^1.0.0", + "@jupyter/ydoc": "^2.0.1", "@jupyterlab/application": "^4.0.0", "@jupyterlab/application-extension": "^4.0.0", "@jupyterlab/apputils": "^4.0.0", diff --git a/packages/react/src/app/tabs/components/NotebookComponent.tsx b/packages/react/src/app/tabs/components/NotebookComponent.tsx index 85f1e281..ae7daffc 100644 --- a/packages/react/src/app/tabs/components/NotebookComponent.tsx +++ b/packages/react/src/app/tabs/components/NotebookComponent.tsx @@ -15,7 +15,7 @@ const NotebookComponent = () => { {/* { constructor(model: KernelSpyModel) { super(model); - this.id = `kernelspy-messagelog-${UUID.uuid4()}`; + this.id = `kernelspy-messagelog-${UUID.uid4()}`; this.addClass('dla-KernelInspector-messagelog'); } @@ -216,7 +216,7 @@ export class KernelSpyView extends Widget { super(); this._model = new KernelSpyModel(kernel); this.addClass('dla-KernelInspector-view'); - this.id = `kernelspy-${UUID.uuid4()}`; + this.id = `kernelspy-${UUID.uid4()}`; this.title.label = 'Kernel spy'; this.title.closable = true; this.title.icon = jsonIcon; diff --git a/packages/react/src/components/notebook/Notebook.tsx b/packages/react/src/components/notebook/Notebook.tsx index 076987e1..466d924a 100644 --- a/packages/react/src/components/notebook/Notebook.tsx +++ b/packages/react/src/components/notebook/Notebook.tsx @@ -63,7 +63,7 @@ export type INotebookProps = { path?: string; readOnly: boolean; renderers: IRenderMime.IRendererFactory[]; - uid: string; + id: string; url?: string; CellSidebar?: (props: CellSidebarProps) => JSX.Element; Toolbar?: (props: any) => JSX.Element; @@ -91,30 +91,30 @@ export const Notebook = (props: INotebookProps) => { } = props; const notebookStore = useNotebookStore(); - const [uid] = useState(props.uid || newUuid()); + const [id] = useState(props.id || newUuid()); const [adapter, setAdapter] = useState(); const kernel = propsKernel || defaultKernel; - const portals = notebookStore.selectNotebookPortals(uid); + const portals = notebookStore.selectNotebookPortals(id); const newAdapterState = () => { - if (uid && serviceManager && kernelManager && kernel) { + if (id && serviceManager && kernelManager && kernel) { kernel.ready.then(() => { const adapter = new NotebookAdapter( { ...props, kernel, - uid, + id, }, serviceManager, lite ); setAdapter(adapter); - notebookStore.update({ uid, partialState: { adapter: adapter } }) + notebookStore.update({ id, partialState: { adapter: adapter } }) adapter.serviceManager.ready.then(() => { if (!readOnly) { const activeCell = adapter.notebookPanel!.content.activeCell; if (activeCell) { notebookStore.activeCellChange({ - uid, + id, cellModel: activeCell, }); } @@ -122,7 +122,7 @@ export const Notebook = (props: INotebookProps) => { adapter.notebookPanel!.content.activeCellChanged ); activeCellChanged$.subscribe((cellModel: Cell) => { - notebookStore.activeCellChange({ uid, cellModel }); + notebookStore.activeCellChange({ id, cellModel }); const panelDiv = document.getElementById( 'right-panel-id' ) as HTMLDivElement; @@ -130,7 +130,7 @@ export const Notebook = (props: INotebookProps) => { const cellMetadataOptions = ( @@ -138,7 +138,7 @@ export const Notebook = (props: INotebookProps) => { ); const portal = createPortal(cellMetadataOptions, panelDiv); notebookStore.setPortalDisplay({ - uid, + id, portalDisplay: { portal, pinned: false }, }); } @@ -146,32 +146,32 @@ export const Notebook = (props: INotebookProps) => { } adapter.notebookPanel?.model?.contentChanged.connect( (notebookModel, _) => { - notebookStore.modelChange({ uid, notebookModel }); + notebookStore.modelChange({ id, notebookModel }); } ); /* adapter.notebookPanel?.model!.sharedModel.changed.connect((_, notebookChange) => { - notebookStore.notebookChange({ uid, notebookChange }); + notebookStore.notebookChange({ id, notebookChange }); }); adapter.notebookPanel?.content.modelChanged.connect((notebook, _) => { - dispatÅch(notebookStore.notebookChange({ uid, notebook })); + dispatÅch(notebookStore.notebookChange({ id, notebook })); }); */ adapter.notebookPanel?.content.activeCellChanged.connect( (_, cellModel) => { if (cellModel === null) { notebookStore.activeCellChange({ - uid, + id, cellModel: undefined, }); } else { - notebookStore.activeCellChange({ uid, cellModel }); + notebookStore.activeCellChange({ id, cellModel }); } } ); adapter.notebookPanel?.sessionContext.statusChanged.connect( (_, kernelStatus) => { - notebookStore.kernelStatusChanged({ uid, kernelStatus }); + notebookStore.kernelStatusChanged({ id, kernelStatus }); } ); }); @@ -184,10 +184,10 @@ export const Notebook = (props: INotebookProps) => { } newAdapterState(); return () => { - notebookStore.setPortalDisplay({ uid, portalDisplay: undefined }); - notebookStore.dispose(uid); + notebookStore.setPortalDisplay({ id, portalDisplay: undefined }); + notebookStore.dispose(id); }; - }, [uid, serviceManager, kernelManager, kernel, path]); + }, [id, serviceManager, kernelManager, kernel, path]); useEffect(() => { if (adapter && nbformat) { adapter.setNbformat(nbformat); @@ -198,7 +198,7 @@ export const Notebook = (props: INotebookProps) => { style={{ height, width: '100%', position: 'relative' }} id="dla-Jupyter-Notebook" > - {Toolbar && } + {Toolbar && } JSX.Element; constructor( @@ -100,7 +100,7 @@ export class NotebookAdapter { this._path = props.path; this._readOnly = props.readOnly; this._renderers = props.renderers; - this._uid = props.uid; + this._id = props.id; this._CellSidebar = props.CellSidebar; @@ -223,14 +223,14 @@ export class NotebookAdapter { const contentFactory = this._CellSidebar ? new JupyterReactContentFactory( this._CellSidebar, - this._uid, + this._id, this._nbgrader, this._commandRegistry, { editorFactory }, ) : new NotebookPanel.ContentFactory({ editorFactory }); - this._tracker = new NotebookTracker({ namespace: this._uid }); + this._tracker = new NotebookTracker({ namespace: this._id }); const notebookWidgetFactory = new NotebookWidgetFactory({ name: 'Notebook', @@ -418,8 +418,8 @@ export class NotebookAdapter { (this._context as Context).model.dirty = false; const now = new Date().toISOString(); const model: Contents.IModel = { - path: this._uid, - name: this._uid, + path: this._id, + name: this._id, type: 'notebook', content: undefined, writable: true, @@ -492,8 +492,8 @@ export class NotebookAdapter { }); } - get uid(): string { - return this._uid; + get id(): string { + return this._id; } get notebookPanel(): NotebookPanel | undefined { diff --git a/packages/react/src/components/notebook/NotebookState.ts b/packages/react/src/components/notebook/NotebookState.ts index bd50c826..ed36cfb7 100644 --- a/packages/react/src/components/notebook/NotebookState.ts +++ b/packages/react/src/components/notebook/NotebookState.ts @@ -36,217 +36,217 @@ export interface INotebooksState { notebooks: Map; } -type UpdateUid = { - uid: string; +type UpdateId = { + id: string; partialState: Partial; }; -type NotebookChangeUid = { - uid: string; +type NotebookChangeId = { + id: string; notebookChange: NotebookChange; }; -type NotebookModelUid = { - uid: string; +type NotebookModelId = { + id: string; notebookModel: INotebookModel; }; -type CellModelUid = { - uid: string; +type CellModelId = { + id: string; cellModel?: Cell; }; type KernelStatusMutation = { - uid: string; + id: string; kernelStatus: JupyterKernel.Status; }; type KernelChangeMutation = { - uid: string; + id: string; kernel: Kernel; }; type ReactPortalsMutation = { - uid: string; + id: string; portals: ReactPortal[]; }; type PortalDisplayMutation = { - uid: string; + id: string; portalDisplay: PortalDisplay | undefined; }; type DateMutation = { - uid: string; + id: string; date: Date | undefined; }; type CellMutation = { - uid: string; + id: string; cellType: nbformat.CellType; source?: string; }; export type NotebookState = INotebooksState & { setNotebooks: (notebooks: Map) => void; - selectNotebook: (uid: string) => INotebookState | undefined; - selectNotebookModel: (uid: string) => { model: INotebookModel | undefined; changed: any } | undefined; - selectKernelStatus: (uid: string) => string | undefined; - selectActiveCell: (uid: string) => Cell | undefined; - selectNotebookPortals: (uid: string) => React.ReactPortal[] | undefined; - selectSaveRequest: (uid: string) => Date | undefined; - selectNotebookPortalDisplay: (uid: string) => PortalDisplay | undefined; - run: (uid: string) => void; - runAll: (uid: string) => void; - interrupt: (uid: string) => void; + selectNotebook: (id: string) => INotebookState | undefined; + selectNotebookModel: (id: string) => { model: INotebookModel | undefined; changed: any } | undefined; + selectKernelStatus: (id: string) => string | undefined; + selectActiveCell: (id: string) => Cell | undefined; + selectNotebookPortals: (id: string) => React.ReactPortal[] | undefined; + selectSaveRequest: (id: string) => Date | undefined; + selectNotebookPortalDisplay: (id: string) => PortalDisplay | undefined; + run: (id: string) => void; + runAll: (id: string) => void; + interrupt: (id: string) => void; insertAbove: (mutation: CellMutation) => void; insertBelow: (mutation: CellMutation) => void; - delete: (uid: string) => void; + delete: (id: string) => void; changeCellType: (mutation: CellMutation) => void; save: (mutation: DateMutation) => void; reset: () => void; - update: (update: UpdateUid) => void; - activeCellChange: (cellModelUid: CellModelUid) => void; - modelChange: (notebookModelUid: NotebookModelUid) => void; - notebookChange: (notebookChangeUid: NotebookChangeUid) => void; - kernelStatusChanged: (kernelStatusUid: KernelStatusMutation) => void; + update: (update: UpdateId) => void; + activeCellChange: (cellModelId: CellModelId) => void; + modelChange: (notebookModelId: NotebookModelId) => void; + notebookChange: (notebookChangeId: NotebookChangeId) => void; + kernelStatusChanged: (kernelStatusId: KernelStatusMutation) => void; changeKernel: (kernelChange: KernelChangeMutation) => void; - addPortals: (portalsUid: ReactPortalsMutation) => void; - dispose: (uid: string) => void; - setPortals: (portalsUid: ReactPortalsMutation) => void; - setPortalDisplay: (portalDisplayUid: PortalDisplayMutation) => void; + addPortals: (portalsId: ReactPortalsMutation) => void; + dispose: (id: string) => void; + setPortals: (portalsId: ReactPortalsMutation) => void; + setPortalDisplay: (portalDisplayId: PortalDisplayMutation) => void; }; export const notebookStore = createStore((set, get) => ({ notebooks: new Map(), setNotebooks: (notebooks: Map) => set((state: NotebookState) => ({ notebooks })), - selectNotebook: (uid: string): INotebookState | undefined => { - return get().notebooks.get(uid); + selectNotebook: (id: string): INotebookState | undefined => { + return get().notebooks.get(id); }, - selectNotebookModel: (uid: string): { model: INotebookModel | undefined; changed: any } | undefined => { - if (get().notebooks.get(uid)) { + selectNotebookModel: (id: string): { model: INotebookModel | undefined; changed: any } | undefined => { + if (get().notebooks.get(id)) { return { - model: get().notebooks.get(uid)?.model, - changed: get().notebooks.get(uid)?.model?.contentChanged, + model: get().notebooks.get(id)?.model, + changed: get().notebooks.get(id)?.model?.contentChanged, }; } return undefined; }, - selectKernelStatus: (uid: string): string | undefined => { - return get().notebooks.get(uid)?.kernelStatus; + selectKernelStatus: (id: string): string | undefined => { + return get().notebooks.get(id)?.kernelStatus; }, - selectActiveCell: (uid: string): Cell | undefined => { - return get().notebooks.get(uid)?.activeCell; + selectActiveCell: (id: string): Cell | undefined => { + return get().notebooks.get(id)?.activeCell; }, - selectNotebookPortals: (uid: string): React.ReactPortal[] | undefined => { - return get().notebooks.get(uid)?.portals; + selectNotebookPortals: (id: string): React.ReactPortal[] | undefined => { + return get().notebooks.get(id)?.portals; }, - selectSaveRequest: (uid: string): Date | undefined => { - return get().notebooks.get(uid)?.saveRequest; + selectSaveRequest: (id: string): Date | undefined => { + return get().notebooks.get(id)?.saveRequest; }, - selectNotebookPortalDisplay: (uid: string): PortalDisplay | undefined => { - return get().notebooks.get(uid)?.portalDisplay; + selectNotebookPortalDisplay: (id: string): PortalDisplay | undefined => { + return get().notebooks.get(id)?.portalDisplay; }, - run: (uid: string): void => { get().notebooks.get(uid)?.adapter?.commands.execute(cmdIds.run); }, - runAll: (uid: string): void => { get().notebooks.get(uid)?.adapter?.commands.execute(cmdIds.runAll); }, - interrupt: (uid: string): void => { get().notebooks.get(uid)?.adapter?.commands.execute(cmdIds.interrupt); }, + run: (id: string): void => { get().notebooks.get(id)?.adapter?.commands.execute(cmdIds.run); }, + runAll: (id: string): void => { get().notebooks.get(id)?.adapter?.commands.execute(cmdIds.runAll); }, + interrupt: (id: string): void => { get().notebooks.get(id)?.adapter?.commands.execute(cmdIds.interrupt); }, insertAbove: (mutation: CellMutation) => { - get().notebooks.get(mutation.uid)?.adapter?.setDefaultCellType(mutation.cellType); - get().notebooks.get(mutation.uid)?.adapter?.insertAbove(mutation.source); + get().notebooks.get(mutation.id)?.adapter?.setDefaultCellType(mutation.cellType); + get().notebooks.get(mutation.id)?.adapter?.insertAbove(mutation.source); }, insertBelow: (mutation: CellMutation) => { - get().notebooks.get(mutation.uid)?.adapter?.setDefaultCellType(mutation.cellType); - get().notebooks.get(mutation.uid)?.adapter?.insertBelow(mutation.source); + get().notebooks.get(mutation.id)?.adapter?.setDefaultCellType(mutation.cellType); + get().notebooks.get(mutation.id)?.adapter?.insertBelow(mutation.source); }, - delete: (uid: string): void => { get().notebooks.get(uid)?.adapter?.commands.execute(cmdIds.deleteCells); }, + delete: (id: string): void => { get().notebooks.get(id)?.adapter?.commands.execute(cmdIds.deleteCells); }, changeCellType: (mutation: CellMutation) => { - get().notebooks.get(mutation.uid)?.adapter?.changeCellType(mutation.cellType); + get().notebooks.get(mutation.id)?.adapter?.changeCellType(mutation.cellType); }, save: (mutation: DateMutation) => { - get().notebooks.get(mutation.uid)?.adapter?.commands.execute(cmdIds.save); + get().notebooks.get(mutation.id)?.adapter?.commands.execute(cmdIds.save); const notebooks = get().notebooks; - const notebook = notebooks.get(mutation.uid); + const notebook = notebooks.get(mutation.id); if (notebook) { notebook.saveRequest = mutation.date; set((state: NotebookState) => ({ notebooks })); } }, reset: () => set((state: NotebookState) => ({ notebooks: new Map() })), - update: (update: UpdateUid) => { + update: (update: UpdateId) => { const notebooks = get().notebooks; - let notebook = notebooks.get(update.uid); + let notebook = notebooks.get(update.id); if (notebook) { notebook = { ...notebook, ...update.partialState }; set((state: NotebookState) => ({ notebooks })); } else { - notebooks.set(update.uid, { + notebooks.set(update.id, { adapter: update.partialState.adapter, portals: [], }); set((state: NotebookState) => ({ notebooks })); } }, - activeCellChange: (cellModelUid: CellModelUid) => { + activeCellChange: (cellModelId: CellModelId) => { const notebooks = get().notebooks; - const notebook = notebooks.get(cellModelUid.uid); + const notebook = notebooks.get(cellModelId.id); if (notebook) { - notebook.activeCell = cellModelUid.cellModel; + notebook.activeCell = cellModelId.cellModel; set((state: NotebookState) => ({ notebooks })); } }, - modelChange: (notebookModelUid: NotebookModelUid) => { + modelChange: (notebookModelId: NotebookModelId) => { const notebooks = get().notebooks; - const notebook = notebooks.get(notebookModelUid.uid); + const notebook = notebooks.get(notebookModelId.id); if (notebook) { - notebook.model = notebookModelUid.notebookModel; + notebook.model = notebookModelId.notebookModel; set((state: NotebookState) => ({ notebooks })); } }, - notebookChange: (notebookChangeUid: NotebookChangeUid) => { + notebookChange: (notebookChangeId: NotebookChangeId) => { const notebooks = get().notebooks; - const notebook = notebooks.get(notebookChangeUid.uid); + const notebook = notebooks.get(notebookChangeId.id); if (notebook) { - notebook.notebookChange = notebookChangeUid.notebookChange; + notebook.notebookChange = notebookChangeId.notebookChange; set((state: NotebookState) => ({ notebooks })); } }, - kernelStatusChanged: (kernelStatusUid: KernelStatusMutation) => { + kernelStatusChanged: (kernelStatusId: KernelStatusMutation) => { const notebooks = get().notebooks; - const notebook = notebooks.get(kernelStatusUid.uid); + const notebook = notebooks.get(kernelStatusId.id); if (notebook) { - notebook.kernelStatus = kernelStatusUid.kernelStatus; + notebook.kernelStatus = kernelStatusId.kernelStatus; set((state: NotebookState) => ({ notebooks })); } }, changeKernel: (kernelChange: KernelChangeMutation) => { const notebooks = get().notebooks; - const notebook = notebooks.get(kernelChange.uid); + const notebook = notebooks.get(kernelChange.id); if (notebook) { notebook.adapter?.assignKernel(kernelChange.kernel); set((state: NotebookState) => ({ notebooks })); } }, - addPortals: (portalsUid: ReactPortalsMutation) => { + addPortals: (portalsId: ReactPortalsMutation) => { const notebooks = get().notebooks; - const notebook = notebooks.get(portalsUid.uid); + const notebook = notebooks.get(portalsId.id); if (notebook) { - notebook.portals = notebook.portals.concat(portalsUid.portals); + notebook.portals = notebook.portals.concat(portalsId.portals); set((state: NotebookState) => ({ notebooks })); } }, - dispose: (uid: string): void => { + dispose: (id: string): void => { const notebooks = get().notebooks; - const notebook = notebooks.get(uid); + const notebook = notebooks.get(id); if(notebook){ notebook.adapter?.dispose(); - notebooks.delete(uid); + notebooks.delete(id); } set((state: NotebookState) => ({ notebooks })); }, - setPortals: (portalsUid: ReactPortalsMutation) => { + setPortals: (portalsId: ReactPortalsMutation) => { const notebooks = get().notebooks; - const notebook = notebooks.get(portalsUid.uid); + const notebook = notebooks.get(portalsId.id); if (notebook) { - notebook.portals = portalsUid.portals; + notebook.portals = portalsId.portals; set((state: NotebookState) => ({ notebooks })); } }, - setPortalDisplay: (portalDisplayUid: PortalDisplayMutation) => { + setPortalDisplay: (portalDisplayId: PortalDisplayMutation) => { const notebooks = get().notebooks; - const notebook = notebooks.get(portalDisplayUid.uid); + const notebook = notebooks.get(portalDisplayId.id); if (notebook) { - notebook.portalDisplay = portalDisplayUid.portalDisplay; + notebook.portalDisplay = portalDisplayId.portalDisplay; set((state: NotebookState) => ({ notebooks })); } }, diff --git a/packages/react/src/components/notebook/cell/sidebar/CellSidebar.tsx b/packages/react/src/components/notebook/cell/sidebar/CellSidebar.tsx index 035cf8b7..7deecb23 100644 --- a/packages/react/src/components/notebook/cell/sidebar/CellSidebar.tsx +++ b/packages/react/src/components/notebook/cell/sidebar/CellSidebar.tsx @@ -92,7 +92,7 @@ export const CellSidebar = (props: CellSidebarProps) => { onClick={(e: any) => { e.preventDefault(); notebookStore.insertAbove({ - uid: notebookId, + id: notebookId, cellType: 'code', }); }} @@ -109,7 +109,7 @@ export const CellSidebar = (props: CellSidebarProps) => { onClick={(e: any) => { e.preventDefault(); notebookStore.insertAbove({ - uid: notebookId, + id: notebookId, cellType: 'markdown', }); }} @@ -127,7 +127,7 @@ export const CellSidebar = (props: CellSidebarProps) => { onClick={(e: any) => { e.preventDefault(); notebookStore.changeCellType({ - uid: notebookId, + id: notebookId, cellType: 'markdown', }); }} @@ -143,7 +143,7 @@ export const CellSidebar = (props: CellSidebarProps) => { onClick={(e: any) => { e.preventDefault(); notebookStore.changeCellType({ - uid: notebookId, + id: notebookId, cellType: 'code', }); }} @@ -161,7 +161,7 @@ export const CellSidebar = (props: CellSidebarProps) => { onClick={(e: any) => { e.preventDefault(); notebookStore.insertBelow({ - uid: notebookId, + id: notebookId, cellType: 'markdown', }); }} @@ -178,7 +178,7 @@ export const CellSidebar = (props: CellSidebarProps) => { onClick={(e: any) => { e.preventDefault(); notebookStore.insertBelow({ - uid: notebookId, + id: notebookId, cellType: 'code', }); }} diff --git a/packages/react/src/components/notebook/cell/sidebar/CellSidebarButton.tsx b/packages/react/src/components/notebook/cell/sidebar/CellSidebarButton.tsx index c0ed6751..96f39119 100644 --- a/packages/react/src/components/notebook/cell/sidebar/CellSidebarButton.tsx +++ b/packages/react/src/components/notebook/cell/sidebar/CellSidebarButton.tsx @@ -73,7 +73,7 @@ export const CellSidebarNew = (props: CellSidebarProps) => { onClick={e => { e.preventDefault(); notebookStore.insertAbove({ - uid: notebookId, + id: notebookId, cellType: 'code', }); }} @@ -90,7 +90,7 @@ export const CellSidebarNew = (props: CellSidebarProps) => { onClick={e => { e.preventDefault(); notebookStore.insertAbove({ - uid: notebookId, + id: notebookId, cellType: 'markdown', }); }} @@ -109,7 +109,7 @@ export const CellSidebarNew = (props: CellSidebarProps) => { onClick={e => { e.preventDefault(); notebookStore.changeCellType({ - uid: notebookId, + id: notebookId, cellType: 'markdown', }); }} @@ -124,7 +124,7 @@ export const CellSidebarNew = (props: CellSidebarProps) => { onClick={(e: any) => { e.preventDefault(); notebookStore.changeCellType({ - uid: notebookId, + id: notebookId, cellType: 'code', }); }} @@ -140,7 +140,7 @@ export const CellSidebarNew = (props: CellSidebarProps) => { onClick={e => { e.preventDefault(); notebookStore.insertBelow({ - uid: notebookId, + id: notebookId, cellType: 'markdown', }); }} @@ -157,7 +157,7 @@ export const CellSidebarNew = (props: CellSidebarProps) => { onClick={e => { e.preventDefault(); notebookStore.insertBelow({ - uid: notebookId, + id: notebookId, cellType: 'code', }); }} diff --git a/packages/react/src/components/notebook/cell/sidebar/CellSidebarWidget.tsx b/packages/react/src/components/notebook/cell/sidebar/CellSidebarWidget.tsx index e5c715b6..26ab666c 100644 --- a/packages/react/src/components/notebook/cell/sidebar/CellSidebarWidget.tsx +++ b/packages/react/src/components/notebook/cell/sidebar/CellSidebarWidget.tsx @@ -48,7 +48,7 @@ export class CellSidebarWidget ); const portal = createPortal(portalDiv, this.node); notebookStore.getState().addPortals({ - uid: notebookId, + id: notebookId, portals: [portal], }); } diff --git a/packages/react/src/components/output/Output.tsx b/packages/react/src/components/output/Output.tsx index 8d43b274..52145a22 100644 --- a/packages/react/src/components/output/Output.tsx +++ b/packages/react/src/components/output/Output.tsx @@ -70,7 +70,7 @@ export const Output = (props: IOutputProps) => { const [adapter, setAdapter] = useState(); useEffect(() => { if (!id) { - setId(UUID.uuid4()); + setId(UUID.uid4()); } }, []); useEffect(() => { diff --git a/packages/react/src/components/output/OutputState.ts b/packages/react/src/components/output/OutputState.ts index 5232c6d5..1a3520d1 100644 --- a/packages/react/src/components/output/OutputState.ts +++ b/packages/react/src/components/output/OutputState.ts @@ -35,7 +35,6 @@ export type IOutputState = { adapter?: OutputAdapter; source?: OutputState.ISource; dataset?: OutputState.IDataset; - setSource?: OutputState.ISource; execute?: OutputState.IExecute; grade?: OutputState.IGrade; }; @@ -93,16 +92,19 @@ export const outputStore = createStore((set, get) => ({ } set((state: OutputState) => ({ outputs })) }, - setSource: (setSource: OutputState.ISource) => { - const sourceId = setSource.sourceId; + setSource: (sourceState: OutputState.ISource) => { const outputs = get().outputs; - const s = outputs.get(sourceId); - if (s) { - s.setSource = setSource; - } else { - outputs.set(sourceId, { setSource }); + const output = outputs.get(sourceState.sourceId); + if (output?.source?.source === sourceState.source) { + return; } - set((state: OutputState) => ({ outputs })) + outputs.set(sourceState.sourceId, { + source: { + sourceId: sourceState.sourceId, + source: sourceState.source + } + }); + get().setOutputs(outputs); }, setGrade: (grade: OutputState.IGrade) => { const sourceId = grade.sourceId; @@ -111,7 +113,7 @@ export const outputStore = createStore((set, get) => ({ if (g) { g.grade = grade; } else { - outputs.set(sourceId, { grade }); + outputs.set(sourceId, { grade: grade }); } set((state: OutputState) => ({ outputs })) }, diff --git a/packages/react/src/examples/All.tsx b/packages/react/src/examples/All.tsx index 663b7332..8023b20d 100644 --- a/packages/react/src/examples/All.tsx +++ b/packages/react/src/examples/All.tsx @@ -26,9 +26,9 @@ import notebook from './notebooks/NotebookExample1.ipynb.json'; const SOURCE_1 = '1+1'; -const NOTEBOOK_UID_1 = 'notebook-1-uid'; -const NOTEBOOK_UID_2 = 'notebook-2-uid'; -const NOTEBOOK_UID_3 = 'notebook-3-uid'; +const NOTEBOOK_ID_1 = 'notebook-1-id'; +const NOTEBOOK_ID_2 = 'notebook-2-id'; +const NOTEBOOK_ID_3 = 'notebook-3-id'; const SOURCE_1_OUTPUTS: IOutput[] = [ { @@ -101,7 +101,7 @@ const NotebookToolbar = () => { size="small" onClick={() => notebookStore.save({ - uid: NOTEBOOK_UID_1, + id: NOTEBOOK_ID_1, date: new Date(), }) } @@ -112,7 +112,7 @@ const NotebookToolbar = () => { variant="default" size="small" onClick={() => - notebookStore.runAll(NOTEBOOK_UID_1) + notebookStore.runAll(NOTEBOOK_ID_1) } > Run all @@ -136,7 +136,7 @@ const NotebookKernelChange = () => { sessionManager: serviceManager.sessions, }); kernel.ready.then(() => { - notebookStore.changeKernel({ uid: NOTEBOOK_UID_2, kernel }); + notebookStore.changeKernel({ id: NOTEBOOK_ID_2, kernel }); alert('Kernel is changed.'); }); } @@ -153,7 +153,7 @@ const NotebookKernelChange = () => { ); @@ -195,7 +195,7 @@ root.render(
diff --git a/packages/react/src/examples/Bokeh.tsx b/packages/react/src/examples/Bokeh.tsx index 7d92dcb6..f2aa2df7 100644 --- a/packages/react/src/examples/Bokeh.tsx +++ b/packages/react/src/examples/Bokeh.tsx @@ -14,7 +14,7 @@ const Bokeh = () => ( ( ( ( ( ( ( ( ( ( { /> { const [nbformat, setNbFormat] = useState(); const [updatedNbFormat, setUpdatedNbFormat] = useState(); - const model = notebookStore.getState().selectNotebookModel(NOTEBOOK_UID)?.model; + const model = notebookStore.getState().selectNotebookModel(NOTEBOOK_ID)?.model; useEffect(() => { // Set nbformat with any content. // This may come from an external storage that you fetch in this react effect. @@ -58,7 +58,7 @@ const NotebookExternalContent = () => {
{ // console.log("You can use one of these commands:", notebook.adapter?.commands.listCommands()); // notebook.adapter?.commands.execute("notebook:run-all"); notebookStore.insertAbove({ - uid: NOTEBOOK_ID, + id: NOTEBOOK_ID, cellType: 'code', source: 'print("Hello 🪐 ⚛️ Jupyter React")', }); @@ -78,7 +78,7 @@ const NotebookInit: React.FC = () => { return kernel ? ( { sessionManager: serviceManager.sessions, }); kernel.ready.then(() => { - notebookStore.changeKernel({ uid: NOTEBOOK_UID, kernel }); + notebookStore.changeKernel({ id: NOTEBOOK_ID, kernel }); alert( `The kernel is changed (was python3, now ${NEW_KERNEL_NAME}). Bummer, all your variables are lost!` ); @@ -48,7 +48,7 @@ const NotebookKernelChange = () => { diff --git a/packages/react/src/examples/NotebookLite.tsx b/packages/react/src/examples/NotebookLite.tsx index 9541ae1e..eab0cb8f 100644 --- a/packages/react/src/examples/NotebookLite.tsx +++ b/packages/react/src/examples/NotebookLite.tsx @@ -21,7 +21,7 @@ const NotebookLite = () => ( A Jupyter Notebook with a Lite Kernel ( { const notebookStore = useNotebookStore(); @@ -24,7 +24,7 @@ const NotebookNbFormatChange = () => { const changeModel = () => { console.log( 'Notebook NbFormat from store', - notebookStore.notebooks.get(NOTEBOOK_UID)?.model?.toJSON() as INotebookContent + notebookStore.notebooks.get(NOTEBOOK_ID)?.model?.toJSON() as INotebookContent ); nbformat === nbformat1 ? setNbformat(nbformat2) : setNbformat(nbformat1); }; @@ -38,7 +38,7 @@ const NotebookNbFormatChange = () => {
( { diff --git a/packages/react/src/examples/NotebookSkeleton.tsx b/packages/react/src/examples/NotebookSkeleton.tsx index 81ac39f6..d9671b0f 100644 --- a/packages/react/src/examples/NotebookSkeleton.tsx +++ b/packages/react/src/examples/NotebookSkeleton.tsx @@ -11,7 +11,7 @@ import Notebook from '../components/notebook/Notebook'; import NotebookToolbar from './toolbars/NotebookToolbar'; import CellSidebar from '../components/notebook/cell/sidebar/CellSidebarButton'; -const NOTEBOOK_UID = 'notebook-uid'; +const NOTEBOOK_ID = 'notebook-id'; const div = document.createElement('div'); document.body.appendChild(div); @@ -21,7 +21,7 @@ root.render( }> { /> { { { const outputStore = useJupyterStore().outputStore(); - console.log('Outputs 1', outputStore.getAdapter(SOURCE_ID_1)?.outputArea.model.toJSON()); + console.log( + 'Outputs 1', + outputStore.getAdapter(SOURCE_ID_1)?.outputArea.model.toJSON(), + outputStore.getSource(SOURCE_ID_1), + ); return ( <> Output without Code Editor @@ -56,7 +59,11 @@ const OutputWithoutEditor = () => { const OutputWithEditor = () => { const { defaultKernel } = useJupyter(); const outputStore = useJupyterStore().outputStore(); - console.log('Outputs 2', outputStore.getAdapter(SOURCE_ID_2)?.outputArea.model.toJSON()); + console.log( + 'Outputs 2', + outputStore.getAdapter(SOURCE_ID_2)?.outputArea.model.toJSON(), + outputStore.getSource(SOURCE_ID_2), + ); return ( <> Output with Code Editor diff --git a/packages/react/src/examples/Panel.tsx b/packages/react/src/examples/Panel.tsx index 7f9e9fdf..48cde2a8 100644 --- a/packages/react/src/examples/Panel.tsx +++ b/packages/react/src/examples/Panel.tsx @@ -15,7 +15,7 @@ const Panel = () => { ( ( { onClick={(e: any) => { e.preventDefault(); notebookStore.insertAbove({ - uid: notebookId, + id: notebookId, cellType: 'code', source: "print('Hello 🪐 ⚛️ Jupyter React, I have been inserted up ⬆️.')", @@ -90,7 +90,7 @@ export const CellSidebarSource = (props: CellSidebarProps) => { onClick={(e: any) => { e.preventDefault(); notebookStore.insertAbove({ - uid: notebookId, + id: notebookId, cellType: 'markdown', }); }} @@ -107,7 +107,7 @@ export const CellSidebarSource = (props: CellSidebarProps) => { onClick={(e: any) => { e.preventDefault(); notebookStore.changeCellType({ - uid: notebookId, + id: notebookId, cellType: 'markdown', }); }} @@ -122,7 +122,7 @@ export const CellSidebarSource = (props: CellSidebarProps) => { onClick={(e: any) => { e.preventDefault(); notebookStore.changeCellType({ - uid: notebookId, + id: notebookId, cellType: 'code', }); }} @@ -139,7 +139,7 @@ export const CellSidebarSource = (props: CellSidebarProps) => { onClick={(e: any) => { e.preventDefault(); notebookStore.insertBelow({ - uid: notebookId, + id: notebookId, cellType: 'markdown', }); }} @@ -155,7 +155,7 @@ export const CellSidebarSource = (props: CellSidebarProps) => { onClick={(e: any) => { e.preventDefault(); notebookStore.insertBelow({ - uid: notebookId, + id: notebookId, cellType: 'code', source: "print('Hello 🪐 ⚛️ Jupyter React, I have been inserted down ⬇️.')", diff --git a/packages/react/src/examples/toolbars/NotebookToolbar.tsx b/packages/react/src/examples/toolbars/NotebookToolbar.tsx index 0d685631..5a1c295c 100644 --- a/packages/react/src/examples/toolbars/NotebookToolbar.tsx +++ b/packages/react/src/examples/toolbars/NotebookToolbar.tsx @@ -53,7 +53,7 @@ export const NotebookToolbar = (props: { notebookId: string }) => { onClick={e => { e.preventDefault(); notebookStore.save({ - uid: notebookId, + id: notebookId, date: new Date(), }); }} @@ -132,17 +132,17 @@ export const NotebookToolbar = (props: { notebookId: string }) => { e.preventDefault(); if (type === 'raw') { notebookStore.insertBelow({ - uid: notebookId, + id: notebookId, cellType: 'raw', }); } else if (type === 'code') { notebookStore.insertBelow({ - uid: notebookId, + id: notebookId, cellType: 'code', }); } else if (type === 'markdown') { notebookStore.insertBelow({ - uid: notebookId, + id: notebookId, cellType: 'markdown', }); } diff --git a/packages/react/src/examples/toolbars/NotebookToolbarAutoSave.tsx b/packages/react/src/examples/toolbars/NotebookToolbarAutoSave.tsx index 2762c12b..c711c24c 100644 --- a/packages/react/src/examples/toolbars/NotebookToolbarAutoSave.tsx +++ b/packages/react/src/examples/toolbars/NotebookToolbarAutoSave.tsx @@ -57,7 +57,7 @@ export const NotebookToolbarAutoSave = (props: { notebookId: string }) => { onClick={e => { e.preventDefault(); notebookStore.save({ - uid: notebookId, + id: notebookId, date: new Date(), }); }} @@ -145,7 +145,7 @@ export const NotebookToolbarAutoSave = (props: { notebookId: string }) => { onClick={e => { e.preventDefault(); notebookStore.insertBelow({ - uid: notebookId, + id: notebookId, cellType: addType, }); }} diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 400464e1..5b5b61cd 100755 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -101,7 +101,7 @@ export * from './components/notebook/cell/sidebar/CellSidebarRun'; // Outputs. // @todo CodeMirrorEditor imported by Output breaks the JupyterLab extension loading. // @see https://github.com/datalayer/jupyter-ui/issues/170 -// export * from './components/output/Output'; +export * from './components/output/Output'; export * from './components/output/OutputAdapter'; export * from './components/output/OutputState'; export * from './components/output/OutputIPyWidgets'; diff --git a/packages/react/src/jupyter/kernel/Kernel.ts b/packages/react/src/jupyter/kernel/Kernel.ts index 8859b8f0..70d09a7c 100755 --- a/packages/react/src/jupyter/kernel/Kernel.ts +++ b/packages/react/src/jupyter/kernel/Kernel.ts @@ -80,7 +80,7 @@ export class Kernel { } else { let path = getCookie(this.cookieName); if (!path) { - path = 'kernel-' + UUID.uuid4(); + path = 'kernel-' + UUID.uid4(); document.cookie = this.cookieName + '=' + path; } this._path = path; diff --git a/packages/react/src/utils/Utils.ts b/packages/react/src/utils/Utils.ts index be66eb17..05bf1135 100644 --- a/packages/react/src/utils/Utils.ts +++ b/packages/react/src/utils/Utils.ts @@ -7,17 +7,9 @@ import { ICell, IOutput } from '@jupyterlab/nbformat'; import { UUID } from '@lumino/coreutils'; -// const MAX = Number.MAX_SAFE_INTEGER; -// const MAX = 999999; - -export const newSourceId = (base: string) => { - // return base + Math.floor(Math.random() * MAX).toString(); - return base; -}; - export const newUuid = () => { return UUID.uuid4(); -}; +} export const cellSourceAsString = (cell: ICell) => { let source = cell.source; @@ -25,7 +17,7 @@ export const cellSourceAsString = (cell: ICell) => { source = (source as []).join('\n'); } return source; -}; +} export const outputsAsString = (outputs: IOutput[]) => { let result = ''; @@ -89,4 +81,4 @@ export const getCookie = (name: string): string | null => { return decodeURIComponent(cookie.substring(nameLenPlus)); })[0] || null ); -}; +} From 53402227c4987c21c4aa60ddc20c351c1ced884e Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Thu, 25 Jul 2024 10:26:57 +0200 Subject: [PATCH 02/15] fix: build --- packages/react/src/components/kernel/inspector/widget.tsx | 4 ++-- packages/react/src/components/output/Output.tsx | 2 +- packages/react/src/jupyter/kernel/Kernel.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react/src/components/kernel/inspector/widget.tsx b/packages/react/src/components/kernel/inspector/widget.tsx index a60886f7..b1ae5870 100644 --- a/packages/react/src/components/kernel/inspector/widget.tsx +++ b/packages/react/src/components/kernel/inspector/widget.tsx @@ -127,7 +127,7 @@ namespace Message { export class MessageLogView extends VDomRenderer { constructor(model: KernelSpyModel) { super(model); - this.id = `kernelspy-messagelog-${UUID.uid4()}`; + this.id = `kernelspy-messagelog-${UUID.uuid4()}`; this.addClass('dla-KernelInspector-messagelog'); } @@ -216,7 +216,7 @@ export class KernelSpyView extends Widget { super(); this._model = new KernelSpyModel(kernel); this.addClass('dla-KernelInspector-view'); - this.id = `kernelspy-${UUID.uid4()}`; + this.id = `kernelspy-${UUID.uuid4()}`; this.title.label = 'Kernel spy'; this.title.closable = true; this.title.icon = jsonIcon; diff --git a/packages/react/src/components/output/Output.tsx b/packages/react/src/components/output/Output.tsx index 52145a22..8d43b274 100644 --- a/packages/react/src/components/output/Output.tsx +++ b/packages/react/src/components/output/Output.tsx @@ -70,7 +70,7 @@ export const Output = (props: IOutputProps) => { const [adapter, setAdapter] = useState(); useEffect(() => { if (!id) { - setId(UUID.uid4()); + setId(UUID.uuid4()); } }, []); useEffect(() => { diff --git a/packages/react/src/jupyter/kernel/Kernel.ts b/packages/react/src/jupyter/kernel/Kernel.ts index 70d09a7c..8859b8f0 100755 --- a/packages/react/src/jupyter/kernel/Kernel.ts +++ b/packages/react/src/jupyter/kernel/Kernel.ts @@ -80,7 +80,7 @@ export class Kernel { } else { let path = getCookie(this.cookieName); if (!path) { - path = 'kernel-' + UUID.uid4(); + path = 'kernel-' + UUID.uuid4(); document.cookie = this.cookieName + '=' + path; } this._path = path; From 1f43117f191cd810b9e4d257fdd4a5133cf43a08 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Mon, 29 Jul 2024 18:58:39 +0200 Subject: [PATCH 03/15] output: fix nbgrader --- attic/prosemirror/package.json | 2 +- examples/cra/package.json | 2 +- examples/next-js/package.json | 2 +- examples/slate/package.json | 2 +- packages/docusaurus-plugin/package.json | 2 +- packages/lexical/package.json | 2 +- packages/react/package.json | 3 +- .../components/kernel/inspector/widget.tsx | 6 ++-- .../cell/metadata/CellMetadataEditor.tsx | 33 ++++++++++++++----- .../notebook/cell/metadata/NbGraderCells.tsx | 8 ++--- .../react/src/components/output/Output.tsx | 4 +-- packages/react/src/jupyter/kernel/Kernel.ts | 6 ++-- packages/react/src/utils/Utils.ts | 5 +++ storybook/package.json | 2 +- 14 files changed, 50 insertions(+), 29 deletions(-) diff --git a/attic/prosemirror/package.json b/attic/prosemirror/package.json index f953bfbc..c6a3126e 100644 --- a/attic/prosemirror/package.json +++ b/attic/prosemirror/package.json @@ -23,7 +23,7 @@ }, "dependencies": { "@codemirror/lang-python": "6.0.1", - "@datalayer/jupyter-react": "^0.12.0", + "@datalayer/jupyter-react": "^0.15.0", "@prosemirror-adapter/react": "0.2.4", "@types/orderedmap": "1.0.0", "codemirror": "6.0.1", diff --git a/examples/cra/package.json b/examples/cra/package.json index e58e39e6..f12b8b4d 100644 --- a/examples/cra/package.json +++ b/examples/cra/package.json @@ -13,7 +13,7 @@ }, "dependencies": { "@datalayer/icons-react": "^0.3.0", - "@datalayer/jupyter-react": "^0.12.0", + "@datalayer/jupyter-react": "^0.15.0", "@datalayer/primer-addons": "0.3.0", "jupyterlab-plotly": "^5.17.0", "plotly.js": "^2.26.2", diff --git a/examples/next-js/package.json b/examples/next-js/package.json index 5a3a3859..3ae2d7db 100644 --- a/examples/next-js/package.json +++ b/examples/next-js/package.json @@ -11,7 +11,7 @@ }, "dependencies": { "@datalayer/icons-react": "^0.3.0", - "@datalayer/jupyter-react": "^0.12.0", + "@datalayer/jupyter-react": "^0.15.0", "@datalayer/primer-addons": "0.3.0", "autoprefixer": "^10.4.14", "eslint": "^8.40.0", diff --git a/examples/slate/package.json b/examples/slate/package.json index f35ef4ba..cd982ba7 100644 --- a/examples/slate/package.json +++ b/examples/slate/package.json @@ -24,7 +24,7 @@ }, "dependencies": { "@datalayer/icons-react": "^0.3.0", - "@datalayer/jupyter-react": "^0.12.0", + "@datalayer/jupyter-react": "^0.15.0", "@datalayer/primer-addons": "0.3.0", "@emotion/css": "^11.1.3", "@emotion/react": "^11.10.6", diff --git a/packages/docusaurus-plugin/package.json b/packages/docusaurus-plugin/package.json index ec592de3..e5cc53d5 100644 --- a/packages/docusaurus-plugin/package.json +++ b/packages/docusaurus-plugin/package.json @@ -23,7 +23,7 @@ }, "dependencies": { "@datalayer/icons-react": "^0.3.0", - "@datalayer/jupyter-react": "^0.12.0", + "@datalayer/jupyter-react": "^0.15.0", "@datalayer/primer-addons": "0.3.0", "@docusaurus/core": "^2.4.0", "@docusaurus/types": "^2.1.0" diff --git a/packages/lexical/package.json b/packages/lexical/package.json index 0a7e498d..d0445be4 100644 --- a/packages/lexical/package.json +++ b/packages/lexical/package.json @@ -59,7 +59,7 @@ }, "dependencies": { "@datalayer/icons-react": "^0.3.0", - "@datalayer/jupyter-react": "^0.12.0", + "@datalayer/jupyter-react": "^0.15.0", "@datalayer/primer-addons": "0.3.0", "@jupyterlab/application": "^4.0.0", "@jupyterlab/coreutils": "^6.0.0", diff --git a/packages/react/package.json b/packages/react/package.json index 0ce06d97..382cde88 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@datalayer/jupyter-react", - "version": "0.14.0", + "version": "0.15.0", "description": "Jupyter React - React.js components 100% compatible with Jupyter.", "license": "MIT", "main": "lib/index.js", @@ -146,6 +146,7 @@ "react-sparklines": "^1.7.0", "rxjs": "^6.6.0", "styled-components": "^5.3.10", + "ulid": "^2.3.0", "usehooks-ts": "^2.9.1", "utf-8-validate": "^6.0.3", "wildcard-match": "^5.1.2", diff --git a/packages/react/src/components/kernel/inspector/widget.tsx b/packages/react/src/components/kernel/inspector/widget.tsx index b1ae5870..99ce4970 100644 --- a/packages/react/src/components/kernel/inspector/widget.tsx +++ b/packages/react/src/components/kernel/inspector/widget.tsx @@ -12,7 +12,6 @@ import { closeIcon, jsonIcon, } from '@jupyterlab/ui-components'; -import { UUID } from '@lumino/coreutils'; import { Message as luminoMessage } from '@lumino/messaging'; import { Widget, BoxLayout } from '@lumino/widgets'; import { @@ -20,6 +19,7 @@ import { ObjectLabel, InspectorNodeParams, } from 'react-inspector'; +import { newUuid } from '../../../utils'; import { KernelSpyModel, ThreadIterator } from './model'; import './kernelinspector.css'; @@ -127,7 +127,7 @@ namespace Message { export class MessageLogView extends VDomRenderer { constructor(model: KernelSpyModel) { super(model); - this.id = `kernelspy-messagelog-${UUID.uuid4()}`; + this.id = `kernelspy-messagelog-${newUuid()}`; this.addClass('dla-KernelInspector-messagelog'); } @@ -216,7 +216,7 @@ export class KernelSpyView extends Widget { super(); this._model = new KernelSpyModel(kernel); this.addClass('dla-KernelInspector-view'); - this.id = `kernelspy-${UUID.uuid4()}`; + this.id = `kernelspy-${newUuid()}`; this.title.label = 'Kernel spy'; this.title.closable = true; this.title.icon = jsonIcon; diff --git a/packages/react/src/components/notebook/cell/metadata/CellMetadataEditor.tsx b/packages/react/src/components/notebook/cell/metadata/CellMetadataEditor.tsx index 829e56d4..ec75ba00 100644 --- a/packages/react/src/components/notebook/cell/metadata/CellMetadataEditor.tsx +++ b/packages/react/src/components/notebook/cell/metadata/CellMetadataEditor.tsx @@ -4,11 +4,12 @@ * MIT License */ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { ActionList, TextInput } from '@primer/react'; import { CheckIcon } from '@primer/octicons-react'; import { Cell, ICellModel } from '@jupyterlab/cells'; import NbGraderType, { getNbGraderType } from './NbGraderCells'; +import { newUlid } from '../../../../utils'; type Props = { notebookId: string; @@ -19,17 +20,23 @@ type Props = { export const CellMetadataEditor = (props: Props) => { const { cell } = props; const [cellGradeType, setCellGradeType] = useState(getNbGraderType(cell)); - const [nbg, setNbg] = useState( - cell.model.getMetadata('nbgrader') || { grade_id: '', points: 0 } + const [nbGrade, setNbGrade] = useState<{grade_id: string; points: number}>( + cell.model.getMetadata('nbgrader') ?? { grade_id: newUlid(), points: 1 } ); + useEffect(() => { + setNbGrade({ + grade_id: nbGrade.grade_id ?? newUlid(), + points: nbGrade.points ?? 1, + }); + }, [nbGrade]); const handleGradeIdChange = (cell: Cell, gradeId: string) => { const nbgrader = cell.model.getMetadata('nbgrader') as any; cell.model.setMetadata('nbgrader', { ...nbgrader, grade_id: gradeId, }); - setNbg({ - ...nbg, + setNbGrade({ + ...nbGrade, grade_id: gradeId, }); }; @@ -41,8 +48,8 @@ export const CellMetadataEditor = (props: Props) => { ...nbgrader, points: points_number, }); - setNbg({ - ...nbg, + setNbGrade({ + ...nbGrade, points: points_number, }); } @@ -65,6 +72,8 @@ export const CellMetadataEditor = (props: Props) => { solution: true, locked: false, task: false, + grade_id: newUlid(), + points: 1, }); setCellGradeType(NbGraderType.AutogradedAnswer); break; @@ -77,6 +86,8 @@ export const CellMetadataEditor = (props: Props) => { solution: false, locked: false, task: false, + grade_id: newUlid(), + points: 1, }); setCellGradeType(NbGraderType.AutogradedTest); break; @@ -89,6 +100,8 @@ export const CellMetadataEditor = (props: Props) => { solution: true, locked: false, task: false, + grade_id: newUlid(), + points: 1, }); setCellGradeType(NbGraderType.ManuallyGradedAnswer); break; @@ -102,6 +115,8 @@ export const CellMetadataEditor = (props: Props) => { solution: false, locked: true, task: true, + grade_id: newUlid(), + points: 1, }); setCellGradeType(NbGraderType.ManuallyGradedTask); break; @@ -217,7 +232,7 @@ export const CellMetadataEditor = (props: Props) => { { { e.preventDefault(); handleGradeIdChange(cell, e.target.value); @@ -230,7 +245,7 @@ export const CellMetadataEditor = (props: Props) => { { { e.preventDefault(); handlePointsChange(cell, e.target.value); diff --git a/packages/react/src/components/notebook/cell/metadata/NbGraderCells.tsx b/packages/react/src/components/notebook/cell/metadata/NbGraderCells.tsx index c7f7ed14..a9fa8f7e 100644 --- a/packages/react/src/components/notebook/cell/metadata/NbGraderCells.tsx +++ b/packages/react/src/components/notebook/cell/metadata/NbGraderCells.tsx @@ -84,14 +84,14 @@ export const getNbGraderType = (cell: Cell) => { if (!grade && solution && !locked) { return NbGraderType.AutogradedAnswer; } - // Autograded test (only for code cells) { grade: true, solution: false, locked: false, points: ... } - if (grade && !solution && !locked) { - return NbGraderType.AutogradedTest; - } // Manually graded task { grade: false, solution: false, locked: true, task: true, points: ... } if (!grade && !solution && locked && task) { return NbGraderType.ManuallyGradedTask; } + // Autograded test (only for code cells) { grade: true, solution: false, locked: true, points: ... } + if (grade && !solution && locked) { + return NbGraderType.AutogradedTest; + } // Manually graded answer { grade: true, solution: true, locked: false, points: ... } if (grade && solution && !locked) { return NbGraderType.ManuallyGradedAnswer; diff --git a/packages/react/src/components/output/Output.tsx b/packages/react/src/components/output/Output.tsx index 8d43b274..78cffc6c 100644 --- a/packages/react/src/components/output/Output.tsx +++ b/packages/react/src/components/output/Output.tsx @@ -6,10 +6,10 @@ import { useState, useEffect } from 'react'; import { Box } from '@primer/react'; -import { UUID } from '@lumino/coreutils'; import { IOutput } from '@jupyterlab/nbformat'; import { IOutputAreaModel } from '@jupyterlab/outputarea'; import { KernelMessage } from '@jupyterlab/services'; +import { newUuid } from '../../utils'; import { useJupyter } from '../../jupyter/JupyterContext'; import Kernel from '../../jupyter/kernel/Kernel'; import { KernelActionMenu, KernelProgressBar } from './../kernel'; @@ -70,7 +70,7 @@ export const Output = (props: IOutputProps) => { const [adapter, setAdapter] = useState(); useEffect(() => { if (!id) { - setId(UUID.uuid4()); + setId(newUuid()); } }, []); useEffect(() => { diff --git a/packages/react/src/jupyter/kernel/Kernel.ts b/packages/react/src/jupyter/kernel/Kernel.ts index 8859b8f0..b818b4d6 100755 --- a/packages/react/src/jupyter/kernel/Kernel.ts +++ b/packages/react/src/jupyter/kernel/Kernel.ts @@ -5,7 +5,7 @@ */ import { find } from '@lumino/algorithm'; -import { PromiseDelegate, UUID } from '@lumino/coreutils'; +import { PromiseDelegate } from '@lumino/coreutils'; import { Kernel as JupyterKernel, KernelMessage, @@ -14,7 +14,7 @@ import { } from '@jupyterlab/services'; import { ISessionConnection } from '@jupyterlab/services/lib/session/session'; import { ConnectionStatus } from '@jupyterlab/services/lib/kernel/kernel'; -import { getCookie } from '../../utils/Utils'; +import { getCookie, newUuid } from '../../utils/Utils'; import KernelExecutor, { IOPubMessageHook, ShellMessageHook, @@ -80,7 +80,7 @@ export class Kernel { } else { let path = getCookie(this.cookieName); if (!path) { - path = 'kernel-' + UUID.uuid4(); + path = 'kernel-' + newUuid(); document.cookie = this.cookieName + '=' + path; } this._path = path; diff --git a/packages/react/src/utils/Utils.ts b/packages/react/src/utils/Utils.ts index 05bf1135..7d65af32 100644 --- a/packages/react/src/utils/Utils.ts +++ b/packages/react/src/utils/Utils.ts @@ -5,8 +5,13 @@ */ import { ICell, IOutput } from '@jupyterlab/nbformat'; +import { ulid } from 'ulid'; import { UUID } from '@lumino/coreutils'; +export const newUlid = () => { + return ulid() +} + export const newUuid = () => { return UUID.uuid4(); } diff --git a/storybook/package.json b/storybook/package.json index be46b93d..12d1f6fa 100644 --- a/storybook/package.json +++ b/storybook/package.json @@ -27,7 +27,7 @@ }, "dependencies": { "@datalayer/jupyter-lexical": "^0.1.0", - "@datalayer/jupyter-react": "^0.12.0", + "@datalayer/jupyter-react": "^0.15.0", "react": "^18.2.0", "react-dom": "^18.2.0" }, From c2a6de63b0395a6a5b76a51fe301f123cac2c964 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Tue, 30 Jul 2024 08:42:10 +0200 Subject: [PATCH 04/15] chore: cell to use kernel executor --- .../cra/src/examples/cell/CellComponents.tsx | 4 +- .../cra/src/examples/cell/CellToolbar.tsx | 4 +- packages/react/package.json | 2 +- packages/react/src/components/cell/Cell.tsx | 45 +++--- .../react/src/components/cell/CellAdapter.ts | 132 ++++++++++++++++- .../react/src/components/cell/CellCommands.ts | 8 +- .../react/src/components/cell/CellState.ts | 42 +++--- .../react/src/components/output/Output.tsx | 28 ++-- .../src/components/output/OutputExecutor.ts | 51 +++++++ packages/react/src/examples/All.tsx | 16 +- packages/react/src/examples/Cell.tsx | 28 ++-- ...KernelExecResult.tsx => KernelExecute.tsx} | 8 +- .../react/src/examples/KernelExecutor.tsx | 6 +- packages/react/src/jupyter/kernel/Kernel.ts | 7 +- .../src/jupyter/kernel/KernelExecutor.ts | 140 ++++++++++-------- packages/react/src/state/State.ts | 6 +- packages/react/webpack.config.js | 6 +- 17 files changed, 358 insertions(+), 175 deletions(-) create mode 100755 packages/react/src/components/output/OutputExecutor.ts rename packages/react/src/examples/{KernelExecResult.tsx => KernelExecute.tsx} (93%) diff --git a/examples/cra/src/examples/cell/CellComponents.tsx b/examples/cra/src/examples/cell/CellComponents.tsx index 408d0b24..b2eda44d 100755 --- a/examples/cra/src/examples/cell/CellComponents.tsx +++ b/examples/cra/src/examples/cell/CellComponents.tsx @@ -4,7 +4,7 @@ * MIT License */ -import { useCellStore, Cell } from "@datalayer/jupyter-react"; +import { useCellsStore, Cell } from "@datalayer/jupyter-react"; import CellToolbar from './CellToolbar'; const SOURCE_EXAMPLE = `""" @@ -36,7 +36,7 @@ ax2.set_ylabel('Undamped') plt.show()`; const CellPreview = () => { - const cellStore = useCellStore(); + const cellStore = useCellsStore(); return ( <>
source: {cellStore.source}
diff --git a/examples/cra/src/examples/cell/CellToolbar.tsx b/examples/cra/src/examples/cell/CellToolbar.tsx index 7d5588c2..01cdebbf 100755 --- a/examples/cra/src/examples/cell/CellToolbar.tsx +++ b/examples/cra/src/examples/cell/CellToolbar.tsx @@ -7,10 +7,10 @@ import React from 'react'; import { Box, IconButton, Text, Tooltip } from '@primer/react'; import { PlayIcon, ReplyIcon, ThreeBarsIcon } from '@primer/octicons-react'; -import { useCellStore } from '@datalayer/jupyter-react'; +import { useCellsStore } from '@datalayer/jupyter-react'; const CellToolbar: React.FC = () => { - const cellStore = useCellStore(); + const cellStore = useCellsStore(); const outputsCount = cellStore.outputsCount; return ( <> diff --git a/packages/react/package.json b/packages/react/package.json index 382cde88..d846a5f3 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@datalayer/jupyter-react", - "version": "0.15.0", + "version": "0.17.0", "description": "Jupyter React - React.js components 100% compatible with Jupyter.", "license": "MIT", "main": "lib/index.js", diff --git a/packages/react/src/components/cell/Cell.tsx b/packages/react/src/components/cell/Cell.tsx index 2a8d7b15..1da192a9 100644 --- a/packages/react/src/components/cell/Cell.tsx +++ b/packages/react/src/components/cell/Cell.tsx @@ -7,10 +7,10 @@ import { useState, useEffect } from 'react'; import { CodeCell, MarkdownCell } from '@jupyterlab/cells'; import { Box } from '@primer/react'; -import CellAdapter from './CellAdapter'; import Lumino from '../lumino/Lumino'; import { useJupyter } from './../../jupyter/JupyterContext'; -import useCellStore from './CellState'; +import CellAdapter from './CellAdapter'; +import useCellsStore from './CellState'; import { newUuid } from '../../utils'; export type ICellProps = { @@ -37,49 +37,45 @@ export type ICellProps = { }; export const Cell = (props: ICellProps) => { - const { type='code', source = '', autoStart, showToolbar=true } = props; - const { serverSettings, defaultKernel } = useJupyter(); + const { + autoStart, + showToolbar=true, + source = '', + type='code', + } = props; + const { defaultKernel, serverSettings } = useJupyter(); + const [id] = useState(props.id || newUuid()); const [adapter, setAdapter] = useState(); - const cellStore = useCellStore(); + + const cellsStore = useCellsStore(); const handleCellInitEvents = (adapter: CellAdapter) => { adapter.cell.model.contentChanged.connect( (cellModel, changedArgs) => { - cellStore.setSource(id, cellModel.sharedModel.getSource()); + cellsStore.setSource(id, cellModel.sharedModel.getSource()); } ); if (adapter.cell instanceof CodeCell) { adapter.cell.outputArea.outputLengthChanged?.connect( (outputArea, outputsCount) => { - cellStore.setOutputsCount(id, outputsCount); + cellsStore.setOutputsCount(id, outputsCount); } ); } adapter.sessionContext.initialize().then(() => { - if (!autoStart || !adapter.cell.model) { - return; - } - - // Perform auto-start for code or markdown cells - if (adapter.cell instanceof CodeCell) { - CodeCell.execute( - adapter.cell, - adapter.sessionContext - ); - } - - if (adapter.cell instanceof MarkdownCell) { - adapter.cell.rendered = true; + if (autoStart && adapter.cell.model) { + // Perform auto-start for code or markdown cells + adapter.execute(); } }); adapter.sessionContext.kernelChanged.connect(() => { void adapter.sessionContext.session?.kernel?.info.then(info => { // Set that session/kernel is ready for this cell when the kernel is guaranteed to be connected - cellStore.setIsKernelSessionAvailable(id, true); + cellsStore.setKernelSessionAvailable(id, true); }) }); } @@ -88,14 +84,15 @@ export const Cell = (props: ICellProps) => { if (id && defaultKernel && serverSettings) { defaultKernel.ready.then(() => { const adapter = new CellAdapter({ + id, type, source, serverSettings, kernel: defaultKernel, boxOptions: {showToolbar} }); - cellStore.setAdapter(id, adapter); - cellStore.setSource(id, source); + cellsStore.setAdapter(id, adapter); + cellsStore.setSource(id, source); handleCellInitEvents(adapter); setAdapter(adapter); diff --git a/packages/react/src/components/cell/CellAdapter.ts b/packages/react/src/components/cell/CellAdapter.ts index 4688d675..e5d9950e 100755 --- a/packages/react/src/components/cell/CellAdapter.ts +++ b/packages/react/src/components/cell/CellAdapter.ts @@ -7,6 +7,7 @@ import { BoxPanel, Widget } from '@lumino/widgets'; import { find } from '@lumino/algorithm'; import { CommandRegistry } from '@lumino/commands'; +import { JSONObject } from '@lumino/coreutils'; import { SessionContext, ISessionContext, @@ -14,6 +15,7 @@ import { ToolbarButton, } from '@jupyterlab/apputils'; import { CodeCellModel, CodeCell, Cell, MarkdownCell, RawCell, MarkdownCellModel } from '@jupyterlab/cells'; +import { Kernel as JupyterKernel, KernelMessage } from '@jupyterlab/services'; import { ybinding, CodeMirrorMimeTypeService, @@ -34,6 +36,7 @@ import { RenderMimeRegistry, standardRendererFactories as initialFactories, } from '@jupyterlab/rendermime'; +import * as OutputExecutor from './../output/OutputExecutor'; import { Session, ServerConnection, @@ -51,21 +54,28 @@ import { requireLoader as loader } from '../../jupyter/ipywidgets/libembed-amd'; import ClassicWidgetManager from '../../jupyter/ipywidgets/classic/manager'; import Kernel from '../../jupyter/kernel/Kernel'; import getMarked from '../notebook/marked/marked'; +import { jupyterStore } from '../../state'; import CellCommands from './CellCommands'; +import { CellsState } from './CellState'; interface BoxOptions { showToolbar?: boolean; } + export class CellAdapter { + private _id: string; private _cell: CodeCell | MarkdownCell | RawCell; + private _cellStore: CellsState; private _kernel: Kernel; private _panel: BoxPanel; private _sessionContext: SessionContext; - private _type: 'code' | 'markdown' | 'raw' + private _type: 'code' | 'markdown' | 'raw'; constructor(options: CellAdapter.ICellAdapterOptions) { - const { type, source, serverSettings, kernel, boxOptions } = options; + const { id, type, source, serverSettings, kernel, boxOptions } = options; + this._id = id; this._kernel = kernel; + this._cellStore = jupyterStore.getState().cellsStore(); this._type = type; this.setupCell(type, source, serverSettings, kernel, boxOptions); } @@ -303,7 +313,7 @@ export class CellAdapter { }); handler.editor = editor; - CellCommands(commands, this._cell!, this._sessionContext, handler); + CellCommands(commands, this._cell!, handler, this); completer.hide(); completer.addClass('jp-Completer-Cell'); Widget.attach(completer, document.body); @@ -313,7 +323,7 @@ export class CellAdapter { icon: runIcon, onClick: () => { if (this._type === 'code') { - CodeCell.execute(this._cell as CodeCell, this._sessionContext); + this.execute(); } else if (this._type === 'markdown') { (this._cell as MarkdownCell).rendered = true; } @@ -377,15 +387,125 @@ export class CellAdapter { execute = () => { if (this._type === 'code') { - CodeCell.execute((this._cell as CodeCell), this._sessionContext); + this._execute((this._cell as CodeCell)); } else if (this._type === 'markdown') { (this._cell as MarkdownCell).rendered = true; } - }; + } + + private async _execute( + cell: CodeCell, + metadata?: JSONObject + ): Promise { + const model = cell.model; + const code = model.sharedModel.getSource(); + if (!code.trim() || !this.kernel) { + model.sharedModel.transact(() => { + model.clearExecution(); + }, false); + return new Promise(() => {}); + } + const cellId = { cellId: model.sharedModel.getId() }; + metadata = { + ...model.metadata, + ...metadata, + ...cellId + }; + const { recordTiming } = metadata; + model.sharedModel.transact(() => { + model.clearExecution(); + cell.outputHidden = false; + }, false); + cell.setPrompt('*'); + model.trusted = true; + let future: + | JupyterKernel.IFuture< + KernelMessage.IExecuteRequestMsg, + KernelMessage.IExecuteReplyMsg + > + | undefined; + try { + const kernelMessagePromise = OutputExecutor.execute( + code, + cell.outputArea, + this._kernel, + metadata + ); + // cell.outputArea.future assigned synchronously in `execute`. + if (recordTiming) { + const recordTimingHook = (msg: KernelMessage.IIOPubMessage) => { + let label: string; + switch (msg.header.msg_type) { + case 'status': + label = `status.${ + (msg as KernelMessage.IStatusMsg).content.execution_state + }`; + break; + case 'execute_input': + label = 'execute_input'; + break; + default: + return true; + } + // If the data is missing, estimate it to now + // Date was added in 5.1: https://jupyter-client.readthedocs.io/en/stable/messaging.html#message-header + const value = msg.header.date || new Date().toISOString(); + const timingInfo: any = Object.assign( + {}, + model.getMetadata('execution') + ); + timingInfo[`iopub.${label}`] = value; + model.setMetadata('execution', timingInfo); + return true; + }; + cell.outputArea.future.registerMessageHook(recordTimingHook); + } else { + model.deleteMetadata('execution'); + } + // Save this execution's future so we can compare in the catch below. + future = cell.outputArea.future; + const executeReplyMessage = (await kernelMessagePromise)!; + model.executionCount = executeReplyMessage.content.execution_count; + if (recordTiming) { + const timingInfo = Object.assign( + {}, + model.getMetadata('execution') as any + ); + const started = executeReplyMessage.metadata.started as string; + // Started is not in the API, but metadata IPyKernel sends + if (started) { + timingInfo['shell.execute_reply.started'] = started; + } + // Per above, the 5.0 spec does not assume date, so we estimate is required + const finished = executeReplyMessage.header.date as string; + timingInfo['shell.execute_reply'] = + finished || new Date().toISOString(); + model.setMetadata('execution', timingInfo); + } + return executeReplyMessage; + } catch (e) { + // If we started executing, and the cell is still indicating this execution, clear the prompt. + if (future && !cell.isDisposed && cell.outputArea.future === future) { + cell.setPrompt(''); + if (recordTiming && future.isDisposed) { + // Record the time when the cell execution was aborted + const timingInfo: any = Object.assign( + {}, + model.getMetadata('execution') + ); + timingInfo['execution_failed'] = new Date().toISOString(); + model.setMetadata('execution', timingInfo); + } + } + throw e; + } + } + } export namespace CellAdapter { export type ICellAdapterOptions = { + id: string; type: 'code' | 'markdown' | 'raw'; source: string; serverSettings: ServerConnection.ISettings; diff --git a/packages/react/src/components/cell/CellCommands.ts b/packages/react/src/components/cell/CellCommands.ts index b08f8b2e..e48f36e0 100644 --- a/packages/react/src/components/cell/CellCommands.ts +++ b/packages/react/src/components/cell/CellCommands.ts @@ -7,7 +7,7 @@ import { CommandRegistry } from '@lumino/commands'; import { CompletionHandler } from '@jupyterlab/completer'; import { CodeCell, MarkdownCell, RawCell } from '@jupyterlab/cells'; -import { SessionContext } from '@jupyterlab/apputils'; +import CellAdapter from './CellAdapter'; const cmdIds = { invoke: 'completer:invoke', @@ -17,8 +17,8 @@ const cmdIds = { export const CellCommands = ( commandRegistry: CommandRegistry, cell: CodeCell | MarkdownCell | RawCell, - sessionContext: SessionContext, - completerHandler: CompletionHandler + completerHandler: CompletionHandler, + cellAdapter: CellAdapter, ): void => { commandRegistry.addCommand(cmdIds.invoke, { label: 'Completer: Invoke', @@ -31,7 +31,7 @@ export const CellCommands = ( commandRegistry.addCommand('run:cell', { execute: () => { if (cell instanceof CodeCell) { - CodeCell.execute(cell, sessionContext) + cellAdapter.execute(); } else if (cell instanceof MarkdownCell) { (cell as MarkdownCell).rendered = true; } diff --git a/packages/react/src/components/cell/CellState.ts b/packages/react/src/components/cell/CellState.ts index 0a7d1cdb..8414e5c8 100644 --- a/packages/react/src/components/cell/CellState.ts +++ b/packages/react/src/components/cell/CellState.ts @@ -20,16 +20,16 @@ export interface ICellsState { areAllKernelSessionsReady: boolean; // Control the state for all cells } -export type CellState = ICellsState & { +export type CellsState = ICellsState & { setCells: (cells: Map) => void; setSource: (id: string, source: string) => void; setOutputsCount: (id: string, outputsCount: number) => void; - setIsKernelSessionAvailable: (id: string, kernelAvailable: boolean) => void; + setKernelSessionAvailable: (id: string, kernelAvailable: boolean) => void; setAdapter: (id: string, adapter?: CellAdapter) => void; getAdapter: (id: string) => CellAdapter | undefined; getSource: (id: string) => string | undefined; getOutputsCount: (id: string) => number | undefined; - getIsKernelSessionAvailable: (id: string) => boolean | undefined; + isKernelSessionAvailable: (id: string) => boolean | undefined; execute: (id?: string) => void; }; @@ -45,24 +45,22 @@ const areAllKernelSessionsAvailable = (cells: Map): boolean return true; }; -export const cellStore = createStore((set, get) => ({ +export const cellsStore = createStore((set, get) => ({ cells: new Map(), source: '', outputsCount: 0, - isKernelSessionAvailable: false, areAllKernelSessionsReady: false, adapter: undefined, - setCells: (cells: Map) => set((cell: CellState) => ({ cells })), - + setCells: (cells: Map) => set((cell: CellsState) => ({ cells })), setSource: (id: string, source: string) => { const cells = get().cells; const cell = cells.get(id); if (cell) { cell.source = source; } else { - cells.set(id, {source}); + cells.set(id, { source }); } - set((cell: CellState) => ({ cells })) + set((cell: CellsState) => ({ cells })) }, setOutputsCount: (id: string, outputsCount: number) => { const cells = get().cells; @@ -70,11 +68,11 @@ export const cellStore = createStore((set, get) => ({ if (cell) { cell.outputsCount = outputsCount; } else { - cells.set(id, {outputsCount}); + cells.set(id, { outputsCount }); } - set((state: CellState) => ({ cells })) + set((state: CellsState) => ({ cells })); }, - setIsKernelSessionAvailable: (id: string, isKernelSessionAvailable: boolean) => { + setKernelSessionAvailable: (id: string, isKernelSessionAvailable: boolean) => { const cells = get().cells; const cell = cells.get(id); if (cell) { @@ -83,7 +81,7 @@ export const cellStore = createStore((set, get) => ({ cells.set(id, {isKernelSessionAvailable}); } const areAllKernelSessionsReady = areAllKernelSessionsAvailable(cells); - set((cell: CellState) => ({ cells, areAllKernelSessionsReady })); + set((cell: CellsState) => ({ cells, areAllKernelSessionsReady })); }, setAdapter: (id: string, adapter?: CellAdapter) => { const cells = get().cells; @@ -93,7 +91,7 @@ export const cellStore = createStore((set, get) => ({ } else { cells.set(id, { adapter }); } - set((cell: CellState) => ({ cells })) + set((cell: CellsState) => ({ cells })) }, getAdapter: (id: string) => { return get().cells.get(id)?.adapter; @@ -104,24 +102,24 @@ export const cellStore = createStore((set, get) => ({ getOutputsCount: (id: string): number | undefined => { return get().cells.get(id)?.outputsCount; }, - getIsKernelSessionAvailable: (id: string): boolean | undefined => { + isKernelSessionAvailable: (id: string): boolean | undefined => { return get().cells.get(id)?.isKernelSessionAvailable; }, execute: (id: string) => { const cells = get().cells; const cell = cells.get(id); if (cell) { - cell.adapter?.execute() + cell.adapter?.execute(); } else { - get().cells.forEach((cell) => cell.adapter?.execute()) + get().cells.forEach((cell) => cell.adapter?.execute()); } }, })); -export function useCellStore(): CellState; -export function useCellStore(selector: (state: CellState) => T): T; -export function useCellStore(selector?: (state: CellState) => T) { - return useStore(cellStore, selector!); +export function useCellsStore(): CellsState; +export function useCellsStore(selector: (state: CellsState) => T): T; +export function useCellsStore(selector?: (state: CellsState) => T) { + return useStore(cellsStore, selector!); } -export default useCellStore; +export default useCellsStore; diff --git a/packages/react/src/components/output/Output.tsx b/packages/react/src/components/output/Output.tsx index 78cffc6c..d0d2f5c9 100644 --- a/packages/react/src/components/output/Output.tsx +++ b/packages/react/src/components/output/Output.tsx @@ -30,7 +30,7 @@ export type IOutputProps = { disableRun: boolean; executeTrigger: number; insertText?: (payload?: any) => string; - kernel: Kernel; + kernel?: Kernel; lumino: boolean; model?: IOutputAreaModel; outputs?: IOutput[]; @@ -43,7 +43,7 @@ export type IOutputProps = { }; export const Output = (props: IOutputProps) => { - const { defaultKernel: kernel } = useJupyter(); + const { defaultKernel } = useJupyter(); const outputStore = useOutputStore(); const { adapter: propsAdapter, @@ -54,6 +54,7 @@ export const Output = (props: IOutputProps) => { disableRun, executeTrigger, insertText, + kernel, lumino, model, receipt, @@ -63,6 +64,7 @@ export const Output = (props: IOutputProps) => { sourceId, toolbarPosition, } = props; + const outputKernel = kernel ?? defaultKernel; const [id, setId] = useState(sourceId); const [kernelStatus, setKernelStatus] = useState('unknown'); @@ -74,9 +76,9 @@ export const Output = (props: IOutputProps) => { } }, []); useEffect(() => { - if (id && kernel) { + if (id && outputKernel) { const adapter = - propsAdapter ?? new OutputAdapter(kernel, outputs ?? [], model); + propsAdapter ?? new OutputAdapter(outputKernel, outputs ?? [], model); if (receipt) { adapter.outputArea.model.changed.connect((sender, change) => { if (change.type === 'add') { @@ -102,11 +104,11 @@ export const Output = (props: IOutputProps) => { setOutputs(outputModel.toJSON()); }); } - }, [id, kernel]); + }, [id, outputKernel]); useEffect(() => { if (adapter) { if (!adapter.kernel) { - adapter.kernel = kernel; + adapter.kernel = outputKernel; } if (autoRun) { adapter.execute(code); @@ -114,10 +116,10 @@ export const Output = (props: IOutputProps) => { } }, [adapter]); useEffect(() => { - if (kernel) { - kernel.ready.then(() => { - setKernelStatus(kernel.connection!.status); - kernel.connection!.statusChanged.connect((kernelConnection, status) => { + if (outputKernel) { + outputKernel.ready.then(() => { + setKernelStatus(outputKernel.connection!.status); + outputKernel.connection!.statusChanged.connect((kernelConnection, status) => { setKernelStatus(status); }); }); @@ -125,7 +127,7 @@ export const Output = (props: IOutputProps) => { // kernel.connection.then(k => k.shutdown().then(() => console.log(`Kernel ${k.id} is terminated.`))); }; } - }, [kernel]); + }, [outputKernel]); const executeRequest = outputStore.getExecute(sourceId); useEffect(() => { if (adapter && executeRequest && executeRequest.sourceId === id) { @@ -158,7 +160,7 @@ export const Output = (props: IOutputProps) => { codePre={codePre} disableRun={disableRun} insertText={insertText} - kernel={kernel} + kernel={outputKernel} outputAdapter={adapter} sourceId={id} toolbarPosition={toolbarPosition} @@ -172,7 +174,7 @@ export const Output = (props: IOutputProps) => { {showControl && ( - + )} diff --git a/packages/react/src/components/output/OutputExecutor.ts b/packages/react/src/components/output/OutputExecutor.ts new file mode 100755 index 00000000..89df71e8 --- /dev/null +++ b/packages/react/src/components/output/OutputExecutor.ts @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2021-2023 Datalayer, Inc. + * + * MIT License + */ + +import { JSONObject } from '@lumino/coreutils'; +import { KernelMessage } from '@jupyterlab/services'; +import { OutputArea } from '@jupyterlab/outputarea'; +import { Kernel } from './../../jupyter/kernel/Kernel'; + +/** + * Execute code on an output area. + */ +export async function execute( + code: string, + output: OutputArea, + kernel: Kernel, + metadata?: JSONObject +): Promise { + // Override the default for `stop_on_error`. + let stopOnError = true; + if ( + metadata && + Array.isArray(metadata.tags) && + metadata.tags.indexOf('raises-exception') !== -1 + ) { + stopOnError = false; + } + if (!kernel) { + throw new Error('No kernel avaiable.'); + } + /* + const content: KernelMessage.IExecuteRequestMsg['content'] = { + code, + stop_on_error: stopOnError + }; + */ + const kernelExecutor = kernel.execute( + code, + { + model: output.model, + stopOnError, + } + ); + const future = kernelExecutor!.future; + (output as any)._onIOPub = future!.onIOPub; + (output as any)._onExecuteReply = future!.onReply; + output.future = future!; + return future!.done; +} diff --git a/packages/react/src/examples/All.tsx b/packages/react/src/examples/All.tsx index 8023b20d..60760ac6 100644 --- a/packages/react/src/examples/All.tsx +++ b/packages/react/src/examples/All.tsx @@ -19,7 +19,7 @@ import Terminal from '../components/terminal/Terminal'; import CellSidebarNew from '../components/notebook/cell/sidebar/CellSidebarButton'; import CellSidebar from '../components/notebook/cell/sidebar/CellSidebar'; import Console from '../components/console/Console'; -import { useCellStore } from '../components/cell/CellState'; +import { useCellsStore } from '../components/cell/CellState'; import useNotebookStore from '../components/notebook/NotebookState'; import notebook from './notebooks/NotebookExample1.ipynb.json'; @@ -54,18 +54,18 @@ interface ICellToolProps { } const CellPreview = (props: ICellToolProps) => { - const cellStore = useCellStore(); + const cellsStore = useCellsStore(); return ( <> - <>source: {cellStore.getSource(props.id)} - <>kernel available: {String(cellStore.getIsKernelSessionAvailable(props.id))} + <>source: {cellsStore.getSource(props.id)} + <>kernel available: {String(cellsStore.isKernelSessionAvailable(props.id))} ); }; const CellToolbar = (props: ICellToolProps) => { const {id} = props; - const cellStore = useCellStore(); + const cellsStore = useCellsStore(); return ( <> @@ -73,20 +73,20 @@ const CellToolbar = (props: ICellToolProps) => { - Outputs count: {cellStore.getOutputsCount(id)} + Outputs count: {cellsStore.getOutputsCount(id)} ); }; diff --git a/packages/react/src/examples/Cell.tsx b/packages/react/src/examples/Cell.tsx index 8b9da052..2af16b47 100644 --- a/packages/react/src/examples/Cell.tsx +++ b/packages/react/src/examples/Cell.tsx @@ -7,39 +7,39 @@ import { createRoot } from 'react-dom/client'; import { Box, Button } from '@primer/react'; import { CodeCell } from '@jupyterlab/cells'; -import { useJupyterStore } from './../state'; import Jupyter from '../jupyter/Jupyter'; import Cell from '../components/cell/Cell'; +import { useCellsStore } from '../components/cell/CellState'; -const div = document.createElement('div'); -document.body.appendChild(div); -const root = createRoot(div); +const CELL_ID = 'cell-example-1'; const DEFAULT_SOURCE = `from IPython.display import display -for i in range(100): - display('I am a long string which is repeatedly added to the dom in separated divs: %d' % i)` +for i in range(10): + display('I am a long string which is repeatedly added to the dom in separated divs: %d' % i)`; const CellExample = () => { - const cellStore = useJupyterStore().cellStore(); - const cellId = 'cell-1' - - console.log('Cell Outputs', (cellStore.getAdapter(cellId)?.cell as CodeCell).outputArea.model.toJSON()); + const cellsStore = useCellsStore(); + console.log('Cell Outputs', (cellsStore.getAdapter(CELL_ID)?.cell as CodeCell)?.outputArea.model.toJSON()); return ( A Jupyter Cell - Outputs Count: {cellStore.getOutputsCount(cellId)} + Source: {cellsStore.getSource(CELL_ID)} - Source: {cellStore.getSource(cellId)} + Outputs Count: {cellsStore.getOutputsCount(CELL_ID)} - + - + ) } +const div = document.createElement('div'); +document.body.appendChild(div); +const root = createRoot(div); + root.render(); diff --git a/packages/react/src/examples/KernelExecResult.tsx b/packages/react/src/examples/KernelExecute.tsx similarity index 93% rename from packages/react/src/examples/KernelExecResult.tsx rename to packages/react/src/examples/KernelExecute.tsx index 2bfa9f2b..6d527815 100644 --- a/packages/react/src/examples/KernelExecResult.tsx +++ b/packages/react/src/examples/KernelExecute.tsx @@ -11,7 +11,7 @@ import Jupyter from '../jupyter/Jupyter'; import { useJupyter } from '../jupyter/JupyterContext'; import KernelProgressBar from './../components/kernel/KernelProgressBar'; -export const KernelExecResultView = () => { +export const KernelExecuteView = () => { const { defaultKernel } = useJupyter(); const [running, setRunning] = useState(false); const [code, setCode] = useState(''); @@ -72,10 +72,10 @@ export const KernelExecResultView = () => { ); }; -const KernelExecResult = () => { +const KernelExecute = () => { return ( - + ); }; @@ -84,4 +84,4 @@ const div = document.createElement('div'); document.body.appendChild(div); const root = createRoot(div); -root.render(); +root.render(); diff --git a/packages/react/src/examples/KernelExecutor.tsx b/packages/react/src/examples/KernelExecutor.tsx index ef583895..62e9c43d 100644 --- a/packages/react/src/examples/KernelExecutor.tsx +++ b/packages/react/src/examples/KernelExecutor.tsx @@ -36,14 +36,14 @@ const KernelExecutorView = () => { msg: KernelMessage.IIOPubMessage ) => { // Do something with the IOPub message. - console.debug('---iopubMessage', msg); + console.log('---iopubMessage', msg); return true; }; const shellMessageHook: ShellMessageHook = ( msg: KernelMessage.IShellControlMessage ) => { - // Do something with the IOPub message. - console.debug('---shellMessage', msg); + // Do something with the Shell message. + console.log('---shellMessage', msg); return true; }; const kernelExecutor = defaultKernel.execute(CODE, { diff --git a/packages/react/src/jupyter/kernel/Kernel.ts b/packages/react/src/jupyter/kernel/Kernel.ts index b818b4d6..da5cb8bb 100755 --- a/packages/react/src/jupyter/kernel/Kernel.ts +++ b/packages/react/src/jupyter/kernel/Kernel.ts @@ -185,19 +185,21 @@ export class Kernel { execute( code: string, { + model, iopubMessageHooks = [], shellMessageHooks = [], - model, silent, stopOnError, storeHistory, + allowStdin, }: { + model?: IOutputAreaModel; iopubMessageHooks?: IOPubMessageHook[]; shellMessageHooks?: ShellMessageHook[]; - model?: IOutputAreaModel; silent?: boolean; stopOnError?: boolean; storeHistory?: boolean; + allowStdin?: boolean; } = {} ): KernelExecutor | undefined { if (this._kernelConnection) { @@ -211,6 +213,7 @@ export class Kernel { silent, stopOnError, storeHistory, + allowStdin, }); return kernelExecutor; } diff --git a/packages/react/src/jupyter/kernel/KernelExecutor.ts b/packages/react/src/jupyter/kernel/KernelExecutor.ts index 6f98267c..8166b61d 100644 --- a/packages/react/src/jupyter/kernel/KernelExecutor.ts +++ b/packages/react/src/jupyter/kernel/KernelExecutor.ts @@ -30,7 +30,7 @@ export type ShellMessageHook = ( /** * KernelExecutor options */ -export interface IKernelExecutorOptions { +export type IKernelExecutorOptions = { /** * Kernel connection */ @@ -42,23 +42,23 @@ export interface IKernelExecutorOptions { } export class KernelExecutor { + private _executed: PromiseDelegate; private _kernelConnection: Kernel.IKernelConnection; - private _outputs: IOutput[]; - private _outputsChanged = new Signal(this); private _model: IOutputAreaModel; private _modelChanged = new Signal(this); + private _outputs: IOutput[]; + private _outputsChanged = new Signal(this); private _future?: Kernel.IFuture< KernelMessage.IExecuteRequestMsg, KernelMessage.IExecuteReplyMsg >; private _shellMessageHooks = new Array(); - private _executed: PromiseDelegate; constructor({ connection, model }: IKernelExecutorOptions) { + this._executed = new PromiseDelegate(); this._kernelConnection = connection; - this._outputs = []; this._model = model ?? new OutputAreaModel(); - this._executed = new PromiseDelegate(); + this._outputs = []; } /** @@ -84,34 +84,34 @@ export class KernelExecutor { silent = false, stopOnError = false, storeHistory = true, + allowStdin = false, }: { iopubMessageHooks?: IOPubMessageHook[]; shellMessageHooks?: ShellMessageHook[]; silent?: boolean; stopOnError?: boolean; storeHistory?: boolean; + allowStdin?: boolean; } = {} ): Promise { this._shellMessageHooks = shellMessageHooks; this._future = this._kernelConnection.requestExecute({ code, - allow_stdin: false, + allow_stdin: allowStdin, silent, stop_on_error: stopOnError, store_history: storeHistory, }); - iopubMessageHooks.forEach(hook => this._future!.registerMessageHook(hook)); this._future.onIOPub = this._onIOPub; this._future.onReply = this._onReply; - /* - FIXME Handle stdin. It will require updating the `allow_stdin` param aboove . - future.onStdin = msg => { + iopubMessageHooks.forEach(hook => this._future!.registerMessageHook(hook)); + this._future.onStdin = msg => { if (KernelMessage.isInputRequestMsg(msg)) { - this.onInputRequest(msg, value); + // FIXME Implement this. + // this.onInputRequest(msg, value); } }; - */ - // Wait for future to be done before resolving. + // Wait for future to be done before resolving the exectud promise. this._future.done.then(() => { this._executed.resolve(this._model); }); @@ -127,7 +127,68 @@ export class KernelExecutor { this._model.clear(); } + registerIOPubMessageHook = (msg: IOPubMessageHook) => { + this._future?.registerMessageHook(msg); + }; + + /** + * + */ + get future(): Kernel.IFuture< + KernelMessage.IExecuteRequestMsg, + KernelMessage.IExecuteReplyMsg + > | undefined { + return this._future; + } + + /** + * Promise that resolves when the execution is done. + */ + get done(): Promise { + return this._executed.promise.then(() => { + return; + }); + } + + /** + * Code execution result as serialized JSON + */ + get result(): Promise { + return this._executed.promise.then(model => { + return outputsAsString(model.toJSON()); + }); + } + + /** + * Kernel outputs emitted. + */ + get outputs(): IOutput[] { + return this._outputs; + } + + /** + * Kernel outputs wrapped in a model. + */ + get model(): IOutputAreaModel { + return this._model; + } + + /** + * Signal emitted when the outputs list changes. + */ + get outputsChanged(): ISignal {0 + return this._outputsChanged; + } + + /** + * Signal emitted when the outputs model changes. + */ + get modelChanged(): ISignal { + return this._modelChanged; + } + private _onIOPub = (message: KernelMessage.IIOPubMessage): void => { + console.log('---IOPub', message); if (this._future?.msg.header.msg_id !== message.parent_header.msg_id) { return; } @@ -175,6 +236,7 @@ export class KernelExecutor { }; private _onReply = (message: KernelMessage.IExecuteReplyMsg): void => { + console.log('---Reply', message); if (this._future?.msg.header.msg_id !== message.parent_header.msg_id) { return; } @@ -219,56 +281,6 @@ export class KernelExecutor { } }; - registerIOPubMessageHook = (msg: IOPubMessageHook) => { - this._future?.registerMessageHook(msg); - }; - - /** - * Promise that resolves when the execution is done. - */ - get done(): Promise { - return this._executed.promise.then(() => { - return; - }); - } - - /** - * Code execution result as serialized JSON - */ - get result(): Promise { - return this._executed.promise.then(model => { - return outputsAsString(model.toJSON()); - }); - } - - /** - * Kernel outputs emitted. - */ - get outputs(): IOutput[] { - return this._outputs; - } - - /** - * Kernel outputs wrapped in a model. - */ - get model(): IOutputAreaModel { - return this._model; - } - - /** - * Signal emitted when the outputs list changes. - */ - get outputsChanged(): ISignal {0 - return this._outputsChanged; - } - - /** - * Signal emitted when the outputs model changes. - */ - get modelChanged(): ISignal { - return this._modelChanged; - } - } export default KernelExecutor; diff --git a/packages/react/src/state/State.ts b/packages/react/src/state/State.ts index 6f0b9fcf..f166498a 100644 --- a/packages/react/src/state/State.ts +++ b/packages/react/src/state/State.ts @@ -10,7 +10,7 @@ import { useStore } from 'zustand'; import { ServiceManager } from '@jupyterlab/services'; import type { IDatalayerConfig } from './IState'; import { IJupyterConfig, loadJupyterConfig } from '../jupyter/JupyterConfig'; -import useCellStore from '../components/cell/CellState'; +import { cellsStore } from '../components/cell/CellState'; import useConsoleStore from '../components/console/ConsoleState'; import useNotebookStore from '../components/notebook/NotebookState'; import useOutputStore from '../components/output/OutputState'; @@ -21,7 +21,7 @@ import { ensureJupyterAuth, createServerSettings, JupyterContextPropsType } from import Kernel from '../jupyter/kernel/Kernel'; export type JupyterState = { - cellStore: typeof useCellStore; + cellsStore: typeof cellsStore.getState; consoleStore: typeof useConsoleStore; datalayerConfig?: IDatalayerConfig; jupyterConfig?: IJupyterConfig; @@ -63,7 +63,7 @@ export const jupyterStore = createStore((set, get) => ({ kernel: undefined, serviceManager: undefined, serverSettings: undefined, - cellStore: useCellStore, + cellsStore: cellsStore.getState, consoleStore: useConsoleStore, notebookStore: useNotebookStore, outputStore: useOutputStore, diff --git a/packages/react/webpack.config.js b/packages/react/webpack.config.js index 0c0345d1..3f5991ee 100644 --- a/packages/react/webpack.config.js +++ b/packages/react/webpack.config.js @@ -18,7 +18,7 @@ function shim(regExp) { const ENTRY = // './src/app/App'; - // './src/examples/Cell'; + './src/examples/Cell'; // './src/examples/CellLite'; // './src/examples/Console'; // './src/examples/ConsoleLite'; @@ -30,11 +30,11 @@ const ENTRY = // './src/examples/JupyterLabApp'; // './src/examples/JupyterLabHeadlessApp'; // './src/examples/Kernels'; + // './src/examples/KernelExecute'; // './src/examples/KernelExecutor'; - // './src/examples/KernelExecResult'; // './src/examples/Lumino'; // './src/examples/Matplotlib'; - './src/examples/Notebook'; + // './src/examples/Notebook'; // './src/examples/NotebookUrl'; // './src/examples/NotebookColorMode'; // './src/examples/NotebookKernelChange'; From 691ea6c7b55509157c788c76104450ceacb924cf Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Tue, 30 Jul 2024 12:01:52 +0200 Subject: [PATCH 05/15] chore: output and cell to use kernel executor --- .../react/src/components/cell/CellAdapter.ts | 8 +- .../codemirror/CodeMirrorEditor.tsx | 16 +-- .../react/src/components/output/Output.tsx | 79 ++++++++------- .../src/components/output/OutputAdapter.ts | 24 ++--- .../src/components/output/OutputExecutor.ts | 7 +- .../src/components/output/OutputState.ts | 97 +++++++++++-------- packages/react/src/examples/All.tsx | 1 - packages/react/src/examples/Cells.tsx | 7 +- packages/react/src/examples/NotebookInit.tsx | 1 - .../src/examples/NotebookKernelChange.tsx | 1 - .../react/src/examples/NotebookUnmount.tsx | 1 - packages/react/src/examples/Output.tsx | 89 ++++++++++++++--- packages/react/src/jupyter/kernel/Kernel.ts | 16 ++- .../src/jupyter/kernel/KernelExecutor.ts | 4 +- packages/react/src/state/State.ts | 32 +++--- packages/react/webpack.config.js | 5 +- 16 files changed, 229 insertions(+), 159 deletions(-) diff --git a/packages/react/src/components/cell/CellAdapter.ts b/packages/react/src/components/cell/CellAdapter.ts index e5d9950e..2295523c 100755 --- a/packages/react/src/components/cell/CellAdapter.ts +++ b/packages/react/src/components/cell/CellAdapter.ts @@ -54,28 +54,22 @@ import { requireLoader as loader } from '../../jupyter/ipywidgets/libembed-amd'; import ClassicWidgetManager from '../../jupyter/ipywidgets/classic/manager'; import Kernel from '../../jupyter/kernel/Kernel'; import getMarked from '../notebook/marked/marked'; -import { jupyterStore } from '../../state'; import CellCommands from './CellCommands'; -import { CellsState } from './CellState'; interface BoxOptions { showToolbar?: boolean; } export class CellAdapter { - private _id: string; private _cell: CodeCell | MarkdownCell | RawCell; - private _cellStore: CellsState; private _kernel: Kernel; private _panel: BoxPanel; private _sessionContext: SessionContext; private _type: 'code' | 'markdown' | 'raw'; constructor(options: CellAdapter.ICellAdapterOptions) { - const { id, type, source, serverSettings, kernel, boxOptions } = options; - this._id = id; + const { type, source, serverSettings, kernel, boxOptions } = options; this._kernel = kernel; - this._cellStore = jupyterStore.getState().cellsStore(); this._type = type; this.setupCell(type, source, serverSettings, kernel, boxOptions); } diff --git a/packages/react/src/components/codemirror/CodeMirrorEditor.tsx b/packages/react/src/components/codemirror/CodeMirrorEditor.tsx index 9139cf1f..7b2e33f0 100644 --- a/packages/react/src/components/codemirror/CodeMirrorEditor.tsx +++ b/packages/react/src/components/codemirror/CodeMirrorEditor.tsx @@ -40,7 +40,7 @@ export const CodeMirrorEditor = (props: { const outputStore = useOutputStore(); const [view, setView] = useState(); const dataset = outputStore.getDataset(sourceId); - const source = outputStore.getSource(sourceId); + const source = outputStore.getInput(sourceId); const editorDiv = useRef(); const setEditorSource = (source: string | undefined) => { if (view && source) { @@ -78,9 +78,9 @@ export const CodeMirrorEditor = (props: { return true; }; useEffect(() => { - outputStore.setSource({ - sourceId, - source: code, + outputStore.setInput({ + id: sourceId, + input: code, }) const language = new Compartment(); const keyBinding = [ @@ -101,9 +101,9 @@ export const CodeMirrorEditor = (props: { EditorView.updateListener.of((viewUpdate: ViewUpdate) => { if (viewUpdate.docChanged) { const source = viewUpdate.state.doc.toString(); - outputStore.setSource({ - sourceId, - source, + outputStore.setInput({ + id: sourceId, + input: source, }); } }), @@ -125,7 +125,7 @@ export const CodeMirrorEditor = (props: { doInsertText(dataset?.dataset); }, [dataset]); useEffect(() => { - setEditorSource(source?.source); + setEditorSource(source?.input); }, [source]); return ( <> diff --git a/packages/react/src/components/output/Output.tsx b/packages/react/src/components/output/Output.tsx index d0d2f5c9..01fba97c 100644 --- a/packages/react/src/components/output/Output.tsx +++ b/packages/react/src/components/output/Output.tsx @@ -11,12 +11,12 @@ import { IOutputAreaModel } from '@jupyterlab/outputarea'; import { KernelMessage } from '@jupyterlab/services'; import { newUuid } from '../../utils'; import { useJupyter } from '../../jupyter/JupyterContext'; -import Kernel from '../../jupyter/kernel/Kernel'; +import { Kernel } from '../../jupyter/kernel/Kernel'; import { KernelActionMenu, KernelProgressBar } from './../kernel'; -import Lumino from '../lumino/Lumino'; -import CodeMirrorEditor from '../codemirror/CodeMirrorEditor'; -import OutputAdapter from './OutputAdapter'; -import OutputRenderer from './OutputRenderer'; +import { Lumino } from '../lumino/Lumino'; +import { CodeMirrorEditor } from '../codemirror/CodeMirrorEditor'; +import { OutputAdapter } from './OutputAdapter'; +import { OutputRenderer } from './OutputRenderer'; import { useOutputStore } from './OutputState'; import './Output.css'; @@ -29,6 +29,7 @@ export type IOutputProps = { codePre?: string; disableRun: boolean; executeTrigger: number; + id: string; insertText?: (payload?: any) => string; kernel?: Kernel; lumino: boolean; @@ -38,7 +39,6 @@ export type IOutputProps = { showControl?: boolean; showEditor: boolean; showKernelProgressBar?: boolean; - sourceId: string; toolbarPosition: 'up' | 'middle' | 'none'; }; @@ -54,21 +54,21 @@ export const Output = (props: IOutputProps) => { disableRun, executeTrigger, insertText, - kernel, + kernel: propsKernel, lumino, model, + outputs: propsOutputs, receipt, showControl, showEditor, showKernelProgressBar = true, - sourceId, + id: sourceId, toolbarPosition, } = props; - const outputKernel = kernel ?? defaultKernel; + const kernel = propsKernel ?? defaultKernel; const [id, setId] = useState(sourceId); - const [kernelStatus, setKernelStatus] = - useState('unknown'); - const [outputs, setOutputs] = useState(props.outputs); + const [kernelStatus, setKernelStatus] = useState('unknown'); + const [outputs, setOutputs] = useState(propsOutputs); const [adapter, setAdapter] = useState(); useEffect(() => { if (!id) { @@ -76,9 +76,29 @@ export const Output = (props: IOutputProps) => { } }, []); useEffect(() => { - if (id && outputKernel) { - const adapter = - propsAdapter ?? new OutputAdapter(outputKernel, outputs ?? [], model); + if (id && kernel) { + const adapter = propsAdapter ?? new OutputAdapter(kernel, outputs ?? [], model); + setAdapter(adapter); + outputStore.setAdapter(id, adapter); + if (model) { + outputStore.setOutput({ + id, + model: model, + }) + } + if (code) { + outputStore.setInput({ + id, + input: code, + }) + } + adapter.outputArea.model.changed.connect((model, change) => { + setOutputs(model.toJSON()); + outputStore.setOutput({ + id, + model, + }); + }); if (receipt) { adapter.outputArea.model.changed.connect((sender, change) => { if (change.type === 'add') { @@ -88,7 +108,7 @@ export const Output = (props: IOutputProps) => { if (out) { if ((out as string).indexOf(receipt) > -1) { outputStore.setGrade({ - sourceId, + id: sourceId, success: true, }); } @@ -98,28 +118,20 @@ export const Output = (props: IOutputProps) => { } }); } - setAdapter(adapter); - outputStore.setAdapter(sourceId, adapter); - adapter.outputArea.model.changed.connect((outputModel, args) => { - setOutputs(outputModel.toJSON()); - }); } - }, [id, outputKernel]); + }, [id, kernel]); useEffect(() => { if (adapter) { - if (!adapter.kernel) { - adapter.kernel = outputKernel; - } if (autoRun) { adapter.execute(code); } } }, [adapter]); useEffect(() => { - if (outputKernel) { - outputKernel.ready.then(() => { - setKernelStatus(outputKernel.connection!.status); - outputKernel.connection!.statusChanged.connect((kernelConnection, status) => { + if (kernel) { + kernel.ready.then(() => { + setKernelStatus(kernel.connection!.status); + kernel.connection!.statusChanged.connect((kernelConnection, status) => { setKernelStatus(status); }); }); @@ -127,10 +139,10 @@ export const Output = (props: IOutputProps) => { // kernel.connection.then(k => k.shutdown().then(() => console.log(`Kernel ${k.id} is terminated.`))); }; } - }, [outputKernel]); + }, [kernel]); const executeRequest = outputStore.getExecute(sourceId); useEffect(() => { - if (adapter && executeRequest && executeRequest.sourceId === id) { + if (adapter && executeRequest && executeRequest.id === id) { adapter.execute(executeRequest.source); } }, [executeRequest, adapter]); @@ -160,7 +172,7 @@ export const Output = (props: IOutputProps) => { codePre={codePre} disableRun={disableRun} insertText={insertText} - kernel={outputKernel} + kernel={kernel} outputAdapter={adapter} sourceId={id} toolbarPosition={toolbarPosition} @@ -174,7 +186,7 @@ export const Output = (props: IOutputProps) => { {showControl && ( - + )} @@ -216,6 +228,7 @@ export const Output = (props: IOutputProps) => { }; Output.defaultProps = { + autoRun: false, clearTrigger: 0, disableRun: false, executeTrigger: 0, diff --git a/packages/react/src/components/output/OutputAdapter.ts b/packages/react/src/components/output/OutputAdapter.ts index 0304a48f..708d159d 100755 --- a/packages/react/src/components/output/OutputAdapter.ts +++ b/packages/react/src/components/output/OutputAdapter.ts @@ -5,25 +5,15 @@ */ import { IOutput } from '@jupyterlab/nbformat'; -import { - IOutputAreaModel, - OutputArea, - OutputAreaModel, -} from '@jupyterlab/outputarea'; -import { - IRenderMime, - RenderMimeRegistry, - standardRendererFactories, -} from '@jupyterlab/rendermime'; +import { IOutputAreaModel, OutputArea, OutputAreaModel } from '@jupyterlab/outputarea'; +import { IRenderMime, RenderMimeRegistry, standardRendererFactories } from '@jupyterlab/rendermime'; import { rendererFactory as jsonRendererFactory } from '@jupyterlab/json-extension'; import { rendererFactory as javascriptRendererFactory } from '@jupyterlab/javascript-extension'; -import { - WIDGET_MIMETYPE, - WidgetRenderer, -} from '@jupyter-widgets/html-manager/lib/output_renderers'; +import { WIDGET_MIMETYPE, WidgetRenderer } from '@jupyter-widgets/html-manager/lib/output_renderers'; import { requireLoader as loader } from '../../jupyter/ipywidgets/libembed-amd'; import { ClassicWidgetManager } from '../../jupyter/ipywidgets/classic/manager'; -import Kernel from '../../jupyter/kernel/Kernel'; +import { Kernel } from '../../jupyter/kernel/Kernel'; +import { execute } from './OutputExecutor'; export class OutputAdapter { private _kernel?: Kernel; @@ -89,8 +79,8 @@ export class OutputAdapter { public async execute(code: string) { if (this._kernel) { this.clear(); - await this._kernel?.execute(code, { model: this._outputArea.model }) - ?.done; + const done = execute(code, this._outputArea, this._kernel); + await done; } } diff --git a/packages/react/src/components/output/OutputExecutor.ts b/packages/react/src/components/output/OutputExecutor.ts index 89df71e8..bc687509 100755 --- a/packages/react/src/components/output/OutputExecutor.ts +++ b/packages/react/src/components/output/OutputExecutor.ts @@ -30,12 +30,6 @@ export async function execute( if (!kernel) { throw new Error('No kernel avaiable.'); } - /* - const content: KernelMessage.IExecuteRequestMsg['content'] = { - code, - stop_on_error: stopOnError - }; - */ const kernelExecutor = kernel.execute( code, { @@ -44,6 +38,7 @@ export async function execute( } ); const future = kernelExecutor!.future; + // TODO review this if possible in upstream JupyterLab. (output as any)._onIOPub = future!.onIOPub; (output as any)._onExecuteReply = future!.onReply; output.future = future!; diff --git a/packages/react/src/components/output/OutputState.ts b/packages/react/src/components/output/OutputState.ts index 1a3520d1..bf971b30 100644 --- a/packages/react/src/components/output/OutputState.ts +++ b/packages/react/src/components/output/OutputState.ts @@ -6,26 +6,31 @@ import { createStore } from 'zustand/vanilla'; import { useStore } from 'zustand'; +import { IOutputAreaModel } from '@jupyterlab/outputarea'; import OutputAdapter from './OutputAdapter'; export namespace OutputState { - export type ISource = { - sourceId: string; - source: string; + export type IOutput = { + id: string; + model: IOutputAreaModel; + }; + export type IInput = { + id: string; + input: string; increment?: number; }; export type IDataset = { - sourceId: string; + id: string; dataset: any; increment?: number; }; export type IExecute = { - sourceId: string; + id: string; source: string; increment?: number; }; export type IGrade = { - sourceId: string; + id: string; success: boolean; increment?: number; }; @@ -33,7 +38,8 @@ export namespace OutputState { export type IOutputState = { adapter?: OutputAdapter; - source?: OutputState.ISource; + output?: OutputState.IOutput; + source?: OutputState.IInput; dataset?: OutputState.IDataset; execute?: OutputState.IExecute; grade?: OutputState.IGrade; @@ -44,21 +50,41 @@ export interface IOutputsState { } export type OutputState = IOutputsState & { - setOutputs: (outputs: Map) => void; - setAdapter: (id: string, adapter: OutputAdapter) => void; - setDataset: (dataset: OutputState.IDataset) => void; - setExecute: (execute: OutputState.IExecute) => void; - setSource: (source: OutputState.ISource) => void; - setGrade: (grade: OutputState.IGrade) => void; getAdapter: (id: string) => OutputAdapter | undefined; - getSource: (id: string) => OutputState.ISource | undefined; getDataset: (id: string) => OutputState.IDataset | undefined; getExecute: (id: string) => OutputState.IExecute | undefined; getGrade: (id: string) => OutputState.IGrade | undefined; + getOutput: (id: string) => OutputState.IOutput | undefined; + getInput: (id: string) => OutputState.IInput | undefined; + setAdapter: (id: string, adapter: OutputAdapter) => void; + setDataset: (dataset: OutputState.IDataset) => void; + setExecute: (execute: OutputState.IExecute) => void; + setGrade: (grade: OutputState.IGrade) => void; + setOutput: (output: OutputState.IOutput) => void; + setOutputs: (outputs: Map) => void; + setInput: (source: OutputState.IInput) => void; }; export const outputStore = createStore((set, get) => ({ outputs: new Map(), + getAdapter: (id: string) => { + return get().outputs.get(id)?.adapter; + }, + getInput: (id: string): OutputState.IInput | undefined => { + return get().outputs.get(id)?.source; + }, + getOutput: (id: string): OutputState.IOutput | undefined => { + return get().outputs.get(id)?.output; + }, + getDataset: (id: string): OutputState.IDataset | undefined => { + return get().outputs.get(id)?.dataset; + }, + getExecute: (id: string): OutputState.IExecute | undefined => { + return get().outputs.get(id)?.execute; + }, + getGrade: (id: string): OutputState.IGrade | undefined => { + return get().outputs.get(id)?.grade; + }, setOutputs: (outputs: Map) => set((state: OutputState) => ({ outputs })), setAdapter: (id: string, adapter: OutputAdapter) => { const outputs = get().outputs; @@ -71,7 +97,7 @@ export const outputStore = createStore((set, get) => ({ set((state: OutputState) => ({ outputs })) }, setDataset: (dataset: OutputState.IDataset) => { - const sourceId = dataset.sourceId; + const sourceId = dataset.id; const outputs = get().outputs; const d = outputs.get(sourceId); if (d) { @@ -82,7 +108,7 @@ export const outputStore = createStore((set, get) => ({ set((state: OutputState) => ({ outputs })) }, setExecute: (execute: OutputState.IExecute) => { - const sourceId = execute.sourceId; + const sourceId = execute.id; const outputs = get().outputs; const e = outputs.get(sourceId); if (e) { @@ -92,22 +118,32 @@ export const outputStore = createStore((set, get) => ({ } set((state: OutputState) => ({ outputs })) }, - setSource: (sourceState: OutputState.ISource) => { + setOutput: (outputState: OutputState.IOutput) => { + const outputs = get().outputs; + outputs.set(outputState.id, { + output: { + id: outputState.id, + model: outputState.model, + } + }); + get().setOutputs(outputs); + }, + setInput: (sourceState: OutputState.IInput) => { const outputs = get().outputs; - const output = outputs.get(sourceState.sourceId); - if (output?.source?.source === sourceState.source) { + const output = outputs.get(sourceState.id); + if (output?.source?.input === sourceState.input) { return; } - outputs.set(sourceState.sourceId, { + outputs.set(sourceState.id, { source: { - sourceId: sourceState.sourceId, - source: sourceState.source + id: sourceState.id, + input: sourceState.input, } }); get().setOutputs(outputs); }, setGrade: (grade: OutputState.IGrade) => { - const sourceId = grade.sourceId; + const sourceId = grade.id; const outputs = get().outputs; const g = outputs.get(sourceId); if (g) { @@ -117,21 +153,6 @@ export const outputStore = createStore((set, get) => ({ } set((state: OutputState) => ({ outputs })) }, - getAdapter: (id: string) => { - return get().outputs.get(id)?.adapter; - }, - getSource: (id: string): OutputState.ISource | undefined => { - return get().outputs.get(id)?.source; - }, - getDataset: (id: string): OutputState.IDataset | undefined => { - return get().outputs.get(id)?.dataset; - }, - getExecute: (id: string): OutputState.IExecute | undefined => { - return get().outputs.get(id)?.execute; - }, - getGrade: (id: string): OutputState.IGrade | undefined => { - return get().outputs.get(id)?.grade; - }, })); export function useOutputStore(): OutputState; diff --git a/packages/react/src/examples/All.tsx b/packages/react/src/examples/All.tsx index 60760ac6..2f8de0af 100644 --- a/packages/react/src/examples/All.tsx +++ b/packages/react/src/examples/All.tsx @@ -131,7 +131,6 @@ const NotebookKernelChange = () => { kernelManager, kernelName: 'defaultKernel', kernelSpecName: 'python', - kernelType: 'notebook', kernelspecsManager: serviceManager.kernelspecs, sessionManager: serviceManager.sessions, }); diff --git a/packages/react/src/examples/Cells.tsx b/packages/react/src/examples/Cells.tsx index 036a48e2..08f18795 100644 --- a/packages/react/src/examples/Cells.tsx +++ b/packages/react/src/examples/Cells.tsx @@ -16,10 +16,9 @@ const root = createRoot(div); root.render( Jupyter Cells wrapped in a single Jupyter Context - - - + + diff --git a/packages/react/src/examples/NotebookInit.tsx b/packages/react/src/examples/NotebookInit.tsx index ffef04bb..8de181ef 100644 --- a/packages/react/src/examples/NotebookInit.tsx +++ b/packages/react/src/examples/NotebookInit.tsx @@ -37,7 +37,6 @@ const useKernel = () => { kernelManager, kernelName: JUPYTER_KERNEL_NAME, kernelSpecName: JUPYTER_KERNEL_NAME, - kernelType: 'notebook', kernelspecsManager: serviceManager.kernelspecs, sessionManager: serviceManager.sessions, }); diff --git a/packages/react/src/examples/NotebookKernelChange.tsx b/packages/react/src/examples/NotebookKernelChange.tsx index 5d08f994..892ea9e3 100755 --- a/packages/react/src/examples/NotebookKernelChange.tsx +++ b/packages/react/src/examples/NotebookKernelChange.tsx @@ -26,7 +26,6 @@ const NotebookKernelChange = () => { kernelManager, kernelName: NEW_KERNEL_NAME, kernelSpecName: NEW_KERNEL_NAME, - kernelType: 'notebook', kernelspecsManager: serviceManager.kernelspecs, sessionManager: serviceManager.sessions, }); diff --git a/packages/react/src/examples/NotebookUnmount.tsx b/packages/react/src/examples/NotebookUnmount.tsx index 22286b1d..fc7caa1f 100755 --- a/packages/react/src/examples/NotebookUnmount.tsx +++ b/packages/react/src/examples/NotebookUnmount.tsx @@ -26,7 +26,6 @@ const NotebookUnmount = () => { kernelManager, kernelName: 'defaultKernel', kernelSpecName: 'python', - kernelType: 'notebook', kernelspecsManager: serviceManager.kernelspecs, sessionManager: serviceManager.sessions, }); diff --git a/packages/react/src/examples/Output.tsx b/packages/react/src/examples/Output.tsx index 49462b08..a4faa5c8 100644 --- a/packages/react/src/examples/Output.tsx +++ b/packages/react/src/examples/Output.tsx @@ -4,15 +4,18 @@ * MIT License */ +import { useEffect, useState } from 'react'; import { createRoot } from 'react-dom/client'; import { IOutput } from '@jupyterlab/nbformat'; import { Text } from '@primer/react'; -import { useJupyterStore } from './../state'; +import { useOutputStore } from './../components/output/OutputState'; import { useJupyter } from '../jupyter/JupyterContext'; -import Jupyter from '../jupyter/Jupyter'; -import Output from '../components/output/Output'; +import { Jupyter } from '../jupyter/Jupyter'; +import { Kernel } from '../jupyter/kernel/Kernel'; +import { newUuid } from '../utils/Utils'; +import { Output } from '../components/output/Output'; -const SOURCE_ID_1 = '1'; +const SOURCE_ID_1 = 'output-id-1'; const OUTPUTS_1: IOutput[] = [ { data: { @@ -24,7 +27,7 @@ const OUTPUTS_1: IOutput[] = [ }, ]; -const SOURCE_ID_2 = '2'; +const SOURCE_ID_2 = 'output-id-2'; const SOURCE_2 = '2+2'; const OUTPUTS_2: IOutput[] = [ { @@ -37,20 +40,34 @@ const OUTPUTS_2: IOutput[] = [ }, ]; +const SOURCE_ID_3 = 'output-id-3'; +const SOURCE_3 = 'x=2'; +const OUTPUTS_3: IOutput[] = [ + { + data: { + 'text/plain': ['2'], + }, + execution_count: 1, + metadata: {}, + output_type: 'execute_result', + }, +]; + const OutputWithoutEditor = () => { - const outputStore = useJupyterStore().outputStore(); + const outputStore = useOutputStore(); console.log( 'Outputs 1', - outputStore.getAdapter(SOURCE_ID_1)?.outputArea.model.toJSON(), - outputStore.getSource(SOURCE_ID_1), + outputStore.getOutput(SOURCE_ID_1)?.model.toJSON(), + outputStore.getInput(SOURCE_ID_1), ); return ( <> Output without Code Editor ); @@ -58,27 +75,66 @@ const OutputWithoutEditor = () => { const OutputWithEditor = () => { const { defaultKernel } = useJupyter(); - const outputStore = useJupyterStore().outputStore(); + const outputStore = useOutputStore(); console.log( 'Outputs 2', - outputStore.getAdapter(SOURCE_ID_2)?.outputArea.model.toJSON(), - outputStore.getSource(SOURCE_ID_2), + outputStore.getOutput(SOURCE_ID_2)?.model.toJSON(), + outputStore.getInput(SOURCE_ID_2), ); return ( <> Output with Code Editor ); }; +const OutputWithEmptyOutput = () => { + const { kernelManager, serviceManager } = useJupyter(); + const [kernel, setKernel] = useState(); + useEffect(() => { + if (serviceManager && kernelManager) { + const kernel = new Kernel({ + kernelManager, + kernelName: 'kernel-example', + kernelSpecName: 'python', + kernelspecsManager: serviceManager.kernelspecs, + path: newUuid(), + sessionManager: serviceManager.sessions, + }); + setKernel(kernel); + } + }, [serviceManager, kernelManager]); + const outputStore = useOutputStore(); + console.log( + 'Outputs 3', + outputStore.getOutput(SOURCE_ID_3)?.model.toJSON(), + outputStore.getInput(SOURCE_ID_3), + ); + return ( + <> + Output with empty Output + { kernel && + + } + + ); +}; + const div = document.createElement('div'); document.body.appendChild(div); const root = createRoot(div); @@ -87,5 +143,6 @@ root.render( + ); diff --git a/packages/react/src/jupyter/kernel/Kernel.ts b/packages/react/src/jupyter/kernel/Kernel.ts index da5cb8bb..62bcff12 100755 --- a/packages/react/src/jupyter/kernel/Kernel.ts +++ b/packages/react/src/jupyter/kernel/Kernel.ts @@ -51,20 +51,22 @@ export class Kernel { kernelspecsManager, kernelSpecName, kernelModel, + path, sessionManager, } = props; this._kernelSpecManager = kernelspecsManager; this._kernelManager = kernelManager; this._kernelName = kernelName; - this._kernelType = kernelType; + this._kernelType = kernelType ?? 'notebook'; this._kernelSpecName = kernelSpecName; this._sessionManager = sessionManager; this._ready = new PromiseDelegate(); - this.requestKernel(kernelModel); + this.requestKernel(kernelModel, path); } private async requestKernel( - kernelModel?: JupyterKernel.IModel + kernelModel?: JupyterKernel.IModel, + propsPath?: string, ): Promise { await this._kernelManager.ready; await this._sessionManager.ready; @@ -78,7 +80,7 @@ export class Kernel { this._session = this._sessionManager.connectTo({ model }); } } else { - let path = getCookie(this.cookieName); + let path = propsPath ?? getCookie(this.cookieName); if (!path) { path = 'kernel-' + newUuid(); document.cookie = this.cookieName + '=' + path; @@ -267,6 +269,10 @@ export namespace Kernel { * Kernel options */ export type IKernelProps = { + /** + * A path + */ + path?: string; /** * Kernel manager */ @@ -286,7 +292,7 @@ export namespace Kernel { /** * Kernel type */ - kernelType: 'notebook' | 'file'; + kernelType?: 'notebook' | 'file' | undefined; /** * Session manager */ diff --git a/packages/react/src/jupyter/kernel/KernelExecutor.ts b/packages/react/src/jupyter/kernel/KernelExecutor.ts index 8166b61d..16c6ed25 100644 --- a/packages/react/src/jupyter/kernel/KernelExecutor.ts +++ b/packages/react/src/jupyter/kernel/KernelExecutor.ts @@ -188,10 +188,10 @@ export class KernelExecutor { } private _onIOPub = (message: KernelMessage.IIOPubMessage): void => { - console.log('---IOPub', message); if (this._future?.msg.header.msg_id !== message.parent_header.msg_id) { return; } + console.debug('Kernel IOPub message', message); const messageType: KernelMessage.IOPubMessageType = message.header.msg_type; const output = { ...message.content, output_type: messageType }; switch (messageType) { @@ -236,10 +236,10 @@ export class KernelExecutor { }; private _onReply = (message: KernelMessage.IExecuteReplyMsg): void => { - console.log('---Reply', message); if (this._future?.msg.header.msg_id !== message.parent_header.msg_id) { return; } + console.debug('Kernel Reply message', message); this._shellMessageHooks.forEach(hook => hook(message)); const content = message.content; if (content.status !== 'ok') { diff --git a/packages/react/src/state/State.ts b/packages/react/src/state/State.ts index f166498a..e3c662b3 100644 --- a/packages/react/src/state/State.ts +++ b/packages/react/src/state/State.ts @@ -10,29 +10,29 @@ import { useStore } from 'zustand'; import { ServiceManager } from '@jupyterlab/services'; import type { IDatalayerConfig } from './IState'; import { IJupyterConfig, loadJupyterConfig } from '../jupyter/JupyterConfig'; -import { cellsStore } from '../components/cell/CellState'; -import useConsoleStore from '../components/console/ConsoleState'; -import useNotebookStore from '../components/notebook/NotebookState'; -import useOutputStore from '../components/output/OutputState'; -import useTerminalStore from '../components/terminal/TerminalState'; +import { cellsStore, CellsState } from '../components/cell/CellState'; +import { consoleStore, ConsoleState } from '../components/console/ConsoleState'; +import { notebookStore, NotebookState } from '../components/notebook/NotebookState'; +import { outputStore, OutputState } from '../components/output/OutputState'; +import { terminalStore, TerminalState } from '../components/terminal/TerminalState'; import { createLiteServer } from '../jupyter/lite/LiteServer'; import { getJupyterServerUrl } from '../jupyter/JupyterConfig'; import { ensureJupyterAuth, createServerSettings, JupyterContextPropsType } from '../jupyter/JupyterContext'; import Kernel from '../jupyter/kernel/Kernel'; export type JupyterState = { - cellsStore: typeof cellsStore.getState; - consoleStore: typeof useConsoleStore; + cellsStore: CellsState; + consoleStore: ConsoleState; datalayerConfig?: IDatalayerConfig; jupyterConfig?: IJupyterConfig; kernel?: Kernel; kernelIsLoading: boolean; - notebookStore: typeof useNotebookStore; - outputStore: typeof useOutputStore; + notebookStore: NotebookState; + outputStore: OutputState; serviceManager?: ServiceManager; setDatalayerConfig: (configuration?: IDatalayerConfig) => void; setVersion: (version: string) => void; - terminalStore: typeof useTerminalStore; + terminalStore: TerminalState; version: string; }; @@ -63,11 +63,11 @@ export const jupyterStore = createStore((set, get) => ({ kernel: undefined, serviceManager: undefined, serverSettings: undefined, - cellsStore: cellsStore.getState, - consoleStore: useConsoleStore, - notebookStore: useNotebookStore, - outputStore: useOutputStore, - terminalStore: useTerminalStore, + cellsStore: cellsStore.getState(), + consoleStore: consoleStore.getState(), + notebookStore: notebookStore.getState(), + outputStore: outputStore.getState(), + terminalStore: terminalStore.getState(), })); // TODO Reuse code portions from JupyterContext @@ -187,7 +187,6 @@ export function useJupyterStoreFromContext(props: JupyterContextPropsType): Jupy kernelName: defaultKernelName, kernelSpecName: defaultKernelName, kernelModel: kernel.value, - kernelType: 'notebook', kernelspecsManager: serviceManager.kernelspecs, sessionManager: serviceManager.sessions, }); @@ -215,7 +214,6 @@ export function useJupyterStoreFromContext(props: JupyterContextPropsType): Jupy kernelManager, kernelName: defaultKernelName, kernelSpecName: defaultKernelName, - kernelType: 'notebook', kernelspecsManager: serviceManager.kernelspecs, sessionManager: serviceManager.sessions, }); diff --git a/packages/react/webpack.config.js b/packages/react/webpack.config.js index 3f5991ee..4c31cb1c 100644 --- a/packages/react/webpack.config.js +++ b/packages/react/webpack.config.js @@ -18,7 +18,8 @@ function shim(regExp) { const ENTRY = // './src/app/App'; - './src/examples/Cell'; + // './src/examples/Cell'; + // './src/examples/Cells'; // './src/examples/CellLite'; // './src/examples/Console'; // './src/examples/ConsoleLite'; @@ -47,7 +48,7 @@ const ENTRY = // './src/examples/NotebookTheme'; // './src/examples/NotebookThemeColorMode'; // './src/examples/ObservableHQ'; - // './src/examples/Output'; + './src/examples/Output'; // './src/examples/Outputs'; // './src/examples/Plotly'; // './src/examples/RunningSessions'; From ebc0b9089d7417c0073a3b653f48943fb942a326 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Tue, 30 Jul 2024 13:35:35 +0200 Subject: [PATCH 06/15] chore: simplify output state --- .../codemirror/CodeMirrorEditor.tsx | 18 +- .../react/src/components/output/Output.tsx | 30 +--- .../src/components/output/OutputState.ts | 154 +++++++----------- packages/react/src/examples/Output.tsx | 38 +++-- packages/react/src/jupyter/kernel/Kernel.ts | 1 + .../src/jupyter/kernel/KernelExecutor.ts | 24 ++- .../react/src/jupyter/kernel/KernelState.ts | 63 +++++++ packages/react/src/state/State.ts | 4 +- 8 files changed, 180 insertions(+), 152 deletions(-) create mode 100644 packages/react/src/jupyter/kernel/KernelState.ts diff --git a/packages/react/src/components/codemirror/CodeMirrorEditor.tsx b/packages/react/src/components/codemirror/CodeMirrorEditor.tsx index 7b2e33f0..aeee1352 100644 --- a/packages/react/src/components/codemirror/CodeMirrorEditor.tsx +++ b/packages/react/src/components/codemirror/CodeMirrorEditor.tsx @@ -13,7 +13,7 @@ import Kernel from '../../jupyter/kernel/Kernel'; import codeMirrorTheme from './CodeMirrorTheme'; import OutputAdapter from '../output/OutputAdapter'; import CodeMirrorOutputToolbar from './CodeMirrorOutputToolbar'; -import useOutputStore from '../output/OutputState'; +import useOutputsStore from '../output/OutputState'; export const CodeMirrorEditor = (props: { code: string; @@ -37,7 +37,7 @@ export const CodeMirrorEditor = (props: { insertText, kernel, } = props; - const outputStore = useOutputStore(); + const outputStore = useOutputsStore(); const [view, setView] = useState(); const dataset = outputStore.getDataset(sourceId); const source = outputStore.getInput(sourceId); @@ -78,10 +78,7 @@ export const CodeMirrorEditor = (props: { return true; }; useEffect(() => { - outputStore.setInput({ - id: sourceId, - input: code, - }) + outputStore.setInput(sourceId, code); const language = new Compartment(); const keyBinding = [ { @@ -101,10 +98,7 @@ export const CodeMirrorEditor = (props: { EditorView.updateListener.of((viewUpdate: ViewUpdate) => { if (viewUpdate.docChanged) { const source = viewUpdate.state.doc.toString(); - outputStore.setInput({ - id: sourceId, - input: source, - }); + outputStore.setInput(sourceId, source); } }), ], @@ -122,10 +116,10 @@ export const CodeMirrorEditor = (props: { }; }, [code]); useEffect(() => { - doInsertText(dataset?.dataset); + doInsertText(dataset); }, [dataset]); useEffect(() => { - setEditorSource(source?.input); + setEditorSource(source); }, [source]); return ( <> diff --git a/packages/react/src/components/output/Output.tsx b/packages/react/src/components/output/Output.tsx index 01fba97c..3fa820af 100644 --- a/packages/react/src/components/output/Output.tsx +++ b/packages/react/src/components/output/Output.tsx @@ -17,7 +17,7 @@ import { Lumino } from '../lumino/Lumino'; import { CodeMirrorEditor } from '../codemirror/CodeMirrorEditor'; import { OutputAdapter } from './OutputAdapter'; import { OutputRenderer } from './OutputRenderer'; -import { useOutputStore } from './OutputState'; +import { useOutputsStore } from './OutputState'; import './Output.css'; @@ -44,7 +44,7 @@ export type IOutputProps = { export const Output = (props: IOutputProps) => { const { defaultKernel } = useJupyter(); - const outputStore = useOutputStore(); + const outputStore = useOutputsStore(); const { adapter: propsAdapter, autoRun, @@ -81,23 +81,14 @@ export const Output = (props: IOutputProps) => { setAdapter(adapter); outputStore.setAdapter(id, adapter); if (model) { - outputStore.setOutput({ - id, - model: model, - }) + outputStore.setModel(id, model); } if (code) { - outputStore.setInput({ - id, - input: code, - }) + outputStore.setInput(id,code); } adapter.outputArea.model.changed.connect((model, change) => { setOutputs(model.toJSON()); - outputStore.setOutput({ - id, - model, - }); + outputStore.setModel(id, model); }); if (receipt) { adapter.outputArea.model.changed.connect((sender, change) => { @@ -107,10 +98,7 @@ export const Output = (props: IOutputProps) => { const out = val.data['text/html']; // val.data['application/vnd.jupyter.stdout']; if (out) { if ((out as string).indexOf(receipt) > -1) { - outputStore.setGrade({ - id: sourceId, - success: true, - }); + outputStore.setGradeSuccess(sourceId, true) } } } @@ -140,10 +128,10 @@ export const Output = (props: IOutputProps) => { }; } }, [kernel]); - const executeRequest = outputStore.getExecute(sourceId); + const executeRequest = outputStore.getExecuteRequestId(sourceId); useEffect(() => { - if (adapter && executeRequest && executeRequest.id === id) { - adapter.execute(executeRequest.source); + if (adapter && executeRequest && executeRequest === id) { + adapter.execute(code); } }, [executeRequest, adapter]); useEffect(() => { diff --git a/packages/react/src/components/output/OutputState.ts b/packages/react/src/components/output/OutputState.ts index bf971b30..a77049a7 100644 --- a/packages/react/src/components/output/OutputState.ts +++ b/packages/react/src/components/output/OutputState.ts @@ -7,42 +7,15 @@ import { createStore } from 'zustand/vanilla'; import { useStore } from 'zustand'; import { IOutputAreaModel } from '@jupyterlab/outputarea'; -import OutputAdapter from './OutputAdapter'; - -export namespace OutputState { - export type IOutput = { - id: string; - model: IOutputAreaModel; - }; - export type IInput = { - id: string; - input: string; - increment?: number; - }; - export type IDataset = { - id: string; - dataset: any; - increment?: number; - }; - export type IExecute = { - id: string; - source: string; - increment?: number; - }; - export type IGrade = { - id: string; - success: boolean; - increment?: number; - }; -} +import { OutputAdapter } from './OutputAdapter'; export type IOutputState = { adapter?: OutputAdapter; - output?: OutputState.IOutput; - source?: OutputState.IInput; - dataset?: OutputState.IDataset; - execute?: OutputState.IExecute; - grade?: OutputState.IGrade; + model?: IOutputAreaModel; + input?: string; + dataset?: any; + executeId?: string; + gradeSuccess?: boolean; }; export interface IOutputsState { @@ -51,41 +24,41 @@ export interface IOutputsState { export type OutputState = IOutputsState & { getAdapter: (id: string) => OutputAdapter | undefined; - getDataset: (id: string) => OutputState.IDataset | undefined; - getExecute: (id: string) => OutputState.IExecute | undefined; - getGrade: (id: string) => OutputState.IGrade | undefined; - getOutput: (id: string) => OutputState.IOutput | undefined; - getInput: (id: string) => OutputState.IInput | undefined; + getDataset: (id: string) => any | undefined; + getExecuteRequestId: (id: string) => string | undefined; + getGradeSuccess: (id: string) => boolean | undefined; + getInput: (id: string) => string | undefined; + getModel: (id: string) => IOutputAreaModel | undefined; setAdapter: (id: string, adapter: OutputAdapter) => void; - setDataset: (dataset: OutputState.IDataset) => void; - setExecute: (execute: OutputState.IExecute) => void; - setGrade: (grade: OutputState.IGrade) => void; - setOutput: (output: OutputState.IOutput) => void; - setOutputs: (outputs: Map) => void; - setInput: (source: OutputState.IInput) => void; + setDataset: (id: string, dataset: any) => void; + setExecuteRequestId: (id: string, executeId: string) => void; + setGradeSuccess: (id: string, gradeSuccess: boolean) => void; + setInput: (id: string, source: string) => void; + setModel: (id: string, output: IOutputAreaModel) => void; + setOutputs: (id: string, outputs: Map) => void; }; -export const outputStore = createStore((set, get) => ({ +export const outputsStore = createStore((set, get) => ({ outputs: new Map(), getAdapter: (id: string) => { return get().outputs.get(id)?.adapter; }, - getInput: (id: string): OutputState.IInput | undefined => { - return get().outputs.get(id)?.source; + getInput: (id: string): string | undefined => { + return get().outputs.get(id)?.input; }, - getOutput: (id: string): OutputState.IOutput | undefined => { - return get().outputs.get(id)?.output; + getModel: (id: string): IOutputAreaModel | undefined => { + return get().outputs.get(id)?.model; }, - getDataset: (id: string): OutputState.IDataset | undefined => { + getDataset: (id: string): any | undefined => { return get().outputs.get(id)?.dataset; }, - getExecute: (id: string): OutputState.IExecute | undefined => { - return get().outputs.get(id)?.execute; + getExecuteRequestId: (id: string): string | undefined => { + return get().outputs.get(id)?.executeId; }, - getGrade: (id: string): OutputState.IGrade | undefined => { - return get().outputs.get(id)?.grade; + getGradeSuccess: (id: string): boolean | undefined => { + return get().outputs.get(id)?.gradeSuccess; }, - setOutputs: (outputs: Map) => set((state: OutputState) => ({ outputs })), + setOutputs: (id: string, outputs: Map) => set((state: OutputState) => ({ outputs })), setAdapter: (id: string, adapter: OutputAdapter) => { const outputs = get().outputs; const d = outputs.get(id); @@ -96,69 +69,62 @@ export const outputStore = createStore((set, get) => ({ } set((state: OutputState) => ({ outputs })) }, - setDataset: (dataset: OutputState.IDataset) => { - const sourceId = dataset.id; + setDataset: (id: string, dataset: string) => { const outputs = get().outputs; - const d = outputs.get(sourceId); + const d = outputs.get(id); if (d) { d.dataset = dataset; } else { - outputs.set(sourceId, { dataset }); + outputs.set(id, { dataset }); } set((state: OutputState) => ({ outputs })) }, - setExecute: (execute: OutputState.IExecute) => { - const sourceId = execute.id; + setExecuteRequestId: (id: string, executeId: string) => { const outputs = get().outputs; - const e = outputs.get(sourceId); + const e = outputs.get(id); if (e) { - e.execute = execute; + e.executeId = executeId; } else { - outputs.set(sourceId, { execute }); + outputs.set(id, { executeId }); } set((state: OutputState) => ({ outputs })) }, - setOutput: (outputState: OutputState.IOutput) => { + setModel: (id: string, model: IOutputAreaModel) => { const outputs = get().outputs; - outputs.set(outputState.id, { - output: { - id: outputState.id, - model: outputState.model, - } - }); - get().setOutputs(outputs); + const e = outputs.get(id); + if (e) { + e.model = model; + } else { + outputs.set(id, { model }); + } + set((state: OutputState) => ({ outputs })) }, - setInput: (sourceState: OutputState.IInput) => { + setInput: (id: string, input: string) => { const outputs = get().outputs; - const output = outputs.get(sourceState.id); - if (output?.source?.input === sourceState.input) { - return; + const e = outputs.get(id); + if (e) { + e.input = input; + } else { + outputs.set(id, { input }); } - outputs.set(sourceState.id, { - source: { - id: sourceState.id, - input: sourceState.input, - } - }); - get().setOutputs(outputs); + set((state: OutputState) => ({ outputs })) }, - setGrade: (grade: OutputState.IGrade) => { - const sourceId = grade.id; + setGradeSuccess: (id: string, gradeSuccess: boolean) => { const outputs = get().outputs; - const g = outputs.get(sourceId); - if (g) { - g.grade = grade; + const e = outputs.get(id); + if (e) { + e.gradeSuccess = gradeSuccess; } else { - outputs.set(sourceId, { grade: grade }); + outputs.set(id, { gradeSuccess }); } set((state: OutputState) => ({ outputs })) }, })); -export function useOutputStore(): OutputState; -export function useOutputStore(selector: (state: OutputState) => T): T; -export function useOutputStore(selector?: (state: OutputState) => T) { - return useStore(outputStore, selector!); +export function useOutputsStore(): OutputState; +export function useOutputsStore(selector: (state: OutputState) => T): T; +export function useOutputsStore(selector?: (state: OutputState) => T) { + return useStore(outputsStore, selector!); } -export default useOutputStore; +export default useOutputsStore; diff --git a/packages/react/src/examples/Output.tsx b/packages/react/src/examples/Output.tsx index a4faa5c8..41370919 100644 --- a/packages/react/src/examples/Output.tsx +++ b/packages/react/src/examples/Output.tsx @@ -8,10 +8,11 @@ import { useEffect, useState } from 'react'; import { createRoot } from 'react-dom/client'; import { IOutput } from '@jupyterlab/nbformat'; import { Text } from '@primer/react'; -import { useOutputStore } from './../components/output/OutputState'; +import { useOutputsStore } from './../components/output/OutputState'; import { useJupyter } from '../jupyter/JupyterContext'; import { Jupyter } from '../jupyter/Jupyter'; import { Kernel } from '../jupyter/kernel/Kernel'; +import { kernelsStore } from '../jupyter/kernel/KernelState'; import { newUuid } from '../utils/Utils'; import { Output } from '../components/output/Output'; @@ -54,10 +55,10 @@ const OUTPUTS_3: IOutput[] = [ ]; const OutputWithoutEditor = () => { - const outputStore = useOutputStore(); + const outputStore = useOutputsStore(); console.log( 'Outputs 1', - outputStore.getOutput(SOURCE_ID_1)?.model.toJSON(), + outputStore.getModel(SOURCE_ID_1)?.toJSON(), outputStore.getInput(SOURCE_ID_1), ); return ( @@ -75,10 +76,10 @@ const OutputWithoutEditor = () => { const OutputWithEditor = () => { const { defaultKernel } = useJupyter(); - const outputStore = useOutputStore(); + const outputStore = useOutputsStore(); console.log( 'Outputs 2', - outputStore.getOutput(SOURCE_ID_2)?.model.toJSON(), + outputStore.getModel(SOURCE_ID_2)?.toJSON(), outputStore.getInput(SOURCE_ID_2), ); return ( @@ -99,7 +100,7 @@ const OutputWithEditor = () => { const OutputWithEmptyOutput = () => { const { kernelManager, serviceManager } = useJupyter(); const [kernel, setKernel] = useState(); - useEffect(() => { + useEffect( () => { if (serviceManager && kernelManager) { const kernel = new Kernel({ kernelManager, @@ -112,24 +113,29 @@ const OutputWithEmptyOutput = () => { setKernel(kernel); } }, [serviceManager, kernelManager]); - const outputStore = useOutputStore(); + const outputStore = useOutputsStore(); console.log( 'Outputs 3', - outputStore.getOutput(SOURCE_ID_3)?.model.toJSON(), + outputStore.getModel(SOURCE_ID_3)?.toJSON(), outputStore.getInput(SOURCE_ID_3), ); return ( <> Output with empty Output { kernel && - + <> + <> + Kernel Execution State: {kernelsStore.getState().getExecutionState(kernel.id)} + + + } ); diff --git a/packages/react/src/jupyter/kernel/Kernel.ts b/packages/react/src/jupyter/kernel/Kernel.ts index 62bcff12..781b5e56 100755 --- a/packages/react/src/jupyter/kernel/Kernel.ts +++ b/packages/react/src/jupyter/kernel/Kernel.ts @@ -206,6 +206,7 @@ export class Kernel { ): KernelExecutor | undefined { if (this._kernelConnection) { const kernelExecutor = new KernelExecutor({ + kernel: this, connection: this._kernelConnection, model, }); diff --git a/packages/react/src/jupyter/kernel/KernelExecutor.ts b/packages/react/src/jupyter/kernel/KernelExecutor.ts index 16c6ed25..1ec3f826 100644 --- a/packages/react/src/jupyter/kernel/KernelExecutor.ts +++ b/packages/react/src/jupyter/kernel/KernelExecutor.ts @@ -15,9 +15,11 @@ import { IMimeBundle, } from '@jupyterlab/nbformat'; import { IOutputAreaModel, OutputAreaModel } from '@jupyterlab/outputarea'; -import { Kernel, KernelMessage } from '@jupyterlab/services'; +import { Kernel as JupyterKernel, KernelMessage } from '@jupyterlab/services'; import { IClearOutputMsg } from '@jupyterlab/services/lib/kernel/messages'; import { outputsAsString } from '../../utils/Utils'; +import { Kernel } from './Kernel'; +import { KernelsState, kernelsStore } from './KernelState'; export type IOPubMessageHook = ( msg: KernelMessage.IIOPubMessage @@ -31,10 +33,14 @@ export type ShellMessageHook = ( * KernelExecutor options */ export type IKernelExecutorOptions = { + /** + * Kernel + */ + kernel: Kernel; /** * Kernel connection */ - connection: Kernel.IKernelConnection; + connection: JupyterKernel.IKernelConnection; /** * Outputs model to populate with the execution results. */ @@ -43,22 +49,26 @@ export type IKernelExecutorOptions = { export class KernelExecutor { private _executed: PromiseDelegate; - private _kernelConnection: Kernel.IKernelConnection; + private _kernelConnection: JupyterKernel.IKernelConnection; + private _kernel: Kernel; + private _kernelState: KernelsState; private _model: IOutputAreaModel; private _modelChanged = new Signal(this); private _outputs: IOutput[]; private _outputsChanged = new Signal(this); - private _future?: Kernel.IFuture< + private _future?: JupyterKernel.IFuture< KernelMessage.IExecuteRequestMsg, KernelMessage.IExecuteReplyMsg >; private _shellMessageHooks = new Array(); - constructor({ connection, model }: IKernelExecutorOptions) { + constructor({ kernel, connection, model }: IKernelExecutorOptions) { this._executed = new PromiseDelegate(); + this._kernel = kernel; this._kernelConnection = connection; this._model = model ?? new OutputAreaModel(); this._outputs = []; + this._kernelState = kernelsStore.getState(); } /** @@ -134,7 +144,7 @@ export class KernelExecutor { /** * */ - get future(): Kernel.IFuture< + get future(): JupyterKernel.IFuture< KernelMessage.IExecuteRequestMsg, KernelMessage.IExecuteReplyMsg > | undefined { @@ -226,8 +236,8 @@ export class KernelExecutor { this._modelChanged.emit(this._model); break; case 'status': - // execution_state: 'busy' 'starting' 'terminating' 'restarting' 'initializing' 'connecting' 'disconnected' 'dead' 'unknown' 'idle' const executionState = (message.content as any).execution_state; + this._kernelState.setExecutionState(this._kernel.id, executionState); executionState; break; default: diff --git a/packages/react/src/jupyter/kernel/KernelState.ts b/packages/react/src/jupyter/kernel/KernelState.ts new file mode 100644 index 00000000..afb2bd31 --- /dev/null +++ b/packages/react/src/jupyter/kernel/KernelState.ts @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2021-2023 Datalayer, Inc. + * + * MIT License + */ + +import { createStore } from 'zustand/vanilla'; +import { useStore } from 'zustand'; + +export enum ExecutionState { + 'busy', + 'connecting', + 'dead', + 'disconnected', + 'idle', + 'initializing', + 'restarting', + 'starting', + 'terminating', + 'unknown', +} + +export type IKernelState = { + id: string; + executionState?: ExecutionState; +}; + +export interface IKernelsState { + kernels: Map; +} + +export type KernelsState = IKernelsState & { + getExecutionState: (id: string) => ExecutionState | undefined; + setExecutionState: (id: string, executionState: ExecutionState) => void; +}; + +export const kernelsStore = createStore((set, get) => ({ + kernels: new Map(), + getExecutionState: (id: string) => { + return get().kernels.get(id)?.executionState; + }, + setExecutionState: (id: string, executionState: ExecutionState) => { + const kernels = get().kernels; + const k = kernels.get(id); + if (k) { + k.executionState = executionState; + } else { + kernels.set(id, { + id, + executionState + }); + } + set((state: KernelsState) => ({ kernels })) + }, +})); + +export function useKernelsStore(): KernelsState; +export function useKernelsStore(selector: (state: KernelsState) => T): T; +export function useKernelsStore(selector?: (state: KernelsState) => T) { + return useStore(kernelsStore, selector!); +} + +export default useKernelsStore; diff --git a/packages/react/src/state/State.ts b/packages/react/src/state/State.ts index e3c662b3..0748ecda 100644 --- a/packages/react/src/state/State.ts +++ b/packages/react/src/state/State.ts @@ -13,7 +13,7 @@ import { IJupyterConfig, loadJupyterConfig } from '../jupyter/JupyterConfig'; import { cellsStore, CellsState } from '../components/cell/CellState'; import { consoleStore, ConsoleState } from '../components/console/ConsoleState'; import { notebookStore, NotebookState } from '../components/notebook/NotebookState'; -import { outputStore, OutputState } from '../components/output/OutputState'; +import { outputsStore, OutputState } from '../components/output/OutputState'; import { terminalStore, TerminalState } from '../components/terminal/TerminalState'; import { createLiteServer } from '../jupyter/lite/LiteServer'; import { getJupyterServerUrl } from '../jupyter/JupyterConfig'; @@ -66,7 +66,7 @@ export const jupyterStore = createStore((set, get) => ({ cellsStore: cellsStore.getState(), consoleStore: consoleStore.getState(), notebookStore: notebookStore.getState(), - outputStore: outputStore.getState(), + outputStore: outputsStore.getState(), terminalStore: terminalStore.getState(), })); From 269b5f8fe98cd243a81b5ddbe17e12c0b67ec42d Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Tue, 30 Jul 2024 13:56:46 +0200 Subject: [PATCH 07/15] chore: kernel state --- .../react/src/components/cell/CellState.ts | 2 +- .../src/components/kernel/Kernelndicator.tsx | 51 ++++++++++++------- packages/react/src/examples/Cell.tsx | 11 +++- packages/react/src/examples/Output.tsx | 35 ++++++++----- .../src/jupyter/kernel/KernelExecutor.ts | 9 ++-- .../react/src/jupyter/kernel/KernelState.ts | 28 +++------- 6 files changed, 79 insertions(+), 57 deletions(-) diff --git a/packages/react/src/components/cell/CellState.ts b/packages/react/src/components/cell/CellState.ts index 8414e5c8..459cebcd 100644 --- a/packages/react/src/components/cell/CellState.ts +++ b/packages/react/src/components/cell/CellState.ts @@ -6,7 +6,7 @@ import { createStore } from 'zustand/vanilla'; import { useStore } from 'zustand'; -import CellAdapter from './CellAdapter'; +import { CellAdapter } from './CellAdapter'; export interface ICellState { source?: string; diff --git a/packages/react/src/components/kernel/Kernelndicator.tsx b/packages/react/src/components/kernel/Kernelndicator.tsx index 0a2059de..2acedbe2 100644 --- a/packages/react/src/components/kernel/Kernelndicator.tsx +++ b/packages/react/src/components/kernel/Kernelndicator.tsx @@ -23,7 +23,22 @@ import { KernelMessage } from '@jupyterlab/services'; import { ConnectionStatus, IKernelConnection } from '@jupyterlab/services/lib/kernel/kernel'; import { Environment } from '../environment/Environment'; -type KernelState = +/* +export enum ExecutionState { + 'busy', + 'connecting', + 'dead', + 'disconnected', + 'idle', + 'initializing', + 'restarting', + 'starting', + 'terminating', + 'unknown', +} +*/ + +export type KernelState = 'connecting' | 'connected-unknown' | 'connected-starting' | @@ -34,7 +49,8 @@ type KernelState = 'connected-autorestarting' | 'connected-dead' | 'disconnecting' | - 'undefined'; + 'undefined' + ; /** * The valid kernel connection states. @@ -68,27 +84,28 @@ export const KERNEL_STATES: Map = new Map([ ['undefined', ], ]); -type Props = { +export const toKernelState = ( + connectionStatus: ConnectionStatus, + status: KernelMessage.Status +): KernelState => { + if ( + connectionStatus === 'connecting' || + connectionStatus === 'disconnected' + ) { + return connectionStatus as KernelState; + } + return connectionStatus + '-' + status as KernelState; +}; + +type KernelIndicatorProps = { kernel?: IKernelConnection | null; env?: Environment; }; -export const KernelIndicator = (props: Props) => { +export const KernelIndicator = (props: KernelIndicatorProps) => { const { kernel, env } = props; const [connectionStatus, setConnectionStatus] = useState(); const [status, setStatus] = useState(); - const toState = ( - connectionStatus: ConnectionStatus, - status: KernelMessage.Status - ): KernelState => { - if ( - connectionStatus === 'connecting' || - connectionStatus === 'disconnected' - ) { - return connectionStatus as KernelState; - } - return connectionStatus + '-' + status as KernelState; - }; useEffect(() => { if (kernel) { setConnectionStatus(kernel?.connectionStatus); @@ -105,7 +122,7 @@ export const KernelIndicator = (props: Props) => { }, [kernel]); return connectionStatus && status ? ( - {KERNEL_STATES.get(toState(connectionStatus, status))} + {KERNEL_STATES.get(toKernelState(connectionStatus, status))} ) : ( diff --git a/packages/react/src/examples/Cell.tsx b/packages/react/src/examples/Cell.tsx index 2af16b47..a7abc7eb 100644 --- a/packages/react/src/examples/Cell.tsx +++ b/packages/react/src/examples/Cell.tsx @@ -7,8 +7,10 @@ import { createRoot } from 'react-dom/client'; import { Box, Button } from '@primer/react'; import { CodeCell } from '@jupyterlab/cells'; -import Jupyter from '../jupyter/Jupyter'; -import Cell from '../components/cell/Cell'; +import { Jupyter } from '../jupyter/Jupyter'; +import { useJupyter } from '../jupyter/JupyterContext'; +import { Cell } from '../components/cell/Cell'; +import { useKernelsStore } from '../jupyter/kernel/KernelState'; import { useCellsStore } from '../components/cell/CellState'; const CELL_ID = 'cell-example-1'; @@ -19,7 +21,9 @@ for i in range(10): display('I am a long string which is repeatedly added to the dom in separated divs: %d' % i)`; const CellExample = () => { + const { defaultKernel } = useJupyter(); const cellsStore = useCellsStore(); + const kernelsStore = useKernelsStore(); console.log('Cell Outputs', (cellsStore.getAdapter(CELL_ID)?.cell as CodeCell)?.outputArea.model.toJSON()); return ( @@ -30,6 +34,9 @@ const CellExample = () => { Outputs Count: {cellsStore.getOutputsCount(CELL_ID)} + <> + Kernel Execution State: {defaultKernel && kernelsStore.getExecutionState(defaultKernel.id)} + diff --git a/packages/react/src/examples/Output.tsx b/packages/react/src/examples/Output.tsx index 41370919..a5735451 100644 --- a/packages/react/src/examples/Output.tsx +++ b/packages/react/src/examples/Output.tsx @@ -7,12 +7,13 @@ import { useEffect, useState } from 'react'; import { createRoot } from 'react-dom/client'; import { IOutput } from '@jupyterlab/nbformat'; -import { Text } from '@primer/react'; +import { Box, Text } from '@primer/react'; import { useOutputsStore } from './../components/output/OutputState'; import { useJupyter } from '../jupyter/JupyterContext'; import { Jupyter } from '../jupyter/Jupyter'; import { Kernel } from '../jupyter/kernel/Kernel'; -import { kernelsStore } from '../jupyter/kernel/KernelState'; +import { useKernelsStore } from '../jupyter/kernel/KernelState'; +import { KernelIndicator } from '../components/kernel/Kernelndicator'; import { newUuid } from '../utils/Utils'; import { Output } from '../components/output/Output'; @@ -99,6 +100,8 @@ const OutputWithEditor = () => { const OutputWithEmptyOutput = () => { const { kernelManager, serviceManager } = useJupyter(); + const outputStore = useOutputsStore(); + const kernelsStore = useKernelsStore(); const [kernel, setKernel] = useState(); useEffect( () => { if (serviceManager && kernelManager) { @@ -113,7 +116,6 @@ const OutputWithEmptyOutput = () => { setKernel(kernel); } }, [serviceManager, kernelManager]); - const outputStore = useOutputsStore(); console.log( 'Outputs 3', outputStore.getModel(SOURCE_ID_3)?.toJSON(), @@ -124,17 +126,22 @@ const OutputWithEmptyOutput = () => { Output with empty Output { kernel && <> - <> - Kernel Execution State: {kernelsStore.getState().getExecutionState(kernel.id)} - - + + Kernel Execution State: {kernelsStore.getExecutionState(kernel.id)} + + + + + + + } diff --git a/packages/react/src/jupyter/kernel/KernelExecutor.ts b/packages/react/src/jupyter/kernel/KernelExecutor.ts index 1ec3f826..883d35dd 100644 --- a/packages/react/src/jupyter/kernel/KernelExecutor.ts +++ b/packages/react/src/jupyter/kernel/KernelExecutor.ts @@ -20,6 +20,7 @@ import { IClearOutputMsg } from '@jupyterlab/services/lib/kernel/messages'; import { outputsAsString } from '../../utils/Utils'; import { Kernel } from './Kernel'; import { KernelsState, kernelsStore } from './KernelState'; +import { toKernelState } from '../../components/kernel'; export type IOPubMessageHook = ( msg: KernelMessage.IIOPubMessage @@ -236,9 +237,11 @@ export class KernelExecutor { this._modelChanged.emit(this._model); break; case 'status': - const executionState = (message.content as any).execution_state; - this._kernelState.setExecutionState(this._kernel.id, executionState); - executionState; + const executionState = (message.content as any).execution_state as KernelMessage.Status; + const connectionStatus = this._kernel.connection?.connectionStatus; + const kernelState = toKernelState(connectionStatus!, executionState); + console.log('---', kernelState) + this._kernelState.setExecutionState(this._kernel.id, kernelState); break; default: break; diff --git a/packages/react/src/jupyter/kernel/KernelState.ts b/packages/react/src/jupyter/kernel/KernelState.ts index afb2bd31..0cacbc50 100644 --- a/packages/react/src/jupyter/kernel/KernelState.ts +++ b/packages/react/src/jupyter/kernel/KernelState.ts @@ -6,23 +6,11 @@ import { createStore } from 'zustand/vanilla'; import { useStore } from 'zustand'; - -export enum ExecutionState { - 'busy', - 'connecting', - 'dead', - 'disconnected', - 'idle', - 'initializing', - 'restarting', - 'starting', - 'terminating', - 'unknown', -} +import {KernelState } from './../../components/kernel/Kernelndicator'; export type IKernelState = { id: string; - executionState?: ExecutionState; + kernelState?: KernelState; }; export interface IKernelsState { @@ -30,24 +18,24 @@ export interface IKernelsState { } export type KernelsState = IKernelsState & { - getExecutionState: (id: string) => ExecutionState | undefined; - setExecutionState: (id: string, executionState: ExecutionState) => void; + getExecutionState: (id: string) => KernelState | undefined; + setExecutionState: (id: string, executionState: KernelState) => void; }; export const kernelsStore = createStore((set, get) => ({ kernels: new Map(), getExecutionState: (id: string) => { - return get().kernels.get(id)?.executionState; + return get().kernels.get(id)?.kernelState; }, - setExecutionState: (id: string, executionState: ExecutionState) => { + setExecutionState: (id: string, executionState: KernelState) => { const kernels = get().kernels; const k = kernels.get(id); if (k) { - k.executionState = executionState; + k.kernelState = executionState; } else { kernels.set(id, { id, - executionState + kernelState: executionState }); } set((state: KernelsState) => ({ kernels })) From 757c9c0607364400d76352845fb41a4419c9058c Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Tue, 30 Jul 2024 14:30:02 +0200 Subject: [PATCH 08/15] chore: kernel phase --- .../react/src/components/cell/CellAdapter.ts | 7 +++- .../src/components/kernel/Kernelndicator.tsx | 25 +++-------- .../react/src/components/output/Output.tsx | 2 +- .../src/components/output/OutputAdapter.ts | 5 ++- .../src/components/output/OutputExecutor.ts | 5 ++- packages/react/src/examples/Cell.tsx | 13 ++++-- packages/react/src/examples/Output.tsx | 5 ++- .../src/jupyter/kernel/KernelExecutor.ts | 16 +++++--- .../react/src/jupyter/kernel/KernelState.ts | 41 +++++++++++++++---- packages/react/webpack.config.js | 4 +- 10 files changed, 77 insertions(+), 46 deletions(-) diff --git a/packages/react/src/components/cell/CellAdapter.ts b/packages/react/src/components/cell/CellAdapter.ts index 2295523c..a0774542 100755 --- a/packages/react/src/components/cell/CellAdapter.ts +++ b/packages/react/src/components/cell/CellAdapter.ts @@ -61,14 +61,16 @@ interface BoxOptions { } export class CellAdapter { + private _id: string; private _cell: CodeCell | MarkdownCell | RawCell; private _kernel: Kernel; private _panel: BoxPanel; private _sessionContext: SessionContext; private _type: 'code' | 'markdown' | 'raw'; - constructor(options: CellAdapter.ICellAdapterOptions) { - const { type, source, serverSettings, kernel, boxOptions } = options; + public constructor(options: CellAdapter.ICellAdapterOptions) { + const { id, type, source, serverSettings, kernel, boxOptions } = options; + this._id = id; this._kernel = kernel; this._type = type; this.setupCell(type, source, serverSettings, kernel, boxOptions); @@ -420,6 +422,7 @@ export class CellAdapter { | undefined; try { const kernelMessagePromise = OutputExecutor.execute( + this._id, code, cell.outputArea, this._kernel, diff --git a/packages/react/src/components/kernel/Kernelndicator.tsx b/packages/react/src/components/kernel/Kernelndicator.tsx index 2acedbe2..f1428e77 100644 --- a/packages/react/src/components/kernel/Kernelndicator.tsx +++ b/packages/react/src/components/kernel/Kernelndicator.tsx @@ -23,22 +23,7 @@ import { KernelMessage } from '@jupyterlab/services'; import { ConnectionStatus, IKernelConnection } from '@jupyterlab/services/lib/kernel/kernel'; import { Environment } from '../environment/Environment'; -/* -export enum ExecutionState { - 'busy', - 'connecting', - 'dead', - 'disconnected', - 'idle', - 'initializing', - 'restarting', - 'starting', - 'terminating', - 'unknown', -} -*/ - -export type KernelState = +export type ExecutionState = 'connecting' | 'connected-unknown' | 'connected-starting' | @@ -70,7 +55,7 @@ export type KernelState = * * Status = 'unknown' | 'starting' | 'idle' | 'busy' | 'terminating' | 'restarting' | 'autorestarting' | 'dead'; */ -export const KERNEL_STATES: Map = new Map([ +export const KERNEL_STATES: Map = new Map([ ['connecting', ], ['connected-unknown', ], ['connected-starting', ], @@ -87,14 +72,14 @@ export const KERNEL_STATES: Map = new Map([ export const toKernelState = ( connectionStatus: ConnectionStatus, status: KernelMessage.Status -): KernelState => { +): ExecutionState => { if ( connectionStatus === 'connecting' || connectionStatus === 'disconnected' ) { - return connectionStatus as KernelState; + return connectionStatus as ExecutionState; } - return connectionStatus + '-' + status as KernelState; + return connectionStatus + '-' + status as ExecutionState; }; type KernelIndicatorProps = { diff --git a/packages/react/src/components/output/Output.tsx b/packages/react/src/components/output/Output.tsx index 3fa820af..0e5edb0e 100644 --- a/packages/react/src/components/output/Output.tsx +++ b/packages/react/src/components/output/Output.tsx @@ -77,7 +77,7 @@ export const Output = (props: IOutputProps) => { }, []); useEffect(() => { if (id && kernel) { - const adapter = propsAdapter ?? new OutputAdapter(kernel, outputs ?? [], model); + const adapter = propsAdapter ?? new OutputAdapter(id, kernel, outputs ?? [], model); setAdapter(adapter); outputStore.setAdapter(id, adapter); if (model) { diff --git a/packages/react/src/components/output/OutputAdapter.ts b/packages/react/src/components/output/OutputAdapter.ts index 708d159d..36c50214 100755 --- a/packages/react/src/components/output/OutputAdapter.ts +++ b/packages/react/src/components/output/OutputAdapter.ts @@ -16,6 +16,7 @@ import { Kernel } from '../../jupyter/kernel/Kernel'; import { execute } from './OutputExecutor'; export class OutputAdapter { + private _id: string; private _kernel?: Kernel; private _renderers: IRenderMime.IRendererFactory[]; private _outputArea: OutputArea; @@ -23,10 +24,12 @@ export class OutputAdapter { private _iPyWidgetsClassicManager: ClassicWidgetManager; public constructor( + id: string, kernel?: Kernel, outputs?: IOutput[], outputAreaModel?: IOutputAreaModel ) { + this._id = id; this._kernel = kernel; this._renderers = standardRendererFactories.filter( factory => factory.mimeTypes[0] !== 'text/javascript' @@ -79,7 +82,7 @@ export class OutputAdapter { public async execute(code: string) { if (this._kernel) { this.clear(); - const done = execute(code, this._outputArea, this._kernel); + const done = execute(this._id, code, this._outputArea, this._kernel); await done; } } diff --git a/packages/react/src/components/output/OutputExecutor.ts b/packages/react/src/components/output/OutputExecutor.ts index bc687509..6a9d6995 100755 --- a/packages/react/src/components/output/OutputExecutor.ts +++ b/packages/react/src/components/output/OutputExecutor.ts @@ -13,6 +13,7 @@ import { Kernel } from './../../jupyter/kernel/Kernel'; * Execute code on an output area. */ export async function execute( + id: string, code: string, output: OutputArea, kernel: Kernel, @@ -38,9 +39,9 @@ export async function execute( } ); const future = kernelExecutor!.future; - // TODO review this if possible in upstream JupyterLab. + // TODO fix in upstream jupyterlab if possible... (output as any)._onIOPub = future!.onIOPub; (output as any)._onExecuteReply = future!.onReply; output.future = future!; - return future!.done; + return future?.done; } diff --git a/packages/react/src/examples/Cell.tsx b/packages/react/src/examples/Cell.tsx index a7abc7eb..58cafd54 100644 --- a/packages/react/src/examples/Cell.tsx +++ b/packages/react/src/examples/Cell.tsx @@ -10,6 +10,7 @@ import { CodeCell } from '@jupyterlab/cells'; import { Jupyter } from '../jupyter/Jupyter'; import { useJupyter } from '../jupyter/JupyterContext'; import { Cell } from '../components/cell/Cell'; +import { KernelIndicator } from '../components/kernel/Kernelndicator'; import { useKernelsStore } from '../jupyter/kernel/KernelState'; import { useCellsStore } from '../components/cell/CellState'; @@ -34,9 +35,15 @@ const CellExample = () => { Outputs Count: {cellsStore.getOutputsCount(CELL_ID)} - <> - Kernel Execution State: {defaultKernel && kernelsStore.getExecutionState(defaultKernel.id)} - + defaultKernel + Kernel State: {defaultKernel && kernelsStore.getExecutionState(defaultKernel.id)} + + + Kernel Phase: {defaultKernel && kernelsStore.getExecutionPhase(defaultKernel.id)} + + + + diff --git a/packages/react/src/examples/Output.tsx b/packages/react/src/examples/Output.tsx index a5735451..7775bc40 100644 --- a/packages/react/src/examples/Output.tsx +++ b/packages/react/src/examples/Output.tsx @@ -127,7 +127,10 @@ const OutputWithEmptyOutput = () => { { kernel && <> - Kernel Execution State: {kernelsStore.getExecutionState(kernel.id)} + Kernel State: {kernelsStore.getExecutionState(kernel.id)} + + + Kernel Phase: {kernelsStore.getExecutionPhase(kernel.id)} diff --git a/packages/react/src/jupyter/kernel/KernelExecutor.ts b/packages/react/src/jupyter/kernel/KernelExecutor.ts index 883d35dd..a5ad5dec 100644 --- a/packages/react/src/jupyter/kernel/KernelExecutor.ts +++ b/packages/react/src/jupyter/kernel/KernelExecutor.ts @@ -19,7 +19,7 @@ import { Kernel as JupyterKernel, KernelMessage } from '@jupyterlab/services'; import { IClearOutputMsg } from '@jupyterlab/services/lib/kernel/messages'; import { outputsAsString } from '../../utils/Utils'; import { Kernel } from './Kernel'; -import { KernelsState, kernelsStore } from './KernelState'; +import { ExecutionPhase, KernelsState, kernelsStore } from './KernelState'; import { toKernelState } from '../../components/kernel'; export type IOPubMessageHook = ( @@ -56,11 +56,9 @@ export class KernelExecutor { private _model: IOutputAreaModel; private _modelChanged = new Signal(this); private _outputs: IOutput[]; + private _stopOnError: boolean; private _outputsChanged = new Signal(this); - private _future?: JupyterKernel.IFuture< - KernelMessage.IExecuteRequestMsg, - KernelMessage.IExecuteReplyMsg - >; + private _future?: JupyterKernel.IFuture; private _shellMessageHooks = new Array(); constructor({ kernel, connection, model }: IKernelExecutorOptions) { @@ -105,7 +103,9 @@ export class KernelExecutor { allowStdin?: boolean; } = {} ): Promise { + this._stopOnError = stopOnError; this._shellMessageHooks = shellMessageHooks; + kernelsStore.getState().setExecutionPhase(this._kernel.id, ExecutionPhase.running); this._future = this._kernelConnection.requestExecute({ code, allow_stdin: allowStdin, @@ -124,6 +124,8 @@ export class KernelExecutor { }; // Wait for future to be done before resolving the exectud promise. this._future.done.then(() => { + console.log('---- DONE') + kernelsStore.getState().setExecutionPhase(this._kernel.id, ExecutionPhase.completed); this._executed.resolve(this._model); }); return this._executed.promise; @@ -224,6 +226,9 @@ export class KernelExecutor { this._outputsChanged.emit(this._outputs); this._model.add(output); this._modelChanged.emit(this._model); + if (this._stopOnError) { + kernelsStore.getState().setExecutionPhase(this._kernel.id, ExecutionPhase.completed); + } break; case 'clear_output': const wait = (message as IClearOutputMsg).content.wait; @@ -240,7 +245,6 @@ export class KernelExecutor { const executionState = (message.content as any).execution_state as KernelMessage.Status; const connectionStatus = this._kernel.connection?.connectionStatus; const kernelState = toKernelState(connectionStatus!, executionState); - console.log('---', kernelState) this._kernelState.setExecutionState(this._kernel.id, kernelState); break; default: diff --git a/packages/react/src/jupyter/kernel/KernelState.ts b/packages/react/src/jupyter/kernel/KernelState.ts index 0cacbc50..3dbb26cb 100644 --- a/packages/react/src/jupyter/kernel/KernelState.ts +++ b/packages/react/src/jupyter/kernel/KernelState.ts @@ -6,11 +6,18 @@ import { createStore } from 'zustand/vanilla'; import { useStore } from 'zustand'; -import {KernelState } from './../../components/kernel/Kernelndicator'; +import {ExecutionState } from './../../components/kernel/Kernelndicator'; + +export enum ExecutionPhase { + ready_to_run = 'READY_TO_RUN', + running = 'RUNNING', + completed = 'COMPLETED', +} export type IKernelState = { id: string; - kernelState?: KernelState; + executionState?: ExecutionState; + executionPhase?: ExecutionPhase; }; export interface IKernelsState { @@ -18,24 +25,42 @@ export interface IKernelsState { } export type KernelsState = IKernelsState & { - getExecutionState: (id: string) => KernelState | undefined; - setExecutionState: (id: string, executionState: KernelState) => void; + getExecutionState: (id: string) => ExecutionState | undefined; + setExecutionState: (id: string, executionState: ExecutionState) => void; + getExecutionPhase: (id: string) => ExecutionPhase | undefined; + setExecutionPhase: (id: string, executionState: ExecutionPhase) => void; }; export const kernelsStore = createStore((set, get) => ({ kernels: new Map(), getExecutionState: (id: string) => { - return get().kernels.get(id)?.kernelState; + return get().kernels.get(id)?.executionState; + }, + setExecutionState: (id: string, executionState: ExecutionState) => { + const kernels = get().kernels; + const k = kernels.get(id); + if (k) { + k.executionState = executionState; + } else { + kernels.set(id, { + id, + executionState: executionState + }); + } + set((state: KernelsState) => ({ kernels })) + }, + getExecutionPhase: (id: string) => { + return get().kernels.get(id)?.executionPhase; }, - setExecutionState: (id: string, executionState: KernelState) => { + setExecutionPhase: (id: string, executionPhase: ExecutionPhase) => { const kernels = get().kernels; const k = kernels.get(id); if (k) { - k.kernelState = executionState; + k.executionPhase = executionPhase; } else { kernels.set(id, { id, - kernelState: executionState + executionPhase, }); } set((state: KernelsState) => ({ kernels })) diff --git a/packages/react/webpack.config.js b/packages/react/webpack.config.js index 4c31cb1c..c98d0702 100644 --- a/packages/react/webpack.config.js +++ b/packages/react/webpack.config.js @@ -18,7 +18,7 @@ function shim(regExp) { const ENTRY = // './src/app/App'; - // './src/examples/Cell'; + './src/examples/Cell'; // './src/examples/Cells'; // './src/examples/CellLite'; // './src/examples/Console'; @@ -48,7 +48,7 @@ const ENTRY = // './src/examples/NotebookTheme'; // './src/examples/NotebookThemeColorMode'; // './src/examples/ObservableHQ'; - './src/examples/Output'; + // './src/examples/Output'; // './src/examples/Outputs'; // './src/examples/Plotly'; // './src/examples/RunningSessions'; From a2059871f6f1e1e9290f14a1c6fec85789688441 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Tue, 30 Jul 2024 14:44:08 +0200 Subject: [PATCH 09/15] chore: lint --- packages/react/src/components/cell/Cell.tsx | 2 +- packages/react/webpack.config.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react/src/components/cell/Cell.tsx b/packages/react/src/components/cell/Cell.tsx index 1da192a9..b9571d86 100644 --- a/packages/react/src/components/cell/Cell.tsx +++ b/packages/react/src/components/cell/Cell.tsx @@ -67,7 +67,7 @@ export const Cell = (props: ICellProps) => { adapter.sessionContext.initialize().then(() => { if (autoStart && adapter.cell.model) { - // Perform auto-start for code or markdown cells + // Perform auto-start for code or markdown cells. adapter.execute(); } }); diff --git a/packages/react/webpack.config.js b/packages/react/webpack.config.js index c98d0702..65becb77 100644 --- a/packages/react/webpack.config.js +++ b/packages/react/webpack.config.js @@ -18,7 +18,7 @@ function shim(regExp) { const ENTRY = // './src/app/App'; - './src/examples/Cell'; + // './src/examples/Cell'; // './src/examples/Cells'; // './src/examples/CellLite'; // './src/examples/Console'; @@ -35,7 +35,7 @@ const ENTRY = // './src/examples/KernelExecutor'; // './src/examples/Lumino'; // './src/examples/Matplotlib'; - // './src/examples/Notebook'; + './src/examples/Notebook'; // './src/examples/NotebookUrl'; // './src/examples/NotebookColorMode'; // './src/examples/NotebookKernelChange'; From 4261dda28780b4ec3f06eed7ed560b42396d8517 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Tue, 30 Jul 2024 15:03:01 +0200 Subject: [PATCH 10/15] fix: build --- packages/lexical/src/components/JupyterOutputComponent.tsx | 2 +- packages/lexical/src/examples/App2.tsx | 2 +- packages/lexical/src/nodes/JupyterOutputNode.tsx | 4 ++-- packages/lexical/src/plugins/JupyterPlugin.tsx | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/lexical/src/components/JupyterOutputComponent.tsx b/packages/lexical/src/components/JupyterOutputComponent.tsx index 7eda41d5..a37ca3b9 100644 --- a/packages/lexical/src/components/JupyterOutputComponent.tsx +++ b/packages/lexical/src/components/JupyterOutputComponent.tsx @@ -27,7 +27,7 @@ export const JupyterOutputComponent = (props: Props) => { adapter={outputAdapter} showEditor={false} autoRun={autoRun} - sourceId={outputNodeUuid} + id={outputNodeUuid} executeTrigger={executeTrigger} lumino={false} /> diff --git a/packages/lexical/src/examples/App2.tsx b/packages/lexical/src/examples/App2.tsx index 950e2e40..818c17e1 100644 --- a/packages/lexical/src/examples/App2.tsx +++ b/packages/lexical/src/examples/App2.tsx @@ -90,7 +90,7 @@ const Tabs = () => { diff --git a/packages/lexical/src/nodes/JupyterOutputNode.tsx b/packages/lexical/src/nodes/JupyterOutputNode.tsx index 0e68f3b8..f10263aa 100644 --- a/packages/lexical/src/nodes/JupyterOutputNode.tsx +++ b/packages/lexical/src/nodes/JupyterOutputNode.tsx @@ -8,7 +8,7 @@ import { LexicalEditor, EditorConfig, DecoratorNode, LexicalNode, NodeKey, Sprea import { UUID } from '@lumino/coreutils'; import { IOutput } from '@jupyterlab/nbformat'; import { OUTPUT_UUID_TO_CODE_UUID, CODE_UUID_TO_OUTPUT_KEY, CODE_UUID_TO_OUTPUT_UUID, OUTPUT_UUID_TO_OUTPUT_KEY } from "../plugins/JupyterPlugin"; -import { OutputAdapter } from "@datalayer/jupyter-react"; +import { OutputAdapter, newUuid } from "@datalayer/jupyter-react"; import JupyterOutputComponent from "../components/JupyterOutputComponent"; export type SerializedJupyterOutputNode = Spread< @@ -52,7 +52,7 @@ export class JupyterOutputNode extends DecoratorNode { /** @override */ static importJSON(serializedNode: SerializedJupyterOutputNode): JupyterOutputNode { - return $createJupyterOutputNode(serializedNode.source, new OutputAdapter(undefined, []), serializedNode.outputs, false, serializedNode.codeNodeUuid, serializedNode.outputNodeUuid); + return $createJupyterOutputNode(serializedNode.source, new OutputAdapter(newUuid(), undefined, []), serializedNode.outputs, false, serializedNode.codeNodeUuid, serializedNode.outputNodeUuid); } /** @override */ diff --git a/packages/lexical/src/plugins/JupyterPlugin.tsx b/packages/lexical/src/plugins/JupyterPlugin.tsx index 4a6d8adc..69bd4b71 100644 --- a/packages/lexical/src/plugins/JupyterPlugin.tsx +++ b/packages/lexical/src/plugins/JupyterPlugin.tsx @@ -10,7 +10,7 @@ import { $getNodeByKey, $createNodeSelection, $setSelection } from "lexical"; import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"; import { $setBlocksType } from "@lexical/selection"; import { $insertNodeToNearestRoot } from '@lexical/utils'; -import { OutputAdapter } from '@datalayer/jupyter-react'; +import { OutputAdapter, newUuid } from '@datalayer/jupyter-react'; import { UUID } from '@lumino/coreutils'; import { IOutput } from '@jupyterlab/nbformat'; import { $createJupyterCodeNode, JupyterCodeNode, $isJupyterCodeNode } from "../nodes/JupyterCodeNode"; @@ -77,7 +77,7 @@ export const JupyterPlugin = () => { return true; } } - const jupyterOutputNode = $createJupyterOutputNode(code, new OutputAdapter(undefined, []), [], true, codeNodeUuid, UUID.uuid4()); + const jupyterOutputNode = $createJupyterOutputNode(code, new OutputAdapter(newUuid(), undefined, []), [], true, codeNodeUuid, UUID.uuid4()); $insertNodeToNearestRoot(jupyterOutputNode); const nodeSelection = $createNodeSelection(); nodeSelection.add(parentNode.__key); @@ -163,7 +163,7 @@ export const JupyterPlugin = () => { } else { selection.insertNodes([jupyterCodeNode]); } - const outputAdapter = new OutputAdapter(undefined, outputs); + const outputAdapter = new OutputAdapter(newUuid(), undefined, outputs); const jupyterOutputNode = $createJupyterOutputNode(code, outputAdapter, outputs || [], false, jupyterCodeNode.getCodeNodeUuid(), UUID.uuid4()) ; outputAdapter.outputArea.model.changed.connect((outputModel, args) => { editor.update(() => { From 73a7d38800b6215c00b9c1ecfae790675f7e7f58 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Tue, 30 Jul 2024 15:14:37 +0200 Subject: [PATCH 11/15] chore: relax kernel executor constructor --- packages/react/src/jupyter/kernel/Kernel.ts | 1 - .../src/jupyter/kernel/KernelExecutor.ts | 20 ++++++------------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/packages/react/src/jupyter/kernel/Kernel.ts b/packages/react/src/jupyter/kernel/Kernel.ts index 781b5e56..62bcff12 100755 --- a/packages/react/src/jupyter/kernel/Kernel.ts +++ b/packages/react/src/jupyter/kernel/Kernel.ts @@ -206,7 +206,6 @@ export class Kernel { ): KernelExecutor | undefined { if (this._kernelConnection) { const kernelExecutor = new KernelExecutor({ - kernel: this, connection: this._kernelConnection, model, }); diff --git a/packages/react/src/jupyter/kernel/KernelExecutor.ts b/packages/react/src/jupyter/kernel/KernelExecutor.ts index a5ad5dec..c9c43f20 100644 --- a/packages/react/src/jupyter/kernel/KernelExecutor.ts +++ b/packages/react/src/jupyter/kernel/KernelExecutor.ts @@ -18,7 +18,6 @@ import { IOutputAreaModel, OutputAreaModel } from '@jupyterlab/outputarea'; import { Kernel as JupyterKernel, KernelMessage } from '@jupyterlab/services'; import { IClearOutputMsg } from '@jupyterlab/services/lib/kernel/messages'; import { outputsAsString } from '../../utils/Utils'; -import { Kernel } from './Kernel'; import { ExecutionPhase, KernelsState, kernelsStore } from './KernelState'; import { toKernelState } from '../../components/kernel'; @@ -34,10 +33,6 @@ export type ShellMessageHook = ( * KernelExecutor options */ export type IKernelExecutorOptions = { - /** - * Kernel - */ - kernel: Kernel; /** * Kernel connection */ @@ -51,7 +46,6 @@ export type IKernelExecutorOptions = { export class KernelExecutor { private _executed: PromiseDelegate; private _kernelConnection: JupyterKernel.IKernelConnection; - private _kernel: Kernel; private _kernelState: KernelsState; private _model: IOutputAreaModel; private _modelChanged = new Signal(this); @@ -61,9 +55,8 @@ export class KernelExecutor { private _future?: JupyterKernel.IFuture; private _shellMessageHooks = new Array(); - constructor({ kernel, connection, model }: IKernelExecutorOptions) { + public constructor({ connection, model }: IKernelExecutorOptions) { this._executed = new PromiseDelegate(); - this._kernel = kernel; this._kernelConnection = connection; this._model = model ?? new OutputAreaModel(); this._outputs = []; @@ -105,7 +98,7 @@ export class KernelExecutor { ): Promise { this._stopOnError = stopOnError; this._shellMessageHooks = shellMessageHooks; - kernelsStore.getState().setExecutionPhase(this._kernel.id, ExecutionPhase.running); + kernelsStore.getState().setExecutionPhase(this._kernelConnection.id, ExecutionPhase.running); this._future = this._kernelConnection.requestExecute({ code, allow_stdin: allowStdin, @@ -124,8 +117,7 @@ export class KernelExecutor { }; // Wait for future to be done before resolving the exectud promise. this._future.done.then(() => { - console.log('---- DONE') - kernelsStore.getState().setExecutionPhase(this._kernel.id, ExecutionPhase.completed); + kernelsStore.getState().setExecutionPhase(this._kernelConnection.id, ExecutionPhase.completed); this._executed.resolve(this._model); }); return this._executed.promise; @@ -227,7 +219,7 @@ export class KernelExecutor { this._model.add(output); this._modelChanged.emit(this._model); if (this._stopOnError) { - kernelsStore.getState().setExecutionPhase(this._kernel.id, ExecutionPhase.completed); + kernelsStore.getState().setExecutionPhase(this._kernelConnection.id, ExecutionPhase.completed); } break; case 'clear_output': @@ -243,9 +235,9 @@ export class KernelExecutor { break; case 'status': const executionState = (message.content as any).execution_state as KernelMessage.Status; - const connectionStatus = this._kernel.connection?.connectionStatus; + const connectionStatus = this._kernelConnection.connectionStatus; const kernelState = toKernelState(connectionStatus!, executionState); - this._kernelState.setExecutionState(this._kernel.id, kernelState); + this._kernelState.setExecutionState(this._kernelConnection.id, kernelState); break; default: break; From 1103979da49eaef354715bcb5d0e12603a83e7bc Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Tue, 30 Jul 2024 15:38:03 +0200 Subject: [PATCH 12/15] chore: output state code --- packages/react/src/components/output/Output.tsx | 2 +- .../react/src/components/output/OutputState.ts | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/react/src/components/output/Output.tsx b/packages/react/src/components/output/Output.tsx index 0e5edb0e..d8ebdc98 100644 --- a/packages/react/src/components/output/Output.tsx +++ b/packages/react/src/components/output/Output.tsx @@ -128,7 +128,7 @@ export const Output = (props: IOutputProps) => { }; } }, [kernel]); - const executeRequest = outputStore.getExecuteRequestId(sourceId); + const executeRequest = outputStore.getExecuteRequest(sourceId); useEffect(() => { if (adapter && executeRequest && executeRequest === id) { adapter.execute(code); diff --git a/packages/react/src/components/output/OutputState.ts b/packages/react/src/components/output/OutputState.ts index a77049a7..c2024dcf 100644 --- a/packages/react/src/components/output/OutputState.ts +++ b/packages/react/src/components/output/OutputState.ts @@ -14,7 +14,7 @@ export type IOutputState = { model?: IOutputAreaModel; input?: string; dataset?: any; - executeId?: string; + code?: string; gradeSuccess?: boolean; }; @@ -25,13 +25,13 @@ export interface IOutputsState { export type OutputState = IOutputsState & { getAdapter: (id: string) => OutputAdapter | undefined; getDataset: (id: string) => any | undefined; - getExecuteRequestId: (id: string) => string | undefined; + getExecuteRequest: (id: string) => string | undefined; getGradeSuccess: (id: string) => boolean | undefined; getInput: (id: string) => string | undefined; getModel: (id: string) => IOutputAreaModel | undefined; setAdapter: (id: string, adapter: OutputAdapter) => void; setDataset: (id: string, dataset: any) => void; - setExecuteRequestId: (id: string, executeId: string) => void; + setExecuteRequest: (id: string, code: string) => void; setGradeSuccess: (id: string, gradeSuccess: boolean) => void; setInput: (id: string, source: string) => void; setModel: (id: string, output: IOutputAreaModel) => void; @@ -52,8 +52,8 @@ export const outputsStore = createStore((set, get) => ({ getDataset: (id: string): any | undefined => { return get().outputs.get(id)?.dataset; }, - getExecuteRequestId: (id: string): string | undefined => { - return get().outputs.get(id)?.executeId; + getExecuteRequest: (id: string): string | undefined => { + return get().outputs.get(id)?.code; }, getGradeSuccess: (id: string): boolean | undefined => { return get().outputs.get(id)?.gradeSuccess; @@ -79,13 +79,13 @@ export const outputsStore = createStore((set, get) => ({ } set((state: OutputState) => ({ outputs })) }, - setExecuteRequestId: (id: string, executeId: string) => { + setExecuteRequest: (id: string, code: string) => { const outputs = get().outputs; const e = outputs.get(id); if (e) { - e.executeId = executeId; + e.code = code; } else { - outputs.set(id, { executeId }); + outputs.set(id, { code }); } set((state: OutputState) => ({ outputs })) }, From cf3ca2bbb6763d2708c9aa8fe936f3231f1414d9 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Tue, 30 Jul 2024 17:44:44 +0200 Subject: [PATCH 13/15] chore: add completed_with_error execution phase --- packages/react/src/jupyter/kernel/KernelExecutor.ts | 2 +- packages/react/src/jupyter/kernel/KernelState.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/react/src/jupyter/kernel/KernelExecutor.ts b/packages/react/src/jupyter/kernel/KernelExecutor.ts index c9c43f20..28023a97 100644 --- a/packages/react/src/jupyter/kernel/KernelExecutor.ts +++ b/packages/react/src/jupyter/kernel/KernelExecutor.ts @@ -219,7 +219,7 @@ export class KernelExecutor { this._model.add(output); this._modelChanged.emit(this._model); if (this._stopOnError) { - kernelsStore.getState().setExecutionPhase(this._kernelConnection.id, ExecutionPhase.completed); + kernelsStore.getState().setExecutionPhase(this._kernelConnection.id, ExecutionPhase.completed_with_error); } break; case 'clear_output': diff --git a/packages/react/src/jupyter/kernel/KernelState.ts b/packages/react/src/jupyter/kernel/KernelState.ts index 3dbb26cb..b071c4a2 100644 --- a/packages/react/src/jupyter/kernel/KernelState.ts +++ b/packages/react/src/jupyter/kernel/KernelState.ts @@ -12,6 +12,7 @@ export enum ExecutionPhase { ready_to_run = 'READY_TO_RUN', running = 'RUNNING', completed = 'COMPLETED', + completed_with_error = 'COMPLETED_WITH_ERROR', } export type IKernelState = { From 771d78096376ba5f4d007ccc8e4bed81801236ff Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Tue, 30 Jul 2024 19:15:38 +0200 Subject: [PATCH 14/15] chore: disconnect signals --- .../react/src/components/cell/CellAdapter.ts | 6 +-- .../react/src/components/output/Output.tsx | 46 +++++++++++-------- .../src/components/output/OutputExecutor.ts | 3 -- 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/packages/react/src/components/cell/CellAdapter.ts b/packages/react/src/components/cell/CellAdapter.ts index a0774542..81c4a12b 100755 --- a/packages/react/src/components/cell/CellAdapter.ts +++ b/packages/react/src/components/cell/CellAdapter.ts @@ -36,7 +36,7 @@ import { RenderMimeRegistry, standardRendererFactories as initialFactories, } from '@jupyterlab/rendermime'; -import * as OutputExecutor from './../output/OutputExecutor'; +import { execute as executeOutput } from './../output/OutputExecutor'; import { Session, ServerConnection, @@ -383,7 +383,7 @@ export class CellAdapter { execute = () => { if (this._type === 'code') { - this._execute((this._cell as CodeCell)); + this._execute(this._cell as CodeCell); } else if (this._type === 'markdown') { (this._cell as MarkdownCell).rendered = true; } @@ -421,7 +421,7 @@ export class CellAdapter { > | undefined; try { - const kernelMessagePromise = OutputExecutor.execute( + const kernelMessagePromise = executeOutput( this._id, code, cell.outputArea, diff --git a/packages/react/src/components/output/Output.tsx b/packages/react/src/components/output/Output.tsx index d8ebdc98..3bc53fdd 100644 --- a/packages/react/src/components/output/Output.tsx +++ b/packages/react/src/components/output/Output.tsx @@ -76,6 +76,26 @@ export const Output = (props: IOutputProps) => { } }, []); useEffect(() => { + const outputsCallback = (model: IOutputAreaModel, change: IOutputAreaModel.ChangedArgs) => { + setOutputs(model.toJSON()); + if (id) { + outputStore.setModel(id, model); + } + }; + const receiptCallback = (model: IOutputAreaModel, change: IOutputAreaModel.ChangedArgs) => { + if (receipt && change.type === 'add') { + change.newValues.map(val => { + if (val && val.data) { + const out = val.data['text/html']; // val.data['application/vnd.jupyter.stdout']; + if (out) { + if ((out as string).indexOf(receipt) > -1) { + outputStore.setGradeSuccess(sourceId, true) + } + } + } + }); + } + }; if (id && kernel) { const adapter = propsAdapter ?? new OutputAdapter(id, kernel, outputs ?? [], model); setAdapter(adapter); @@ -86,25 +106,15 @@ export const Output = (props: IOutputProps) => { if (code) { outputStore.setInput(id,code); } - adapter.outputArea.model.changed.connect((model, change) => { - setOutputs(model.toJSON()); - outputStore.setModel(id, model); - }); + adapter.outputArea.model.changed.connect(outputsCallback); if (receipt) { - adapter.outputArea.model.changed.connect((sender, change) => { - if (change.type === 'add') { - change.newValues.map(val => { - if (val && val.data) { - const out = val.data['text/html']; // val.data['application/vnd.jupyter.stdout']; - if (out) { - if ((out as string).indexOf(receipt) > -1) { - outputStore.setGradeSuccess(sourceId, true) - } - } - } - }); - } - }); + adapter.outputArea.model.changed.connect(receiptCallback) + } + } + return () => { + if (adapter) { + adapter.outputArea.model.changed.connect(outputsCallback); + adapter.outputArea.model.changed.connect(receiptCallback) } } }, [id, kernel]); diff --git a/packages/react/src/components/output/OutputExecutor.ts b/packages/react/src/components/output/OutputExecutor.ts index 6a9d6995..66d34531 100755 --- a/packages/react/src/components/output/OutputExecutor.ts +++ b/packages/react/src/components/output/OutputExecutor.ts @@ -28,9 +28,6 @@ export async function execute( ) { stopOnError = false; } - if (!kernel) { - throw new Error('No kernel avaiable.'); - } const kernelExecutor = kernel.execute( code, { From 52ee3165637693952f2a87361ae3450e1cf1ab77 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Wed, 31 Jul 2024 10:35:09 +0200 Subject: [PATCH 15/15] Update packages/react/src/components/output/Output.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Frédéric Collonval --- packages/react/src/components/output/Output.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react/src/components/output/Output.tsx b/packages/react/src/components/output/Output.tsx index 3bc53fdd..3dc2c8d7 100644 --- a/packages/react/src/components/output/Output.tsx +++ b/packages/react/src/components/output/Output.tsx @@ -113,8 +113,8 @@ export const Output = (props: IOutputProps) => { } return () => { if (adapter) { - adapter.outputArea.model.changed.connect(outputsCallback); - adapter.outputArea.model.changed.connect(receiptCallback) + adapter.outputArea.model.changed.disconnect(outputsCallback); + adapter.outputArea.model.changed.disconnect(receiptCallback) } } }, [id, kernel]);