@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.
Files changed (194) hide show
  1. package/README.md +520 -0
  2. package/dist/attempt/TestSession.d.ts +90 -0
  3. package/dist/attempt/TestSession.d.ts.map +1 -0
  4. package/dist/attempt/TestSession.js +174 -0
  5. package/dist/attempt/TestSession.js.map +1 -0
  6. package/dist/core/TypedEventBus.d.ts +48 -0
  7. package/dist/core/TypedEventBus.d.ts.map +1 -0
  8. package/dist/core/TypedEventBus.js +63 -0
  9. package/dist/core/TypedEventBus.js.map +1 -0
  10. package/dist/index.d.ts +37 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +44 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/item-loader.d.ts +45 -0
  15. package/dist/item-loader.d.ts.map +1 -0
  16. package/dist/item-loader.js +62 -0
  17. package/dist/item-loader.js.map +1 -0
  18. package/dist/player/AssessmentPlayer.d.ts +388 -0
  19. package/dist/player/AssessmentPlayer.d.ts.map +1 -0
  20. package/dist/player/AssessmentPlayer.js +965 -0
  21. package/dist/player/AssessmentPlayer.js.map +1 -0
  22. package/dist/player/index.d.ts +17 -0
  23. package/dist/player/index.d.ts.map +1 -0
  24. package/dist/player/index.js +15 -0
  25. package/dist/player/index.js.map +1 -0
  26. package/dist/player/navigation-types.d.ts +63 -0
  27. package/dist/player/navigation-types.d.ts.map +1 -0
  28. package/dist/player/navigation-types.js +7 -0
  29. package/dist/player/navigation-types.js.map +1 -0
  30. package/dist/player/qti-navigation.d.ts +29 -0
  31. package/dist/player/qti-navigation.d.ts.map +1 -0
  32. package/dist/player/qti-navigation.js +160 -0
  33. package/dist/player/qti-navigation.js.map +1 -0
  34. package/dist/reference-layout/index.d.ts +19 -0
  35. package/dist/reference-layout/index.d.ts.map +1 -0
  36. package/dist/reference-layout/index.js +20 -0
  37. package/dist/reference-layout/index.js.map +1 -0
  38. package/dist/services/AccessibilityCatalogResolver.d.ts +141 -0
  39. package/dist/services/AccessibilityCatalogResolver.d.ts.map +1 -0
  40. package/dist/services/AccessibilityCatalogResolver.js +249 -0
  41. package/dist/services/AccessibilityCatalogResolver.js.map +1 -0
  42. package/dist/services/AssessmentAuthoringService.d.ts +60 -0
  43. package/dist/services/AssessmentAuthoringService.d.ts.map +1 -0
  44. package/dist/services/AssessmentAuthoringService.js +183 -0
  45. package/dist/services/AssessmentAuthoringService.js.map +1 -0
  46. package/dist/services/ContextVariableStore.d.ts +114 -0
  47. package/dist/services/ContextVariableStore.d.ts.map +1 -0
  48. package/dist/services/ContextVariableStore.js +207 -0
  49. package/dist/services/ContextVariableStore.js.map +1 -0
  50. package/dist/services/ElementToolStateStore.d.ts +125 -0
  51. package/dist/services/ElementToolStateStore.d.ts.map +1 -0
  52. package/dist/services/ElementToolStateStore.js +200 -0
  53. package/dist/services/ElementToolStateStore.js.map +1 -0
  54. package/dist/services/HighlightCoordinator.d.ts +179 -0
  55. package/dist/services/HighlightCoordinator.d.ts.map +1 -0
  56. package/dist/services/HighlightCoordinator.js +446 -0
  57. package/dist/services/HighlightCoordinator.js.map +1 -0
  58. package/dist/services/I18nService.d.ts +121 -0
  59. package/dist/services/I18nService.d.ts.map +1 -0
  60. package/dist/services/I18nService.js +276 -0
  61. package/dist/services/I18nService.js.map +1 -0
  62. package/dist/services/PNPMapper.d.ts +71 -0
  63. package/dist/services/PNPMapper.d.ts.map +1 -0
  64. package/dist/services/PNPMapper.js +98 -0
  65. package/dist/services/PNPMapper.js.map +1 -0
  66. package/dist/services/PNPToolResolver.d.ts +127 -0
  67. package/dist/services/PNPToolResolver.d.ts.map +1 -0
  68. package/dist/services/PNPToolResolver.js +222 -0
  69. package/dist/services/PNPToolResolver.js.map +1 -0
  70. package/dist/services/SSMLExtractor.d.ts +61 -0
  71. package/dist/services/SSMLExtractor.d.ts.map +1 -0
  72. package/dist/services/SSMLExtractor.js +181 -0
  73. package/dist/services/SSMLExtractor.js.map +1 -0
  74. package/dist/services/TTSService.d.ts +162 -0
  75. package/dist/services/TTSService.d.ts.map +1 -0
  76. package/dist/services/TTSService.js +465 -0
  77. package/dist/services/TTSService.js.map +1 -0
  78. package/dist/services/ThemeProvider.d.ts +95 -0
  79. package/dist/services/ThemeProvider.d.ts.map +1 -0
  80. package/dist/services/ThemeProvider.js +299 -0
  81. package/dist/services/ThemeProvider.js.map +1 -0
  82. package/dist/services/ToolConfigResolver.d.ts +162 -0
  83. package/dist/services/ToolConfigResolver.d.ts.map +1 -0
  84. package/dist/services/ToolConfigResolver.js +230 -0
  85. package/dist/services/ToolConfigResolver.js.map +1 -0
  86. package/dist/services/ToolCoordinator.d.ts +137 -0
  87. package/dist/services/ToolCoordinator.d.ts.map +1 -0
  88. package/dist/services/ToolCoordinator.js +318 -0
  89. package/dist/services/ToolCoordinator.js.map +1 -0
  90. package/dist/services/ToolkitCoordinator.d.ts +188 -0
  91. package/dist/services/ToolkitCoordinator.d.ts.map +1 -0
  92. package/dist/services/ToolkitCoordinator.js +252 -0
  93. package/dist/services/ToolkitCoordinator.js.map +1 -0
  94. package/dist/services/interfaces.d.ts +391 -0
  95. package/dist/services/interfaces.d.ts.map +1 -0
  96. package/dist/services/interfaces.js +12 -0
  97. package/dist/services/interfaces.js.map +1 -0
  98. package/dist/services/tool-providers/DesmosToolProvider.d.ts +103 -0
  99. package/dist/services/tool-providers/DesmosToolProvider.d.ts.map +1 -0
  100. package/dist/services/tool-providers/DesmosToolProvider.js +127 -0
  101. package/dist/services/tool-providers/DesmosToolProvider.js.map +1 -0
  102. package/dist/services/tool-providers/IToolProvider.d.ts +137 -0
  103. package/dist/services/tool-providers/IToolProvider.d.ts.map +1 -0
  104. package/dist/services/tool-providers/IToolProvider.js +13 -0
  105. package/dist/services/tool-providers/IToolProvider.js.map +1 -0
  106. package/dist/services/tool-providers/TIToolProvider.d.ts +107 -0
  107. package/dist/services/tool-providers/TIToolProvider.d.ts.map +1 -0
  108. package/dist/services/tool-providers/TIToolProvider.js +123 -0
  109. package/dist/services/tool-providers/TIToolProvider.js.map +1 -0
  110. package/dist/services/tool-providers/TTSToolProvider.d.ts +143 -0
  111. package/dist/services/tool-providers/TTSToolProvider.d.ts.map +1 -0
  112. package/dist/services/tool-providers/TTSToolProvider.js +168 -0
  113. package/dist/services/tool-providers/TTSToolProvider.js.map +1 -0
  114. package/dist/services/tool-providers/ToolProviderRegistry.d.ts +165 -0
  115. package/dist/services/tool-providers/ToolProviderRegistry.d.ts.map +1 -0
  116. package/dist/services/tool-providers/ToolProviderRegistry.js +240 -0
  117. package/dist/services/tool-providers/ToolProviderRegistry.js.map +1 -0
  118. package/dist/services/tool-providers/index.d.ts +18 -0
  119. package/dist/services/tool-providers/index.d.ts.map +1 -0
  120. package/dist/services/tool-providers/index.js +15 -0
  121. package/dist/services/tool-providers/index.js.map +1 -0
  122. package/dist/services/tts/browser-provider.d.ts +24 -0
  123. package/dist/services/tts/browser-provider.d.ts.map +1 -0
  124. package/dist/services/tts/browser-provider.js +177 -0
  125. package/dist/services/tts/browser-provider.js.map +1 -0
  126. package/dist/services/tts/provider-interface.d.ts +131 -0
  127. package/dist/services/tts/provider-interface.d.ts.map +1 -0
  128. package/dist/services/tts/provider-interface.js +10 -0
  129. package/dist/services/tts/provider-interface.js.map +1 -0
  130. package/dist/tools/calculators/desmos-provider.d.ts +54 -0
  131. package/dist/tools/calculators/desmos-provider.d.ts.map +1 -0
  132. package/dist/tools/calculators/desmos-provider.js +384 -0
  133. package/dist/tools/calculators/desmos-provider.js.map +1 -0
  134. package/dist/tools/calculators/mathjs-provider.d.ts +50 -0
  135. package/dist/tools/calculators/mathjs-provider.d.ts.map +1 -0
  136. package/dist/tools/calculators/mathjs-provider.js +660 -0
  137. package/dist/tools/calculators/mathjs-provider.js.map +1 -0
  138. package/dist/tools/calculators/ti-provider.d.ts +122 -0
  139. package/dist/tools/calculators/ti-provider.d.ts.map +1 -0
  140. package/dist/tools/calculators/ti-provider.js +587 -0
  141. package/dist/tools/calculators/ti-provider.js.map +1 -0
  142. package/dist/tools/client.d.ts +17 -0
  143. package/dist/tools/client.d.ts.map +1 -0
  144. package/dist/tools/client.js +26 -0
  145. package/dist/tools/client.js.map +1 -0
  146. package/dist/tools/index.d.ts +8 -0
  147. package/dist/tools/index.d.ts.map +1 -0
  148. package/dist/tools/index.js +10 -0
  149. package/dist/tools/index.js.map +1 -0
  150. package/dist/tools/library-loader.d.ts +63 -0
  151. package/dist/tools/library-loader.d.ts.map +1 -0
  152. package/dist/tools/library-loader.js +292 -0
  153. package/dist/tools/library-loader.js.map +1 -0
  154. package/dist/tools/response-discovery.d.ts +72 -0
  155. package/dist/tools/response-discovery.d.ts.map +1 -0
  156. package/dist/tools/response-discovery.js +183 -0
  157. package/dist/tools/response-discovery.js.map +1 -0
  158. package/dist/tools/tool-coordinator.d.ts +76 -0
  159. package/dist/tools/tool-coordinator.d.ts.map +1 -0
  160. package/dist/tools/tool-coordinator.js +197 -0
  161. package/dist/tools/tool-coordinator.js.map +1 -0
  162. package/dist/tools/types.d.ts +427 -0
  163. package/dist/tools/types.d.ts.map +1 -0
  164. package/dist/tools/types.js +13 -0
  165. package/dist/tools/types.js.map +1 -0
  166. package/dist/tools/variant-resolver.d.ts +48 -0
  167. package/dist/tools/variant-resolver.d.ts.map +1 -0
  168. package/dist/tools/variant-resolver.js +214 -0
  169. package/dist/tools/variant-resolver.js.map +1 -0
  170. package/dist/types/events.d.ts +157 -0
  171. package/dist/types/events.d.ts.map +1 -0
  172. package/dist/types/events.js +11 -0
  173. package/dist/types/events.js.map +1 -0
  174. package/dist/utils/logger.d.ts +7 -0
  175. package/dist/utils/logger.d.ts.map +1 -0
  176. package/dist/utils/logger.js +11 -0
  177. package/dist/utils/logger.js.map +1 -0
  178. package/package.json +71 -0
  179. package/src/README.md +626 -0
  180. package/src/components/QuestionToolBar.svelte +406 -0
  181. package/src/player/AssessmentLayout.svelte +68 -0
  182. package/src/player/README.md +154 -0
  183. package/src/reference-layout/README.md +135 -0
  184. package/src/reference-layout/ReferenceLayout.svelte +209 -0
  185. package/src/reference-layout/components/AssessmentContent.svelte +259 -0
  186. package/src/reference-layout/components/AssessmentFooter.svelte +89 -0
  187. package/src/reference-layout/components/AssessmentHeader.svelte +250 -0
  188. package/src/reference-layout/components/AssessmentNavigation.svelte +134 -0
  189. package/src/reference-layout/components/AssessmentToolsBar.svelte +176 -0
  190. package/src/reference-layout/components/ItemPanel.svelte +200 -0
  191. package/src/reference-layout/components/NotesPanel.svelte +179 -0
  192. package/src/reference-layout/components/PassagePanel.svelte +76 -0
  193. package/src/tools/README.md +616 -0
  194. package/src/tools/calculators/README.md +395 -0
