@pie-players/pie-assessment-toolkit 0.2.0
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 +520 -0
- package/dist/attempt/TestSession.d.ts +90 -0
- package/dist/attempt/TestSession.d.ts.map +1 -0
- package/dist/attempt/TestSession.js +174 -0
- package/dist/attempt/TestSession.js.map +1 -0
- package/dist/core/TypedEventBus.d.ts +48 -0
- package/dist/core/TypedEventBus.d.ts.map +1 -0
- package/dist/core/TypedEventBus.js +63 -0
- package/dist/core/TypedEventBus.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -0
- package/dist/item-loader.d.ts +45 -0
- package/dist/item-loader.d.ts.map +1 -0
- package/dist/item-loader.js +62 -0
- package/dist/item-loader.js.map +1 -0
- package/dist/player/AssessmentPlayer.d.ts +388 -0
- package/dist/player/AssessmentPlayer.d.ts.map +1 -0
- package/dist/player/AssessmentPlayer.js +965 -0
- package/dist/player/AssessmentPlayer.js.map +1 -0
- package/dist/player/index.d.ts +17 -0
- package/dist/player/index.d.ts.map +1 -0
- package/dist/player/index.js +15 -0
- package/dist/player/index.js.map +1 -0
- package/dist/player/navigation-types.d.ts +63 -0
- package/dist/player/navigation-types.d.ts.map +1 -0
- package/dist/player/navigation-types.js +7 -0
- package/dist/player/navigation-types.js.map +1 -0
- package/dist/player/qti-navigation.d.ts +29 -0
- package/dist/player/qti-navigation.d.ts.map +1 -0
- package/dist/player/qti-navigation.js +160 -0
- package/dist/player/qti-navigation.js.map +1 -0
- package/dist/reference-layout/index.d.ts +19 -0
- package/dist/reference-layout/index.d.ts.map +1 -0
- package/dist/reference-layout/index.js +20 -0
- package/dist/reference-layout/index.js.map +1 -0
- package/dist/services/AccessibilityCatalogResolver.d.ts +141 -0
- package/dist/services/AccessibilityCatalogResolver.d.ts.map +1 -0
- package/dist/services/AccessibilityCatalogResolver.js +249 -0
- package/dist/services/AccessibilityCatalogResolver.js.map +1 -0
- package/dist/services/AssessmentAuthoringService.d.ts +60 -0
- package/dist/services/AssessmentAuthoringService.d.ts.map +1 -0
- package/dist/services/AssessmentAuthoringService.js +183 -0
- package/dist/services/AssessmentAuthoringService.js.map +1 -0
- package/dist/services/ContextVariableStore.d.ts +114 -0
- package/dist/services/ContextVariableStore.d.ts.map +1 -0
- package/dist/services/ContextVariableStore.js +207 -0
- package/dist/services/ContextVariableStore.js.map +1 -0
- package/dist/services/ElementToolStateStore.d.ts +125 -0
- package/dist/services/ElementToolStateStore.d.ts.map +1 -0
- package/dist/services/ElementToolStateStore.js +200 -0
- package/dist/services/ElementToolStateStore.js.map +1 -0
- package/dist/services/HighlightCoordinator.d.ts +179 -0
- package/dist/services/HighlightCoordinator.d.ts.map +1 -0
- package/dist/services/HighlightCoordinator.js +446 -0
- package/dist/services/HighlightCoordinator.js.map +1 -0
- package/dist/services/I18nService.d.ts +121 -0
- package/dist/services/I18nService.d.ts.map +1 -0
- package/dist/services/I18nService.js +276 -0
- package/dist/services/I18nService.js.map +1 -0
- package/dist/services/PNPMapper.d.ts +71 -0
- package/dist/services/PNPMapper.d.ts.map +1 -0
- package/dist/services/PNPMapper.js +98 -0
- package/dist/services/PNPMapper.js.map +1 -0
- package/dist/services/PNPToolResolver.d.ts +127 -0
- package/dist/services/PNPToolResolver.d.ts.map +1 -0
- package/dist/services/PNPToolResolver.js +222 -0
- package/dist/services/PNPToolResolver.js.map +1 -0
- package/dist/services/SSMLExtractor.d.ts +61 -0
- package/dist/services/SSMLExtractor.d.ts.map +1 -0
- package/dist/services/SSMLExtractor.js +181 -0
- package/dist/services/SSMLExtractor.js.map +1 -0
- package/dist/services/TTSService.d.ts +162 -0
- package/dist/services/TTSService.d.ts.map +1 -0
- package/dist/services/TTSService.js +465 -0
- package/dist/services/TTSService.js.map +1 -0
- package/dist/services/ThemeProvider.d.ts +95 -0
- package/dist/services/ThemeProvider.d.ts.map +1 -0
- package/dist/services/ThemeProvider.js +299 -0
- package/dist/services/ThemeProvider.js.map +1 -0
- package/dist/services/ToolConfigResolver.d.ts +162 -0
- package/dist/services/ToolConfigResolver.d.ts.map +1 -0
- package/dist/services/ToolConfigResolver.js +230 -0
- package/dist/services/ToolConfigResolver.js.map +1 -0
- package/dist/services/ToolCoordinator.d.ts +137 -0
- package/dist/services/ToolCoordinator.d.ts.map +1 -0
- package/dist/services/ToolCoordinator.js +318 -0
- package/dist/services/ToolCoordinator.js.map +1 -0
- package/dist/services/ToolkitCoordinator.d.ts +188 -0
- package/dist/services/ToolkitCoordinator.d.ts.map +1 -0
- package/dist/services/ToolkitCoordinator.js +252 -0
- package/dist/services/ToolkitCoordinator.js.map +1 -0
- package/dist/services/interfaces.d.ts +391 -0
- package/dist/services/interfaces.d.ts.map +1 -0
- package/dist/services/interfaces.js +12 -0
- package/dist/services/interfaces.js.map +1 -0
- package/dist/services/tool-providers/DesmosToolProvider.d.ts +103 -0
- package/dist/services/tool-providers/DesmosToolProvider.d.ts.map +1 -0
- package/dist/services/tool-providers/DesmosToolProvider.js +127 -0
- package/dist/services/tool-providers/DesmosToolProvider.js.map +1 -0
- package/dist/services/tool-providers/IToolProvider.d.ts +137 -0
- package/dist/services/tool-providers/IToolProvider.d.ts.map +1 -0
- package/dist/services/tool-providers/IToolProvider.js +13 -0
- package/dist/services/tool-providers/IToolProvider.js.map +1 -0
- package/dist/services/tool-providers/TIToolProvider.d.ts +107 -0
- package/dist/services/tool-providers/TIToolProvider.d.ts.map +1 -0
- package/dist/services/tool-providers/TIToolProvider.js +123 -0
- package/dist/services/tool-providers/TIToolProvider.js.map +1 -0
- package/dist/services/tool-providers/TTSToolProvider.d.ts +143 -0
- package/dist/services/tool-providers/TTSToolProvider.d.ts.map +1 -0
- package/dist/services/tool-providers/TTSToolProvider.js +168 -0
- package/dist/services/tool-providers/TTSToolProvider.js.map +1 -0
- package/dist/services/tool-providers/ToolProviderRegistry.d.ts +165 -0
- package/dist/services/tool-providers/ToolProviderRegistry.d.ts.map +1 -0
- package/dist/services/tool-providers/ToolProviderRegistry.js +240 -0
- package/dist/services/tool-providers/ToolProviderRegistry.js.map +1 -0
- package/dist/services/tool-providers/index.d.ts +18 -0
- package/dist/services/tool-providers/index.d.ts.map +1 -0
- package/dist/services/tool-providers/index.js +15 -0
- package/dist/services/tool-providers/index.js.map +1 -0
- package/dist/services/tts/browser-provider.d.ts +24 -0
- package/dist/services/tts/browser-provider.d.ts.map +1 -0
- package/dist/services/tts/browser-provider.js +177 -0
- package/dist/services/tts/browser-provider.js.map +1 -0
- package/dist/services/tts/provider-interface.d.ts +131 -0
- package/dist/services/tts/provider-interface.d.ts.map +1 -0
- package/dist/services/tts/provider-interface.js +10 -0
- package/dist/services/tts/provider-interface.js.map +1 -0
- package/dist/tools/calculators/desmos-provider.d.ts +54 -0
- package/dist/tools/calculators/desmos-provider.d.ts.map +1 -0
- package/dist/tools/calculators/desmos-provider.js +384 -0
- package/dist/tools/calculators/desmos-provider.js.map +1 -0
- package/dist/tools/calculators/mathjs-provider.d.ts +50 -0
- package/dist/tools/calculators/mathjs-provider.d.ts.map +1 -0
- package/dist/tools/calculators/mathjs-provider.js +660 -0
- package/dist/tools/calculators/mathjs-provider.js.map +1 -0
- package/dist/tools/calculators/ti-provider.d.ts +122 -0
- package/dist/tools/calculators/ti-provider.d.ts.map +1 -0
- package/dist/tools/calculators/ti-provider.js +587 -0
- package/dist/tools/calculators/ti-provider.js.map +1 -0
- package/dist/tools/client.d.ts +17 -0
- package/dist/tools/client.d.ts.map +1 -0
- package/dist/tools/client.js +26 -0
- package/dist/tools/client.js.map +1 -0
- package/dist/tools/index.d.ts +8 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +10 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/library-loader.d.ts +63 -0
- package/dist/tools/library-loader.d.ts.map +1 -0
- package/dist/tools/library-loader.js +292 -0
- package/dist/tools/library-loader.js.map +1 -0
- package/dist/tools/response-discovery.d.ts +72 -0
- package/dist/tools/response-discovery.d.ts.map +1 -0
- package/dist/tools/response-discovery.js +183 -0
- package/dist/tools/response-discovery.js.map +1 -0
- package/dist/tools/tool-coordinator.d.ts +76 -0
- package/dist/tools/tool-coordinator.d.ts.map +1 -0
- package/dist/tools/tool-coordinator.js +197 -0
- package/dist/tools/tool-coordinator.js.map +1 -0
- package/dist/tools/types.d.ts +427 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +13 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/tools/variant-resolver.d.ts +48 -0
- package/dist/tools/variant-resolver.d.ts.map +1 -0
- package/dist/tools/variant-resolver.js +214 -0
- package/dist/tools/variant-resolver.js.map +1 -0
- package/dist/types/events.d.ts +157 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/events.js +11 -0
- package/dist/types/events.js.map +1 -0
- package/dist/utils/logger.d.ts +7 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +11 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +71 -0
- package/src/README.md +626 -0
- package/src/components/QuestionToolBar.svelte +406 -0
- package/src/player/AssessmentLayout.svelte +68 -0
- package/src/player/README.md +154 -0
- package/src/reference-layout/README.md +135 -0
- package/src/reference-layout/ReferenceLayout.svelte +209 -0
- package/src/reference-layout/components/AssessmentContent.svelte +259 -0
- package/src/reference-layout/components/AssessmentFooter.svelte +89 -0
- package/src/reference-layout/components/AssessmentHeader.svelte +250 -0
- package/src/reference-layout/components/AssessmentNavigation.svelte +134 -0
- package/src/reference-layout/components/AssessmentToolsBar.svelte +176 -0
- package/src/reference-layout/components/ItemPanel.svelte +200 -0
- package/src/reference-layout/components/NotesPanel.svelte +179 -0
- package/src/reference-layout/components/PassagePanel.svelte +76 -0
- package/src/tools/README.md +616 -0
- package/src/tools/calculators/README.md +395 -0
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
<svelte:options
|
|
2
|
+
customElement={{
|
|
3
|
+
tag: 'pie-question-toolbar',
|
|
4
|
+
shadow: 'none',
|
|
5
|
+
props: {
|
|
6
|
+
itemId: { type: 'String', attribute: 'item-id' },
|
|
7
|
+
catalogId: { type: 'String', attribute: 'catalog-id' },
|
|
8
|
+
tools: { type: 'String', attribute: 'tools' },
|
|
9
|
+
size: { type: 'String', attribute: 'size' },
|
|
10
|
+
language: { type: 'String', attribute: 'language' },
|
|
11
|
+
|
|
12
|
+
// Services (passed as JS properties, not attributes)
|
|
13
|
+
ttsService: { type: 'Object', reflect: false },
|
|
14
|
+
toolCoordinator: { type: 'Object', reflect: false },
|
|
15
|
+
highlightCoordinator: { type: 'Object', reflect: false },
|
|
16
|
+
scopeElement: { type: 'Object', reflect: false },
|
|
17
|
+
elementToolStateStore: { type: 'Object', reflect: false },
|
|
18
|
+
|
|
19
|
+
// IDs for element-level state (passed as JS properties, not attributes)
|
|
20
|
+
assessmentId: { type: 'String', reflect: false },
|
|
21
|
+
sectionId: { type: 'String', reflect: false }
|
|
22
|
+
}
|
|
23
|
+
}}
|
|
24
|
+
/>
|
|
25
|
+
|
|
26
|
+
<!--
|
|
27
|
+
QuestionToolBar - Inline toolbar for question/passage headers
|
|
28
|
+
|
|
29
|
+
Displays tool buttons (TTS, Answer Eliminator, etc.) in a compact,
|
|
30
|
+
horizontal layout suitable for embedding in question headers.
|
|
31
|
+
|
|
32
|
+
Similar to the SchoolCity pattern where tool buttons appear next to
|
|
33
|
+
question titles rather than in a global sidebar.
|
|
34
|
+
-->
|
|
35
|
+
<script lang="ts">
|
|
36
|
+
import type {
|
|
37
|
+
IToolCoordinator,
|
|
38
|
+
ITTSService,
|
|
39
|
+
IHighlightCoordinator,
|
|
40
|
+
} from '../services/interfaces';
|
|
41
|
+
|
|
42
|
+
// NOTE: Tool web components must be imported by parent components
|
|
43
|
+
// that use QuestionToolBar (to avoid circular dependencies).
|
|
44
|
+
// Parent components should:
|
|
45
|
+
// import '@pie-players/pie-tool-answer-eliminator';
|
|
46
|
+
// import '@pie-players/pie-tool-tts-inline';
|
|
47
|
+
// import '@pie-players/pie-tool-calculator-inline';
|
|
48
|
+
|
|
49
|
+
const isBrowser = typeof window !== 'undefined';
|
|
50
|
+
|
|
51
|
+
// Track if tools have been loaded
|
|
52
|
+
let toolsLoaded = $state(false);
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Detect if the scoped element contains compatible answer choices for the answer eliminator.
|
|
56
|
+
* Checks for PIE multiple-choice, EBSR, and inline-dropdown elements.
|
|
57
|
+
*/
|
|
58
|
+
function hasCompatibleAnswerChoices(element: HTMLElement): boolean {
|
|
59
|
+
// Check for multiple-choice elements
|
|
60
|
+
const hasMultipleChoice = element.querySelector('.corespring-checkbox, .corespring-radio-button, .multiple-choice');
|
|
61
|
+
if (hasMultipleChoice) return true;
|
|
62
|
+
|
|
63
|
+
// Check for EBSR parts (extended constructed response with selectable parts)
|
|
64
|
+
const hasEBSR = element.querySelector('[class*="ebsr"]');
|
|
65
|
+
if (hasEBSR) return true;
|
|
66
|
+
|
|
67
|
+
// Check for inline-dropdown elements
|
|
68
|
+
const hasInlineDropdown = element.querySelector('inline-dropdown, .inline-dropdown, [class*="inline-dropdown"]');
|
|
69
|
+
if (hasInlineDropdown) return true;
|
|
70
|
+
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Props
|
|
75
|
+
let {
|
|
76
|
+
itemId = '',
|
|
77
|
+
catalogId = '',
|
|
78
|
+
tools = 'calculator,tts,answerEliminator',
|
|
79
|
+
ttsService,
|
|
80
|
+
toolCoordinator,
|
|
81
|
+
highlightCoordinator,
|
|
82
|
+
scopeElement = null,
|
|
83
|
+
elementToolStateStore,
|
|
84
|
+
assessmentId = '',
|
|
85
|
+
sectionId = '',
|
|
86
|
+
class: className = '',
|
|
87
|
+
size = 'md' as 'sm' | 'md' | 'lg',
|
|
88
|
+
language = 'en-US'
|
|
89
|
+
}: {
|
|
90
|
+
itemId?: string;
|
|
91
|
+
catalogId?: string;
|
|
92
|
+
tools?: string;
|
|
93
|
+
ttsService?: ITTSService;
|
|
94
|
+
toolCoordinator?: IToolCoordinator;
|
|
95
|
+
highlightCoordinator?: IHighlightCoordinator;
|
|
96
|
+
scopeElement?: HTMLElement | null;
|
|
97
|
+
elementToolStateStore?: any;
|
|
98
|
+
assessmentId?: string;
|
|
99
|
+
sectionId?: string;
|
|
100
|
+
class?: string;
|
|
101
|
+
size?: 'sm' | 'md' | 'lg';
|
|
102
|
+
language?: string;
|
|
103
|
+
} = $props();
|
|
104
|
+
|
|
105
|
+
// Generate globalElementId using store utility
|
|
106
|
+
let globalElementId = $derived.by(() => {
|
|
107
|
+
if (!elementToolStateStore || !assessmentId || !sectionId || !itemId) return null;
|
|
108
|
+
return elementToolStateStore.getGlobalElementId(assessmentId, sectionId, itemId, itemId);
|
|
109
|
+
// Note: Using itemId as elementId since toolbar is scoped to one item/element
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Parse enabled tools
|
|
113
|
+
let enabledTools = $derived(
|
|
114
|
+
tools
|
|
115
|
+
.split(',')
|
|
116
|
+
.map((t) => t.trim())
|
|
117
|
+
.filter(Boolean),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
// Detect if current item has compatible choices for answer eliminator
|
|
121
|
+
let hasCompatibleChoices = $state(false);
|
|
122
|
+
|
|
123
|
+
$effect(() => {
|
|
124
|
+
if (!isBrowser || !scopeElement) {
|
|
125
|
+
hasCompatibleChoices = false;
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Use MutationObserver to detect when PIE elements are rendered
|
|
130
|
+
const checkChoices = () => {
|
|
131
|
+
const hasChoices = hasCompatibleAnswerChoices(scopeElement);
|
|
132
|
+
hasCompatibleChoices = hasChoices;
|
|
133
|
+
console.debug('[QuestionToolBar] Checked choices:', {
|
|
134
|
+
scopeElementTag: scopeElement.tagName,
|
|
135
|
+
hasCompatibleChoices
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// Initial check
|
|
140
|
+
checkChoices();
|
|
141
|
+
|
|
142
|
+
// Watch for DOM changes (PIE elements load asynchronously)
|
|
143
|
+
const observer = new MutationObserver(() => {
|
|
144
|
+
checkChoices();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
observer.observe(scopeElement, {
|
|
148
|
+
childList: true,
|
|
149
|
+
subtree: true
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
return () => observer.disconnect();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Dynamically load tool components based on enabled tools
|
|
156
|
+
$effect(() => {
|
|
157
|
+
if (!isBrowser || toolsLoaded) return;
|
|
158
|
+
|
|
159
|
+
(async () => {
|
|
160
|
+
const loadPromises = [];
|
|
161
|
+
|
|
162
|
+
if (enabledTools.includes('tts')) {
|
|
163
|
+
loadPromises.push(import('@pie-players/pie-tool-tts-inline'));
|
|
164
|
+
}
|
|
165
|
+
if (enabledTools.includes('answerEliminator')) {
|
|
166
|
+
loadPromises.push(import('@pie-players/pie-tool-answer-eliminator'));
|
|
167
|
+
}
|
|
168
|
+
if (enabledTools.includes('calculator')) {
|
|
169
|
+
loadPromises.push(
|
|
170
|
+
import('@pie-players/pie-tool-calculator-inline'),
|
|
171
|
+
import('@pie-players/pie-tool-calculator')
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
await Promise.all(loadPromises);
|
|
176
|
+
toolsLoaded = true;
|
|
177
|
+
})();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Should show each tool
|
|
181
|
+
let showTTS = $derived(enabledTools.includes('tts') && ttsService && toolsLoaded);
|
|
182
|
+
let showAnswerEliminator = $derived(
|
|
183
|
+
enabledTools.includes('answerEliminator') &&
|
|
184
|
+
toolCoordinator &&
|
|
185
|
+
hasCompatibleChoices &&
|
|
186
|
+
toolsLoaded
|
|
187
|
+
);
|
|
188
|
+
let showCalculator = $derived(enabledTools.includes('calculator') && toolCoordinator && toolsLoaded);
|
|
189
|
+
|
|
190
|
+
// Track answer eliminator visibility state
|
|
191
|
+
let answerEliminatorVisible = $state(false);
|
|
192
|
+
|
|
193
|
+
// Subscribe to tool coordinator to update button state
|
|
194
|
+
$effect(() => {
|
|
195
|
+
if (!isBrowser || !toolCoordinator) return;
|
|
196
|
+
|
|
197
|
+
const unsubscribe = toolCoordinator.subscribe(() => {
|
|
198
|
+
answerEliminatorVisible = toolCoordinator.isToolVisible(
|
|
199
|
+
`answerEliminator-${itemId}`,
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Initial update
|
|
204
|
+
answerEliminatorVisible = toolCoordinator.isToolVisible(
|
|
205
|
+
`answerEliminator-${itemId}`,
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
return unsubscribe;
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// TTS element reference for service binding
|
|
212
|
+
let ttsToolElement = $state<HTMLElement | null>(null);
|
|
213
|
+
let ttsBound = $state(false);
|
|
214
|
+
|
|
215
|
+
// Bind services to TTS tool
|
|
216
|
+
$effect(() => {
|
|
217
|
+
if (ttsToolElement && !ttsBound) {
|
|
218
|
+
if (toolCoordinator) {
|
|
219
|
+
(ttsToolElement as any).coordinator = toolCoordinator;
|
|
220
|
+
}
|
|
221
|
+
if (ttsService) {
|
|
222
|
+
(ttsToolElement as any).ttsService = ttsService;
|
|
223
|
+
}
|
|
224
|
+
if (highlightCoordinator) {
|
|
225
|
+
(ttsToolElement as any).highlightCoordinator = highlightCoordinator;
|
|
226
|
+
}
|
|
227
|
+
ttsBound = true;
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// Answer eliminator element reference for scope binding
|
|
232
|
+
let answerEliminatorElement = $state<HTMLElement | null>(null);
|
|
233
|
+
let eliminatorBound = $state(false);
|
|
234
|
+
|
|
235
|
+
// Bind scope, coordinator, store, and globalElementId to answer eliminator
|
|
236
|
+
$effect(() => {
|
|
237
|
+
if (answerEliminatorElement && !eliminatorBound) {
|
|
238
|
+
if (toolCoordinator) {
|
|
239
|
+
(answerEliminatorElement as any).coordinator = toolCoordinator;
|
|
240
|
+
}
|
|
241
|
+
if (scopeElement) {
|
|
242
|
+
(answerEliminatorElement as any).scopeElement = scopeElement;
|
|
243
|
+
}
|
|
244
|
+
if (elementToolStateStore && globalElementId) {
|
|
245
|
+
(answerEliminatorElement as any).elementToolStateStore = elementToolStateStore;
|
|
246
|
+
(answerEliminatorElement as any).globalElementId = globalElementId;
|
|
247
|
+
}
|
|
248
|
+
eliminatorBound = true;
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Handle answer eliminator toggle
|
|
253
|
+
function toggleAnswerEliminator() {
|
|
254
|
+
if (!toolCoordinator) return;
|
|
255
|
+
toolCoordinator.toggleTool(`answerEliminator-${itemId}`);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Calculator element reference for coordinator binding
|
|
259
|
+
let calculatorInlineElement = $state<HTMLElement | null>(null);
|
|
260
|
+
let calculatorBound = $state(false);
|
|
261
|
+
|
|
262
|
+
// Bind coordinator to calculator inline tool
|
|
263
|
+
$effect(() => {
|
|
264
|
+
if (calculatorInlineElement && !calculatorBound) {
|
|
265
|
+
if (toolCoordinator) {
|
|
266
|
+
(calculatorInlineElement as any).coordinator = toolCoordinator;
|
|
267
|
+
}
|
|
268
|
+
calculatorBound = true;
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
</script>
|
|
272
|
+
|
|
273
|
+
{#if isBrowser}
|
|
274
|
+
<div class="question-toolbar {className} question-toolbar--{size}">
|
|
275
|
+
<!-- Calculator Button (inline tool) -->
|
|
276
|
+
{#if showCalculator}
|
|
277
|
+
<pie-tool-calculator-inline
|
|
278
|
+
bind:this={calculatorInlineElement}
|
|
279
|
+
tool-id="calculator-inline-{itemId}"
|
|
280
|
+
calculator-type="scientific"
|
|
281
|
+
available-types="basic,scientific,graphing"
|
|
282
|
+
size={size}
|
|
283
|
+
></pie-tool-calculator-inline>
|
|
284
|
+
{/if}
|
|
285
|
+
|
|
286
|
+
<!-- TTS Button (inline tool) -->
|
|
287
|
+
{#if showTTS}
|
|
288
|
+
<pie-tool-tts-inline
|
|
289
|
+
bind:this={ttsToolElement}
|
|
290
|
+
tool-id="tts-{itemId}"
|
|
291
|
+
catalog-id={catalogId || itemId}
|
|
292
|
+
language={language}
|
|
293
|
+
size={size}
|
|
294
|
+
></pie-tool-tts-inline>
|
|
295
|
+
{/if}
|
|
296
|
+
|
|
297
|
+
<!-- Answer Eliminator Toggle Button -->
|
|
298
|
+
{#if showAnswerEliminator}
|
|
299
|
+
<button
|
|
300
|
+
type="button"
|
|
301
|
+
class="question-toolbar__button"
|
|
302
|
+
class:question-toolbar__button--active={answerEliminatorVisible}
|
|
303
|
+
onclick={toggleAnswerEliminator}
|
|
304
|
+
aria-label="Answer Eliminator"
|
|
305
|
+
aria-pressed={answerEliminatorVisible}
|
|
306
|
+
title="Toggle answer elimination mode"
|
|
307
|
+
>
|
|
308
|
+
<!-- X in Circle Icon (matches SchoolCity) -->
|
|
309
|
+
<svg
|
|
310
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
311
|
+
viewBox="0 0 24 24"
|
|
312
|
+
width="20"
|
|
313
|
+
height="20"
|
|
314
|
+
fill="currentColor"
|
|
315
|
+
aria-hidden="true"
|
|
316
|
+
>
|
|
317
|
+
<path
|
|
318
|
+
d="M19,3H16.3H7.7H5A2,2 0 0,0 3,5V7.7V16.4V19A2,2 0 0,0 5,21H7.7H16.4H19A2,2 0 0,0 21,19V16.3V7.7V5A2,2 0 0,0 19,3M15.6,17L12,13.4L8.4,17L7,15.6L10.6,12L7,8.4L8.4,7L12,10.6L15.6,7L17,8.4L13.4,12L17,15.6L15.6,17Z"
|
|
319
|
+
/>
|
|
320
|
+
</svg>
|
|
321
|
+
</button>
|
|
322
|
+
|
|
323
|
+
<!-- Answer Eliminator Tool Instance (hidden, manages state) -->
|
|
324
|
+
<pie-tool-answer-eliminator
|
|
325
|
+
bind:this={answerEliminatorElement}
|
|
326
|
+
visible={answerEliminatorVisible}
|
|
327
|
+
tool-id="answerEliminator-{itemId}"
|
|
328
|
+
strategy="strikethrough"
|
|
329
|
+
button-alignment="inline"
|
|
330
|
+
coordinator={toolCoordinator}
|
|
331
|
+
></pie-tool-answer-eliminator>
|
|
332
|
+
{/if}
|
|
333
|
+
</div>
|
|
334
|
+
{/if}
|
|
335
|
+
|
|
336
|
+
<style>
|
|
337
|
+
.question-toolbar {
|
|
338
|
+
display: flex;
|
|
339
|
+
align-items: center;
|
|
340
|
+
gap: 0.5rem;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.question-toolbar__button {
|
|
344
|
+
display: flex;
|
|
345
|
+
align-items: center;
|
|
346
|
+
justify-content: center;
|
|
347
|
+
width: 2rem;
|
|
348
|
+
height: 2rem;
|
|
349
|
+
padding: 0.25rem;
|
|
350
|
+
border: 1px solid var(--pie-border, #ccc);
|
|
351
|
+
border-radius: 0.25rem;
|
|
352
|
+
background-color: var(--pie-background, white);
|
|
353
|
+
color: var(--pie-text, #333);
|
|
354
|
+
cursor: pointer;
|
|
355
|
+
transition: all 0.15s ease;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
.question-toolbar--sm .question-toolbar__button {
|
|
359
|
+
width: 1.5rem;
|
|
360
|
+
height: 1.5rem;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.question-toolbar--lg .question-toolbar__button {
|
|
364
|
+
width: 2.5rem;
|
|
365
|
+
height: 2.5rem;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.question-toolbar__button:hover:not(:disabled) {
|
|
369
|
+
background-color: var(
|
|
370
|
+
--pie-secondary-background,
|
|
371
|
+
#f5f5f5
|
|
372
|
+
);
|
|
373
|
+
transform: translateY(-1px);
|
|
374
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.question-toolbar__button:active:not(:disabled) {
|
|
378
|
+
transform: translateY(0);
|
|
379
|
+
box-shadow: none;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.question-toolbar__button--active {
|
|
383
|
+
background-color: var(--pie-primary, #1976d2);
|
|
384
|
+
color: white;
|
|
385
|
+
border-color: var(--pie-primary, #1976d2);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.question-toolbar__button--active:hover:not(:disabled) {
|
|
389
|
+
background-color: var(--pie-primary-dark, #1565c0);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.question-toolbar__button:focus-visible {
|
|
393
|
+
outline: 2px solid var(--pie-primary, #1976d2);
|
|
394
|
+
outline-offset: 2px;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.question-toolbar__button:disabled {
|
|
398
|
+
opacity: 0.5;
|
|
399
|
+
cursor: not-allowed;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.question-toolbar__button svg {
|
|
403
|
+
width: 100%;
|
|
404
|
+
height: 100%;
|
|
405
|
+
}
|
|
406
|
+
</style>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { AssessmentEntity } from '@pie-players/pie-players-shared';
|
|
3
|
+
import { onDestroy, onMount } from 'svelte';
|
|
4
|
+
import { ReferenceLayout } from '../reference-layout';
|
|
5
|
+
import { AssessmentPlayer } from './AssessmentPlayer';
|
|
6
|
+
|
|
7
|
+
let {
|
|
8
|
+
assessment,
|
|
9
|
+
bundleHost,
|
|
10
|
+
organizationId,
|
|
11
|
+
userName
|
|
12
|
+
}: {
|
|
13
|
+
assessment: AssessmentEntity;
|
|
14
|
+
bundleHost?: string;
|
|
15
|
+
organizationId?: string | null;
|
|
16
|
+
userName?: string;
|
|
17
|
+
} = $props();
|
|
18
|
+
|
|
19
|
+
let player = $state<AssessmentPlayer | null>(null);
|
|
20
|
+
|
|
21
|
+
onMount(() => {
|
|
22
|
+
// Initialize reference player
|
|
23
|
+
player = new AssessmentPlayer({
|
|
24
|
+
assessment,
|
|
25
|
+
organizationId: organizationId || undefined,
|
|
26
|
+
bundleHost,
|
|
27
|
+
mode: 'gather',
|
|
28
|
+
role: 'student',
|
|
29
|
+
enableTTS: true,
|
|
30
|
+
theme: {
|
|
31
|
+
highContrast: false,
|
|
32
|
+
fontSize: 'normal'
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Start at first item
|
|
37
|
+
player.start();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
onDestroy(() => {
|
|
41
|
+
player?.destroy();
|
|
42
|
+
});
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
{#if player}
|
|
46
|
+
<ReferenceLayout {player} {assessment} {bundleHost} {organizationId} {userName} />
|
|
47
|
+
{:else}
|
|
48
|
+
<div class="loading-container">
|
|
49
|
+
<span class="loading loading-spinner loading-lg"></span>
|
|
50
|
+
<p>Loading assessment...</p>
|
|
51
|
+
</div>
|
|
52
|
+
{/if}
|
|
53
|
+
|
|
54
|
+
<style>
|
|
55
|
+
.loading-container {
|
|
56
|
+
display: flex;
|
|
57
|
+
flex-direction: column;
|
|
58
|
+
align-items: center;
|
|
59
|
+
justify-content: center;
|
|
60
|
+
height: 100vh;
|
|
61
|
+
gap: 1rem;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.loading-container p {
|
|
65
|
+
color: var(--pie-text-secondary, #666);
|
|
66
|
+
}
|
|
67
|
+
</style>
|
|
68
|
+
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# PIE Assessment Toolkit - Assessment Player
|
|
2
|
+
|
|
3
|
+
This directory contains an **optional assessment player implementation** showing how to wire toolkit services together.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
- **Learning**: See how services work together
|
|
8
|
+
- **Quick Start**: Products can use as-is or extend
|
|
9
|
+
- **Customization**: Extend or copy patterns
|
|
10
|
+
|
|
11
|
+
Products like Quiz Engine can ignore this and use toolkit services directly.
|
|
12
|
+
|
|
13
|
+
## What's Included
|
|
14
|
+
|
|
15
|
+
- **AssessmentPlayer** - Core player class with navigation and state management
|
|
16
|
+
- **ReferenceAssessmentLayout** - Complete assessment UI using reference layout
|
|
17
|
+
- Linear navigation (sequential items)
|
|
18
|
+
- Basic session state management
|
|
19
|
+
- Event wiring examples
|
|
20
|
+
|
|
21
|
+
## Architecture
|
|
22
|
+
|
|
23
|
+
The default implementation consists of two parts:
|
|
24
|
+
|
|
25
|
+
1. **AssessmentPlayer** (`AssessmentPlayer.ts`)
|
|
26
|
+
- Core business logic
|
|
27
|
+
- Navigation and state management
|
|
28
|
+
- Integration with assessment toolkit services
|
|
29
|
+
- Framework-agnostic (plain TypeScript class)
|
|
30
|
+
|
|
31
|
+
2. **ReferenceAssessmentLayout** (TBD)
|
|
32
|
+
- Visual presentation layer
|
|
33
|
+
- Uses `ReferenceLayout` from `../reference-layout`
|
|
34
|
+
- Wires AssessmentPlayer to visual components
|
|
35
|
+
- Demonstrates complete assessment UI
|
|
36
|
+
|
|
37
|
+
This separation allows:
|
|
38
|
+
- **AssessmentPlayer** to be used with any layout
|
|
39
|
+
- **ReferenceLayout** to be used independently or with other players
|
|
40
|
+
- Products to mix and match components as needed
|
|
41
|
+
|
|
42
|
+
## What's Not Included (Product-Specific)
|
|
43
|
+
|
|
44
|
+
- Backend integration (each product different)
|
|
45
|
+
- Adaptive navigation algorithms
|
|
46
|
+
- Branching logic
|
|
47
|
+
- Scoring/grading
|
|
48
|
+
- LTI integration
|
|
49
|
+
- Authentication/authorization
|
|
50
|
+
|
|
51
|
+
## Usage
|
|
52
|
+
|
|
53
|
+
### Using ReferenceAssessmentLayout (Recommended)
|
|
54
|
+
|
|
55
|
+
The simplest way to get a complete assessment UI:
|
|
56
|
+
|
|
57
|
+
```svelte
|
|
58
|
+
<script>
|
|
59
|
+
import { ReferenceAssessmentLayout } from '@pie-framework/pie-assessment-toolkit/assessment-player/Layout.svelte';
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
<ReferenceAssessmentLayout
|
|
63
|
+
{assessment}
|
|
64
|
+
{organizationId}
|
|
65
|
+
bundleHost="prod"
|
|
66
|
+
/>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Using AssessmentPlayer Directly
|
|
70
|
+
|
|
71
|
+
If you want to use your own layout:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { AssessmentPlayer } from '@pie-framework/pie-assessment-toolkit/assessment-player';
|
|
75
|
+
|
|
76
|
+
const player = new AssessmentPlayer({
|
|
77
|
+
assessment,
|
|
78
|
+
organizationId,
|
|
79
|
+
bundleHost,
|
|
80
|
+
mode: 'gather',
|
|
81
|
+
role: 'student',
|
|
82
|
+
// ...
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Subscribe to state changes
|
|
86
|
+
player.onNavigationChange((state) => {
|
|
87
|
+
// Handle navigation state
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
player.onItemChange((item) => {
|
|
91
|
+
// Handle item changes
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Navigate to first item
|
|
95
|
+
await player.start();
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Using ReferenceLayout Directly
|
|
99
|
+
|
|
100
|
+
If you want to use the reference layout with a different player:
|
|
101
|
+
|
|
102
|
+
```svelte
|
|
103
|
+
<script>
|
|
104
|
+
import { ReferenceLayout } from '$lib/assessment-toolkit/reference-layout';
|
|
105
|
+
// ... create your own player instance
|
|
106
|
+
</script>
|
|
107
|
+
|
|
108
|
+
<ReferenceLayout {player} {assessment} />
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Using Services Directly
|
|
112
|
+
|
|
113
|
+
If you want complete control:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import { TypedEventBus, ToolCoordinator, TTSService } from '$lib/assessment-toolkit';
|
|
117
|
+
|
|
118
|
+
// Wire up services yourself
|
|
119
|
+
const eventBus = new TypedEventBus();
|
|
120
|
+
const toolCoordinator = new ToolCoordinator();
|
|
121
|
+
const ttsService = TTSService.getInstance();
|
|
122
|
+
// ...
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Directory Structure
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
reference-implementation/
|
|
129
|
+
├── index.ts # Exports AssessmentPlayer and layout
|
|
130
|
+
├── AssessmentPlayer.ts # Core player class (business logic)
|
|
131
|
+
├── ReferenceAssessmentLayout.svelte # Complete UI wrapper (TBD)
|
|
132
|
+
└── README.md # This file
|
|
133
|
+
|
|
134
|
+
reference-layout/ # Reference layout implementation
|
|
135
|
+
├── ReferenceLayout.svelte # Main layout component
|
|
136
|
+
├── components/ # Sub-components
|
|
137
|
+
└── README.md # Layout-specific docs
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Status
|
|
141
|
+
|
|
142
|
+
🚧 **Under development** - Implementation in progress.
|
|
143
|
+
|
|
144
|
+
### Completed
|
|
145
|
+
- ✅ AssessmentPlayer class
|
|
146
|
+
- ✅ Navigation and state management
|
|
147
|
+
- ✅ Integration with assessment toolkit services
|
|
148
|
+
|
|
149
|
+
### In Progress
|
|
150
|
+
- 🚧 ReferenceAssessmentLayout wrapper
|
|
151
|
+
- 🚧 ReferenceLayout implementation
|
|
152
|
+
- 🚧 Visual components
|
|
153
|
+
|
|
154
|
+
See [../reference-layout/LAYOUT-PLAN.md](../reference-layout/LAYOUT-PLAN.md) for detailed implementation plan.
|