From 561977703cf180327b61e474e49940106f1bde91 Mon Sep 17 00:00:00 2001 From: Dylan Wootton Date: Sun, 14 Jul 2024 22:16:04 -0700 Subject: [PATCH] Rehydrate Widget State (#426) @dwootton * added types * initial metadata join * clean pipeing, no widgetstate in passive or active renderer * pre rm widgetstate to tree rm * rm merging metadata to tree * piping render * updated to metadata reference * rm spacing and ts ignore * init working * refactor with fixed model * updated comment * refactor * spacing * state return * hidden --- package-lock.json | 7 +++-- package.json | 5 +++- packages/common/src/types.ts | 22 +++++++++++++++ packages/jupyter/src/execute/actions.ts | 16 +++++++++-- packages/jupyter/src/execute/hooks.ts | 2 ++ packages/jupyter/src/execute/provider.tsx | 33 ++++++++++++++++++++--- packages/jupyter/src/execute/reducer.ts | 23 ++++++++++++++++ packages/jupyter/src/execute/types.ts | 6 ++++- packages/jupyter/src/execute/utils.ts | 10 ++++--- packages/jupyter/src/jupyter.tsx | 26 +++++++++++------- 10 files changed, 127 insertions(+), 23 deletions(-) diff --git a/package-lock.json b/package-lock.json index d82b48ff8..aaf62b564 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,9 @@ "styles", "docs" ], + "dependencies": { + "thebe-core": "file:../../GitHub/thebe/thebe/packages/core/thebe-core-0.4.7.tgz" + }, "devDependencies": { "@changesets/cli": "^2.26.1", "@remix-run/node": "~1.17.0", @@ -36643,8 +36646,8 @@ }, "node_modules/thebe-core": { "version": "0.4.7", - "resolved": "https://registry.npmjs.org/thebe-core/-/thebe-core-0.4.7.tgz", - "integrity": "sha512-VwspRu64Yn4O0mAq7pR9Ehoy0b/uGMyHsJrbJ3uiD1doorCk8bRbD9+R7PjhzNV8Zju6g8C2i2tKsG6a9Guk0Q==", + "resolved": "file:../../GitHub/thebe/thebe/packages/core/thebe-core-0.4.7.tgz", + "integrity": "sha512-C1+6rEQ9He8nEO543MArUvKc872Fv9YqmbFiYtYJkDU2qV2FJJwGXRRpuAyKqG3gWyNy+gqgJUH+3pgj1ERd8Q==", "license": "MIT", "dependencies": { "@jupyter-widgets/base": "^6.0.6", diff --git a/package.json b/package.json index b94d33f6c..e1fdc3341 100644 --- a/package.json +++ b/package.json @@ -51,5 +51,8 @@ "npm": ">=7.0.0", "node": ">=14.0.0" }, - "packageManager": "npm@8.10.0" + "packageManager": "npm@8.10.0", + "dependencies": { + "thebe-core": "file:../../GitHub/thebe/thebe/packages/core/thebe-core-0.4.7.tgz" + } } diff --git a/packages/common/src/types.ts b/packages/common/src/types.ts index 6195aed8d..536dea4e9 100644 --- a/packages/common/src/types.ts +++ b/packages/common/src/types.ts @@ -49,6 +49,27 @@ type PageFrontmatterWithDownloads = Omit { @@ -297,6 +298,7 @@ export function useCellExecution(id: IdOrKey, clearOutputsOnExecute = false) { return { canCompute: context.canCompute, kind, + passive, ready, execute, clear, diff --git a/packages/jupyter/src/execute/provider.tsx b/packages/jupyter/src/execute/provider.tsx index 9291790fa..edaa1968a 100644 --- a/packages/jupyter/src/execute/provider.tsx +++ b/packages/jupyter/src/execute/provider.tsx @@ -1,6 +1,6 @@ import type { Dependency } from 'myst-spec-ext'; import { SourceFileKind } from 'myst-spec-ext'; -import React, { useEffect, useReducer, useRef } from 'react'; +import React, { useEffect, useReducer, useRef, useState } from 'react'; import { selectAll } from 'unist-util-select'; import type { ExecuteScopeAction } from './actions.js'; import type { Computable, ExecuteScopeState, IdKeyMap } from './types.js'; @@ -13,6 +13,8 @@ import { } from './selectors.js'; import { MdastFetcher, NotebookBuilder, ServerMonitor, SessionStarter } from './leaf.js'; import type { GenericParent } from 'myst-common'; +import { WidgetsMetaData } from '../../../common/dist/types.js'; +import {useThebeLoader} from 'thebe-react'; export interface ExecuteScopeType { canCompute: boolean; @@ -30,6 +32,7 @@ type ArticleContents = { mdast: GenericParent; location?: string; dependencies?: Dependency[]; + widgets?:WidgetsMetaData; }; function useScopeNavigate({ @@ -106,12 +109,35 @@ export function ExecuteScopeProvider({ children, enable, contents, -}: React.PropsWithChildren<{ enable: boolean; contents: ArticleContents }>) { +}: React.PropsWithChildren<{ enable: boolean; contents: ArticleContents ;}>) { // compute incoming for first render const computables: Computable[] = listComputables(contents.mdast); - const fallbackLocation = contents.kind === SourceFileKind.Notebook ? '/fallback.ipynb' : '/'; + const { core } = useThebeLoader(); + const [isCoreLoaded, setIsCoreLoaded] = useState(false); + useEffect(() => { + if (core) { + setIsCoreLoaded(true); + } + }, [core]); + + useEffect(() => { + if (isCoreLoaded && core) { + const rendermime = core.makeRenderMimeRegistry(); + const manager = new core.ThebePassiveManager(rendermime, contents?.widgets?.["application/vnd.jupyter.widget-state+json"]); + + dispatch({ + type: 'ADD_PASSIVE', + payload: { + rendermime, + manager, + pageSlug:contents.slug + }, + }); + } + }, [isCoreLoaded, core, contents?.widgets]); + const initialState: ExecuteScopeState = { mdast: { [contents.slug]: { root: contents.mdast }, @@ -126,6 +152,7 @@ export function ExecuteScopeProvider({ computables, ready: false, scopes: {}, + passive: undefined }, }, builds: {}, diff --git a/packages/jupyter/src/execute/reducer.ts b/packages/jupyter/src/execute/reducer.ts index 9a9c88adf..b6a405664 100644 --- a/packages/jupyter/src/execute/reducer.ts +++ b/packages/jupyter/src/execute/reducer.ts @@ -7,6 +7,7 @@ import { isBuildStatusPayload, isNavigatePayload, isSlugPayload, + isPassivePayload, } from './actions.js'; import type { ExecuteScopeState } from './types.js'; @@ -161,6 +162,28 @@ export function reducer(state: ExecuteScopeState, action: ExecuteScopeAction): E }, }; } + case 'ADD_PASSIVE': { + if (!isPassivePayload(action.payload)) { + console.error(action.payload); + throw new Error('invalid ADD_PASSIVE payload'); + } + const { rendermime, manager, pageSlug} = action.payload; + + return { + ...state, + pages: { + ...state.pages, + [pageSlug]: { + ...state.pages[pageSlug], + passive: { + rendermime: rendermime, + manager: manager, + }, + }, + }, + }; + } + case 'ADD_SESSION': { if (!isAddSessionPayload(action.payload)) { console.error(action.payload); diff --git a/packages/jupyter/src/execute/types.ts b/packages/jupyter/src/execute/types.ts index e684435e9..f40a826b0 100644 --- a/packages/jupyter/src/execute/types.ts +++ b/packages/jupyter/src/execute/types.ts @@ -1,6 +1,6 @@ import type { GenericParent } from 'myst-common'; import type { SourceFileKind, Dependency } from 'myst-spec-ext'; -import type { IRenderMimeRegistry, ThebeNotebook, ThebeSession } from 'thebe-core'; +import type { IRenderMimeRegistry, ThebeNotebook, ThebeSession, ThebePassiveManager } from 'thebe-core'; export type BuildStatus = | 'pending' @@ -33,6 +33,10 @@ export interface ExecuteScopeState { scopes: { [notebookSlug: string]: ExecutionScope; }; + passive? : { + manager: ThebePassiveManager; + rendermime: IRenderMimeRegistry + } }; }; builds: { diff --git a/packages/jupyter/src/execute/utils.ts b/packages/jupyter/src/execute/utils.ts index 65432d9e8..20e8c5771 100644 --- a/packages/jupyter/src/execute/utils.ts +++ b/packages/jupyter/src/execute/utils.ts @@ -75,17 +75,19 @@ export function notebookFromMdast( if (codeCell.identifier) idkmap[codeCell.identifier] = target; if (output.identifier) idkmap[output.identifier] = target; + // TODO Future Fix: pass through metadata to sync passive and active state + const metadata = {}; + return new core.ThebeCodeCell( target.cellId, notebook.id, - codeCell.value ?? '', + codeCell.value ?? '', //source + [block.data] ?? [{}], config, - block.data ?? {}, + metadata, notebook.rendermime, ); } else { - // assume content - concatenate it - // TODO inject cell metadata const cell = new core.ThebeMarkdownCell( block.key, notebook.id, diff --git a/packages/jupyter/src/jupyter.tsx b/packages/jupyter/src/jupyter.tsx index 72daa6348..66d0a82b8 100644 --- a/packages/jupyter/src/jupyter.tsx +++ b/packages/jupyter/src/jupyter.tsx @@ -7,7 +7,7 @@ import { fetchAndEncodeOutputImages } from './convertImages.js'; import type { ThebeCore } from 'thebe-core'; import { SourceFileKind } from 'myst-spec-ext'; import { useXRefState } from '@myst-theme/providers'; -import { useThebeLoader } from 'thebe-react'; +import { useThebeLoader } from 'thebe-react'; import { useCellExecution } from './execute/index.js'; import { usePlaceholder } from './decoration.js'; import { MyST } from 'myst-to-react'; @@ -37,7 +37,7 @@ function ActiveOutputRenderer({ console.debug(`${verb} cell ${exec.cell.id} to DOM at:`, { el: ref.current, connected: ref.current.isConnected, - data: core?.stripWidgets(initialData) ?? initialData, + data: initialData, }); exec.cell.attachToDOM(ref.current); @@ -76,19 +76,25 @@ function PassiveOutputRenderer({ core: ThebeCore; kind: SourceFileKind; }) { - const rendermime = core.makeRenderMimeRegistry(); + const exec = useCellExecution(id); - const cell = useRef(new core.PassiveCellRenderer(id, rendermime, undefined)); + const cell = useRef(null); // Initially null const ref = useRef(null); - const { loaded } = usePlotlyPassively(rendermime, data); + // Use exec.passive.rendermime or fallback to core.makeRenderMimeRegistry() + const { loaded } = usePlotlyPassively(exec.passive?.rendermime ?? core.makeRenderMimeRegistry(), data); useEffect(() => { - if (!ref.current || !loaded) return; - // eslint-disable-next-line import/no-extraneous-dependencies - cell.current.attachToDOM(ref.current ?? undefined, true); - cell.current.render(core?.stripWidgets(data) ?? data); - }, [ref, loaded]); + if (!ref.current || !loaded || !exec.passive?.rendermime) return; + + // Initialize cell when exec.passive.rendermime is available + cell.current = new core.PassiveCellRenderer(id, data, exec.passive.rendermime); + + cell.current.attachToDOM(ref.current ?? undefined, { appendExisting: true }); + + // Render regular output + cell.current.render(data); + }, [ref, loaded, exec.passive?.rendermime, id, data, core]); return
; }