@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 +67 -0
- package/dist/assignment-viewer.css +29 -0
- package/dist/index.d.ts +178 -0
- package/dist/index.js +397 -489
- package/package.json +13 -4
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
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -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 { }
|