Skip to content

Commit 6ba31c5

Browse files
dwoottonstevejpurves
authored andcommitted
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
1 parent 6ca6dbb commit 6ba31c5

File tree

10 files changed

+127
-23
lines changed

10 files changed

+127
-23
lines changed

package-lock.json

Lines changed: 5 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,8 @@
5151
"npm": ">=7.0.0",
5252
"node": ">=14.0.0"
5353
},
54-
"packageManager": "[email protected]"
54+
"packageManager": "[email protected]",
55+
"dependencies": {
56+
"thebe-core": "file:../../GitHub/thebe/thebe/packages/core/thebe-core-0.4.7.tgz"
57+
}
5558
}

packages/common/src/types.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,27 @@ type PageFrontmatterWithDownloads = Omit<PageFrontmatter, 'downloads' | 'exports
4949
exports?: SiteExport[];
5050
};
5151

52+
export type WidgetState = {
53+
model_module: string;
54+
model_module_version: string;
55+
model_name: string;
56+
state: {
57+
[key: string]: any;
58+
};
59+
};
60+
61+
export type Widgets = {
62+
state: {
63+
[key: string]: WidgetState;
64+
};
65+
version_major: number;
66+
version_minor: number;
67+
};
68+
69+
export type WidgetsMetaData = {
70+
"application/vnd.jupyter.widget-state+json": Widgets
71+
};
72+
5273
export type PageLoader = {
5374
kind: SourceFileKind;
5475
location: string;
@@ -58,6 +79,7 @@ export type PageLoader = {
5879
project: string; // This is written in at render time in the site
5980
frontmatter: PageFrontmatterWithDownloads;
6081
mdast: GenericParent;
82+
widgets?: WidgetsMetaData;
6183
references: References;
6284
footer?: FooterLinks;
6385
// This may not be defined

packages/jupyter/src/execute/actions.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,16 @@ interface AddNotebookPayload {
8989
rendermime: IRenderMimeRegistry;
9090
}
9191

92+
interface AddPassivePayload {
93+
manager: any; //TODO ThebePassiveManager
94+
rendermime: IRenderMimeRegistry;
95+
pageSlug: string;
96+
}
97+
98+
export function isPassivePayload(payload: unknown): payload is AddPassivePayload {
99+
const maybePayload = payload as AddPassivePayload;
100+
return typeof maybePayload.manager === 'object' && typeof maybePayload.rendermime === 'object';
101+
}
92102
export function isAddSessionPayload(payload: unknown): payload is AddSessionPayload {
93103
const maybePayload = payload as AddSessionPayload;
94104
return (
@@ -114,13 +124,15 @@ export interface ExecuteScopeAction {
114124
| 'ADD_NOTEBOOK'
115125
| 'ADD_SESSION'
116126
| 'SET_FIRST_EXECUTION'
117-
| 'SET_RENDERING_READY';
127+
| 'SET_RENDERING_READY'
128+
| 'ADD_PASSIVE';
118129
payload:
119130
| NavigatePayload
120131
| SlugPayload
121132
| DoubleSlugPayload
122133
| BuildStatusPayload
123134
| AddMdastPayload
124135
| AddNotebookPayload
125-
| AddSessionPayload;
136+
| AddSessionPayload
137+
| AddPassivePayload;
126138
}

packages/jupyter/src/execute/hooks.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ export function useCellExecution(id: IdOrKey, clearOutputsOnExecute = false) {
262262
}
263263

264264
const ready = context.state.pages[context.slug]?.ready;
265+
const passive = context.state.pages[context.slug]?.passive;
265266
const kind = context.state.pages[context.slug]?.kind ?? SourceFileKind.Article;
266267

267268
const execute = useCallback(() => {
@@ -297,6 +298,7 @@ export function useCellExecution(id: IdOrKey, clearOutputsOnExecute = false) {
297298
return {
298299
canCompute: context.canCompute,
299300
kind,
301+
passive,
300302
ready,
301303
execute,
302304
clear,

packages/jupyter/src/execute/provider.tsx

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Dependency } from 'myst-spec-ext';
22
import { SourceFileKind } from 'myst-spec-ext';
3-
import React, { useEffect, useReducer, useRef } from 'react';
3+
import React, { useEffect, useReducer, useRef, useState } from 'react';
44
import { selectAll } from 'unist-util-select';
55
import type { ExecuteScopeAction } from './actions.js';
66
import type { Computable, ExecuteScopeState, IdKeyMap } from './types.js';
@@ -13,6 +13,8 @@ import {
1313
} from './selectors.js';
1414
import { MdastFetcher, NotebookBuilder, ServerMonitor, SessionStarter } from './leaf.js';
1515
import type { GenericParent } from 'myst-common';
16+
import { WidgetsMetaData } from '../../../common/dist/types.js';
17+
import {useThebeLoader} from 'thebe-react';
1618

1719
export interface ExecuteScopeType {
1820
canCompute: boolean;
@@ -30,6 +32,7 @@ type ArticleContents = {
3032
mdast: GenericParent;
3133
location?: string;
3234
dependencies?: Dependency[];
35+
widgets?:WidgetsMetaData;
3336
};
3437

3538
function useScopeNavigate({
@@ -106,12 +109,35 @@ export function ExecuteScopeProvider({
106109
children,
107110
enable,
108111
contents,
109-
}: React.PropsWithChildren<{ enable: boolean; contents: ArticleContents }>) {
112+
}: React.PropsWithChildren<{ enable: boolean; contents: ArticleContents ;}>) {
110113
// compute incoming for first render
111114
const computables: Computable[] = listComputables(contents.mdast);
112-
113115
const fallbackLocation = contents.kind === SourceFileKind.Notebook ? '/fallback.ipynb' : '/';
114116

117+
const { core } = useThebeLoader();
118+
const [isCoreLoaded, setIsCoreLoaded] = useState(false);
119+
useEffect(() => {
120+
if (core) {
121+
setIsCoreLoaded(true);
122+
}
123+
}, [core]);
124+
125+
useEffect(() => {
126+
if (isCoreLoaded && core) {
127+
const rendermime = core.makeRenderMimeRegistry();
128+
const manager = new core.ThebePassiveManager(rendermime, contents?.widgets?.["application/vnd.jupyter.widget-state+json"]);
129+
130+
dispatch({
131+
type: 'ADD_PASSIVE',
132+
payload: {
133+
rendermime,
134+
manager,
135+
pageSlug:contents.slug
136+
},
137+
});
138+
}
139+
}, [isCoreLoaded, core, contents?.widgets]);
140+
115141
const initialState: ExecuteScopeState = {
116142
mdast: {
117143
[contents.slug]: { root: contents.mdast },
@@ -126,6 +152,7 @@ export function ExecuteScopeProvider({
126152
computables,
127153
ready: false,
128154
scopes: {},
155+
passive: undefined
129156
},
130157
},
131158
builds: {},

packages/jupyter/src/execute/reducer.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
isBuildStatusPayload,
88
isNavigatePayload,
99
isSlugPayload,
10+
isPassivePayload,
1011
} from './actions.js';
1112
import type { ExecuteScopeState } from './types.js';
1213

@@ -161,6 +162,28 @@ export function reducer(state: ExecuteScopeState, action: ExecuteScopeAction): E
161162
},
162163
};
163164
}
165+
case 'ADD_PASSIVE': {
166+
if (!isPassivePayload(action.payload)) {
167+
console.error(action.payload);
168+
throw new Error('invalid ADD_PASSIVE payload');
169+
}
170+
const { rendermime, manager, pageSlug} = action.payload;
171+
172+
return {
173+
...state,
174+
pages: {
175+
...state.pages,
176+
[pageSlug]: {
177+
...state.pages[pageSlug],
178+
passive: {
179+
rendermime: rendermime,
180+
manager: manager,
181+
},
182+
},
183+
},
184+
};
185+
}
186+
164187
case 'ADD_SESSION': {
165188
if (!isAddSessionPayload(action.payload)) {
166189
console.error(action.payload);

packages/jupyter/src/execute/types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { GenericParent } from 'myst-common';
22
import type { SourceFileKind, Dependency } from 'myst-spec-ext';
3-
import type { IRenderMimeRegistry, ThebeNotebook, ThebeSession } from 'thebe-core';
3+
import type { IRenderMimeRegistry, ThebeNotebook, ThebeSession, ThebePassiveManager } from 'thebe-core';
44

55
export type BuildStatus =
66
| 'pending'
@@ -33,6 +33,10 @@ export interface ExecuteScopeState {
3333
scopes: {
3434
[notebookSlug: string]: ExecutionScope;
3535
};
36+
passive? : {
37+
manager: ThebePassiveManager;
38+
rendermime: IRenderMimeRegistry
39+
}
3640
};
3741
};
3842
builds: {

packages/jupyter/src/execute/utils.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,17 +75,19 @@ export function notebookFromMdast(
7575
if (codeCell.identifier) idkmap[codeCell.identifier] = target;
7676
if (output.identifier) idkmap[output.identifier] = target;
7777

78+
// TODO Future Fix: pass through metadata to sync passive and active state
79+
const metadata = {};
80+
7881
return new core.ThebeCodeCell(
7982
target.cellId,
8083
notebook.id,
81-
codeCell.value ?? '',
84+
codeCell.value ?? '', //source
85+
[block.data] ?? [{}],
8286
config,
83-
block.data ?? {},
87+
metadata,
8488
notebook.rendermime,
8589
);
8690
} else {
87-
// assume content - concatenate it
88-
// TODO inject cell metadata
8991
const cell = new core.ThebeMarkdownCell(
9092
block.key,
9193
notebook.id,

packages/jupyter/src/jupyter.tsx

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { fetchAndEncodeOutputImages } from './convertImages.js';
77
import type { ThebeCore } from 'thebe-core';
88
import { SourceFileKind } from 'myst-spec-ext';
99
import { useXRefState } from '@myst-theme/providers';
10-
import { useThebeLoader } from 'thebe-react';
10+
import { useThebeLoader } from 'thebe-react';
1111
import { useCellExecution } from './execute/index.js';
1212
import { usePlaceholder } from './decoration.js';
1313
import { MyST } from 'myst-to-react';
@@ -37,7 +37,7 @@ function ActiveOutputRenderer({
3737
console.debug(`${verb} cell ${exec.cell.id} to DOM at:`, {
3838
el: ref.current,
3939
connected: ref.current.isConnected,
40-
data: core?.stripWidgets(initialData) ?? initialData,
40+
data: initialData,
4141
});
4242

4343
exec.cell.attachToDOM(ref.current);
@@ -76,19 +76,25 @@ function PassiveOutputRenderer({
7676
core: ThebeCore;
7777
kind: SourceFileKind;
7878
}) {
79-
const rendermime = core.makeRenderMimeRegistry();
79+
const exec = useCellExecution(id);
8080

81-
const cell = useRef(new core.PassiveCellRenderer(id, rendermime, undefined));
81+
const cell = useRef<any>(null); // Initially null
8282
const ref = useRef<HTMLDivElement>(null);
8383

84-
const { loaded } = usePlotlyPassively(rendermime, data);
84+
// Use exec.passive.rendermime or fallback to core.makeRenderMimeRegistry()
85+
const { loaded } = usePlotlyPassively(exec.passive?.rendermime ?? core.makeRenderMimeRegistry(), data);
8586

8687
useEffect(() => {
87-
if (!ref.current || !loaded) return;
88-
// eslint-disable-next-line import/no-extraneous-dependencies
89-
cell.current.attachToDOM(ref.current ?? undefined, true);
90-
cell.current.render(core?.stripWidgets(data) ?? data);
91-
}, [ref, loaded]);
88+
if (!ref.current || !loaded || !exec.passive?.rendermime) return;
89+
90+
// Initialize cell when exec.passive.rendermime is available
91+
cell.current = new core.PassiveCellRenderer(id, data, exec.passive.rendermime);
92+
93+
cell.current.attachToDOM(ref.current ?? undefined, { appendExisting: true });
94+
95+
// Render regular output
96+
cell.current.render(data);
97+
}, [ref, loaded, exec.passive?.rendermime, id, data, core]);
9298

9399
return <div ref={ref} data-thebe-passive-ref="true" />;
94100
}

0 commit comments

Comments
 (0)