@doenet/doenetml-iframe 0.7.20-dev.327 → 0.7.20-dev.329
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -1
- package/index.d.ts +20 -5
- package/index.js +100 -40
- package/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -140,7 +140,38 @@ either `flags.allowSaveState` (the wrapper snapshots the flushed
|
|
|
140
140
|
`reportScoreAndState` and seeds `initialState` on restore) or
|
|
141
141
|
`flags.allowLocalState` (IndexedDB restores on reboot). Windowed viewers
|
|
142
142
|
with neither flag still mount lazily but, once booted, always stay live
|
|
143
|
-
(a console warning points this out).
|
|
143
|
+
(a console warning — once per page — points this out). Hosts that consume
|
|
144
|
+
reports through the `reportScoreAndStateCallback` prop are fully supported:
|
|
145
|
+
the wrapper captures the flushed report for its park snapshot before
|
|
146
|
+
forwarding it to the callback.
|
|
147
|
+
|
|
148
|
+
Parking also requires a standalone bundle new enough to acknowledge the
|
|
149
|
+
flush (v0.7.21+). A viewer pinned to an older `doenetmlVersion` mounts
|
|
150
|
+
lazily and obeys the boot cap, but is never parked. A host-specified
|
|
151
|
+
`standaloneUrl` is assumed modern (hosts shipping a custom URL control both
|
|
152
|
+
sides — note a `-dev.N` prerelease *version string* compares as its base
|
|
153
|
+
release, so dev-channel hosts should pin `standaloneUrl` rather than
|
|
154
|
+
`doenetmlVersion`).
|
|
155
|
+
|
|
156
|
+
#### `keepLive`: prefetching hosts
|
|
157
|
+
|
|
158
|
+
A windowed viewer inside a hidden (`display:none`) or far-off-screen
|
|
159
|
+
container never intersects the viewport, so it would stay parked forever.
|
|
160
|
+
A host that knows the viewer is about to be shown — e.g. a paginator
|
|
161
|
+
prefetching the pages adjacent to the current one — sets the dynamic
|
|
162
|
+
`keepLive` prop to treat it as visible: it boots eagerly (still subject to
|
|
163
|
+
`maxConcurrentBoots`, visible-first) and is never parked while the hint is
|
|
164
|
+
set. Clear the hint and the viewer becomes an ordinary windowed citizen
|
|
165
|
+
again (parked when off-screen and over budget).
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
<DoenetViewer
|
|
169
|
+
doenetML={doenetML}
|
|
170
|
+
flags={{ allowSaveState: true }}
|
|
171
|
+
mountPolicy={{ mode: "windowed", maxLiveViewers: 3 }}
|
|
172
|
+
keepLive={Math.abs(itemIndex - currentIndex) <= 1}
|
|
173
|
+
/>
|
|
174
|
+
```
|
|
144
175
|
|
|
145
176
|
Notes:
|
|
146
177
|
|
package/index.d.ts
CHANGED
|
@@ -86,7 +86,7 @@ declare type DoenetEditorProps = Omit<React.ComponentProps<typeof DoenetEditor_2
|
|
|
86
86
|
* v0.7.18 cannot re-render in place; for those the wrapper falls back to
|
|
87
87
|
* reloading the iframe on any prop change, its historical behavior.)
|
|
88
88
|
*/
|
|
89
|
-
export declare function DoenetViewer({ doenetML, standaloneUrl: specifiedStandaloneUrl, cssUrl: specifiedCssUrl, doenetmlVersion: specifiedDoenetmlVersion, autodetectVersion, useSharedCoreWorker, mountPolicy, ...doenetViewerProps }: DoenetViewerIframeProps): default_2.JSX.Element | null;
|
|
89
|
+
export declare function DoenetViewer({ doenetML, standaloneUrl: specifiedStandaloneUrl, cssUrl: specifiedCssUrl, doenetmlVersion: specifiedDoenetmlVersion, autodetectVersion, useSharedCoreWorker, mountPolicy, keepLive, ...doenetViewerProps }: DoenetViewerIframeProps): default_2.JSX.Element | null;
|
|
90
90
|
|
|
91
91
|
export declare type DoenetViewerIframeProps = DoenetViewerProps & {
|
|
92
92
|
doenetML: string;
|
|
@@ -134,12 +134,27 @@ export declare type DoenetViewerIframeProps = DoenetViewerProps & {
|
|
|
134
134
|
* viewport. Parking requires a persistence path: it only activates for
|
|
135
135
|
* viewers with `flags.allowSaveState` (the wrapper snapshots the
|
|
136
136
|
* flushed `reportScoreAndState` and seeds `initialState` on restore) or
|
|
137
|
-
* `flags.allowLocalState` (IndexedDB restores on reboot)
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
137
|
+
* `flags.allowLocalState` (IndexedDB restores on reboot), and only when
|
|
138
|
+
* the selected bundle is new enough to acknowledge the flush (v0.7.21+,
|
|
139
|
+
* or a host-specified `standaloneUrl`); otherwise the viewer boots on
|
|
140
|
+
* visibility but then always stays live. The policy is read at mount;
|
|
141
|
+
* changing it afterwards is not supported. Default off (no prop =
|
|
142
|
+
* today's behavior).
|
|
141
143
|
*/
|
|
142
144
|
mountPolicy?: MountPolicy;
|
|
145
|
+
/**
|
|
146
|
+
* Windowed-mounting hint: treat this viewer as visible regardless of
|
|
147
|
+
* viewport intersection — boot it eagerly (still subject to the
|
|
148
|
+
* page-wide `maxConcurrentBoots` slot queue) and never park it while
|
|
149
|
+
* set. For hosts that know an off-screen or `display:none` viewer is
|
|
150
|
+
* about to be shown, e.g. a paginator prefetching the pages adjacent to
|
|
151
|
+
* the current one. Dynamic, unlike `mountPolicy`; no effect without a
|
|
152
|
+
* windowed `mountPolicy`. The hint counts as visibility for boot
|
|
153
|
+
* ordering and the least-recently-visible eviction order, so keep it on
|
|
154
|
+
* a few viewers at a time (a page-wide `keepLive` would defeat the
|
|
155
|
+
* budget).
|
|
156
|
+
*/
|
|
157
|
+
keepLive?: boolean;
|
|
143
158
|
};
|
|
144
159
|
|
|
145
160
|
declare type DoenetViewerProps = Omit<React.ComponentProps<typeof DoenetViewer_2>, "doenetML" | "externalVirtualKeyboardProvided">;
|
package/index.js
CHANGED
|
@@ -25618,7 +25618,7 @@ function requireCssesc$1() {
|
|
|
25618
25618
|
return cssesc_1$1;
|
|
25619
25619
|
}
|
|
25620
25620
|
requireCssesc$1();
|
|
25621
|
-
const viewerIframeJsSource = '(function() {\n "use strict";\n let lastAugmentedProps = null;\n let currentFunctionProps = {};\n const functionPropDispatchers = {};\n function getFunctionPropDispatcher(key) {\n let dispatcher = functionPropDispatchers[key];\n if (!dispatcher) {\n dispatcher = (...callArgs) => currentFunctionProps[key]?.(...callArgs);\n functionPropDispatchers[key] = dispatcher;\n }\n return dispatcher;\n }\n function functionPropArgsToMap(args) {\n const map = {};\n for (let i = 0; i < args.length; i += 2) {\n const key = args[i];\n const fn = args[i + 1];\n if (typeof key === "string" && typeof fn === "function") {\n map[key] = fn;\n }\n }\n return map;\n }\n function releaseFunctionProxy(fn) {\n try {\n fn?.[ComlinkViewer.releaseProxy]?.();\n } catch (e) {\n console.warn(\n "iframe DoenetViewer: failed to release stale Comlink proxy",\n e\n );\n }\n }\n function setCurrentFunctionProps(incoming) {\n for (const [key, fn] of Object.entries(currentFunctionProps)) {\n if (incoming[key] !== fn) {\n releaseFunctionProxy(fn);\n }\n }\n currentFunctionProps = incoming;\n }\n function defaultRequestScrollTo(offset) {\n messageParentFromViewer({ type: "scrollTo", offset });\n }\n function syncAugmentedFunctionProps() {\n if (!lastAugmentedProps) {\n return;\n }\n const next = {};\n for (const [key, val] of Object.entries(lastAugmentedProps)) {\n if (typeof val !== "function") {\n next[key] = val;\n }\n }\n for (const key of Object.keys(currentFunctionProps)) {\n next[key] = getFunctionPropDispatcher(key);\n }\n if (!("requestScrollTo" in next)) {\n next.requestScrollTo = defaultRequestScrollTo;\n }\n lastAugmentedProps = next;\n }\n let currentDoenetMLSource = null;\n function resolveDoenetMLSource(root) {\n if (currentDoenetMLSource !== null) {\n return currentDoenetMLSource;\n }\n const scriptTag = root.querySelector(\'script[type="text/doenetml"]\');\n currentDoenetMLSource = scriptTag?.innerHTML ?? "";\n return currentDoenetMLSource;\n }\n async function waitForStandaloneBundle(timeoutMs) {\n if (typeof window.renderDoenetViewerToContainer === "function") {\n return true;\n }\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n await new Promise((resolve) => setTimeout(resolve, 50));\n if (typeof window.renderDoenetViewerToContainer === "function") {\n return true;\n }\n }\n return false;\n }\n ComlinkViewer.expose(\n {\n renderViewerWithFunctionProps,\n updateViewerProps(updatedSerializableProps) {\n if (!lastAugmentedProps) {\n console.warn(\n "iframe DoenetViewer: updateViewerProps arrived before renderViewerWithFunctionProps completed — likely a bug in the iframe wrapper\'s queue/replay sequencing."\n );\n return;\n }\n const { doenetML, ...rest } = updatedSerializableProps;\n if (typeof doenetML === "string") {\n currentDoenetMLSource = doenetML;\n }\n lastAugmentedProps = {\n ...lastAugmentedProps,\n ...rest\n };\n renderWithLastAugmentedProps();\n },\n updateViewerFunctionProps(...args) {\n if (!lastAugmentedProps) {\n console.warn(\n "iframe DoenetViewer: updateViewerFunctionProps arrived before renderViewerWithFunctionProps completed — likely a bug in the iframe wrapper\'s queue/replay sequencing."\n );\n return;\n }\n const incoming = functionPropArgsToMap(args);\n const prevKeys = Object.keys(currentFunctionProps).sort();\n const nextKeys = Object.keys(incoming).sort();\n const keysChanged = prevKeys.length !== nextKeys.length || prevKeys.some((key, i) => key !== nextKeys[i]);\n setCurrentFunctionProps(incoming);\n if (keysChanged) {\n syncAugmentedFunctionProps();\n renderWithLastAugmentedProps();\n }\n }\n },\n ComlinkViewer.windowEndpoint(globalThis.parent)\n );\n function renderViewerWithFunctionProps(...args) {\n setCurrentFunctionProps(functionPropArgsToMap(args));\n const augmentedDoenetViewerProps = { ...doenetViewerProps };\n augmentedDoenetViewerProps.externalVirtualKeyboardProvided = true;\n for (const propName of doenetViewerPropsSpecified) {\n if (!(propName in doenetViewerProps) && propName in currentFunctionProps) {\n augmentedDoenetViewerProps[propName] = getFunctionPropDispatcher(propName);\n }\n }\n if (!doenetViewerPropsSpecified.includes("requestScrollTo")) {\n augmentedDoenetViewerProps.requestScrollTo = defaultRequestScrollTo;\n }\n lastAugmentedProps = augmentedDoenetViewerProps;\n renderWithLastAugmentedProps();\n }\n function renderWithLastAugmentedProps() {\n const root = document.getElementById("root");\n window.renderDoenetViewerToContainer(\n root,\n resolveDoenetMLSource(root),\n lastAugmentedProps\n );\n }\n function installSharedCorePortProvider() {\n let coreCounter = 0;\n window.doenetGlobalConfig.createExternalCoreWorkerPort = () => {\n const coreId = `${viewerId}-core-${++coreCounter}`;\n const channel = new MessageChannel();\n window.parent.postMessage(\n { origin: viewerId, data: { type: "createSharedCore", coreId } },\n window.parent.origin,\n [channel.port2]\n );\n return {\n port: channel.port1,\n destroy: (suspectWedge) => {\n messageParentFromViewer({\n type: "destroySharedCore",\n coreId,\n suspectWedge: Boolean(suspectWedge)\n });\n }\n };\n };\n }\n (async () => {\n if (await waitForStandaloneBundle(6e4)) {\n if (typeof doenetSharedCoreWorker !== "undefined" && doenetSharedCoreWorker) {\n installSharedCorePortProvider();\n }\n messageParentFromViewer({ iframeReady: true });\n } else {\n messageParentFromViewer({\n error: "Invalid DoenetML version or DoenetML package not found"\n });\n }\n })().catch((err) => {\n console.error(\n "iframe DoenetViewer: unexpected failure while signalling iframeReady",\n err\n );\n try {\n messageParentFromViewer({\n error: "iframe viewer failed to initialize"\n });\n } catch {\n }\n });\n function messageParentFromViewer(data) {\n window.parent.postMessage(\n {\n origin: viewerId,\n data\n },\n window.parent.origin\n );\n }\n})();\n';
|
|
25621
|
+
const viewerIframeJsSource = '(function() {\n "use strict";\n let lastAugmentedProps = null;\n let currentFunctionProps = {};\n const functionPropDispatchers = {};\n function getFunctionPropDispatcher(key) {\n let dispatcher = functionPropDispatchers[key];\n if (!dispatcher) {\n if (key === "reportScoreAndStateCallback" && typeof doenetWindowedViewer !== "undefined" && doenetWindowedViewer) {\n dispatcher = (report) => messageParentFromViewer({\n type: "reportScoreAndState",\n report\n });\n } else {\n dispatcher = (...callArgs) => currentFunctionProps[key]?.(...callArgs);\n }\n functionPropDispatchers[key] = dispatcher;\n }\n return dispatcher;\n }\n function functionPropArgsToMap(args) {\n const map = {};\n for (let i = 0; i < args.length; i += 2) {\n const key = args[i];\n const fn = args[i + 1];\n if (typeof key === "string" && typeof fn === "function") {\n map[key] = fn;\n }\n }\n return map;\n }\n function releaseFunctionProxy(fn) {\n try {\n fn?.[ComlinkViewer.releaseProxy]?.();\n } catch (e) {\n console.warn(\n "iframe DoenetViewer: failed to release stale Comlink proxy",\n e\n );\n }\n }\n function setCurrentFunctionProps(incoming) {\n for (const [key, fn] of Object.entries(currentFunctionProps)) {\n if (incoming[key] !== fn) {\n releaseFunctionProxy(fn);\n }\n }\n currentFunctionProps = incoming;\n }\n function defaultRequestScrollTo(offset) {\n messageParentFromViewer({ type: "scrollTo", offset });\n }\n function syncAugmentedFunctionProps() {\n if (!lastAugmentedProps) {\n return;\n }\n const next = {};\n for (const [key, val] of Object.entries(lastAugmentedProps)) {\n if (typeof val !== "function") {\n next[key] = val;\n }\n }\n for (const key of Object.keys(currentFunctionProps)) {\n next[key] = getFunctionPropDispatcher(key);\n }\n if (!("requestScrollTo" in next)) {\n next.requestScrollTo = defaultRequestScrollTo;\n }\n lastAugmentedProps = next;\n }\n let currentDoenetMLSource = null;\n function resolveDoenetMLSource(root) {\n if (currentDoenetMLSource !== null) {\n return currentDoenetMLSource;\n }\n const scriptTag = root.querySelector(\'script[type="text/doenetml"]\');\n currentDoenetMLSource = scriptTag?.innerHTML ?? "";\n return currentDoenetMLSource;\n }\n async function waitForStandaloneBundle(timeoutMs) {\n if (typeof window.renderDoenetViewerToContainer === "function") {\n return true;\n }\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n await new Promise((resolve) => setTimeout(resolve, 50));\n if (typeof window.renderDoenetViewerToContainer === "function") {\n return true;\n }\n }\n return false;\n }\n ComlinkViewer.expose(\n {\n renderViewerWithFunctionProps,\n updateViewerProps(updatedSerializableProps) {\n if (!lastAugmentedProps) {\n console.warn(\n "iframe DoenetViewer: updateViewerProps arrived before renderViewerWithFunctionProps completed — likely a bug in the iframe wrapper\'s queue/replay sequencing."\n );\n return;\n }\n const { doenetML, ...rest } = updatedSerializableProps;\n if (typeof doenetML === "string") {\n currentDoenetMLSource = doenetML;\n }\n lastAugmentedProps = {\n ...lastAugmentedProps,\n ...rest\n };\n renderWithLastAugmentedProps();\n },\n updateViewerFunctionProps(...args) {\n if (!lastAugmentedProps) {\n console.warn(\n "iframe DoenetViewer: updateViewerFunctionProps arrived before renderViewerWithFunctionProps completed — likely a bug in the iframe wrapper\'s queue/replay sequencing."\n );\n return;\n }\n const incoming = functionPropArgsToMap(args);\n const prevKeys = Object.keys(currentFunctionProps).sort();\n const nextKeys = Object.keys(incoming).sort();\n const keysChanged = prevKeys.length !== nextKeys.length || prevKeys.some((key, i) => key !== nextKeys[i]);\n setCurrentFunctionProps(incoming);\n if (keysChanged) {\n syncAugmentedFunctionProps();\n renderWithLastAugmentedProps();\n }\n }\n },\n ComlinkViewer.windowEndpoint(globalThis.parent)\n );\n function renderViewerWithFunctionProps(...args) {\n setCurrentFunctionProps(functionPropArgsToMap(args));\n const augmentedDoenetViewerProps = { ...doenetViewerProps };\n augmentedDoenetViewerProps.externalVirtualKeyboardProvided = true;\n for (const propName of doenetViewerPropsSpecified) {\n if (!(propName in doenetViewerProps) && propName in currentFunctionProps) {\n augmentedDoenetViewerProps[propName] = getFunctionPropDispatcher(propName);\n }\n }\n if (!doenetViewerPropsSpecified.includes("requestScrollTo")) {\n augmentedDoenetViewerProps.requestScrollTo = defaultRequestScrollTo;\n }\n lastAugmentedProps = augmentedDoenetViewerProps;\n renderWithLastAugmentedProps();\n }\n function renderWithLastAugmentedProps() {\n const root = document.getElementById("root");\n window.renderDoenetViewerToContainer(\n root,\n resolveDoenetMLSource(root),\n lastAugmentedProps\n );\n }\n function installSharedCorePortProvider() {\n let coreCounter = 0;\n window.doenetGlobalConfig.createExternalCoreWorkerPort = () => {\n const coreId = `${viewerId}-core-${++coreCounter}`;\n const channel = new MessageChannel();\n window.parent.postMessage(\n { origin: viewerId, data: { type: "createSharedCore", coreId } },\n window.parent.origin,\n [channel.port2]\n );\n return {\n port: channel.port1,\n destroy: (suspectWedge) => {\n messageParentFromViewer({\n type: "destroySharedCore",\n coreId,\n suspectWedge: Boolean(suspectWedge)\n });\n }\n };\n };\n }\n (async () => {\n if (await waitForStandaloneBundle(6e4)) {\n if (typeof doenetSharedCoreWorker !== "undefined" && doenetSharedCoreWorker) {\n installSharedCorePortProvider();\n }\n messageParentFromViewer({ iframeReady: true });\n } else {\n messageParentFromViewer({\n error: "Invalid DoenetML version or DoenetML package not found"\n });\n }\n })().catch((err) => {\n console.error(\n "iframe DoenetViewer: unexpected failure while signalling iframeReady",\n err\n );\n try {\n messageParentFromViewer({\n error: "iframe viewer failed to initialize"\n });\n } catch {\n }\n });\n function messageParentFromViewer(data) {\n window.parent.postMessage(\n {\n origin: viewerId,\n data\n },\n window.parent.origin\n );\n }\n})();\n';
|
|
25622
25622
|
const editorIframeJsSource = `(function() {
|
|
25623
25623
|
"use strict";
|
|
25624
25624
|
let editorControlHandle = null;
|
|
@@ -25856,7 +25856,7 @@ function createIframeHead(standaloneUrl, cssUrl) {
|
|
|
25856
25856
|
function createIframeBodyOpenTag(darkMode) {
|
|
25857
25857
|
return `<body style="margin:0" ${BODY_BACKGROUND_ATTRIBUTE}="${bodyBackgroundMode(darkMode)}">`;
|
|
25858
25858
|
}
|
|
25859
|
-
function createHtmlForDoenetViewer(id2, doenetML, doenetViewerProps, standaloneUrl, cssUrl, useSharedCoreWorker = false) {
|
|
25859
|
+
function createHtmlForDoenetViewer(id2, doenetML, doenetViewerProps, standaloneUrl, cssUrl, useSharedCoreWorker = false, windowed = false) {
|
|
25860
25860
|
const doenetViewerPropsSpecified = Object.keys(doenetViewerProps);
|
|
25861
25861
|
if (!doenetViewerPropsSpecified.includes("initializedCallback")) {
|
|
25862
25862
|
doenetViewerPropsSpecified.push("initializedCallback");
|
|
@@ -25870,6 +25870,7 @@ function createHtmlForDoenetViewer(id2, doenetML, doenetViewerProps, standaloneU
|
|
|
25870
25870
|
const doenetViewerProps = ${JSON.stringify(doenetViewerProps)};
|
|
25871
25871
|
const doenetViewerPropsSpecified = ${JSON.stringify(doenetViewerPropsSpecified)};
|
|
25872
25872
|
const doenetSharedCoreWorker = ${JSON.stringify(!!useSharedCoreWorker)};
|
|
25873
|
+
const doenetWindowedViewer = ${JSON.stringify(!!windowed)};
|
|
25873
25874
|
import * as ComlinkViewer from "https://unpkg.com/comlink/dist/esm/comlink.mjs";
|
|
25874
25875
|
|
|
25875
25876
|
// This source code has been compiled by vite and should be directly included.
|
|
@@ -26039,6 +26040,14 @@ const records = /* @__PURE__ */ new Map();
|
|
|
26039
26040
|
let visibleOrderCounter = 0;
|
|
26040
26041
|
let effectiveMaxLive = null;
|
|
26041
26042
|
let warnedMixedBudgets = false;
|
|
26043
|
+
let warnedWindowedWithoutPersistence = false;
|
|
26044
|
+
function shouldWarnWindowedWithoutPersistence() {
|
|
26045
|
+
if (warnedWindowedWithoutPersistence) {
|
|
26046
|
+
return false;
|
|
26047
|
+
}
|
|
26048
|
+
warnedWindowedWithoutPersistence = true;
|
|
26049
|
+
return true;
|
|
26050
|
+
}
|
|
26042
26051
|
let evaluateTimerId = null;
|
|
26043
26052
|
let evaluateTimerAt = Infinity;
|
|
26044
26053
|
let effectiveMaxBoots = null;
|
|
@@ -26216,6 +26225,32 @@ function evaluate$1() {
|
|
|
26216
26225
|
scheduleEvaluate(Math.max(0, nextDebounceExpiry - now));
|
|
26217
26226
|
}
|
|
26218
26227
|
}
|
|
26228
|
+
const LIVE_UPDATE_MIN_VERSION = [0, 7, 18];
|
|
26229
|
+
const PARK_MIN_VERSION = [0, 7, 21];
|
|
26230
|
+
function versionAtLeast(version2, min2) {
|
|
26231
|
+
const match2 = version2.replace(/^v/, "").match(/^(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
|
|
26232
|
+
if (!match2) {
|
|
26233
|
+
return true;
|
|
26234
|
+
}
|
|
26235
|
+
const parts = [match2[1], match2[2], match2[3]].map(
|
|
26236
|
+
(p2) => p2 === void 0 ? Infinity : parseInt(p2, 10)
|
|
26237
|
+
);
|
|
26238
|
+
for (let i2 = 0; i2 < 3; i2++) {
|
|
26239
|
+
if (parts[i2] > min2[i2]) {
|
|
26240
|
+
return true;
|
|
26241
|
+
}
|
|
26242
|
+
if (parts[i2] < min2[i2]) {
|
|
26243
|
+
return false;
|
|
26244
|
+
}
|
|
26245
|
+
}
|
|
26246
|
+
return true;
|
|
26247
|
+
}
|
|
26248
|
+
function versionSupportsLiveUpdates(version2) {
|
|
26249
|
+
return versionAtLeast(version2, LIVE_UPDATE_MIN_VERSION);
|
|
26250
|
+
}
|
|
26251
|
+
function versionSupportsParking(version2) {
|
|
26252
|
+
return versionAtLeast(version2, PARK_MIN_VERSION);
|
|
26253
|
+
}
|
|
26219
26254
|
function findNodesWithPositionInfo(nodes) {
|
|
26220
26255
|
if (typeof nodes !== "object" || nodes === null) {
|
|
26221
26256
|
return [];
|
|
@@ -63362,7 +63397,7 @@ function ExternalVirtualKeyboard({
|
|
|
63362
63397
|
}
|
|
63363
63398
|
);
|
|
63364
63399
|
}
|
|
63365
|
-
const version = "0.7.20-dev.
|
|
63400
|
+
const version = "0.7.20-dev.329";
|
|
63366
63401
|
const latestDoenetmlVersion = version;
|
|
63367
63402
|
function subscribeToPinnedTheme() {
|
|
63368
63403
|
return () => {
|
|
@@ -63377,25 +63412,6 @@ function useResolvedTheme(setting) {
|
|
|
63377
63412
|
);
|
|
63378
63413
|
return setting === "system" ? systemTheme : setting;
|
|
63379
63414
|
}
|
|
63380
|
-
const LIVE_UPDATE_MIN_VERSION = [0, 7, 18];
|
|
63381
|
-
function versionSupportsLiveUpdates(version2) {
|
|
63382
|
-
const match2 = version2.replace(/^v/, "").match(/^(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
|
|
63383
|
-
if (!match2) {
|
|
63384
|
-
return true;
|
|
63385
|
-
}
|
|
63386
|
-
const parts = [match2[1], match2[2], match2[3]].map(
|
|
63387
|
-
(p2) => p2 === void 0 ? Infinity : parseInt(p2, 10)
|
|
63388
|
-
);
|
|
63389
|
-
for (let i2 = 0; i2 < 3; i2++) {
|
|
63390
|
-
if (parts[i2] > LIVE_UPDATE_MIN_VERSION[i2]) {
|
|
63391
|
-
return true;
|
|
63392
|
-
}
|
|
63393
|
-
if (parts[i2] < LIVE_UPDATE_MIN_VERSION[i2]) {
|
|
63394
|
-
return false;
|
|
63395
|
-
}
|
|
63396
|
-
}
|
|
63397
|
-
return true;
|
|
63398
|
-
}
|
|
63399
63415
|
function DoenetViewer({
|
|
63400
63416
|
doenetML,
|
|
63401
63417
|
standaloneUrl: specifiedStandaloneUrl,
|
|
@@ -63404,6 +63420,7 @@ function DoenetViewer({
|
|
|
63404
63420
|
autodetectVersion = true,
|
|
63405
63421
|
useSharedCoreWorker = false,
|
|
63406
63422
|
mountPolicy,
|
|
63423
|
+
keepLive = false,
|
|
63407
63424
|
...doenetViewerProps
|
|
63408
63425
|
}) {
|
|
63409
63426
|
const [id2, _2] = React__default.useState(() => Math.random().toString(36).slice(2));
|
|
@@ -63421,7 +63438,8 @@ function DoenetViewer({
|
|
|
63421
63438
|
const [parkGeneration, setParkGeneration] = React__default.useState(0);
|
|
63422
63439
|
const parkedRef = React__default.useRef(windowed);
|
|
63423
63440
|
const parkingRef = React__default.useRef(false);
|
|
63424
|
-
const
|
|
63441
|
+
const intersectingRef = React__default.useRef(false);
|
|
63442
|
+
const keepLiveRef = React__default.useRef(false);
|
|
63425
63443
|
const containerRef = React__default.useRef(null);
|
|
63426
63444
|
const parkFlushCounterRef = React__default.useRef(0);
|
|
63427
63445
|
const parkFlushIdRef = React__default.useRef(null);
|
|
@@ -63510,6 +63528,9 @@ function DoenetViewer({
|
|
|
63510
63528
|
specifiedStandaloneUrl && !foundAutoVersion
|
|
63511
63529
|
);
|
|
63512
63530
|
const liveUpdatesSupported = usingCustomStandaloneUrl || versionSupportsLiveUpdates(selectedDoenetmlVersion);
|
|
63531
|
+
const parkingSupported = usingCustomStandaloneUrl || versionSupportsParking(selectedDoenetmlVersion);
|
|
63532
|
+
const parkingSupportedRef = React__default.useRef(parkingSupported);
|
|
63533
|
+
parkingSupportedRef.current = parkingSupported;
|
|
63513
63534
|
const standaloneUrlRef = React__default.useRef(standaloneUrl);
|
|
63514
63535
|
standaloneUrlRef.current = standaloneUrl;
|
|
63515
63536
|
const propsSnapshotStr = serializePropsSnapshot(
|
|
@@ -63538,7 +63559,8 @@ function DoenetViewer({
|
|
|
63538
63559
|
bakedProps,
|
|
63539
63560
|
standaloneUrl,
|
|
63540
63561
|
cssUrl,
|
|
63541
|
-
useSharedCoreWorker
|
|
63562
|
+
useSharedCoreWorker,
|
|
63563
|
+
windowed
|
|
63542
63564
|
);
|
|
63543
63565
|
}, [
|
|
63544
63566
|
id2,
|
|
@@ -63593,7 +63615,9 @@ function DoenetViewer({
|
|
|
63593
63615
|
}
|
|
63594
63616
|
if (windowed) {
|
|
63595
63617
|
if (event.data.subject === "SPLICE.reportScoreAndState") {
|
|
63596
|
-
lastCapturedReportRef.current =
|
|
63618
|
+
lastCapturedReportRef.current = {
|
|
63619
|
+
state: event.data.state
|
|
63620
|
+
};
|
|
63597
63621
|
return;
|
|
63598
63622
|
}
|
|
63599
63623
|
if (event.data.subject === "SPLICE.flushState.response" && event.data.message_id === parkFlushIdRef.current) {
|
|
@@ -63605,6 +63629,24 @@ function DoenetViewer({
|
|
|
63605
63629
|
return;
|
|
63606
63630
|
}
|
|
63607
63631
|
const data = event.data.data;
|
|
63632
|
+
if (windowed && data.type === "reportScoreAndState") {
|
|
63633
|
+
const report = data.report ?? {};
|
|
63634
|
+
lastCapturedReportRef.current = { state: report.state };
|
|
63635
|
+
const hostCallback = doenetViewerPropsRef.current.reportScoreAndStateCallback;
|
|
63636
|
+
if (typeof hostCallback === "function") {
|
|
63637
|
+
hostCallback(report);
|
|
63638
|
+
} else {
|
|
63639
|
+
window.postMessage({
|
|
63640
|
+
subject: "SPLICE.reportScoreAndState",
|
|
63641
|
+
state: report.state,
|
|
63642
|
+
score: report.score,
|
|
63643
|
+
activity_id: report.activityId,
|
|
63644
|
+
doc_id: report.docId,
|
|
63645
|
+
message_id: Math.random().toString(36).slice(2)
|
|
63646
|
+
});
|
|
63647
|
+
}
|
|
63648
|
+
return;
|
|
63649
|
+
}
|
|
63608
63650
|
if (data.type === "createSharedCore" && event.ports[0]) {
|
|
63609
63651
|
handleCreateSharedCore({
|
|
63610
63652
|
viewerId: id2,
|
|
@@ -63751,7 +63793,7 @@ function DoenetViewer({
|
|
|
63751
63793
|
return Boolean(flags?.allowSaveState || flags?.allowLocalState);
|
|
63752
63794
|
}
|
|
63753
63795
|
function canPark() {
|
|
63754
|
-
return hasPersistencePath() && Boolean(ref.current?.contentWindow);
|
|
63796
|
+
return hasPersistencePath() && parkingSupportedRef.current && Boolean(ref.current?.contentWindow);
|
|
63755
63797
|
}
|
|
63756
63798
|
function beginPark() {
|
|
63757
63799
|
if (parkedRef.current || parkingRef.current) {
|
|
@@ -63788,28 +63830,32 @@ function DoenetViewer({
|
|
|
63788
63830
|
parkFlushCleanupRef.current?.();
|
|
63789
63831
|
parkFlushIdRef.current = null;
|
|
63790
63832
|
parkingRef.current = false;
|
|
63791
|
-
if (
|
|
63833
|
+
if (effectivelyVisible()) {
|
|
63792
63834
|
notifyViewerState(id2, "live");
|
|
63793
63835
|
return false;
|
|
63794
63836
|
}
|
|
63795
63837
|
return true;
|
|
63796
63838
|
}
|
|
63839
|
+
function seedSnapshotFromCapturedReport() {
|
|
63840
|
+
if (lastCapturedReportRef.current?.state) {
|
|
63841
|
+
parkedSnapshotRef.current = lastCapturedReportRef.current.state;
|
|
63842
|
+
}
|
|
63843
|
+
}
|
|
63797
63844
|
function completeParkFlush(ack) {
|
|
63798
63845
|
if (!endParkFlush()) {
|
|
63799
63846
|
return;
|
|
63800
63847
|
}
|
|
63801
63848
|
if (ack.hadState) {
|
|
63802
63849
|
everHadStateRef.current = true;
|
|
63803
|
-
if (lastCapturedReportRef.current?.state) {
|
|
63804
|
-
parkedSnapshotRef.current = lastCapturedReportRef.current.state;
|
|
63805
|
-
}
|
|
63806
63850
|
}
|
|
63851
|
+
seedSnapshotFromCapturedReport();
|
|
63807
63852
|
finishPark();
|
|
63808
63853
|
}
|
|
63809
63854
|
function onParkFlushTimeout() {
|
|
63810
63855
|
if (!endParkFlush()) {
|
|
63811
63856
|
return;
|
|
63812
63857
|
}
|
|
63858
|
+
seedSnapshotFromCapturedReport();
|
|
63813
63859
|
finishPark();
|
|
63814
63860
|
}
|
|
63815
63861
|
function finishPark() {
|
|
@@ -63825,7 +63871,7 @@ function DoenetViewer({
|
|
|
63825
63871
|
return;
|
|
63826
63872
|
}
|
|
63827
63873
|
requestBootSlot(id2, () => {
|
|
63828
|
-
if (!parkedRef.current || !
|
|
63874
|
+
if (!parkedRef.current || !effectivelyVisible()) {
|
|
63829
63875
|
releaseBootSlot(id2);
|
|
63830
63876
|
return;
|
|
63831
63877
|
}
|
|
@@ -63840,6 +63886,18 @@ function DoenetViewer({
|
|
|
63840
63886
|
}, BOOT_SLOT_WATCHDOG_MS);
|
|
63841
63887
|
});
|
|
63842
63888
|
}
|
|
63889
|
+
function effectivelyVisible() {
|
|
63890
|
+
return intersectingRef.current || keepLiveRef.current;
|
|
63891
|
+
}
|
|
63892
|
+
function updateEffectiveVisibility() {
|
|
63893
|
+
const effective = effectivelyVisible();
|
|
63894
|
+
setViewerVisibility(id2, effective);
|
|
63895
|
+
if (effective) {
|
|
63896
|
+
unpark();
|
|
63897
|
+
} else {
|
|
63898
|
+
cancelBootRequest(id2);
|
|
63899
|
+
}
|
|
63900
|
+
}
|
|
63843
63901
|
React__default.useEffect(() => {
|
|
63844
63902
|
if (!windowed || !containerRef.current) {
|
|
63845
63903
|
return;
|
|
@@ -63847,13 +63905,8 @@ function DoenetViewer({
|
|
|
63847
63905
|
const observer = new IntersectionObserver(
|
|
63848
63906
|
(entries) => {
|
|
63849
63907
|
for (const entry of entries) {
|
|
63850
|
-
|
|
63851
|
-
|
|
63852
|
-
if (entry.isIntersecting && parkedRef.current) {
|
|
63853
|
-
unpark();
|
|
63854
|
-
} else if (!entry.isIntersecting) {
|
|
63855
|
-
cancelBootRequest(id2);
|
|
63856
|
-
}
|
|
63908
|
+
intersectingRef.current = entry.isIntersecting;
|
|
63909
|
+
updateEffectiveVisibility();
|
|
63857
63910
|
}
|
|
63858
63911
|
},
|
|
63859
63912
|
{
|
|
@@ -63869,9 +63922,9 @@ function DoenetViewer({
|
|
|
63869
63922
|
if (!windowed) {
|
|
63870
63923
|
return;
|
|
63871
63924
|
}
|
|
63872
|
-
if (!hasPersistencePath()) {
|
|
63925
|
+
if (!hasPersistencePath() && shouldWarnWindowedWithoutPersistence()) {
|
|
63873
63926
|
console.warn(
|
|
63874
|
-
"DoenetViewer mountPolicy: windowed mounting is enabled but neither flags.allowSaveState nor flags.allowLocalState is set, so parking would lose student work.
|
|
63927
|
+
"DoenetViewer mountPolicy: windowed mounting is enabled but neither flags.allowSaveState nor flags.allowLocalState is set, so parking would lose student work. Such viewers will always stay live. (Warned once per page.)"
|
|
63875
63928
|
);
|
|
63876
63929
|
}
|
|
63877
63930
|
const unregister = registerWindowedViewer({
|
|
@@ -63891,6 +63944,13 @@ function DoenetViewer({
|
|
|
63891
63944
|
unregister();
|
|
63892
63945
|
};
|
|
63893
63946
|
}, [windowed]);
|
|
63947
|
+
React__default.useEffect(() => {
|
|
63948
|
+
if (!windowed) {
|
|
63949
|
+
return;
|
|
63950
|
+
}
|
|
63951
|
+
keepLiveRef.current = keepLive;
|
|
63952
|
+
updateEffectiveVisibility();
|
|
63953
|
+
}, [windowed, keepLive]);
|
|
63894
63954
|
if (inErrorState) {
|
|
63895
63955
|
if (foundAutoVersion) {
|
|
63896
63956
|
setIgnoreDetectedVersion(true);
|