@pie-players/pie-assessment-toolkit 0.2.7 → 0.2.9

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.
Files changed (220) hide show
  1. package/README.md +200 -67
  2. package/dist/attempt/TestSession.d.ts +23 -21
  3. package/dist/attempt/TestSession.d.ts.map +1 -1
  4. package/dist/attempt/TestSession.js +17 -15
  5. package/dist/attempt/TestSession.js.map +1 -1
  6. package/dist/attempt/adapters/activity-to-test-attempt-session.d.ts +50 -0
  7. package/dist/attempt/adapters/activity-to-test-attempt-session.d.ts.map +1 -0
  8. package/dist/attempt/adapters/activity-to-test-attempt-session.js +135 -0
  9. package/dist/attempt/adapters/activity-to-test-attempt-session.js.map +1 -0
  10. package/dist/components/ItemToolBar.custom-element.js +628 -0
  11. package/dist/components/PieAssessmentToolkit.custom-element.js +670 -0
  12. package/dist/components/item-toolbar-element.d.ts +3 -0
  13. package/dist/components/item-toolbar-element.d.ts.map +1 -0
  14. package/dist/components/item-toolbar-element.js +2 -0
  15. package/dist/components/item-toolbar-element.js.map +1 -0
  16. package/dist/components/pie-assessment-toolkit-element.d.ts +3 -0
  17. package/dist/components/pie-assessment-toolkit-element.d.ts.map +1 -0
  18. package/dist/components/pie-assessment-toolkit-element.js +2 -0
  19. package/dist/components/pie-assessment-toolkit-element.js.map +1 -0
  20. package/dist/context/assessment-toolkit-context.d.ts +52 -0
  21. package/dist/context/assessment-toolkit-context.d.ts.map +1 -0
  22. package/dist/context/assessment-toolkit-context.js +6 -0
  23. package/dist/context/assessment-toolkit-context.js.map +1 -0
  24. package/dist/context/runtime-context-consumer.d.ts +14 -0
  25. package/dist/context/runtime-context-consumer.d.ts.map +1 -0
  26. package/dist/context/runtime-context-consumer.js +52 -0
  27. package/dist/context/runtime-context-consumer.js.map +1 -0
  28. package/dist/index.d.ts +16 -5
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +13 -11
  31. package/dist/index.js.map +1 -1
  32. package/dist/runtime/RuntimeRegistry.d.ts +13 -0
  33. package/dist/runtime/RuntimeRegistry.d.ts.map +1 -0
  34. package/dist/runtime/RuntimeRegistry.js +50 -0
  35. package/dist/runtime/RuntimeRegistry.js.map +1 -0
  36. package/dist/runtime/SectionRuntimeEngine.d.ts +45 -0
  37. package/dist/runtime/SectionRuntimeEngine.d.ts.map +1 -0
  38. package/dist/runtime/SectionRuntimeEngine.js +81 -0
  39. package/dist/runtime/SectionRuntimeEngine.js.map +1 -0
  40. package/dist/runtime/registration-events.d.ts +19 -0
  41. package/dist/runtime/registration-events.d.ts.map +1 -0
  42. package/dist/runtime/registration-events.js +4 -0
  43. package/dist/runtime/registration-events.js.map +1 -0
  44. package/dist/runtime/runtime-event-guards.d.ts +3 -0
  45. package/dist/runtime/runtime-event-guards.d.ts.map +1 -0
  46. package/dist/runtime/runtime-event-guards.js +9 -0
  47. package/dist/runtime/runtime-event-guards.js.map +1 -0
  48. package/dist/runtime/tool-host-contract.d.ts +30 -0
  49. package/dist/runtime/tool-host-contract.d.ts.map +1 -0
  50. package/dist/runtime/tool-host-contract.js +48 -0
  51. package/dist/runtime/tool-host-contract.js.map +1 -0
  52. package/dist/services/TTSService.d.ts +3 -1
  53. package/dist/services/TTSService.d.ts.map +1 -1
  54. package/dist/services/TTSService.js +13 -12
  55. package/dist/services/TTSService.js.map +1 -1
  56. package/dist/services/ThemeProvider.d.ts +1 -0
  57. package/dist/services/ThemeProvider.d.ts.map +1 -1
  58. package/dist/services/ThemeProvider.js +82 -46
  59. package/dist/services/ThemeProvider.js.map +1 -1
  60. package/dist/services/ToolConfigResolver.d.ts +1 -1
  61. package/dist/services/ToolConfigResolver.js +1 -1
  62. package/dist/services/ToolCoordinator.d.ts +1 -1
  63. package/dist/services/ToolCoordinator.d.ts.map +1 -1
  64. package/dist/services/ToolRegistry.d.ts +64 -67
  65. package/dist/services/ToolRegistry.d.ts.map +1 -1
  66. package/dist/services/ToolRegistry.js +72 -22
  67. package/dist/services/ToolRegistry.js.map +1 -1
  68. package/dist/services/ToolkitCoordinator.d.ts +106 -8
  69. package/dist/services/ToolkitCoordinator.d.ts.map +1 -1
  70. package/dist/services/ToolkitCoordinator.js +502 -71
  71. package/dist/services/ToolkitCoordinator.js.map +1 -1
  72. package/dist/services/createDefaultToolRegistry.d.ts +12 -7
  73. package/dist/services/createDefaultToolRegistry.d.ts.map +1 -1
  74. package/dist/services/createDefaultToolRegistry.js +6 -10
  75. package/dist/services/createDefaultToolRegistry.js.map +1 -1
  76. package/dist/services/defaultPersonalNeedsProfile.d.ts +4 -0
  77. package/dist/services/defaultPersonalNeedsProfile.d.ts.map +1 -0
  78. package/dist/services/defaultPersonalNeedsProfile.js +25 -0
  79. package/dist/services/defaultPersonalNeedsProfile.js.map +1 -0
  80. package/dist/services/interfaces.d.ts +61 -1
  81. package/dist/services/interfaces.d.ts.map +1 -1
  82. package/dist/services/interfaces.js.map +1 -1
  83. package/dist/services/pnp-standard-features.d.ts +3 -4
  84. package/dist/services/pnp-standard-features.d.ts.map +1 -1
  85. package/dist/services/pnp-standard-features.js +0 -3
  86. package/dist/services/pnp-standard-features.js.map +1 -1
  87. package/dist/services/section-controller-types.d.ts +32 -0
  88. package/dist/services/section-controller-types.d.ts.map +1 -0
  89. package/dist/services/section-controller-types.js +2 -0
  90. package/dist/services/section-controller-types.js.map +1 -0
  91. package/dist/services/tool-context.d.ts +4 -4
  92. package/dist/services/tool-context.d.ts.map +1 -1
  93. package/dist/services/tool-context.js +106 -17
  94. package/dist/services/tool-context.js.map +1 -1
  95. package/dist/services/tool-instance-id.d.ts +17 -0
  96. package/dist/services/tool-instance-id.d.ts.map +1 -0
  97. package/dist/services/tool-instance-id.js +61 -0
  98. package/dist/services/tool-instance-id.js.map +1 -0
  99. package/dist/services/tool-providers/IToolProvider.d.ts +2 -3
  100. package/dist/services/tool-providers/IToolProvider.d.ts.map +1 -1
  101. package/dist/services/tool-providers/IToolProvider.js +1 -2
  102. package/dist/services/tool-providers/IToolProvider.js.map +1 -1
  103. package/dist/services/tool-providers/MathJsToolProvider.d.ts +25 -0
  104. package/dist/services/tool-providers/MathJsToolProvider.d.ts.map +1 -0
  105. package/dist/services/tool-providers/MathJsToolProvider.js +53 -0
  106. package/dist/services/tool-providers/MathJsToolProvider.js.map +1 -0
  107. package/dist/services/tool-providers/index.d.ts +2 -0
  108. package/dist/services/tool-providers/index.d.ts.map +1 -1
  109. package/dist/services/tool-providers/index.js +1 -0
  110. package/dist/services/tool-providers/index.js.map +1 -1
  111. package/dist/services/tools-config-normalizer.d.ts +26 -0
  112. package/dist/services/tools-config-normalizer.d.ts.map +1 -0
  113. package/dist/services/tools-config-normalizer.js +63 -0
  114. package/dist/services/tools-config-normalizer.js.map +1 -0
  115. package/dist/tools/calculators/ti-provider.d.ts.map +1 -1
  116. package/dist/tools/calculators/ti-provider.js +127 -4
  117. package/dist/tools/calculators/ti-provider.js.map +1 -1
  118. package/dist/tools/client.d.ts +2 -1
  119. package/dist/tools/client.d.ts.map +1 -1
  120. package/dist/tools/client.js +2 -1
  121. package/dist/tools/client.js.map +1 -1
  122. package/dist/tools/default-tool-module-loaders.d.ts +9 -0
  123. package/dist/tools/default-tool-module-loaders.d.ts.map +1 -0
  124. package/dist/tools/default-tool-module-loaders.js +17 -0
  125. package/dist/tools/default-tool-module-loaders.js.map +1 -0
  126. package/dist/tools/index.d.ts +2 -1
  127. package/dist/tools/index.d.ts.map +1 -1
  128. package/dist/tools/index.js +2 -1
  129. package/dist/tools/index.js.map +1 -1
  130. package/dist/tools/library-loader.d.ts +1 -1
  131. package/dist/tools/library-loader.d.ts.map +1 -1
  132. package/dist/tools/library-loader.js +7 -2
  133. package/dist/tools/library-loader.js.map +1 -1
  134. package/dist/tools/registrations/accessibility-tools.d.ts +1 -9
  135. package/dist/tools/registrations/accessibility-tools.d.ts.map +1 -1
  136. package/dist/tools/registrations/accessibility-tools.js +95 -113
  137. package/dist/tools/registrations/accessibility-tools.js.map +1 -1
  138. package/dist/tools/registrations/calculator.d.ts +2 -2
  139. package/dist/tools/registrations/calculator.d.ts.map +1 -1
  140. package/dist/tools/registrations/calculator.js +34 -55
  141. package/dist/tools/registrations/calculator.js.map +1 -1
  142. package/dist/tools/registrations/interaction-tools.d.ts.map +1 -1
  143. package/dist/tools/registrations/interaction-tools.js +77 -52
  144. package/dist/tools/registrations/interaction-tools.js.map +1 -1
  145. package/dist/tools/registrations/measurement-tools.d.ts.map +1 -1
  146. package/dist/tools/registrations/measurement-tools.js +63 -38
  147. package/dist/tools/registrations/measurement-tools.js.map +1 -1
  148. package/dist/tools/registrations/subject-specific-tools.d.ts.map +1 -1
  149. package/dist/tools/registrations/subject-specific-tools.js +63 -38
  150. package/dist/tools/registrations/subject-specific-tools.js.map +1 -1
  151. package/dist/tools/registrations/tts.d.ts +1 -1
  152. package/dist/tools/registrations/tts.d.ts.map +1 -1
  153. package/dist/tools/registrations/tts.js +27 -41
  154. package/dist/tools/registrations/tts.js.map +1 -1
  155. package/dist/tools/response-discovery.d.ts +12 -14
  156. package/dist/tools/response-discovery.d.ts.map +1 -1
  157. package/dist/tools/response-discovery.js +23 -80
  158. package/dist/tools/response-discovery.js.map +1 -1
  159. package/dist/tools/tool-tag-map.d.ts +3 -3
  160. package/dist/tools/tool-tag-map.d.ts.map +1 -1
  161. package/dist/tools/tool-tag-map.js +2 -3
  162. package/dist/tools/tool-tag-map.js.map +1 -1
  163. package/dist/tools/types.d.ts +3 -1
  164. package/dist/tools/types.d.ts.map +1 -1
  165. package/package.json +25 -28
  166. package/src/README.md +16 -32
  167. package/src/components/ItemToolBar.svelte +489 -0
  168. package/src/components/PNPProvenanceViewer.svelte +14 -4
  169. package/src/components/PieAssessmentToolkit.svelte +517 -0
  170. package/src/components/ToolButton.svelte +11 -11
  171. package/src/components/ToolButtonGroup.svelte +27 -11
  172. package/src/tools/README.md +6 -5
  173. package/src/tools/calculators/README.md +4 -4
  174. package/dist/player/AssessmentPlayer.d.ts +0 -397
  175. package/dist/player/AssessmentPlayer.d.ts.map +0 -1
  176. package/dist/player/AssessmentPlayer.js +0 -974
  177. package/dist/player/AssessmentPlayer.js.map +0 -1
  178. package/dist/player/index.d.ts +0 -17
  179. package/dist/player/index.d.ts.map +0 -1
  180. package/dist/player/index.js +0 -15
  181. package/dist/player/index.js.map +0 -1
  182. package/dist/player/navigation-types.d.ts +0 -63
  183. package/dist/player/navigation-types.d.ts.map +0 -1
  184. package/dist/player/navigation-types.js +0 -7
  185. package/dist/player/navigation-types.js.map +0 -1
  186. package/dist/player/qti-navigation.d.ts +0 -29
  187. package/dist/player/qti-navigation.d.ts.map +0 -1
  188. package/dist/player/qti-navigation.js +0 -160
  189. package/dist/player/qti-navigation.js.map +0 -1
  190. package/dist/reference-layout/index.d.ts +0 -19
  191. package/dist/reference-layout/index.d.ts.map +0 -1
  192. package/dist/reference-layout/index.js +0 -20
  193. package/dist/reference-layout/index.js.map +0 -1
  194. package/dist/services/AnnotationToolbarConfig.d.ts +0 -134
  195. package/dist/services/AnnotationToolbarConfig.d.ts.map +0 -1
  196. package/dist/services/AnnotationToolbarConfig.js +0 -100
  197. package/dist/services/AnnotationToolbarConfig.js.map +0 -1
  198. package/dist/services/AssessmentAuthoringService.d.ts +0 -60
  199. package/dist/services/AssessmentAuthoringService.d.ts.map +0 -1
  200. package/dist/services/AssessmentAuthoringService.js +0 -183
  201. package/dist/services/AssessmentAuthoringService.js.map +0 -1
  202. package/dist/services/PNPMapper.d.ts +0 -81
  203. package/dist/services/PNPMapper.d.ts.map +0 -1
  204. package/dist/services/PNPMapper.js +0 -108
  205. package/dist/services/PNPMapper.js.map +0 -1
  206. package/src/components/PNPProfileTester.example.svelte +0 -188
  207. package/src/components/PNPProfileTester.svelte +0 -397
  208. package/src/components/QuestionToolBar.svelte +0 -483
  209. package/src/player/AssessmentLayout.svelte +0 -68
  210. package/src/player/README.md +0 -154
  211. package/src/reference-layout/README.md +0 -135
  212. package/src/reference-layout/ReferenceLayout.svelte +0 -201
  213. package/src/reference-layout/components/AssessmentContent.svelte +0 -259
  214. package/src/reference-layout/components/AssessmentFooter.svelte +0 -89
  215. package/src/reference-layout/components/AssessmentHeader.svelte +0 -250
  216. package/src/reference-layout/components/AssessmentNavigation.svelte +0 -134
  217. package/src/reference-layout/components/AssessmentToolsBar.svelte +0 -176
  218. package/src/reference-layout/components/ItemPanel.svelte +0 -200
  219. package/src/reference-layout/components/NotesPanel.svelte +0 -179
  220. package/src/reference-layout/components/PassagePanel.svelte +0 -76
