@doenet/assignment-viewer 0.1.0-alpha-14 → 0.1.0-alpha-16

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 CHANGED
@@ -1,3 +1,70 @@
1
1
  # Doenet assignment viewer
2
2
 
3
3
  View assignments from questions written in DoenetML
4
+
5
+ ## Mixed DoenetML versions
6
+
7
+ Each document in an assignment carries its own DoenetML `version`, and every
8
+ embedded viewer downloads and parses the multi-MB standalone bundle for its
9
+ document's version — so an assignment mixing versions multiplies that cost
10
+ by the number of distinct versions on the page.
11
+
12
+ The viewer does not normalize versions itself (that would change how the
13
+ older documents behave). Instead, when an assignment mixes versions, it
14
+ reports the condition to the containing page — a console warning alone would
15
+ be invisible to the site's visitors — via the `reportWarningsCallback` prop,
16
+ and the page decides how to display it:
17
+
18
+ ```tsx
19
+ <ActivityViewer
20
+ source={source}
21
+ reportWarningsCallback={(warnings) => {
22
+ for (const warning of warnings) {
23
+ if (warning.type === "mixedDoenetmlVersions") {
24
+ showBanner(
25
+ `This assignment mixes DoenetML versions (${warning.versions.join(", ")}), ` +
26
+ "which slows loading. Consider updating its documents to one version.",
27
+ );
28
+ }
29
+ }
30
+ }}
31
+ />
32
+ ```
33
+
34
+ To consolidate, normalize the documents' `version` fields in the assignment
35
+ source. Saved student state survives such a change: it is keyed on a source
36
+ hash that deliberately ignores `version`.
37
+
38
+ ## Windowed mounting
39
+
40
+ Every item's viewer component is always mounted, but memory tracks what the
41
+ student can see — the pagination window or viewport — instead of the
42
+ assignment's length. The embedded viewers register with a page-wide
43
+ _windowed mounting_ policy (`mountPolicy` from `@doenet/doenetml-iframe`):
44
+ items only boot their iframe (a multi-MB bundle parse plus a core worker)
45
+ when near the viewport or within the pagination window, simultaneous boots
46
+ are capped, and at most `maxLiveViewers` stay live — the rest are **parked**:
47
+ their state is flushed, their iframe is replaced by a placeholder, and they
48
+ are restored (student's typed work intact) when the student returns to them.
49
+ In paginated mode the current page and its neighbors are kept live, so page
50
+ flips within the window stay instant.
51
+
52
+ This is the default. Tune it with the `mountPolicy` prop (overrides for
53
+ `maxLiveViewers`, `visibleMargin`, `parkDelayMs`, `flushTimeoutMs`,
54
+ `maxConcurrentBoots`):
55
+
56
+ ```tsx
57
+ <ActivityViewer
58
+ source={source}
59
+ flags={{ allowSaveState: true }}
60
+ mountPolicy={{ maxLiveViewers: 5 }}
61
+ />
62
+ ```
63
+
64
+ Parking requires a persistence path (`flags.allowSaveState` or
65
+ `flags.allowLocalState`) so no student work can be lost; without one,
66
+ viewers still mount lazily but stay live once booted.
67
+
68
+ To cut the per-document core cost further, `useSharedCoreWorker` serves all
69
+ documents' cores from a shared worker pool instead of one dedicated ~100 MB
70
+ worker per document (default off; see `@doenet/doenetml-iframe`).
@@ -0,0 +1,29 @@
1
+ .assignment-viewer-root {
2
+ --canvas: white;
3
+ --canvasText: black;
4
+ --mainGray: #e3e3e3;
5
+ --mainBorder: 2px solid black;
6
+ --mainBorderRadius: 5px;
7
+ --buttonSurface: #e3e3e3;
8
+ --buttonSurfaceAlt: rgb(237, 242, 247);
9
+ }
10
+
11
+ .assignment-viewer-root[data-theme="dark"] {
12
+ --canvas: #121212;
13
+ --canvasText: white;
14
+ --mainGray: #a9a9a9;
15
+ --mainBorder: 2px solid white;
16
+ --buttonSurface: #3a3a3a;
17
+ --buttonSurfaceAlt: #2d3748;
18
+ }
19
+
20
+ .assignment-viewer-root {
21
+ background-color: var(--canvas);
22
+ color: var(--canvasText);
23
+ }
24
+
25
+ /* Give iframes the canvas background color so there's no white flash
26
+ while the standalone bundle loads its own dark-mode styles. */
27
+ .assignment-viewer-root iframe {
28
+ background-color: var(--canvas);
29
+ }
@@ -0,0 +1,178 @@
1
+ import { JSX } from 'react/jsx-runtime';
2
+ import { MountPolicy } from '@doenet/doenetml-iframe';
3
+
4
+ /** The source for creating an activity */
5
+ export declare type ActivitySource = SingleDocSource | SelectSource | SequenceSource;
6
+
7
+ export declare function ActivityViewer({ source, flags: specifiedFlags, activityId, userId, requestedVariantIndex, maxAttemptsAllowed, itemLevelAttempts, activityLevelAttempts, paginate, showFinishButton, forceDisable, forceShowCorrectness, forceShowSolution, forceUnsuppressCheckwork, addVirtualKeyboard, externalVirtualKeyboardProvided, doenetViewerUrl, doenetImagesUrl, standaloneUrl, cssUrl, doenetmlVersion, fetchExternalDoenetML, darkMode, showAnswerResponseMenu, answerResponseCountsByItem, includeVariantSelector: _includeVariantSelector, showTitle, itemWord, reportWarningsCallback, mountPolicy, useSharedCoreWorker, }: {
8
+ source: ActivitySource;
9
+ flags?: DoenetMLFlagsSubset;
10
+ activityId?: string;
11
+ userId?: string | null;
12
+ requestedVariantIndex?: number;
13
+ maxAttemptsAllowed?: number;
14
+ itemLevelAttempts?: boolean;
15
+ activityLevelAttempts?: boolean;
16
+ paginate?: boolean;
17
+ showFinishButton?: boolean;
18
+ forceDisable?: boolean;
19
+ forceShowCorrectness?: boolean;
20
+ forceShowSolution?: boolean;
21
+ forceUnsuppressCheckwork?: boolean;
22
+ addVirtualKeyboard?: boolean;
23
+ externalVirtualKeyboardProvided?: boolean;
24
+ /**
25
+ * URL the `<ref>` renderer uses to build links to other Doenet
26
+ * activities. Forwarded to each document's viewer, which defaults it to
27
+ * `https://doenet.org/activityViewer`.
28
+ */
29
+ doenetViewerUrl?: string;
30
+ /**
31
+ * URL used to resolve `<image source="doenet:…">` media references.
32
+ * Forwarded to each document's viewer, which defaults it to
33
+ * `https://doenet.org/api/media`.
34
+ */
35
+ doenetImagesUrl?: string;
36
+ /**
37
+ * URL of a standalone DoenetML bundle to use for every document,
38
+ * instead of the CDN bundle for each document's `version`.
39
+ */
40
+ standaloneUrl?: string;
41
+ /** URL of the CSS file that styles the standalone bundle. */
42
+ cssUrl?: string;
43
+ /**
44
+ * Render every document with this DoenetML version, overriding each
45
+ * document's own `version`.
46
+ */
47
+ doenetmlVersion?: string;
48
+ fetchExternalDoenetML?: (arg: string) => Promise<string>;
49
+ darkMode?: ThemeSetting;
50
+ showAnswerResponseMenu?: boolean;
51
+ answerResponseCountsByItem?: Record<string, number>[];
52
+ includeVariantSelector?: boolean;
53
+ showTitle?: boolean;
54
+ itemWord?: string;
55
+ /**
56
+ * Called with conditions in `source` worth surfacing to the user, e.g.
57
+ * an assignment mixing DoenetML versions. Invoked once per distinct set
58
+ * of warnings — not on every render, and not again when the consumer
59
+ * passes a fresh-but-equal `source` each render. The containing page
60
+ * decides how to display them; a `console.warn` is also emitted for
61
+ * developers.
62
+ */
63
+ reportWarningsCallback?: (warnings: ActivityViewerWarning[]) => void;
64
+ /**
65
+ * Overrides for the windowed mounting policy every document's viewer
66
+ * registers with (see `MountPolicy` in `@doenet/doenetml-iframe`):
67
+ * at most `maxLiveViewers` viewers stay booted, the rest are parked
68
+ * losslessly as placeholders and restored near the viewport. Parking
69
+ * requires `flags.allowSaveState` or `flags.allowLocalState`; without
70
+ * them viewers still mount lazily but stay live once booted.
71
+ */
72
+ mountPolicy?: Partial<Omit<MountPolicy, "mode">>;
73
+ /**
74
+ * Serve all documents' cores from a shared worker pool instead of one
75
+ * dedicated ~100 MB worker per document (see `useSharedCoreWorker` in
76
+ * `@doenet/doenetml-iframe`). Default off.
77
+ */
78
+ useSharedCoreWorker?: boolean;
79
+ }): JSX.Element | null;
80
+
81
+ /**
82
+ * A condition in the provided activity worth surfacing to the user — passed
83
+ * to `reportWarningsCallback`, since a console warning is invisible to the
84
+ * site's visitors. How (and whether) to display it is the containing page's
85
+ * decision.
86
+ */
87
+ export declare type ActivityViewerWarning = {
88
+ type: "mixedDoenetmlVersions";
89
+ /**
90
+ * The distinct DoenetML versions the assignment's documents request, in
91
+ * first-appearance order. Every embedded viewer downloads and parses
92
+ * the multi-MB standalone bundle for its document's version, so mixing
93
+ * versions multiplies that cost by the number of distinct versions.
94
+ * (Normalizing the `version` fields in the source avoids it; saved
95
+ * student state is keyed on a hash that ignores `version`, so it
96
+ * survives such a change.)
97
+ */
98
+ versions: string[];
99
+ };
100
+
101
+ declare type DoenetMLFlags = {
102
+ showCorrectness: boolean;
103
+ readOnly: boolean;
104
+ solutionDisplayMode: string;
105
+ showFeedback: boolean;
106
+ showHints: boolean;
107
+ allowLoadState: boolean;
108
+ allowSaveState: boolean;
109
+ allowLocalState: boolean;
110
+ allowSaveSubmissions: boolean;
111
+ allowSaveEvents: boolean;
112
+ autoSubmit: boolean;
113
+ };
114
+
115
+ declare type DoenetMLFlagsSubset = Partial<DoenetMLFlags>;
116
+
117
+ export declare function isActivitySource(obj: unknown): obj is ActivitySource;
118
+
119
+ /** The source for creating a select activity */
120
+ declare type SelectSource = {
121
+ type: "select";
122
+ id: string;
123
+ title?: string;
124
+ /** The child activities to select from */
125
+ items: ActivitySource[];
126
+ /** The number of child activities to select (without replacement) for each attempt */
127
+ numToSelect: number;
128
+ /**
129
+ * Whether or not to consider each variant of each child a separate option to select from.
130
+ * If `selectByVariant` is `true`, the selection is from the total set of all variants,
131
+ * meaning selection probabilities is weighted by the number of variants each child has,
132
+ * and, if `numToSelect` > 1, a child could be selected multiple times for a given attempt.
133
+ */
134
+ selectByVariant: boolean;
135
+ };
136
+
137
+ /** The source for creating a sequence activity */
138
+ declare type SequenceSource = {
139
+ type: "sequence";
140
+ id: string;
141
+ title?: string;
142
+ /** The child activities that form the sequence. */
143
+ items: ActivitySource[];
144
+ /** If `true`, randomly permute the item order on each new attempt. */
145
+ shuffle: boolean;
146
+ /**
147
+ * Weights given to the credit achieved of each item
148
+ * when averaging them to determine the credit achieved of the sequence activity.
149
+ * Items missing a weight are given the weight 1.
150
+ */
151
+ creditWeights?: number[];
152
+ };
153
+
154
+ /** The source for creating a single doc activity */
155
+ declare type SingleDocSource = {
156
+ type: "singleDoc";
157
+ id: string;
158
+ title?: string;
159
+ /**
160
+ * If `isDescription` is `true`, then this activity is not considered one of the scored items
161
+ * and its credit achieved is ignored.
162
+ */
163
+ isDescription: boolean;
164
+ doenetML: string;
165
+ /** The version of DoenetML that should be used to render this activity. */
166
+ version: string;
167
+ /** The number of variants present in `doenetML` */
168
+ numVariants?: number;
169
+ };
170
+
171
+ /**
172
+ * `ThemeSetting` is the public, author-facing value of the `darkMode` prop:
173
+ * `"light"` and `"dark"` pin a specific theme, while `"system"` follows the
174
+ * OS/browser `prefers-color-scheme` preference and updates live.
175
+ */
176
+ export declare type ThemeSetting = "dark" | "light" | "system";
177
+
178
+ export { }