@@ -0,0 +1,250 @@
1
+ <script lang="ts">
2
+
3
+ import type { AssessmentEntity } from '@pie-players/pie-players-shared';
4
+ import ToolColorScheme from '@pie-players/pie-tool-color-scheme';
5
+ import ToolTextToSpeech from '@pie-players/pie-tool-text-to-speech';
6
+ import { onMount } from 'svelte';
7
+ import type { AssessmentPlayer } from '../../player/AssessmentPlayer';
8
+
9
+ let {
10
+ player,
11
+ assessment,
12
+ currentQuestion,
13
+ totalQuestions,
14
+ userName
15
+ }: {
16
+ player: AssessmentPlayer;
17
+ assessment: AssessmentEntity;
18
+ currentQuestion: number;
19
+ totalQuestions: number;
20
+ userName?: string;
21
+ } = $props();
22
+
23
+ let isFullscreen = $state(false);
24
+ const toolCoordinator = $derived(player.getToolCoordinator());
25
+
26
+ // Accommodation tool visibility
27
+ let showTTS = $state(false);
28
+ let showColorScheme = $state(false);
29
+ let flagged = $state(false);
30
+
31
+ function updateAccommodationVisibility() {
32
+ showTTS = toolCoordinator.isToolVisible('tts-accommodation');
33
+ showColorScheme = toolCoordinator.isToolVisible('colorScheme');
34
+ }
35
+
36
+ onMount(() => {
37
+ const unsubscribe = toolCoordinator.subscribe(() => {
38
+ updateAccommodationVisibility();
39
+ });
40
+ updateAccommodationVisibility();
41
+ return unsubscribe;
42
+ });
43
+
44
+ function toggleFullscreen() {
45
+ if (!document.fullscreenElement) {
46
+ document.documentElement.requestFullscreen().then(() => {
47
+ isFullscreen = true;
48
+ });
49
+ } else {
50
+ document.exitFullscreen().then(() => {
51
+ isFullscreen = false;
52
+ });
53
+ }
54
+ }
55
+
56
+ function handleAction(action: string) {
57
+ // Placeholder: host products can intercept by listening to toolkit events,
58
+ // or swap this component with their own header.
59
+ console.log('[AssessmentHeader] action:', action);
60
+ }
61
+ </script>
62
+
63
+ <header class="assessment-header">
64
+ <!-- Left: Assessment Title -->
65
+ <div class="header-left">
66
+ <h1 class="assessment-title">{assessment?.name || 'Assessment'}</h1>
67
+ <span class="question-count">{totalQuestions} Questions</span>
68
+ </div>
69
+
70
+ <!-- Center: Assessment-level actions -->
71
+ <div class="header-center">
72
+ <button class="action-button" onclick={() => handleAction('overall-instructions')}>
73
+ Overall Instructions
74
+ </button>
75
+ <button class="action-button" onclick={() => handleAction('resources')}>
76
+ Resources
77
+ </button>
78
+ <button class="action-button" onclick={() => handleAction('review-summary')}>
79
+ Review Summary
80
+ </button>
81
+ <button class="action-button" onclick={() => handleAction('finish-later')}>
82
+ Finish Later
83
+ </button>
84
+ <button
85
+ class="action-button flag"
86
+ class:active={flagged}
87
+ onclick={() => (flagged = !flagged)}
88
+ aria-pressed={flagged}
89
+ >
90
+ Flag Question
91
+ </button>
92
+ </div>
93
+
94
+ <!-- Right: User Info and Accommodations -->
95
+ <div class="header-right">
96
+ <span class="user-name">{userName || 'Student Name'}</span>
97
+ <div class="accommodations">
98
+ <!-- Audio (TTS) -->
99
+ <div class="accommodation-item">
100
+ <button
101
+ class="accommodation-button"
102
+ class:active={showTTS}
103
+ onclick={() => {
104
+ toolCoordinator.toggleTool('tts-accommodation');
105
+ updateAccommodationVisibility();
106
+ }}
107
+ aria-label="Toggle audio/text-to-speech"
108
+ >
109
+ Audio
110
+ </button>
111
+ </div>
112
+
113
+ <!-- Contrast -->
114
+ <div class="accommodation-item">
115
+ <button
116
+ class="accommodation-button"
117
+ class:active={showColorScheme}
118
+ onclick={() => {
119
+ toolCoordinator.toggleTool('colorScheme');
120
+ updateAccommodationVisibility();
121
+ }}
122
+ aria-label="Toggle color contrast"
123
+ >
124
+ Contrast
125
+ </button>
126
+ </div>
127
+
128
+ <!-- Fullscreen -->
129
+ <div class="accommodation-item">
130
+ <button
131
+ class="accommodation-button"
132
+ onclick={toggleFullscreen}
133
+ aria-label={isFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'}
134
+ >
135
+ Fullscreen
136
+ </button>
137
+ </div>
138
+ </div>
139
+ </div>
140
+ </header>
141
+
142
+ <!-- Render tools outside header (floating modals) -->
143
+ <ToolTextToSpeech visible={showTTS} toolId="tts-accommodation" coordinator={toolCoordinator} ttsService={player.getTTSService()} />
144
+ <ToolColorScheme visible={showColorScheme} toolId="colorScheme" coordinator={toolCoordinator} />
145
+
146
+ <style>
147
+ .assessment-header {
148
+ display: flex;
149
+ align-items: center;
150
+ justify-content: space-between;
151
+ padding: 0.75rem 1rem;
152
+ background-color: var(--pie-background, #ffffff);
153
+ border-bottom: 1px solid var(--pie-border, #e0e0e0);
154
+ min-height: 56px;
155
+ gap: 1rem;
156
+ }
157
+
158
+ .header-left {
159
+ display: flex;
160
+ flex-direction: column;
161
+ gap: 0.25rem;
162
+ min-width: 200px;
163
+ }
164
+
165
+ .header-center {
166
+ display: flex;
167
+ align-items: center;
168
+ gap: 0.5rem;
169
+ flex: 1;
170
+ justify-content: center;
171
+ min-width: 0;
172
+ }
173
+
174
+ .action-button {
175
+ padding: 0.5rem 0.75rem;
176
+ background-color: transparent;
177
+ border: 1px solid var(--pie-border, #e0e0e0);
178
+ border-radius: 4px;
179
+ cursor: pointer;
180
+ font-size: 0.875rem;
181
+ color: var(--pie-text, #000);
182
+ white-space: nowrap;
183
+ }
184
+
185
+ .action-button:hover {
186
+ background-color: var(--pie-secondary-background, #f5f5f5);
187
+ }
188
+
189
+ .action-button.flag.active {
190
+ background-color: var(--pie-primary, #3f51b5);
191
+ color: #fff;
192
+ border-color: var(--pie-primary, #3f51b5);
193
+ }
194
+
195
+ .assessment-title {
196
+ margin: 0;
197
+ font-size: 1.125rem;
198
+ font-weight: 600;
199
+ white-space: nowrap;
200
+ overflow: hidden;
201
+ text-overflow: ellipsis;
202
+ }
203
+
204
+ .question-count {
205
+ font-size: 0.875rem;
206
+ color: var(--pie-text-secondary, #666);
207
+ }
208
+
209
+ .header-right {
210
+ display: flex;
211
+ align-items: center;
212
+ gap: 1rem;
213
+ min-width: 300px;
214
+ justify-content: flex-end;
215
+ }
216
+
217
+ .user-name {
218
+ font-size: 0.875rem;
219
+ color: var(--pie-text, #000);
220
+ white-space: nowrap;
221
+ }
222
+
223
+ .accommodations {
224
+ display: flex;
225
+ gap: 0.5rem;
226
+ align-items: center;
227
+ }
228
+
229
+ .accommodation-item {
230
+ display: flex;
231
+ flex-direction: column;
232
+ align-items: center;
233
+ gap: 0.25rem;
234
+ }
235
+
236
+ .accommodation-button {
237
+ padding: 0.5rem;
238
+ background-color: transparent;
239
+ border: 1px solid var(--pie-border, #e0e0e0);
240
+ border-radius: 4px;
241
+ cursor: pointer;
242
+ font-size: 0.875rem;
243
+ color: var(--pie-text, #000);
244
+ }
245
+
246
+ .accommodation-button:hover {
247
+ background-color: var(--pie-secondary-background, #f5f5f5);
248
+ }
249
+ </style>
250
+
@@ -0,0 +1,134 @@
1
+ <script lang="ts">
2
+
3
+
4
+ import type { AssessmentPlayer } from '../../player/AssessmentPlayer';
5
+ import type { QuestionRef } from '../../player/qti-navigation';
6
+
7
+ let {
8
+ player,
9
+ currentIndex,
10
+ totalItems
11
+ }: {
12
+ player: AssessmentPlayer;
13
+ currentIndex: number;
14
+ totalItems: number;
15
+ } = $props();
16
+
17
+ function handleQuestionClick(index: number) {
18
+ player.navigate(index);
19
+ }
20
+
21
+ function isQuestionActive(index: number) {
22
+ return index === currentIndex;
23
+ }
24
+
25
+ const questionRefs = $derived.by<QuestionRef[]>(() => {
26
+ // best-effort: not all player implementations expose this, but our default does.
27
+ return (player as any)?.getQuestionRefs?.() ?? [];
28
+ });
29
+
30
+ // Simple “SchoolCity-ish” grouping: groups of 5 (1-5, 6-10, ...)
31
+ const groups = $derived.by(() => {
32
+ const n = totalItems ?? questionRefs.length ?? 0;
33
+ const size = 5;
34
+ const out: Array<{ start: number; end: number; label: string }> = [];
35
+ for (let i = 0; i < n; i += size) {
36
+ const start = i + 1;
37
+ const end = Math.min(n, i + size);
38
+ out.push({ start, end, label: start === end ? `${start}` : `${start}-${end}` });
39
+ }
40
+ return out;
41
+ });
42
+ </script>
43
+
44
+ <nav class="assessment-navigation">
45
+ <div class="navigation-content">
46
+ <!-- Dropdown (placeholder; sections can be layered in later) -->
47
+ <select
48
+ class="question-select"
49
+ onchange={(e) => {
50
+ const v = Number.parseInt((e.target as HTMLSelectElement).value, 10);
51
+ if (Number.isFinite(v)) handleQuestionClick(v);
52
+ }}
53
+ aria-label="All questions"
54
+ >
55
+ <option value={currentIndex}>All Questions</option>
56
+ {#each Array.from({ length: totalItems }) as _, i (i)}
57
+ <option value={i}>Question {i + 1}</option>
58
+ {/each}
59
+ </select>
60
+
61
+ <!-- Range buttons -->
62
+ <div class="range-buttons" aria-label="Question ranges">
63
+ {#each groups as g (g.label)}
64
+ <button
65
+ class="range-button"
66
+ class:active={currentIndex + 1 >= g.start && currentIndex + 1 <= g.end}
67
+ onclick={() => handleQuestionClick(g.start - 1)}
68
+ aria-label="Navigate to questions {g.label}"
69
+ >
70
+ {g.label}
71
+ </button>
72
+ {/each}
73
+ </div>
74
+ </div>
75
+ </nav>
76
+
77
+ <style>
78
+ .assessment-navigation {
79
+ display: flex;
80
+ align-items: center;
81
+ justify-content: flex-start;
82
+ padding: 0.5rem 1rem;
83
+ background-color: var(--pie-background, #ffffff);
84
+ border-bottom: 1px solid var(--pie-border, #e0e0e0);
85
+ gap: 0.5rem;
86
+ }
87
+
88
+ .navigation-content {
89
+ display: grid;
90
+ grid-template-columns: auto 1fr;
91
+ gap: 0.75rem;
92
+ align-items: center;
93
+ width: 100%;
94
+ }
95
+
96
+ .question-select {
97
+ padding: 0.5rem 0.75rem;
98
+ border: 1px solid var(--pie-border, #e0e0e0);
99
+ border-radius: 4px;
100
+ background: var(--pie-background, #fff);
101
+ font-size: 0.875rem;
102
+ }
103
+
104
+ .range-buttons {
105
+ display: flex;
106
+ gap: 0.5rem;
107
+ overflow-x: auto;
108
+ flex: 1;
109
+ }
110
+
111
+ .range-button {
112
+ padding: 0.5rem 0.75rem;
113
+ background-color: var(--pie-background, #ffffff);
114
+ border: 1px solid var(--pie-border, #e0e0e0);
115
+ border-radius: 4px;
116
+ cursor: pointer;
117
+ font-size: 0.875rem;
118
+ color: var(--pie-text, #000);
119
+ white-space: nowrap;
120
+ min-width: 56px;
121
+ text-align: center;
122
+ }
123
+
124
+ .range-button:hover {
125
+ background-color: var(--pie-secondary-background, #f5f5f5);
126
+ }
127
+
128
+ .range-button.active {
129
+ background-color: var(--pie-primary, #3f51b5);
130
+ color: white;
131
+ border-color: var(--pie-primary, #3f51b5);
132
+ }
133
+ </style>
134
+
@@ -0,0 +1,176 @@
1
+ <script lang="ts">
2
+ import { onDestroy, onMount } from 'svelte';
3
+ import ToolCalculator from '$lib/tags/tool-calculator/tool-calculator.svelte';
4
+ import ToolGraph from '$lib/tags/tool-graph/tool-graph.svelte';
5
+ import ToolLineReader from '$lib/tags/tool-line-reader/tool-line-reader.svelte';
6
+ import ToolMagnifier from '$lib/tags/tool-magnifier/tool-magnifier.svelte';
7
+ import ToolPeriodicTable from '$lib/tags/tool-periodic-table/tool-periodic-table.svelte';
8
+ import ToolProtractor from '$lib/tags/tool-protractor/tool-protractor.svelte';
9
+ import ToolRuler from '$lib/tags/tool-ruler/tool-ruler.svelte';
10
+ import type { AssessmentPlayer } from '../../player/AssessmentPlayer';
11
+
12
+ let {
13
+ player,
14
+ enabledTools = [
15
+ 'calculator',
16
+ 'graph',
17
+ 'periodicTable',
18
+ 'protractor',
19
+ 'lineReader',
20
+ 'magnifier',
21
+ 'ruler'
22
+ ]
23
+ }: {
24
+ player: AssessmentPlayer;
25
+ enabledTools?: string[];
26
+ } = $props();
27
+
28
+ const toolCoordinator = $derived(player.getToolCoordinator());
29
+
30
+ // Tool visibility state
31
+ let showCalculator = $state(false);
32
+ let showGraph = $state(false);
33
+ let showPeriodicTable = $state(false);
34
+ let showProtractor = $state(false);
35
+ let showLineReader = $state(false);
36
+ let showMagnifier = $state(false);
37
+ let showRuler = $state(false);
38
+
39
+ // Update visibility state from coordinator
40
+ function updateToolVisibility() {
41
+ if (!toolCoordinator) return;
42
+ showCalculator = toolCoordinator.isToolVisible('calculator');
43
+ showGraph = toolCoordinator.isToolVisible('graph');
44
+ showPeriodicTable = toolCoordinator.isToolVisible('periodicTable');
45
+ showProtractor = toolCoordinator.isToolVisible('protractor');
46
+ showLineReader = toolCoordinator.isToolVisible('lineReader');
47
+ showMagnifier = toolCoordinator.isToolVisible('magnifier');
48
+ showRuler = toolCoordinator.isToolVisible('ruler');
49
+ }
50
+
51
+ function toggleTool(toolId: string) {
52
+ toolCoordinator.toggleTool(toolId);
53
+ // Update visibility after toggle
54
+ updateToolVisibility();
55
+ }
56
+
57
+ // Subscribe to tool coordinator changes
58
+ let unsubscribe: (() => void) | null = null;
59
+
60
+ onMount(() => {
61
+ if (toolCoordinator) {
62
+ updateToolVisibility();
63
+ unsubscribe = toolCoordinator.subscribe(() => {
64
+ updateToolVisibility();
65
+ });
66
+ }
67
+ });
68
+
69
+ onDestroy(() => {
70
+ unsubscribe?.();
71
+ });
72
+
73
+ const toolButtons = $derived([
74
+ { id: 'calculator', label: 'Calculator', icon: '#', visible: showCalculator, enabled: enabledTools.includes('calculator') },
75
+ { id: 'graph', label: 'Graph', icon: '📈', visible: showGraph, enabled: enabledTools.includes('graph') },
76
+ { id: 'periodicTable', label: 'Periodic Table', icon: '△', visible: showPeriodicTable, enabled: enabledTools.includes('periodicTable') },
77
+ { id: 'protractor', label: 'Protractor', icon: '∠', visible: showProtractor, enabled: enabledTools.includes('protractor') },
78
+ { id: 'lineReader', label: 'Line Reader', icon: '—', visible: showLineReader, enabled: enabledTools.includes('lineReader') },
79
+ { id: 'magnifier', label: 'Text Magnifier', icon: '🔍', visible: showMagnifier, enabled: enabledTools.includes('magnifier') },
80
+ { id: 'ruler', label: 'Ruler', icon: '📏', visible: showRuler, enabled: enabledTools.includes('ruler') }
81
+ ]);
82
+ </script>
83
+
84
+ <div class="assessment-tools-bar">
85
+ <div class="tools-buttons">
86
+ {#each toolButtons as tool}
87
+ {#if tool.enabled}
88
+ <button
89
+ class="tool-button"
90
+ class:active={tool.visible}
91
+ onclick={() => toggleTool(tool.id)}
92
+ title={tool.label}
93
+ aria-label={tool.label}
94
+ aria-pressed={tool.visible}
95
+ >
96
+ <span class="tool-icon">{tool.icon}</span>
97
+ <span class="tool-label">{tool.label}</span>
98
+ </button>
99
+ {/if}
100
+ {/each}
101
+ </div>
102
+ </div>
103
+
104
+ <!-- Tool Instances - Rendered outside bar for floating modals/overlays -->
105
+ {#if enabledTools.includes('calculator')}
106
+ <ToolCalculator visible={showCalculator} toolId="calculator" coordinator={toolCoordinator} desmosProvider={player.getDesmosProvider()} tiProvider={player.getTIProvider()} />
107
+ {/if}
108
+ {#if enabledTools.includes('graph')}
109
+ <ToolGraph visible={showGraph} toolId="graph" coordinator={toolCoordinator} />
110
+ {/if}
111
+ {#if enabledTools.includes('periodicTable')}
112
+ <ToolPeriodicTable visible={showPeriodicTable} toolId="periodicTable" coordinator={toolCoordinator} />
113
+ {/if}
114
+ {#if enabledTools.includes('protractor')}
115
+ <ToolProtractor visible={showProtractor} toolId="protractor" coordinator={toolCoordinator} />
116
+ {/if}
117
+ {#if enabledTools.includes('lineReader')}
118
+ <ToolLineReader visible={showLineReader} toolId="lineReader" coordinator={toolCoordinator} />
119
+ {/if}
120
+ {#if enabledTools.includes('magnifier')}
121
+ <ToolMagnifier visible={showMagnifier} toolId="magnifier" coordinator={toolCoordinator} />
122
+ {/if}
123
+ {#if enabledTools.includes('ruler')}
124
+ <ToolRuler visible={showRuler} toolId="ruler" coordinator={toolCoordinator} />
125
+ {/if}
126
+
127
+ <style>
128
+ .assessment-tools-bar {
129
+ display: flex;
130
+ align-items: center;
131
+ padding: 0.75rem 1rem;
132
+ background-color: var(--pie-background, #ffffff);
133
+ border-top: 1px solid var(--pie-border, #e0e0e0);
134
+ min-height: 60px;
135
+ }
136
+
137
+ .tools-buttons {
138
+ display: flex;
139
+ gap: 0.5rem;
140
+ flex-wrap: wrap;
141
+ align-items: center;
142
+ }
143
+
144
+ .tool-button {
145
+ display: flex;
146
+ align-items: center;
147
+ gap: 0.5rem;
148
+ padding: 0.5rem 1rem;
149
+ background-color: var(--pie-background, #ffffff);
150
+ border: 1px solid var(--pie-border, #e0e0e0);
151
+ border-radius: 4px;
152
+ cursor: pointer;
153
+ font-size: 0.875rem;
154
+ color: var(--pie-text, #000);
155
+ white-space: nowrap;
156
+ }
157
+
158
+ .tool-button:hover {
159
+ background-color: var(--pie-secondary-background, #f5f5f5);
160
+ }
161
+
162
+ .tool-button.active {
163
+ background-color: var(--pie-primary, #3f51b5);
164
+ color: white;
165
+ border-color: var(--pie-primary, #3f51b5);
166
+ }
167
+
168
+ .tool-icon {
169
+ font-size: 1rem;
170
+ }
171
+
172
+ .tool-label {
173
+ font-size: 0.875rem;
174
+ }
175
+ </style>
176
+