@@ -1,974 +0,0 @@
1
- /**
2
- * AssessmentPlayer
3
- *
4
- * Reference implementation showing how to wire PIE Assessment Toolkit services
5
- * together for a complete assessment player.
6
- *
7
- * This implementation provides:
8
- * - Navigation through assessment items (linear or nonlinear)
9
- * - QTI navigation model (testParts/sections/questionRefs)
10
- * - Section awareness and rubric block support
11
- * - Event-driven architecture using TypedEventBus
12
- * - Session state management
13
- * - TTS integration via TTSService
14
- * - Theme support via ThemeProvider
15
- * - Tool coordination (optional)
16
- *
17
- * Products can use this as-is, extend it, or use it as a pattern for their own implementation.
18
- */
19
- import { createNewTestSession, createTestSessionIdentifier, getBrowserLocalStorage, loadTestSession, saveTestSession, setCurrentPosition, upsertItemSessionFromPieSessionChange, upsertVisitedItem, } from "../attempt/TestSession.js";
20
- import { HighlightCoordinator, I18nService, ThemeProvider, ToolCoordinator, TTSService, TypedEventBus, } from "../index.js";
21
- import { AssessmentAuthoringService } from "../services/AssessmentAuthoringService.js";
22
- import { ContextVariableStore } from "../services/ContextVariableStore.js";
23
- import { PNPToolResolver, } from "../services/PNPToolResolver.js";
24
- import { createDefaultToolRegistry } from "../services/createDefaultToolRegistry.js";
25
- import { BrowserTTSProvider } from "../services/tts/browser-provider.js";
26
- import { DesmosCalculatorProvider, TICalculatorProvider, } from "../tools/client.js";
27
- import { buildNavigationStructure, detectAssessmentFormat, getAllQuestionRefs, } from "./qti-navigation.js";
28
- export class AssessmentPlayer {
29
- // Toolkit services
30
- eventBus;
31
- ttsService;
32
- themeProvider = null;
33
- toolCoordinator;
34
- highlightCoordinator = null;
35
- i18nService;
36
- desmosProvider;
37
- tiProvider;
38
- authoringService = null;
39
- // Configuration
40
- config;
41
- // PNP-based tool resolution
42
- pnpResolver;
43
- currentTools = [];
44
- // QTI 3.0 Context Variables
45
- contextStore;
46
- // Assessment format and navigation
47
- assessmentFormat;
48
- questionRefs;
49
- navigationStructure;
50
- navigationMode;
51
- // State
52
- currentItemIndex = -1;
53
- currentItem = null;
54
- isLoadingItem = false;
55
- sessionState = { id: "", data: [] };
56
- // QTI-like attempt (client-only)
57
- attemptStorage = null;
58
- testSession = null;
59
- // TTS state
60
- ttsInitialized = false;
61
- ttsSpeaking = false;
62
- ttsPaused = false;
63
- ttsRootElement = null;
64
- // State change listeners
65
- navigationListeners = new Set();
66
- itemListeners = new Set();
67
- loadingListeners = new Set();
68
- sessionListeners = new Set();
69
- ttsStateListeners = new Set();
70
- constructor(config) {
71
- this.config = config;
72
- // Detect assessment format and build navigation structure
73
- this.assessmentFormat = detectAssessmentFormat(config.assessment);
74
- this.questionRefs = getAllQuestionRefs(config.assessment);
75
- this.navigationStructure = buildNavigationStructure(config.assessment);
76
- // Initialize/load QTI-like TestSession (client-only)
77
- this.attemptStorage = config.attemptStorage || getBrowserLocalStorage();
78
- this.initializeOrLoadTestSession();
79
- // Determine navigation mode
80
- // Priority: explicit config > QTI testPart setting > default to nonlinear
81
- if (config.navigationMode) {
82
- this.navigationMode = config.navigationMode;
83
- }
84
- else {
85
- const qtiAssessment = config.assessment;
86
- this.navigationMode =
87
- qtiAssessment.testParts?.[0]?.navigationMode || "nonlinear";
88
- }
89
- // Initialize toolkit services (with dependency injection support)
90
- this.eventBus =
91
- config.services?.eventBus ?? new TypedEventBus();
92
- this.ttsService = config.services?.ttsService ?? new TTSService();
93
- this.toolCoordinator =
94
- config.services?.toolCoordinator ?? new ToolCoordinator();
95
- this.i18nService = config.services?.i18nService ?? new I18nService();
96
- this.desmosProvider =
97
- config.services?.desmosProvider ?? new DesmosCalculatorProvider();
98
- this.tiProvider = config.services?.tiProvider ?? new TICalculatorProvider();
99
- // Initialize PNP resolver with default tool registry
100
- const toolRegistry = createDefaultToolRegistry(config.toolRegistryOptions);
101
- this.pnpResolver = new PNPToolResolver(toolRegistry);
102
- this.initializeTools();
103
- this.applyAssessmentTheme();
104
- this.autoActivateTools();
105
- // Initialize context variable store from QTI 3.0 contextDeclarations
106
- this.contextStore = new ContextVariableStore(config.assessment.contextDeclarations);
107
- this.restoreContextVariables();
108
- // Initialize highlight coordinator
109
- // Use injected coordinator, or create new one if supported
110
- if (config.services?.highlightCoordinator) {
111
- this.highlightCoordinator = config.services.highlightCoordinator;
112
- }
113
- else {
114
- const tempCoordinator = new HighlightCoordinator();
115
- if (tempCoordinator.isSupported()) {
116
- this.highlightCoordinator = tempCoordinator;
117
- }
118
- }
119
- // Connect highlight coordinator to TTS service for word highlighting
120
- if (this.highlightCoordinator) {
121
- this.ttsService.setHighlightCoordinator(this.highlightCoordinator);
122
- }
123
- // Initialize authoring service if in author mode
124
- if (config.mode === "author" && config.authoringCallbacks) {
125
- this.authoringService = new AssessmentAuthoringService(config.assessment, config.authoringCallbacks);
126
- }
127
- // Set up event listeners
128
- this.setupEventListeners();
129
- // Initialize TTS if enabled via PNP
130
- if (this.isToolEnabled("textToSpeech")) {
131
- this.initializeTTS();
132
- }
133
- }
134
- initializeOrLoadTestSession() {
135
- if (!this.attemptStorage) {
136
- return;
137
- }
138
- const assessmentId = this.config.assessment?.id ||
139
- this.config.assessment?._id;
140
- if (!assessmentId) {
141
- return;
142
- }
143
- const { testSessionIdentifier, seed } = createTestSessionIdentifier({
144
- assessmentId,
145
- assignmentId: this.config.assignmentId,
146
- userId: this.config.userId,
147
- storage: this.attemptStorage,
148
- });
149
- const existing = loadTestSession(this.attemptStorage, testSessionIdentifier);
150
- const currentItemIdentifiers = this.questionRefs.map((q) => q.identifier);
151
- if (existing) {
152
- // Keep existing runtime state, but make sure realized order matches current definition
153
- // (for now we don't implement selection/shuffle, so realization is the current flat order).
154
- const needsUpdate = !existing.realization?.itemIdentifiers ||
155
- existing.realization.itemIdentifiers.length !==
156
- currentItemIdentifiers.length;
157
- this.testSession = needsUpdate
158
- ? {
159
- ...existing,
160
- realization: {
161
- ...existing.realization,
162
- seed: existing.realization?.seed || seed,
163
- itemIdentifiers: currentItemIdentifiers,
164
- },
165
- }
166
- : existing;
167
- }
168
- else {
169
- this.testSession = createNewTestSession({
170
- testSessionIdentifier,
171
- assessmentId,
172
- seed,
173
- itemIdentifiers: currentItemIdentifiers,
174
- });
175
- }
176
- // Persist immediately to ensure updatedAt/version exists
177
- if (this.testSession) {
178
- saveTestSession(this.attemptStorage, this.testSession);
179
- }
180
- }
181
- /**
182
- * Set up internal event listeners
183
- */
184
- setupEventListeners() {
185
- // Listen to player events
186
- this.eventBus.on("player:session-changed", async (e) => {
187
- const { id, ...sessionData } = e.detail;
188
- if (id) {
189
- const existingEntry = this.sessionState.data.find((d) => d.id === id);
190
- if (existingEntry) {
191
- Object.assign(existingEntry, sessionData);
192
- }
193
- else {
194
- this.sessionState.data.push({ id, ...sessionData });
195
- }
196
- }
197
- // Notify session listeners
198
- this.notifySessionListeners();
199
- // Call product callback
200
- if (this.config.onSessionChanged) {
201
- await this.config.onSessionChanged(this.sessionState);
202
- }
203
- });
204
- this.eventBus.on("nav:item-changed", async (e) => {
205
- if (this.config.onItemChanged) {
206
- await this.config.onItemChanged(e.detail.currentItemId, e.detail.itemIndex);
207
- }
208
- });
209
- this.eventBus.on("nav:next-requested", async (e) => {
210
- await this.navigateNext();
211
- });
212
- this.eventBus.on("nav:previous-requested", async (e) => {
213
- await this.navigatePrevious();
214
- });
215
- }
216
- /**
217
- * Initialize TTS service
218
- */
219
- async initializeTTS() {
220
- try {
221
- // Initialize TTS with browser provider
222
- const provider = new BrowserTTSProvider();
223
- await this.ttsService.initialize(provider, {
224
- organizationId: this.config.organizationId || undefined,
225
- });
226
- this.ttsInitialized = true;
227
- // Listen to TTS state changes
228
- this.ttsService.onStateChange("reference-player", (state) => {
229
- this.ttsSpeaking = state === "playing";
230
- this.ttsPaused = state === "paused";
231
- this.notifyTTSStateListeners();
232
- });
233
- }
234
- catch (error) {
235
- console.error("[AssessmentPlayer] Failed to initialize TTS:", error);
236
- }
237
- }
238
- /**
239
- * Get event bus for external listeners
240
- */
241
- getEventBus() {
242
- return this.eventBus;
243
- }
244
- /**
245
- * Get current navigation state
246
- */
247
- getNavigationState() {
248
- const totalItems = this.questionRefs.length;
249
- // Find current section if we have navigation structure
250
- let currentSection;
251
- let totalSections = 0;
252
- if (this.navigationStructure.length > 0 && this.currentItemIndex >= 0) {
253
- // Count sections (filter out non-section nodes like testParts)
254
- const sections = this.flattenSections(this.navigationStructure);
255
- totalSections = sections.length;
256
- // Find which section contains the current question
257
- if (totalSections > 0) {
258
- const currentQuestion = this.questionRefs[this.currentItemIndex];
259
- if (currentQuestion) {
260
- for (let i = 0; i < sections.length; i++) {
261
- const section = sections[i];
262
- // Check if this section contains the current question
263
- if (this.sectionContainsQuestion(section, currentQuestion.identifier)) {
264
- currentSection = {
265
- id: section.id,
266
- title: section.title,
267
- index: i,
268
- };
269
- break;
270
- }
271
- }
272
- }
273
- }
274
- }
275
- return {
276
- currentIndex: this.currentItemIndex,
277
- totalItems,
278
- canNext: this.currentItemIndex < totalItems - 1,
279
- canPrevious: this.currentItemIndex > 0,
280
- isLoading: this.isLoadingItem,
281
- currentSection,
282
- totalSections: totalSections > 0 ? totalSections : undefined,
283
- };
284
- }
285
- /**
286
- * Flatten navigation structure to get all sections
287
- */
288
- flattenSections(nodes) {
289
- const sections = [];
290
- for (const node of nodes) {
291
- if (node.type === "section") {
292
- sections.push(node);
293
- }
294
- if (node.children) {
295
- sections.push(...this.flattenSections(node.children));
296
- }
297
- }
298
- return sections;
299
- }
300
- /**
301
- * Check if a section contains a specific question
302
- */
303
- sectionContainsQuestion(section, questionIdentifier) {
304
- if (!section.children) {
305
- return false;
306
- }
307
- for (const child of section.children) {
308
- if (child.type === "question" &&
309
- child.identifier === questionIdentifier) {
310
- return true;
311
- }
312
- // Check nested sections
313
- if (child.type === "section" &&
314
- this.sectionContainsQuestion(child, questionIdentifier)) {
315
- return true;
316
- }
317
- }
318
- return false;
319
- }
320
- /**
321
- * Get current item
322
- */
323
- getCurrentItem() {
324
- return this.currentItem;
325
- }
326
- /**
327
- * Get current item config
328
- */
329
- getCurrentItemConfig() {
330
- return this.currentItem?.config;
331
- }
332
- /**
333
- * Get session state
334
- */
335
- getSessionState() {
336
- return this.sessionState;
337
- }
338
- /**
339
- * Get the client-side QTI-like TestSession (attempt) state.
340
- */
341
- getTestSession() {
342
- return this.testSession;
343
- }
344
- /**
345
- * Get player environment
346
- */
347
- getEnv() {
348
- return {
349
- mode: this.config.mode || "gather",
350
- };
351
- }
352
- /**
353
- * Get ToolCoordinator for tool management
354
- */
355
- getToolCoordinator() {
356
- return this.toolCoordinator;
357
- }
358
- /**
359
- * Get HighlightCoordinator for annotation/TTS highlighting
360
- */
361
- getHighlightCoordinator() {
362
- return this.highlightCoordinator;
363
- }
364
- /**
365
- * Get TTSService for text-to-speech functionality
366
- */
367
- getTTSService() {
368
- return this.ttsService;
369
- }
370
- /**
371
- * Get ThemeProvider for accessibility theming
372
- */
373
- getThemeProvider() {
374
- return this.themeProvider;
375
- }
376
- /**
377
- * Get I18nService for internationalization
378
- */
379
- getI18nService() {
380
- return this.i18nService;
381
- }
382
- /**
383
- * Get Desmos calculator provider
384
- */
385
- getDesmosProvider() {
386
- return this.desmosProvider;
387
- }
388
- /**
389
- * Get TI calculator provider
390
- */
391
- getTIProvider() {
392
- return this.tiProvider;
393
- }
394
- /**
395
- * Navigate to specific item index
396
- */
397
- async navigate(index) {
398
- if (index < 0 || index >= this.questionRefs.length) {
399
- return;
400
- }
401
- // Stop TTS when navigating
402
- if (this.ttsSpeaking) {
403
- this.stopTTS();
404
- }
405
- this.isLoadingItem = true;
406
- this.notifyLoadingListeners(true);
407
- const previousItemId = this.currentItem?.id || null;
408
- this.currentItemIndex = index;
409
- const questionRef = this.questionRefs[index];
410
- // Persist attempt navigation immediately (even if item fetch fails)
411
- this.persistAttemptNavigation(questionRef?.identifier, index);
412
- if (questionRef && questionRef.itemVId) {
413
- try {
414
- this.currentItem = await this.config.loadItem(questionRef.itemVId, {
415
- organizationId: this.config.organizationId ?? null,
416
- });
417
- // Emit item-changed event - use questionRef.itemVId for bookmarkability
418
- this.eventBus.emit("nav:item-changed", {
419
- previousItemId,
420
- currentItemId: questionRef.itemVId, // Use itemVId from assessment question for URL bookmarking
421
- itemIndex: index,
422
- totalItems: this.questionRefs.length,
423
- timestamp: Date.now(),
424
- });
425
- // Notify item listeners
426
- this.notifyItemListeners();
427
- this.notifyNavigationListeners();
428
- }
429
- catch (error) {
430
- console.error("[AssessmentPlayer] Error fetching item:", error);
431
- this.currentItem = null;
432
- }
433
- }
434
- else {
435
- this.currentItem = null;
436
- }
437
- this.isLoadingItem = false;
438
- this.notifyLoadingListeners(false);
439
- this.notifyItemListeners();
440
- this.notifyNavigationListeners();
441
- }
442
- /**
443
- * Navigate to next item
444
- */
445
- async navigateNext() {
446
- if (this.currentItemIndex < this.questionRefs.length - 1) {
447
- // In linear mode, prevent skipping items
448
- // (No additional restrictions needed here - just move to next)
449
- await this.navigate(this.currentItemIndex + 1);
450
- }
451
- }
452
- /**
453
- * Navigate to previous item
454
- */
455
- async navigatePrevious() {
456
- // In linear mode, allow going back to previous items
457
- if (this.currentItemIndex > 0) {
458
- await this.navigate(this.currentItemIndex - 1);
459
- }
460
- }
461
- /**
462
- * Start assessment (navigate to first item)
463
- */
464
- async start() {
465
- const startIndexRaw = this.testSession?.navigationState?.currentItemIndex ?? 0;
466
- const startIndex = startIndexRaw >= 0 ? startIndexRaw : 0;
467
- await this.navigate(startIndex);
468
- this.eventBus.emit("assessment:started", {
469
- assessmentId: this.config.assessment.id || "",
470
- studentId: "current-student", // TODO: Get from auth context
471
- timestamp: Date.now(),
472
- });
473
- }
474
- /**
475
- * TTS: Read current question
476
- */
477
- async readQuestion(rootElement) {
478
- if (!this.ttsInitialized)
479
- return;
480
- this.ttsRootElement = rootElement;
481
- // Get the question prompt
482
- const promptElement = rootElement.querySelector(".pie-question-prompt, [data-pie-prompt], .prompt");
483
- if (!promptElement)
484
- return;
485
- // Extract text (using same extraction as existing implementation)
486
- const text = this.extractText(promptElement);
487
- if (!text.trim())
488
- return;
489
- try {
490
- this.ttsSpeaking = true;
491
- this.ttsPaused = false;
492
- this.notifyTTSStateListeners();
493
- // Set root for highlighting
494
- this.ttsService.setRootElement?.(promptElement);
495
- await this.ttsService.speak(text);
496
- this.ttsSpeaking = false;
497
- this.ttsPaused = false;
498
- this.notifyTTSStateListeners();
499
- }
500
- catch (error) {
501
- console.error("[AssessmentPlayer] TTS error:", error);
502
- this.ttsSpeaking = false;
503
- this.ttsPaused = false;
504
- this.notifyTTSStateListeners();
505
- }
506
- }
507
- /**
508
- * TTS: Toggle play/pause
509
- */
510
- toggleTTS(rootElement) {
511
- if (!this.ttsInitialized)
512
- return;
513
- if (this.ttsSpeaking) {
514
- if (this.ttsPaused) {
515
- this.ttsService.resume();
516
- }
517
- else {
518
- this.ttsService.pause();
519
- }
520
- }
521
- else if (rootElement) {
522
- this.readQuestion(rootElement);
523
- }
524
- }
525
- /**
526
- * TTS: Stop reading
527
- */
528
- stopTTS() {
529
- if (this.ttsSpeaking) {
530
- this.ttsService.stop();
531
- this.ttsSpeaking = false;
532
- this.ttsPaused = false;
533
- this.notifyTTSStateListeners();
534
- }
535
- }
536
- /**
537
- * TTS: Get state
538
- */
539
- getTTSState() {
540
- return {
541
- initialized: this.ttsInitialized,
542
- speaking: this.ttsSpeaking,
543
- paused: this.ttsPaused,
544
- };
545
- }
546
- /**
547
- * Apply theme
548
- */
549
- applyTheme(theme) {
550
- if (!this.themeProvider) {
551
- this.themeProvider = new ThemeProvider();
552
- }
553
- this.themeProvider.applyTheme(theme);
554
- }
555
- /**
556
- * Handle PIE player session-changed event
557
- * Call this from your component when you receive session-changed from pie-iife-player
558
- */
559
- handlePieSessionChanged(detail) {
560
- this.eventBus.emit("player:session-changed", {
561
- itemId: this.currentItem?.id || "",
562
- component: detail.component || "",
563
- complete: detail.complete || false,
564
- session: this.sessionState,
565
- timestamp: Date.now(),
566
- });
567
- // Update/persist TestSession itemSessions mapping (QTI item identifier -> PIE session id)
568
- const pieSessionId = detail?.id;
569
- const currentQuestion = this.questionRefs[this.currentItemIndex];
570
- if (this.attemptStorage &&
571
- this.testSession &&
572
- currentQuestion?.identifier &&
573
- pieSessionId) {
574
- this.testSession = upsertItemSessionFromPieSessionChange(this.testSession, {
575
- itemIdentifier: currentQuestion.identifier,
576
- pieSessionId,
577
- isCompleted: !!detail.complete,
578
- });
579
- saveTestSession(this.attemptStorage, this.testSession);
580
- }
581
- }
582
- persistAttemptNavigation(itemIdentifier, index) {
583
- if (!this.attemptStorage || !this.testSession || !itemIdentifier) {
584
- return;
585
- }
586
- const currentSectionIdentifier = this.findSectionIdentifierForQuestion(itemIdentifier) || undefined;
587
- this.testSession = setCurrentPosition(upsertVisitedItem(this.testSession, itemIdentifier), {
588
- currentItemIndex: index,
589
- currentSectionIdentifier,
590
- });
591
- saveTestSession(this.attemptStorage, this.testSession);
592
- }
593
- findSectionIdentifierForQuestion(questionIdentifier) {
594
- const sections = this.flattenSections(this.navigationStructure);
595
- for (const section of sections) {
596
- if (this.sectionContainsQuestion(section, questionIdentifier)) {
597
- return section.identifier;
598
- }
599
- }
600
- return null;
601
- }
602
- /**
603
- * Subscribe to navigation state changes
604
- */
605
- onNavigationChange(listener) {
606
- this.navigationListeners.add(listener);
607
- return () => this.navigationListeners.delete(listener);
608
- }
609
- /**
610
- * Subscribe to item changes
611
- */
612
- onItemChange(listener) {
613
- this.itemListeners.add(listener);
614
- return () => this.itemListeners.delete(listener);
615
- }
616
- /**
617
- * Subscribe to loading state changes
618
- */
619
- onLoadingChange(listener) {
620
- this.loadingListeners.add(listener);
621
- return () => this.loadingListeners.delete(listener);
622
- }
623
- /**
624
- * Subscribe to session changes
625
- */
626
- onSessionChange(listener) {
627
- this.sessionListeners.add(listener);
628
- return () => this.sessionListeners.delete(listener);
629
- }
630
- /**
631
- * Subscribe to TTS state changes
632
- */
633
- onTTSStateChange(listener) {
634
- this.ttsStateListeners.add(listener);
635
- return () => this.ttsStateListeners.delete(listener);
636
- }
637
- /**
638
- * Notify listeners
639
- */
640
- notifyNavigationListeners() {
641
- const state = this.getNavigationState();
642
- this.navigationListeners.forEach((listener) => listener(state));
643
- }
644
- notifyItemListeners() {
645
- this.itemListeners.forEach((listener) => listener(this.currentItem));
646
- }
647
- notifyLoadingListeners(isLoading) {
648
- this.loadingListeners.forEach((listener) => listener(isLoading));
649
- }
650
- notifySessionListeners() {
651
- this.sessionListeners.forEach((listener) => listener(this.sessionState));
652
- }
653
- notifyTTSStateListeners() {
654
- this.ttsStateListeners.forEach((listener) => listener(this.ttsSpeaking, this.ttsPaused));
655
- }
656
- /**
657
- * Extract text from element (simplified)
658
- * Production would use extractTextFromElement from tts/utils
659
- */
660
- extractText(element) {
661
- // This is simplified - production should use the actual extraction logic
662
- return element.textContent || "";
663
- }
664
- /**
665
- * Get current section's rubric blocks (passages, instructions)
666
- */
667
- getCurrentSectionRubricBlocks() {
668
- const navState = this.getNavigationState();
669
- if (!navState.currentSection || this.navigationStructure.length === 0) {
670
- return [];
671
- }
672
- // Find the current section node
673
- const sections = this.flattenSections(this.navigationStructure);
674
- const currentSectionNode = sections[navState.currentSection.index];
675
- if (!currentSectionNode) {
676
- return [];
677
- }
678
- // Extract rubric blocks from the assessment structure
679
- // This requires accessing the original assessment data
680
- return this.extractRubricBlocksForSection(currentSectionNode.identifier);
681
- }
682
- /**
683
- * Extract rubric blocks for a specific section identifier
684
- */
685
- extractRubricBlocksForSection(sectionIdentifier) {
686
- const format = this.assessmentFormat;
687
- if (format === "qti") {
688
- // Type assertion needed for QTI fields
689
- const qtiAssessment = this.config.assessment;
690
- if (!qtiAssessment.testParts) {
691
- return [];
692
- }
693
- // Search through testParts and sections
694
- for (const testPart of qtiAssessment.testParts) {
695
- const rubricBlocks = this.findRubricBlocksInSections(testPart.sections, sectionIdentifier);
696
- if (rubricBlocks.length > 0) {
697
- return rubricBlocks;
698
- }
699
- }
700
- }
701
- return [];
702
- }
703
- /**
704
- * Recursively search for rubric blocks in sections
705
- */
706
- findRubricBlocksInSections(sections, targetIdentifier) {
707
- if (!sections) {
708
- return [];
709
- }
710
- for (const section of sections) {
711
- if (section.identifier === targetIdentifier && section.rubricBlocks) {
712
- return section.rubricBlocks;
713
- }
714
- // Check nested sections
715
- if (section.sections) {
716
- const rubricBlocks = this.findRubricBlocksInSections(section.sections, targetIdentifier);
717
- if (rubricBlocks.length > 0) {
718
- return rubricBlocks;
719
- }
720
- }
721
- }
722
- return [];
723
- }
724
- /**
725
- * Navigate to a specific section (goes to first item in section)
726
- */
727
- async navigateToSection(sectionIndex) {
728
- if (!this.config.allowSectionNavigation &&
729
- this.config.allowSectionNavigation !== undefined) {
730
- console.warn("[AssessmentPlayer] Section navigation is disabled");
731
- return;
732
- }
733
- const sections = this.flattenSections(this.navigationStructure);
734
- if (sectionIndex < 0 || sectionIndex >= sections.length) {
735
- return;
736
- }
737
- const targetSection = sections[sectionIndex];
738
- // Find the first question in this section
739
- const firstQuestionInSection = this.findFirstQuestionInSection(targetSection);
740
- if (firstQuestionInSection !== -1) {
741
- await this.navigate(firstQuestionInSection);
742
- }
743
- }
744
- /**
745
- * Find the index of the first question in a section
746
- */
747
- findFirstQuestionInSection(section) {
748
- if (!section.children) {
749
- return -1;
750
- }
751
- // Look for first question node
752
- for (const child of section.children) {
753
- if (child.type === "question") {
754
- // Find this question's index in questionRefs
755
- const index = this.questionRefs.findIndex((q) => q.identifier === child.identifier);
756
- if (index !== -1) {
757
- return index;
758
- }
759
- }
760
- // Check nested sections
761
- if (child.type === "section") {
762
- const index = this.findFirstQuestionInSection(child);
763
- if (index !== -1) {
764
- return index;
765
- }
766
- }
767
- }
768
- return -1;
769
- }
770
- /**
771
- * Get navigation mode (linear or nonlinear)
772
- */
773
- getNavigationMode() {
774
- return this.navigationMode;
775
- }
776
- /**
777
- * Get all sections for display (e.g., in a section menu)
778
- */
779
- getAllSections() {
780
- const sections = this.flattenSections(this.navigationStructure);
781
- return sections.map((section, index) => ({
782
- id: section.id,
783
- title: section.title,
784
- index,
785
- }));
786
- }
787
- /**
788
- * Get the authoring service (only available in author mode)
789
- */
790
- getAuthoringService() {
791
- return this.authoringService;
792
- }
793
- /**
794
- * Enable or disable authoring mode
795
- */
796
- setAuthoringMode(enabled) {
797
- if (enabled && !this.authoringService) {
798
- this.authoringService = new AssessmentAuthoringService(this.config.assessment, this.config.authoringCallbacks || {});
799
- this.config.mode = "author";
800
- }
801
- else if (!enabled && this.authoringService) {
802
- this.authoringService = null;
803
- this.config.mode = "gather";
804
- }
805
- }
806
- // ===== PROFILE-BASED CONFIGURATION =====
807
- /**
808
- * Apply an assessment context profile
809
- */
810
- /**
811
- * Initialize tools from assessment PNP
812
- */
813
- initializeTools() {
814
- const assessment = this.config.assessment;
815
- // Resolve tools from PNP + settings
816
- this.currentTools = this.pnpResolver.resolveTools(assessment);
817
- // Register tools with coordinator
818
- for (const tool of this.currentTools) {
819
- if (tool.enabled) {
820
- this.toolCoordinator.registerTool(tool.id, this.humanizeName(tool.id));
821
- // Configure tool-specific settings
822
- if (tool.id === "calculator" && tool.settings) {
823
- this.configureCalculator(tool.settings);
824
- }
825
- }
826
- }
827
- }
828
- /**
829
- * Apply theme from assessment settings
830
- */
831
- applyAssessmentTheme() {
832
- const settings = this.config.assessment.settings;
833
- const themeConfig = settings?.themeConfig;
834
- if (themeConfig) {
835
- this.themeProvider =
836
- this.config.services?.themeProvider ?? new ThemeProvider();
837
- this.themeProvider.applyTheme(themeConfig);
838
- }
839
- }
840
- /**
841
- * Auto-activate tools marked in PNP
842
- */
843
- autoActivateTools() {
844
- const autoActivate = this.pnpResolver.getAutoActivateTools(this.config.assessment);
845
- for (const toolId of autoActivate) {
846
- this.toolCoordinator.showTool(toolId);
847
- }
848
- }
849
- /**
850
- * Configure calculator with settings
851
- */
852
- configureCalculator(settings) {
853
- // Calculator configuration logic
854
- // (existing logic would be here)
855
- }
856
- /**
857
- * Convert tool ID to human-readable name
858
- */
859
- humanizeName(toolId) {
860
- return toolId
861
- .replace(/^pie-tool-/, "")
862
- .replace(/([a-z])([A-Z])/g, "$1 $2")
863
- .replace(/-/g, " ")
864
- .replace(/\b\w/g, (c) => c.toUpperCase());
865
- }
866
- /**
867
- * Get available tools (resolved from PNP)
868
- */
869
- getAvailableTools() {
870
- return this.currentTools;
871
- }
872
- /**
873
- * Check if a specific tool is enabled
874
- */
875
- isToolEnabled(toolId) {
876
- return this.currentTools.some((t) => t.id === toolId && t.enabled);
877
- }
878
- /**
879
- * Check if a specific tool is required
880
- */
881
- isToolRequired(toolId) {
882
- const tool = this.currentTools.find((t) => t.id === toolId);
883
- return tool?.required || tool?.alwaysAvailable || false;
884
- }
885
- /**
886
- * Get configuration for a specific tool
887
- */
888
- getToolConfig(toolId) {
889
- return this.currentTools.find((t) => t.id === toolId)?.settings;
890
- }
891
- /**
892
- * Get layout preferences from settings
893
- */
894
- getLayoutPreferences() {
895
- return this.config.assessment.settings?.themeConfig || {};
896
- }
897
- // ============================================================================
898
- // Context Variables (QTI 3.0 Context Declarations)
899
- // ============================================================================
900
- /**
901
- * Get context variable value
902
- *
903
- * @param identifier Variable identifier
904
- * @returns Variable value or undefined
905
- */
906
- getContextVariable(identifier) {
907
- return this.contextStore.get(identifier);
908
- }
909
- /**
910
- * Set context variable value
911
- *
912
- * @param identifier Variable identifier
913
- * @param value New value
914
- */
915
- setContextVariable(identifier, value) {
916
- this.contextStore.set(identifier, value);
917
- this.persistContextVariables();
918
- }
919
- /**
920
- * Get all context variables as object
921
- *
922
- * @returns Object containing all context variables
923
- */
924
- getContextVariables() {
925
- return this.contextStore.toObject();
926
- }
927
- /**
928
- * Reset context variables to default values
929
- */
930
- resetContextVariables() {
931
- this.contextStore.reset();
932
- this.persistContextVariables();
933
- }
934
- /**
935
- * Restore context variables from session storage
936
- */
937
- restoreContextVariables() {
938
- if (this.testSession?.contextVariables) {
939
- this.contextStore.fromObject(this.testSession.contextVariables);
940
- }
941
- }
942
- /**
943
- * Persist context variables to session storage
944
- */
945
- persistContextVariables() {
946
- if (this.testSession && this.attemptStorage) {
947
- this.testSession = {
948
- ...this.testSession,
949
- contextVariables: this.contextStore.toObject(),
950
- };
951
- saveTestSession(this.attemptStorage, this.testSession);
952
- }
953
- }
954
- /**
955
- * Cleanup
956
- */
957
- destroy() {
958
- if (this.ttsInitialized) {
959
- this.ttsService.offStateChange("reference-player", () => { });
960
- }
961
- if (this.themeProvider) {
962
- this.themeProvider.destroy();
963
- }
964
- if (this.highlightCoordinator) {
965
- this.highlightCoordinator.destroy();
966
- }
967
- this.navigationListeners.clear();
968
- this.itemListeners.clear();
969
- this.loadingListeners.clear();
970
- this.sessionListeners.clear();
971
- this.ttsStateListeners.clear();
972
- }
973
- }
974
- //# sourceMappingURL=AssessmentPlayer.js.map