@pie-players/pie-default-tool-loaders 1.0.0-beta.0 → 1.0.0-beta.10
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/LICENSE +21 -0
- package/README.md +116 -12
- package/dist/afa-profile.d.ts +65 -0
- package/dist/afa-profile.js +135 -0
- package/dist/calculator-providers/LazyCalculatorToolProvider.d.ts +32 -18
- package/dist/calculator-providers/LazyCalculatorToolProvider.js +24 -12
- package/dist/calculator-providers/packaged-calculator-providers.js +26 -0
- package/dist/index.d.ts +7 -5
- package/dist/index.js +10 -5
- package/dist/packaged-capability-composition.js +11 -1
- package/dist/registrations/accessibility-tools.d.ts +8 -0
- package/dist/registrations/accessibility-tools.js +38 -0
- package/dist/registrations/audio-transcript.d.ts +0 -7
- package/dist/registrations/audio-transcript.js +1 -7
- package/dist/registrations/calculator.d.ts +16 -5
- package/dist/registrations/calculator.js +171 -176
- package/dist/registrations/interaction-tools.d.ts +15 -2
- package/dist/registrations/interaction-tools.js +110 -13
- package/dist/registrations/tts.d.ts +23 -2
- package/dist/registrations/tts.js +121 -93
- package/package.json +27 -25
- package/dist/calculator-providers/CortexToolProvider.d.ts +0 -10
- package/dist/calculator-providers/CortexToolProvider.js +0 -18
- package/dist/calculator-providers/DesmosToolProvider.d.ts +0 -11
- package/dist/calculator-providers/DesmosToolProvider.js +0 -18
- package/dist/calculator-providers/GeoGebraToolProvider.d.ts +0 -13
- package/dist/calculator-providers/GeoGebraToolProvider.js +0 -18
- package/dist/registrations/overlay-element-cache.d.ts +0 -23
- package/dist/registrations/overlay-toolbar-render.d.ts +0 -33
- package/dist/registrations/selection-actions.d.ts +0 -24
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PIE Framework contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -5,9 +5,9 @@ they load, and which of them a program grants to everyone. This README is for
|
|
|
5
5
|
host integrators choosing a tool set and a calculator, and for contributors
|
|
6
6
|
adding a packaged capability.
|
|
7
7
|
|
|
8
|
-
The package owns the concrete `pie-tool-*` dependencies and the
|
|
9
|
-
|
|
10
|
-
stays dependency-light and cycle-safe. Core knows the concepts — support ids,
|
|
8
|
+
The package owns the concrete `pie-tool-*` dependencies and the Desmos and
|
|
9
|
+
GeoGebra calculator adapters (`pie-calculator-desmos` and `-geogebra`), so
|
|
10
|
+
toolkit core stays dependency-light and cycle-safe. Core knows the concepts — support ids,
|
|
11
11
|
placement levels, activation kinds and precedence rules — and no concrete
|
|
12
12
|
capability id. A support id is the tool id a profile grants.
|
|
13
13
|
|
|
@@ -15,8 +15,11 @@ capability id. A support id is the tool id a profile grants.
|
|
|
15
15
|
| --- | --- |
|
|
16
16
|
| `PACKAGED_TOOL_REGISTRATIONS`, `createPackagedToolRegistry` | Which capabilities exist, and their toolbar and surface contracts |
|
|
17
17
|
| `PACKAGED_TOOL_TAG_MAP` | Which custom element each one renders as |
|
|
18
|
+
| `createCalculatorToolRegistration` | Which calculator backends `provider.id` selects among |
|
|
19
|
+
| `createTTSToolRegistration` | Which host text-to-speech providers `provider.id` selects among |
|
|
18
20
|
| `SECTION_PLAYER_PREFERRED_TOOL_PLACEMENT` | Where they appear and in what order |
|
|
19
21
|
| `createUniversalPersonalNeedsProfile`, `createEmptyPersonalNeedsProfile` | Which of them the program grants to everyone, or to no one |
|
|
22
|
+
| `createPersonalNeedsProfileFromAfa` | Which of them a student's AfA PNP 3.0 record grants, prohibits and turns on at start, and with which settings |
|
|
20
23
|
| `DEFAULT_TOOL_MODULE_LOADERS` | When each one's bundle loads |
|
|
21
24
|
| `CONTENT_ALTERNATE_REGISTRATIONS` | Which of them carry an authored alternate that renders as a region; the print player mounts these |
|
|
22
25
|
|
|
@@ -57,15 +60,8 @@ Desmos is the provider when none is configured, and it needs an API key: pass
|
|
|
57
60
|
it through `provider.runtime.authFetcher`, a `provider.init.proxyEndpoint` that
|
|
58
61
|
returns it, or `provider.init.apiKey` in development
|
|
59
62
|
([Calculator with host auth](../../docs/tools-and-accomodations/tool_provider_system.md#calculator-with-host-auth)).
|
|
60
|
-
Without one the calculator fails to load.
|
|
61
|
-
|
|
62
|
-
`settings` take the `CortexCalculatorSettings` type that
|
|
63
|
-
`@pie-players/pie-calculator-cortex` exports: `angleMode`,
|
|
64
|
-
`calculationPrecision`, `displayPrecision`, `historyLimit`,
|
|
65
|
-
`evaluationTimeLimitMs`, `allowedFunctions`, `allowClipboard`, `messages`,
|
|
66
|
-
`direction` and `graph`. Clipboard blocking follows `restrictedMode`;
|
|
67
|
-
`allowClipboard` alone blocks nothing. The [Cortex README](../calculator-cortex/README.md)
|
|
68
|
-
covers localization, message overrides and the keypad.
|
|
63
|
+
Without one the calculator fails to load. `provider.id: "calculator-geogebra"`
|
|
64
|
+
selects GeoGebra, which needs no key.
|
|
69
65
|
|
|
70
66
|
```ts
|
|
71
67
|
const tools = {
|
|
@@ -81,6 +77,95 @@ const tools = {
|
|
|
81
77
|
};
|
|
82
78
|
```
|
|
83
79
|
|
|
80
|
+
### Adding a backend
|
|
81
|
+
|
|
82
|
+
The packaged registration selects among Desmos and GeoGebra only, because a
|
|
83
|
+
bundler that inlines dynamic imports inlines every backend the registration
|
|
84
|
+
names, selected or not. `createCalculatorToolRegistration({ providers })` builds
|
|
85
|
+
the registration with more backends, keyed by `provider.id`; an id the packaged
|
|
86
|
+
set already holds is replaced. The keyless, offline-capable Cortex calculator,
|
|
87
|
+
which needs no runtime CDN, is composed in this way from
|
|
88
|
+
`@pie-players/pie-calculator-cortex`, which the host installs:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import {
|
|
92
|
+
createCalculatorToolRegistration,
|
|
93
|
+
createPackagedToolRegistry,
|
|
94
|
+
} from "@pie-players/pie-default-tool-loaders";
|
|
95
|
+
|
|
96
|
+
const registry = createPackagedToolRegistry({
|
|
97
|
+
overrides: {
|
|
98
|
+
calculator: createCalculatorToolRegistration({
|
|
99
|
+
providers: {
|
|
100
|
+
"calculator-cortex": {
|
|
101
|
+
providerName: "PIE Open-Source Calculator",
|
|
102
|
+
version: "1",
|
|
103
|
+
requiresAuth: false,
|
|
104
|
+
backend: "cortex",
|
|
105
|
+
load: async () =>
|
|
106
|
+
(await import("@pie-players/pie-calculator-cortex"))
|
|
107
|
+
.CortexCalculatorProvider,
|
|
108
|
+
initializationErrorMessage:
|
|
109
|
+
"Failed to initialize the Cortex calculator provider.",
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
}),
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`provider.id: "calculator-cortex"` then selects it. Its `settings` take the
|
|
118
|
+
`CortexCalculatorSettings` type that `@pie-players/pie-calculator-cortex`
|
|
119
|
+
exports: `angleMode`, `calculationPrecision`, `displayPrecision`,
|
|
120
|
+
`historyLimit`, `evaluationTimeLimitMs`, `allowedFunctions`, `allowClipboard`,
|
|
121
|
+
`messages`, `direction` and `graph`. Clipboard blocking follows
|
|
122
|
+
`restrictedMode`; `allowClipboard` alone blocks nothing. The
|
|
123
|
+
[Cortex README](../calculator-cortex/README.md) covers localization, message
|
|
124
|
+
overrides and the keypad.
|
|
125
|
+
|
|
126
|
+
## Text-to-speech providers
|
|
127
|
+
|
|
128
|
+
The packaged text-to-speech registration speaks through the browser or a TTS
|
|
129
|
+
server, as `backend` selects. `createTTSToolRegistration({ providers })` adds a
|
|
130
|
+
host's own `ITTSProvider`, keyed by `provider.id`; the class is imported only
|
|
131
|
+
when its id is selected.
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
import {
|
|
135
|
+
createPackagedToolRegistry,
|
|
136
|
+
createTTSToolRegistration,
|
|
137
|
+
} from "@pie-players/pie-default-tool-loaders";
|
|
138
|
+
|
|
139
|
+
const registry = createPackagedToolRegistry({
|
|
140
|
+
overrides: {
|
|
141
|
+
textToSpeech: createTTSToolRegistration({
|
|
142
|
+
providers: {
|
|
143
|
+
"host-tts": {
|
|
144
|
+
load: async () => (await import("./host-tts.js")).HostTTSProvider,
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
}),
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// tools.providers.textToSpeech
|
|
152
|
+
const textToSpeech = {
|
|
153
|
+
enabled: true,
|
|
154
|
+
language: "en-US",
|
|
155
|
+
provider: { id: "host-tts" },
|
|
156
|
+
};
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The provider's `initialize()` receives the config the toolkit builds for every
|
|
160
|
+
backend: `voice`, `rate`, `pitch`, `language`, `providerOptions` (the host's
|
|
161
|
+
`language` as `locale`) and `mathTokenHighlighting`. `apiEndpoint` and the
|
|
162
|
+
backend and transport keys are not required. With `requiresAuth: true` on the
|
|
163
|
+
descriptor, the toolkit first awaits `provider.runtime.authFetcher` and merges what it returns, such as
|
|
164
|
+
`authToken` or `headers`, into that config. A provider that fails to initialize
|
|
165
|
+
falls back to browser speech, as a server backend does. A `provider.id` that
|
|
166
|
+
names no registered provider, or none at all, keeps the browser or server
|
|
167
|
+
backend.
|
|
168
|
+
|
|
84
169
|
## Custom sets
|
|
85
170
|
|
|
86
171
|
A host composing its own set starts from the toolkit's empty `ToolRegistry`,
|
|
@@ -159,6 +244,25 @@ An assessment carrying no profile is left alone. `pnpEnforcement`
|
|
|
159
244
|
auto-detection engages only on host policy material, so with no profile,
|
|
160
245
|
placement alone decides which tools appear.
|
|
161
246
|
|
|
247
|
+
## AfA profiles
|
|
248
|
+
|
|
249
|
+
`createPersonalNeedsProfileFromAfa(record)` builds a fresh profile from a
|
|
250
|
+
student's AfA PNP 3.0 record, held as JSON keyed by the spec's terms. Features
|
|
251
|
+
become support ids in the packaged set's names, the activation sets fill
|
|
252
|
+
`activateAtInit` and `prohibitedSupports`, and the settings the packaged tools
|
|
253
|
+
read become `toolParameters`: read-aloud speed, pitch and volume, the calculator
|
|
254
|
+
type, the sign language and the magnifier's starting zoom. The toolkit's PNP
|
|
255
|
+
configuration guide gives each AfA feature's disposition.
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
import { createPersonalNeedsProfileFromAfa } from "@pie-players/pie-default-tool-loaders";
|
|
259
|
+
|
|
260
|
+
sectionPlayer.assessment = {
|
|
261
|
+
...authoredAssessment,
|
|
262
|
+
personalNeedsProfile: createPersonalNeedsProfileFromAfa(afaRecord),
|
|
263
|
+
};
|
|
264
|
+
```
|
|
265
|
+
|
|
162
266
|
## Packaged capability composition
|
|
163
267
|
|
|
164
268
|
The exports are projections of one internal composition. Each packaged
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The AfA PNP 3.0 adapter: an AfA record, as JSON keyed by the spec's
|
|
3
|
+
* kebab-case terms, becomes the toolkit's personal-needs profile.
|
|
4
|
+
*
|
|
5
|
+
* Each feature the record names is a support id, the term camelCased, except
|
|
6
|
+
* where a packaged tool carries the capability under an id of its own
|
|
7
|
+
* (`TOOL_REGISTRY.md`, Support ids). `spoken` resolves by its `reading-type`:
|
|
8
|
+
* `computer-read-aloud` is `textToSpeech`, and `screen-reader`, AfA's default,
|
|
9
|
+
* is `screenReader` (ADR 0004). `extensions` and the resource-matching
|
|
10
|
+
* preferences name no support and are skipped.
|
|
11
|
+
*
|
|
12
|
+
* A feature at the record's root or in `activate-as-option-set` is granted, one
|
|
13
|
+
* in `activate-at-initialization-set` is granted and on at start, and one in
|
|
14
|
+
* `prohibit-set` is prohibited, which withdraws it from both. The settings of
|
|
15
|
+
* a feature a packaged tool reads become that tool's `toolParameters`.
|
|
16
|
+
*/
|
|
17
|
+
import type { PersonalNeedsProfile } from "@pie-players/pie-players-shared/types";
|
|
18
|
+
/** `spoken`'s settings, in AfA's units. */
|
|
19
|
+
export interface AfaSpoken {
|
|
20
|
+
"reading-type"?: "screen-reader" | "computer-read-aloud";
|
|
21
|
+
/** Words per minute; AfA's default is 180. */
|
|
22
|
+
"speech-rate"?: number;
|
|
23
|
+
/** 0–1; AfA's default is 0.5. */
|
|
24
|
+
pitch?: number;
|
|
25
|
+
/** 0–1; AfA's default is 0.5. */
|
|
26
|
+
volume?: number;
|
|
27
|
+
[setting: string]: unknown;
|
|
28
|
+
}
|
|
29
|
+
/** A set's features: empty elements keyed by feature, or the feature names. */
|
|
30
|
+
export type AfaFeatureSet = Readonly<Record<string, unknown>> | readonly string[];
|
|
31
|
+
/** AfA's `ZoomAmount`: a magnification multiplier. */
|
|
32
|
+
export interface AfaZoomAmount {
|
|
33
|
+
"zoom-amount"?: number;
|
|
34
|
+
}
|
|
35
|
+
/** An AfA PNP 3.0 record in JSON. A feature is present unless absent, `null` or `false`. */
|
|
36
|
+
export interface AfaPnpRecord {
|
|
37
|
+
spoken?: AfaSpoken;
|
|
38
|
+
"calculator-on-screen"?: {
|
|
39
|
+
"calculator-type"?: string;
|
|
40
|
+
[setting: string]: unknown;
|
|
41
|
+
};
|
|
42
|
+
"sign-language"?: {
|
|
43
|
+
language?: string;
|
|
44
|
+
[setting: string]: unknown;
|
|
45
|
+
};
|
|
46
|
+
"item-translation"?: {
|
|
47
|
+
language?: string;
|
|
48
|
+
[setting: string]: unknown;
|
|
49
|
+
};
|
|
50
|
+
/** One selection: the amount for all content, or for text and non-text apart. */
|
|
51
|
+
magnification?: {
|
|
52
|
+
"all-content"?: AfaZoomAmount;
|
|
53
|
+
selectMagnification?: {
|
|
54
|
+
text?: AfaZoomAmount;
|
|
55
|
+
"non-text"?: AfaZoomAmount;
|
|
56
|
+
};
|
|
57
|
+
[setting: string]: unknown;
|
|
58
|
+
};
|
|
59
|
+
"activate-at-initialization-set"?: AfaFeatureSet;
|
|
60
|
+
"activate-as-option-set"?: AfaFeatureSet;
|
|
61
|
+
"prohibit-set"?: AfaFeatureSet;
|
|
62
|
+
[feature: string]: unknown;
|
|
63
|
+
}
|
|
64
|
+
/** A fresh profile carrying `record`'s grants, prohibitions and settings, ids sorted. */
|
|
65
|
+
export declare function createPersonalNeedsProfileFromAfa(record: AfaPnpRecord): PersonalNeedsProfile;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The AfA PNP 3.0 adapter: an AfA record, as JSON keyed by the spec's
|
|
3
|
+
* kebab-case terms, becomes the toolkit's personal-needs profile.
|
|
4
|
+
*
|
|
5
|
+
* Each feature the record names is a support id, the term camelCased, except
|
|
6
|
+
* where a packaged tool carries the capability under an id of its own
|
|
7
|
+
* (`TOOL_REGISTRY.md`, Support ids). `spoken` resolves by its `reading-type`:
|
|
8
|
+
* `computer-read-aloud` is `textToSpeech`, and `screen-reader`, AfA's default,
|
|
9
|
+
* is `screenReader` (ADR 0004). `extensions` and the resource-matching
|
|
10
|
+
* preferences name no support and are skipped.
|
|
11
|
+
*
|
|
12
|
+
* A feature at the record's root or in `activate-as-option-set` is granted, one
|
|
13
|
+
* in `activate-at-initialization-set` is granted and on at start, and one in
|
|
14
|
+
* `prohibit-set` is prohibited, which withdraws it from both. The settings of
|
|
15
|
+
* a feature a packaged tool reads become that tool's `toolParameters`.
|
|
16
|
+
*/
|
|
17
|
+
const RENAMED_FEATURES = {
|
|
18
|
+
"calculator-on-screen": "calculator",
|
|
19
|
+
"answer-masking": "answerEliminator",
|
|
20
|
+
"dictionary-on-screen": "dictionary",
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Keys that name no support: the sets, `extensions`, and the preferences that
|
|
24
|
+
* select among adapted resources by metadata PIE content does not carry. The
|
|
25
|
+
* interface language is the host's `locale`.
|
|
26
|
+
*/
|
|
27
|
+
const NON_SUPPORT_KEYS = new Set([
|
|
28
|
+
"activate-at-initialization-set",
|
|
29
|
+
"activate-as-option-set",
|
|
30
|
+
"prohibit-set",
|
|
31
|
+
"extensions",
|
|
32
|
+
"access-mode-required",
|
|
33
|
+
"adaptation-type-required",
|
|
34
|
+
"at-interoperable",
|
|
35
|
+
"educational-complexity-of-adaptation",
|
|
36
|
+
"hazard-avoidance",
|
|
37
|
+
"input-requirements",
|
|
38
|
+
"language-of-adaptation",
|
|
39
|
+
"language-of-interface",
|
|
40
|
+
"adaptation-detail-required",
|
|
41
|
+
"adaptation-media-required",
|
|
42
|
+
"educational-level-of-adaptation",
|
|
43
|
+
]);
|
|
44
|
+
/** AfA's `standard` calculator has no PIE type, so it keeps the tool's default. */
|
|
45
|
+
const CALCULATOR_TYPES = new Set(["basic", "scientific", "graphing"]);
|
|
46
|
+
/** AfA's default `speech-rate`, which PIE's normal speed 1 stands for. */
|
|
47
|
+
const AFA_NORMAL_SPEECH_RATE = 180;
|
|
48
|
+
const isPresent = (value) => value !== undefined && value !== null && value !== false;
|
|
49
|
+
const finiteNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
50
|
+
const camelCase = (term) => term.replace(/-([a-z0-9])/g, (_, next) => next.toUpperCase());
|
|
51
|
+
function featureNames(set) {
|
|
52
|
+
if (!set)
|
|
53
|
+
return [];
|
|
54
|
+
const names = Array.isArray(set)
|
|
55
|
+
? set.filter((name) => typeof name === "string")
|
|
56
|
+
: Object.entries(set)
|
|
57
|
+
.filter(([, value]) => isPresent(value))
|
|
58
|
+
.map(([name]) => name);
|
|
59
|
+
return names.filter((name) => !NON_SUPPORT_KEYS.has(name));
|
|
60
|
+
}
|
|
61
|
+
function spokenParameters(spoken) {
|
|
62
|
+
const speechRate = finiteNumber(spoken["speech-rate"]);
|
|
63
|
+
const pitch = finiteNumber(spoken.pitch);
|
|
64
|
+
const volume = finiteNumber(spoken.volume);
|
|
65
|
+
return {
|
|
66
|
+
...(speechRate !== undefined && speechRate > 0
|
|
67
|
+
? { rate: speechRate / AFA_NORMAL_SPEECH_RATE }
|
|
68
|
+
: {}),
|
|
69
|
+
...(pitch !== undefined ? { pitch: pitch * 2 } : {}),
|
|
70
|
+
...(volume !== undefined ? { volume } : {}),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The lens's starting zoom. The lens magnifies text and non-text alike, so a
|
|
75
|
+
* record that sets them apart gives the text amount, else the non-text one.
|
|
76
|
+
*/
|
|
77
|
+
function magnifierZoom(magnification) {
|
|
78
|
+
const select = magnification?.selectMagnification;
|
|
79
|
+
return (finiteNumber(magnification?.["all-content"]?.["zoom-amount"]) ??
|
|
80
|
+
finiteNumber(select?.text?.["zoom-amount"]) ??
|
|
81
|
+
finiteNumber(select?.["non-text"]?.["zoom-amount"]));
|
|
82
|
+
}
|
|
83
|
+
/** A fresh profile carrying `record`'s grants, prohibitions and settings, ids sorted. */
|
|
84
|
+
export function createPersonalNeedsProfileFromAfa(record) {
|
|
85
|
+
const spoken = typeof record.spoken === "object" && record.spoken !== null
|
|
86
|
+
? record.spoken
|
|
87
|
+
: {};
|
|
88
|
+
const readAloud = spoken["reading-type"] === "computer-read-aloud";
|
|
89
|
+
const supportId = (feature) => feature === "spoken"
|
|
90
|
+
? readAloud
|
|
91
|
+
? "textToSpeech"
|
|
92
|
+
: "screenReader"
|
|
93
|
+
: (RENAMED_FEATURES[feature] ?? camelCase(feature));
|
|
94
|
+
// PIE cannot withhold a screen reader, so a prohibited `spoken` withholds
|
|
95
|
+
// the spoken presentation PIE makes, read-aloud.
|
|
96
|
+
const prohibited = new Set(featureNames(record["prohibit-set"]).map((feature) => feature === "spoken" ? "textToSpeech" : supportId(feature)));
|
|
97
|
+
const admitted = (ids) => [...new Set(ids)].filter((id) => !prohibited.has(id)).sort();
|
|
98
|
+
const activateAtInit = admitted(featureNames(record["activate-at-initialization-set"]).map(supportId));
|
|
99
|
+
const supports = admitted([
|
|
100
|
+
...featureNames(record).map(supportId),
|
|
101
|
+
...featureNames(record["activate-as-option-set"]).map(supportId),
|
|
102
|
+
...activateAtInit,
|
|
103
|
+
]);
|
|
104
|
+
const toolParameters = {};
|
|
105
|
+
if (readAloud) {
|
|
106
|
+
const speech = spokenParameters(spoken);
|
|
107
|
+
if (Object.keys(speech).length > 0) {
|
|
108
|
+
toolParameters.textToSpeech = speech;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const calculatorType = record["calculator-on-screen"]?.["calculator-type"];
|
|
112
|
+
if (calculatorType && CALCULATOR_TYPES.has(calculatorType)) {
|
|
113
|
+
toolParameters.calculator = {
|
|
114
|
+
type: calculatorType,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const signLang = record["sign-language"]?.language;
|
|
118
|
+
if (typeof signLang === "string" && signLang.length > 0) {
|
|
119
|
+
toolParameters.signLanguage = { signLang };
|
|
120
|
+
}
|
|
121
|
+
const translationLang = record["item-translation"]?.language;
|
|
122
|
+
if (typeof translationLang === "string" && translationLang.length > 0) {
|
|
123
|
+
toolParameters.itemTranslation = { language: translationLang };
|
|
124
|
+
}
|
|
125
|
+
const zoom = magnifierZoom(record.magnification);
|
|
126
|
+
if (zoom !== undefined && zoom > 1) {
|
|
127
|
+
toolParameters.magnification = { zoom };
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
supports,
|
|
131
|
+
prohibitedSupports: [...prohibited].sort(),
|
|
132
|
+
...(activateAtInit.length > 0 ? { activateAtInit } : {}),
|
|
133
|
+
...(Object.keys(toolParameters).length > 0 ? { toolParameters } : {}),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
@@ -1,33 +1,47 @@
|
|
|
1
1
|
import type { CalculatorProvider, CalculatorProviderInit } from "@pie-players/pie-calculator";
|
|
2
2
|
import type { ToolProviderApi } from "@pie-players/pie-assessment-toolkit/tools/registration";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
/**
|
|
4
|
+
* One calculator backend, selected by the toolkit's `provider.id`. `load` runs
|
|
5
|
+
* on first use, but a bundler that inlines dynamic imports inlines what it
|
|
6
|
+
* names whether or not the host selects the backend. The packaged set therefore
|
|
7
|
+
* holds only the thin Desmos and GeoGebra adapters, and a host adds any other
|
|
8
|
+
* backend through `createCalculatorToolRegistration`.
|
|
9
|
+
*/
|
|
10
|
+
export interface CalculatorProviderDefinition {
|
|
11
|
+
/** Names the provider in toolkit logs, telemetry and error messages. */
|
|
12
|
+
readonly providerName: string;
|
|
13
|
+
readonly version: string;
|
|
14
|
+
/**
|
|
15
|
+
* Whether the registry merges the result of `provider.runtime.authFetcher`
|
|
16
|
+
* into the configuration `initialize()` receives.
|
|
17
|
+
*/
|
|
18
|
+
readonly requiresAuth: boolean;
|
|
19
|
+
/** The `backend` of library-load telemetry. */
|
|
20
|
+
readonly backend: string;
|
|
21
|
+
/** Loads the adapter's provider class, usually through a dynamic import. */
|
|
22
|
+
readonly load: () => Promise<new () => CalculatorProvider>;
|
|
23
|
+
/** Thrown, with the failure as its cause, when loading or initializing fails. */
|
|
24
|
+
readonly initializationErrorMessage: string;
|
|
12
25
|
}
|
|
13
26
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
27
|
+
* Lazy-module and lifecycle implementation shared by every calculator backend:
|
|
28
|
+
* concurrent initialization is deduplicated, and `destroy()` cancels one in
|
|
29
|
+
* flight.
|
|
16
30
|
*/
|
|
17
|
-
export declare
|
|
18
|
-
|
|
31
|
+
export declare class LazyCalculatorToolProvider implements ToolProviderApi<CalculatorProvider> {
|
|
32
|
+
readonly providerName: string;
|
|
19
33
|
readonly category: "calculator";
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
34
|
+
readonly version: string;
|
|
35
|
+
readonly requiresAuth: boolean;
|
|
36
|
+
private readonly definition;
|
|
23
37
|
private calculatorProvider;
|
|
24
38
|
private initializationPromise;
|
|
25
39
|
private lifecycleGeneration;
|
|
40
|
+
constructor(definition: CalculatorProviderDefinition);
|
|
26
41
|
private emitTelemetry;
|
|
27
|
-
initialize(config?:
|
|
42
|
+
initialize(config?: CalculatorProviderInit): Promise<void>;
|
|
28
43
|
private initializeProvider;
|
|
29
44
|
createInstance(): Promise<CalculatorProvider>;
|
|
30
45
|
isReady(): boolean;
|
|
31
46
|
destroy(): void;
|
|
32
47
|
}
|
|
33
|
-
export {};
|
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Lazy-module and lifecycle implementation shared by every calculator backend:
|
|
3
|
+
* concurrent initialization is deduplicated, and `destroy()` cancels one in
|
|
4
|
+
* flight.
|
|
4
5
|
*/
|
|
5
6
|
export class LazyCalculatorToolProvider {
|
|
7
|
+
providerName;
|
|
6
8
|
category = "calculator";
|
|
9
|
+
version;
|
|
10
|
+
requiresAuth;
|
|
11
|
+
definition;
|
|
7
12
|
calculatorProvider = null;
|
|
8
13
|
initializationPromise = null;
|
|
9
14
|
lifecycleGeneration = 0;
|
|
15
|
+
constructor(definition) {
|
|
16
|
+
this.definition = definition;
|
|
17
|
+
this.providerName = definition.providerName;
|
|
18
|
+
this.version = definition.version;
|
|
19
|
+
this.requiresAuth = definition.requiresAuth;
|
|
20
|
+
}
|
|
10
21
|
async emitTelemetry(config, eventName, payload) {
|
|
11
22
|
try {
|
|
12
23
|
await config.onTelemetry?.(eventName, payload);
|
|
@@ -33,33 +44,34 @@ export class LazyCalculatorToolProvider {
|
|
|
33
44
|
}
|
|
34
45
|
}
|
|
35
46
|
async initializeProvider(config, generation) {
|
|
36
|
-
const
|
|
47
|
+
const { backend, initializationErrorMessage } = this.definition;
|
|
48
|
+
const operation = `${backend}-provider-module-import`;
|
|
37
49
|
const moduleLoadStartedAt = Date.now();
|
|
38
50
|
await this.emitTelemetry(config, "pie-tool-library-load-start", {
|
|
39
51
|
toolId: "calculator",
|
|
40
|
-
operation
|
|
41
|
-
backend
|
|
52
|
+
operation,
|
|
53
|
+
backend,
|
|
42
54
|
});
|
|
43
55
|
let ProviderConstructor;
|
|
44
56
|
try {
|
|
45
|
-
ProviderConstructor = await definition.
|
|
57
|
+
ProviderConstructor = await this.definition.load();
|
|
46
58
|
await this.emitTelemetry(config, "pie-tool-library-load-success", {
|
|
47
59
|
toolId: "calculator",
|
|
48
|
-
operation
|
|
49
|
-
backend
|
|
60
|
+
operation,
|
|
61
|
+
backend,
|
|
50
62
|
duration: Date.now() - moduleLoadStartedAt,
|
|
51
63
|
});
|
|
52
64
|
}
|
|
53
65
|
catch (error) {
|
|
54
66
|
await this.emitTelemetry(config, "pie-tool-library-load-error", {
|
|
55
67
|
toolId: "calculator",
|
|
56
|
-
operation
|
|
57
|
-
backend
|
|
68
|
+
operation,
|
|
69
|
+
backend,
|
|
58
70
|
duration: Date.now() - moduleLoadStartedAt,
|
|
59
71
|
errorType: "ToolLibraryLoadError",
|
|
60
72
|
message: error instanceof Error ? error.message : String(error),
|
|
61
73
|
});
|
|
62
|
-
throw new Error(
|
|
74
|
+
throw new Error(initializationErrorMessage, { cause: error });
|
|
63
75
|
}
|
|
64
76
|
if (generation !== this.lifecycleGeneration) {
|
|
65
77
|
throw new Error(`${this.providerName} initialization was cancelled`);
|
|
@@ -74,7 +86,7 @@ export class LazyCalculatorToolProvider {
|
|
|
74
86
|
}
|
|
75
87
|
catch (error) {
|
|
76
88
|
candidate.destroy();
|
|
77
|
-
throw new Error(
|
|
89
|
+
throw new Error(initializationErrorMessage, { cause: error });
|
|
78
90
|
}
|
|
79
91
|
}
|
|
80
92
|
async createInstance() {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The calculator backends of the packaged set, by `provider.id`. Every import
|
|
3
|
+
* named here lands in a host bundle that inlines dynamic imports, so only thin
|
|
4
|
+
* adapters over a vendor-hosted engine belong in it.
|
|
5
|
+
*/
|
|
6
|
+
export const DEFAULT_CALCULATOR_PROVIDER_ID = "calculator-desmos";
|
|
7
|
+
export const PACKAGED_CALCULATOR_PROVIDERS = {
|
|
8
|
+
"calculator-desmos": {
|
|
9
|
+
providerName: "Desmos Calculator",
|
|
10
|
+
version: "1.12",
|
|
11
|
+
requiresAuth: true,
|
|
12
|
+
backend: "desmos",
|
|
13
|
+
load: async () => (await import("@pie-players/pie-calculator-desmos"))
|
|
14
|
+
.DesmosCalculatorProvider,
|
|
15
|
+
initializationErrorMessage: "Failed to initialize Desmos calculator provider. Check the application key, preloaded API, runtime endpoint, or network access.",
|
|
16
|
+
},
|
|
17
|
+
"calculator-geogebra": {
|
|
18
|
+
providerName: "GeoGebra Calculator",
|
|
19
|
+
version: "6",
|
|
20
|
+
requiresAuth: false,
|
|
21
|
+
backend: "geogebra",
|
|
22
|
+
load: async () => (await import("@pie-players/pie-calculator-geogebra"))
|
|
23
|
+
.GeoGebraCalculatorProvider,
|
|
24
|
+
initializationErrorMessage: "Failed to initialize GeoGebra calculator provider. Confirm that the deployment may load GeoGebra and that its script URL is reachable.",
|
|
25
|
+
},
|
|
26
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
export type { PackagedToolRegistryOptions } from "./packaged-capability-composition.js";
|
|
2
2
|
export { createEmptyPersonalNeedsProfile, createPackagedToolRegistry, createUniversalPersonalNeedsProfile, DEFAULT_TOOL_MODULE_LOADERS, PACKAGED_TOOL_REGISTRATIONS, PACKAGED_TOOL_TAG_MAP, SECTION_PLAYER_PREFERRED_TOOL_PLACEMENT, } from "./packaged-capability-composition.js";
|
|
3
|
+
export { type AfaFeatureSet, type AfaPnpRecord, type AfaSpoken, type AfaZoomAmount, createPersonalNeedsProfileFromAfa, } from "./afa-profile.js";
|
|
3
4
|
export { CONTENT_ALTERNATE_REGISTRATIONS } from "./content-alternates.js";
|
|
4
|
-
export { annotationToolbarRegistration, lineReaderToolRegistration, themeToolRegistration, } from "./registrations/accessibility-tools.js";
|
|
5
|
-
export { AUDIO_TRANSCRIPT_FEATURE_ID, AUDIO_TRANSCRIPT_REGION_CLASS, AUDIO_TRANSCRIPT_REGION_LABEL, audioTranscriptRegistration,
|
|
6
|
-
export {
|
|
5
|
+
export { annotationToolbarRegistration, lineReaderToolRegistration, magnificationToolRegistration, themeToolRegistration, } from "./registrations/accessibility-tools.js";
|
|
6
|
+
export { AUDIO_TRANSCRIPT_FEATURE_ID, AUDIO_TRANSCRIPT_REGION_CLASS, AUDIO_TRANSCRIPT_REGION_LABEL, audioTranscriptRegistration, resolveAudioTranscript, type ResolvedAudioTranscript, } from "./registrations/audio-transcript.js";
|
|
7
|
+
export { CONTENT_LEAD_SURFACE } from "@pie-players/pie-assessment-toolkit/tools/registration";
|
|
8
|
+
export { type CalculatorProviderDefinition, type CalculatorRegistrationOptions, calculatorToolRegistration, createCalculatorToolRegistration, } from "./registrations/calculator.js";
|
|
7
9
|
export { createDictionaryToolRegistration, createPictureDictionaryToolRegistration, dictionaryToolRegistration, type DictionaryVariantOptions, pictureDictionaryToolRegistration, spanishDictionaryToolRegistration, spanishPictureDictionaryToolRegistration, } from "./registrations/dictionary-tools.js";
|
|
8
|
-
export { answerEliminatorToolRegistration } from "./registrations/interaction-tools.js";
|
|
10
|
+
export { type AnswerEliminatorToolConfig, answerEliminatorToolRegistration, } from "./registrations/interaction-tools.js";
|
|
9
11
|
export { protractorToolRegistration, rulerToolRegistration, } from "./registrations/measurement-tools.js";
|
|
10
12
|
export { graphToolRegistration, periodicTableToolRegistration, } from "./registrations/subject-specific-tools.js";
|
|
11
|
-
export { ttsToolRegistration } from "./registrations/tts.js";
|
|
13
|
+
export { createTTSToolRegistration, type TTSHostProviderDescriptor, type TTSRegistrationOptions, ttsToolRegistration, } from "./registrations/tts.js";
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
export { createEmptyPersonalNeedsProfile, createPackagedToolRegistry, createUniversalPersonalNeedsProfile, DEFAULT_TOOL_MODULE_LOADERS, PACKAGED_TOOL_REGISTRATIONS, PACKAGED_TOOL_TAG_MAP, SECTION_PLAYER_PREFERRED_TOOL_PLACEMENT, } from "./packaged-capability-composition.js";
|
|
2
|
+
// A profile from an AfA PNP 3.0 record, in the packaged set's support ids.
|
|
3
|
+
export { createPersonalNeedsProfileFromAfa, } from "./afa-profile.js";
|
|
2
4
|
// The authored-alternate subset, for a renderer that shows alternates and no
|
|
3
5
|
// toolbar — importable without the packaged composition's tool loaders.
|
|
4
6
|
export { CONTENT_ALTERNATE_REGISTRATIONS } from "./content-alternates.js";
|
|
5
7
|
// Individual registrations, for a host composing a subset rather than the whole
|
|
6
8
|
// packaged set.
|
|
7
|
-
export { annotationToolbarRegistration, lineReaderToolRegistration, themeToolRegistration, } from "./registrations/accessibility-tools.js";
|
|
8
|
-
export { AUDIO_TRANSCRIPT_FEATURE_ID, AUDIO_TRANSCRIPT_REGION_CLASS, AUDIO_TRANSCRIPT_REGION_LABEL, audioTranscriptRegistration,
|
|
9
|
-
|
|
9
|
+
export { annotationToolbarRegistration, lineReaderToolRegistration, magnificationToolRegistration, themeToolRegistration, } from "./registrations/accessibility-tools.js";
|
|
10
|
+
export { AUDIO_TRANSCRIPT_FEATURE_ID, AUDIO_TRANSCRIPT_REGION_CLASS, AUDIO_TRANSCRIPT_REGION_LABEL, audioTranscriptRegistration, resolveAudioTranscript, } from "./registrations/audio-transcript.js";
|
|
11
|
+
// The canonical surface ids live in the toolkit; re-exported so the packaged
|
|
12
|
+
// set's surface stays unchanged.
|
|
13
|
+
export { CONTENT_LEAD_SURFACE } from "@pie-players/pie-assessment-toolkit/tools/registration";
|
|
14
|
+
export { calculatorToolRegistration, createCalculatorToolRegistration, } from "./registrations/calculator.js";
|
|
10
15
|
export { createDictionaryToolRegistration, createPictureDictionaryToolRegistration, dictionaryToolRegistration, pictureDictionaryToolRegistration, spanishDictionaryToolRegistration, spanishPictureDictionaryToolRegistration, } from "./registrations/dictionary-tools.js";
|
|
11
|
-
export { answerEliminatorToolRegistration } from "./registrations/interaction-tools.js";
|
|
16
|
+
export { answerEliminatorToolRegistration, } from "./registrations/interaction-tools.js";
|
|
12
17
|
export { protractorToolRegistration, rulerToolRegistration, } from "./registrations/measurement-tools.js";
|
|
13
18
|
export { graphToolRegistration, periodicTableToolRegistration, } from "./registrations/subject-specific-tools.js";
|
|
14
|
-
export { ttsToolRegistration } from "./registrations/tts.js";
|
|
19
|
+
export { createTTSToolRegistration, ttsToolRegistration, } from "./registrations/tts.js";
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* cannot leave a second hand-maintained catalogue silently behind.
|
|
9
9
|
*/
|
|
10
10
|
import { ToolRegistry, } from "@pie-players/pie-assessment-toolkit/tools/registration";
|
|
11
|
-
import { annotationToolbarRegistration, lineReaderToolRegistration, themeToolRegistration, } from "./registrations/accessibility-tools.js";
|
|
11
|
+
import { annotationToolbarRegistration, lineReaderToolRegistration, magnificationToolRegistration, themeToolRegistration, } from "./registrations/accessibility-tools.js";
|
|
12
12
|
import { audioTranscriptRegistration } from "./registrations/audio-transcript.js";
|
|
13
13
|
import { calculatorToolRegistration } from "./registrations/calculator.js";
|
|
14
14
|
import { dictionaryToolRegistration, pictureDictionaryToolRegistration, spanishDictionaryToolRegistration, spanishPictureDictionaryToolRegistration, } from "./registrations/dictionary-tools.js";
|
|
@@ -28,6 +28,7 @@ const loadProtractorModule = () => loadSideEffectModule(() => import("@pie-playe
|
|
|
28
28
|
const loadAnswerEliminatorModule = () => loadSideEffectModule(() => import("@pie-players/pie-tool-answer-eliminator"));
|
|
29
29
|
const loadAnnotationToolbarModule = () => loadSideEffectModule(() => import("@pie-players/pie-tool-annotation-toolbar"));
|
|
30
30
|
const loadLineReaderModule = () => loadSideEffectModule(() => import("@pie-players/pie-tool-line-reader"));
|
|
31
|
+
const loadMagnifierModule = () => loadSideEffectModule(() => import("@pie-players/pie-tool-magnifier"));
|
|
31
32
|
const loadThemeModule = () => loadSideEffectModule(() => import("@pie-players/pie-tool-theme"));
|
|
32
33
|
const loadGraphModule = () => loadSideEffectModule(() => import("@pie-players/pie-tool-graph"));
|
|
33
34
|
const loadPeriodicTableModule = () => loadSideEffectModule(() => import("@pie-players/pie-tool-periodic-table"));
|
|
@@ -76,6 +77,15 @@ const PACKAGED_CAPABILITY_DEFINITIONS = [
|
|
|
76
77
|
preferredPlacementOrder: { section: 40 },
|
|
77
78
|
universal: true,
|
|
78
79
|
},
|
|
80
|
+
{
|
|
81
|
+
registration: magnificationToolRegistration,
|
|
82
|
+
tagName: "pie-tool-magnifier",
|
|
83
|
+
loadModule: loadMagnifierModule,
|
|
84
|
+
preferredPlacementOrder: { section: 45 },
|
|
85
|
+
// Magnification is a granted low-vision accommodation in AfA PNP, so it
|
|
86
|
+
// is never universal.
|
|
87
|
+
universal: false,
|
|
88
|
+
},
|
|
79
89
|
{
|
|
80
90
|
registration: themeToolRegistration,
|
|
81
91
|
tagName: "pie-tool-theme",
|