@pie-players/pie-assessment-toolkit 0.2.2 → 0.2.4
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 +159 -1
- package/dist/index.d.ts +16 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -2
- package/dist/index.js.map +1 -1
- package/dist/player/AssessmentPlayer.d.ts +9 -0
- package/dist/player/AssessmentPlayer.d.ts.map +1 -1
- package/dist/player/AssessmentPlayer.js +13 -4
- package/dist/player/AssessmentPlayer.js.map +1 -1
- package/dist/services/AnnotationToolbarConfig.d.ts +134 -0
- package/dist/services/AnnotationToolbarConfig.d.ts.map +1 -0
- package/dist/services/AnnotationToolbarConfig.js +100 -0
- package/dist/services/AnnotationToolbarConfig.js.map +1 -0
- package/dist/services/HighlightCoordinator.d.ts +23 -10
- package/dist/services/HighlightCoordinator.d.ts.map +1 -1
- package/dist/services/HighlightCoordinator.js +87 -40
- package/dist/services/HighlightCoordinator.js.map +1 -1
- package/dist/services/PNPToolResolver.d.ts +171 -15
- package/dist/services/PNPToolResolver.d.ts.map +1 -1
- package/dist/services/PNPToolResolver.js +324 -32
- package/dist/services/PNPToolResolver.js.map +1 -1
- package/dist/services/RangeSerializer.d.ts +119 -0
- package/dist/services/RangeSerializer.d.ts.map +1 -0
- package/dist/services/RangeSerializer.js +199 -0
- package/dist/services/RangeSerializer.js.map +1 -0
- package/dist/services/TTSService.d.ts +4 -1
- package/dist/services/TTSService.d.ts.map +1 -1
- package/dist/services/TTSService.js +58 -3
- package/dist/services/TTSService.js.map +1 -1
- package/dist/services/ToolRegistry.d.ts +225 -0
- package/dist/services/ToolRegistry.d.ts.map +1 -0
- package/dist/services/ToolRegistry.js +248 -0
- package/dist/services/ToolRegistry.js.map +1 -0
- package/dist/services/ToolkitCoordinator.d.ts +22 -0
- package/dist/services/ToolkitCoordinator.d.ts.map +1 -1
- package/dist/services/ToolkitCoordinator.js +44 -0
- package/dist/services/ToolkitCoordinator.js.map +1 -1
- package/dist/services/createDefaultToolRegistry.d.ts +72 -0
- package/dist/services/createDefaultToolRegistry.d.ts.map +1 -0
- package/dist/services/createDefaultToolRegistry.js +147 -0
- package/dist/services/createDefaultToolRegistry.js.map +1 -0
- package/dist/services/interfaces.d.ts +8 -0
- package/dist/services/interfaces.d.ts.map +1 -1
- package/dist/services/interfaces.js.map +1 -1
- package/dist/services/pnp-provenance.d.ts +167 -0
- package/dist/services/pnp-provenance.d.ts.map +1 -0
- package/dist/services/pnp-provenance.js +230 -0
- package/dist/services/pnp-provenance.js.map +1 -0
- package/dist/services/pnp-standard-features.d.ts +240 -0
- package/dist/services/pnp-standard-features.d.ts.map +1 -0
- package/dist/services/pnp-standard-features.js +342 -0
- package/dist/services/pnp-standard-features.js.map +1 -0
- package/dist/services/tool-context.d.ts +150 -0
- package/dist/services/tool-context.d.ts.map +1 -0
- package/dist/services/tool-context.js +157 -0
- package/dist/services/tool-context.js.map +1 -0
- package/dist/services/tool-providers/ToolProviderRegistry.d.ts.map +1 -1
- package/dist/services/tool-providers/ToolProviderRegistry.js +7 -7
- package/dist/services/tool-providers/ToolProviderRegistry.js.map +1 -1
- package/dist/tools/registrations/accessibility-tools.d.ts +43 -0
- package/dist/tools/registrations/accessibility-tools.d.ts.map +1 -0
- package/dist/tools/registrations/accessibility-tools.js +230 -0
- package/dist/tools/registrations/accessibility-tools.js.map +1 -0
- package/dist/tools/registrations/calculator.d.ts +21 -0
- package/dist/tools/registrations/calculator.d.ts.map +1 -0
- package/dist/tools/registrations/calculator.js +98 -0
- package/dist/tools/registrations/calculator.js.map +1 -0
- package/dist/tools/registrations/interaction-tools.d.ts +28 -0
- package/dist/tools/registrations/interaction-tools.d.ts.map +1 -0
- package/dist/tools/registrations/interaction-tools.js +124 -0
- package/dist/tools/registrations/interaction-tools.js.map +1 -0
- package/dist/tools/registrations/measurement-tools.d.ts +25 -0
- package/dist/tools/registrations/measurement-tools.d.ts.map +1 -0
- package/dist/tools/registrations/measurement-tools.js +112 -0
- package/dist/tools/registrations/measurement-tools.js.map +1 -0
- package/dist/tools/registrations/subject-specific-tools.d.ts +28 -0
- package/dist/tools/registrations/subject-specific-tools.d.ts.map +1 -0
- package/dist/tools/registrations/subject-specific-tools.js +118 -0
- package/dist/tools/registrations/subject-specific-tools.js.map +1 -0
- package/dist/tools/registrations/tts.d.ts +20 -0
- package/dist/tools/registrations/tts.d.ts.map +1 -0
- package/dist/tools/registrations/tts.js +86 -0
- package/dist/tools/registrations/tts.js.map +1 -0
- package/dist/tools/tool-tag-map.d.ts +24 -0
- package/dist/tools/tool-tag-map.d.ts.map +1 -0
- package/dist/tools/tool-tag-map.js +38 -0
- package/dist/tools/tool-tag-map.js.map +1 -0
- package/package.json +15 -7
- package/src/README.md +25 -11
- package/src/components/PNPProfileTester.example.svelte +188 -0
- package/src/components/PNPProfileTester.svelte +397 -0
- package/src/components/PNPProvenanceViewer.svelte +260 -0
- package/src/components/QuestionToolBar.svelte +160 -83
- package/src/components/ToolButton.svelte +146 -0
- package/src/components/ToolButtonGroup.svelte +109 -0
- package/src/reference-layout/ReferenceLayout.svelte +5 -13
- package/dist/services/PNPMapper.d.ts +0 -71
- package/dist/services/PNPMapper.d.ts.map +0 -1
- package/dist/services/PNPMapper.js +0 -98
- package/dist/services/PNPMapper.js.map +0 -1
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { PNPResolutionProvenance } from '../services/pnp-provenance';
|
|
3
|
+
|
|
4
|
+
// PNP Provenance Viewer Component
|
|
5
|
+
// Visualizes how PNP resolution decisions were made.
|
|
6
|
+
// See PNP_TESTING.md for usage documentation.
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
provenance: PNPResolutionProvenance;
|
|
10
|
+
compact?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let { provenance, compact = false }: Props = $props();
|
|
14
|
+
|
|
15
|
+
let features = $derived(Array.from(provenance.features.values()));
|
|
16
|
+
let enabledFeatures = $derived(features.filter((f) => f.finalState === 'enabled'));
|
|
17
|
+
let blockedFeatures = $derived(features.filter((f) => f.finalState === 'blocked'));
|
|
18
|
+
|
|
19
|
+
function getRuleBadgeClass(rule: string): string {
|
|
20
|
+
const classes: Record<string, string> = {
|
|
21
|
+
'district-block': 'badge-error',
|
|
22
|
+
'test-admin-override': 'badge-warning',
|
|
23
|
+
'item-restriction': 'badge-info',
|
|
24
|
+
'item-requirement': 'badge-success',
|
|
25
|
+
'district-requirement': 'badge-success',
|
|
26
|
+
'pnp-support': 'badge-primary',
|
|
27
|
+
'system-default': 'badge-ghost'
|
|
28
|
+
};
|
|
29
|
+
return classes[rule] || 'badge-ghost';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getActionIcon(action: string): string {
|
|
33
|
+
const icons: Record<string, string> = {
|
|
34
|
+
enable: '✓',
|
|
35
|
+
block: '✗',
|
|
36
|
+
skip: '○'
|
|
37
|
+
};
|
|
38
|
+
return icons[action] || '?';
|
|
39
|
+
}
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<div class="pnp-provenance-viewer" class:compact>
|
|
43
|
+
<!-- Summary -->
|
|
44
|
+
<div class="card bg-base-100 shadow-sm mb-4">
|
|
45
|
+
<div class="card-body">
|
|
46
|
+
<h3 class="card-title text-sm">Resolution Summary</h3>
|
|
47
|
+
<div class="stats stats-vertical lg:stats-horizontal shadow">
|
|
48
|
+
<div class="stat">
|
|
49
|
+
<div class="stat-title">Total Features</div>
|
|
50
|
+
<div class="stat-value text-2xl">{provenance.summary.totalFeatures}</div>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="stat">
|
|
53
|
+
<div class="stat-title">Enabled</div>
|
|
54
|
+
<div class="stat-value text-2xl text-success">{provenance.summary.enabled}</div>
|
|
55
|
+
</div>
|
|
56
|
+
<div class="stat">
|
|
57
|
+
<div class="stat-title">Blocked</div>
|
|
58
|
+
<div class="stat-value text-2xl text-error">{provenance.summary.blocked}</div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<div class="text-xs text-base-content/70 mt-2">
|
|
63
|
+
Resolved at: {provenance.resolvedAt.toLocaleString()}
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<!-- Configuration Sources -->
|
|
69
|
+
{#if !compact}
|
|
70
|
+
<div class="card bg-base-100 shadow-sm mb-4">
|
|
71
|
+
<div class="card-body">
|
|
72
|
+
<h3 class="card-title text-sm">Configuration Sources</h3>
|
|
73
|
+
<div class="space-y-2">
|
|
74
|
+
{#each Object.entries(provenance.sources) as [type, source]}
|
|
75
|
+
<div class="flex items-center gap-2">
|
|
76
|
+
<div class="badge badge-outline">{type}</div>
|
|
77
|
+
<div class="text-sm">
|
|
78
|
+
{source.name || source.id}
|
|
79
|
+
<span class="text-xs text-base-content/50">({source.id})</span>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
{/each}
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
{/if}
|
|
87
|
+
|
|
88
|
+
<!-- Feature Resolution -->
|
|
89
|
+
<div class="space-y-4">
|
|
90
|
+
<!-- Enabled Features -->
|
|
91
|
+
{#if enabledFeatures.length > 0}
|
|
92
|
+
<div class="card bg-base-100 shadow-sm">
|
|
93
|
+
<div class="card-body">
|
|
94
|
+
<h3 class="card-title text-sm text-success">
|
|
95
|
+
Enabled Features ({enabledFeatures.length})
|
|
96
|
+
</h3>
|
|
97
|
+
<div class="space-y-3">
|
|
98
|
+
{#each enabledFeatures as feature}
|
|
99
|
+
<div class="border-l-4 border-success pl-3">
|
|
100
|
+
<div class="flex items-center gap-2 mb-1">
|
|
101
|
+
<code class="text-sm font-semibold">{feature.featureId}</code>
|
|
102
|
+
{#if feature.winningDecision}
|
|
103
|
+
<div class="badge {getRuleBadgeClass(feature.winningDecision.rule)} badge-sm">
|
|
104
|
+
{feature.winningDecision.rule}
|
|
105
|
+
</div>
|
|
106
|
+
{/if}
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
{#if !compact}
|
|
110
|
+
<div class="text-sm text-base-content/80 whitespace-pre-wrap">
|
|
111
|
+
{feature.explanation}
|
|
112
|
+
</div>
|
|
113
|
+
{:else if feature.winningDecision}
|
|
114
|
+
<div class="text-xs text-base-content/70">
|
|
115
|
+
{feature.winningDecision.reason}
|
|
116
|
+
</div>
|
|
117
|
+
{/if}
|
|
118
|
+
|
|
119
|
+
{#if !compact && feature.allDecisions.length > 1}
|
|
120
|
+
<details class="mt-2">
|
|
121
|
+
<summary class="text-xs cursor-pointer text-base-content/60 hover:text-base-content">
|
|
122
|
+
Show all decisions ({feature.allDecisions.length})
|
|
123
|
+
</summary>
|
|
124
|
+
<div class="ml-4 mt-2 space-y-1">
|
|
125
|
+
{#each feature.allDecisions as decision}
|
|
126
|
+
<div class="text-xs flex items-center gap-2">
|
|
127
|
+
<span class="opacity-50">{decision.step}.</span>
|
|
128
|
+
<span class="badge badge-xs {getRuleBadgeClass(decision.rule)}">
|
|
129
|
+
{decision.rule}
|
|
130
|
+
</span>
|
|
131
|
+
<span>{getActionIcon(decision.action)}</span>
|
|
132
|
+
<span class="opacity-70">{decision.reason}</span>
|
|
133
|
+
</div>
|
|
134
|
+
{/each}
|
|
135
|
+
</div>
|
|
136
|
+
</details>
|
|
137
|
+
{/if}
|
|
138
|
+
</div>
|
|
139
|
+
{/each}
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
{/if}
|
|
144
|
+
|
|
145
|
+
<!-- Blocked Features -->
|
|
146
|
+
{#if blockedFeatures.length > 0}
|
|
147
|
+
<div class="card bg-base-100 shadow-sm">
|
|
148
|
+
<div class="card-body">
|
|
149
|
+
<h3 class="card-title text-sm text-error">
|
|
150
|
+
Blocked Features ({blockedFeatures.length})
|
|
151
|
+
</h3>
|
|
152
|
+
<div class="space-y-3">
|
|
153
|
+
{#each blockedFeatures as feature}
|
|
154
|
+
<div class="border-l-4 border-error pl-3">
|
|
155
|
+
<div class="flex items-center gap-2 mb-1">
|
|
156
|
+
<code class="text-sm font-semibold">{feature.featureId}</code>
|
|
157
|
+
{#if feature.winningDecision}
|
|
158
|
+
<div class="badge {getRuleBadgeClass(feature.winningDecision.rule)} badge-sm">
|
|
159
|
+
{feature.winningDecision.rule}
|
|
160
|
+
</div>
|
|
161
|
+
{/if}
|
|
162
|
+
</div>
|
|
163
|
+
|
|
164
|
+
{#if !compact}
|
|
165
|
+
<div class="text-sm text-base-content/80 whitespace-pre-wrap">
|
|
166
|
+
{feature.explanation}
|
|
167
|
+
</div>
|
|
168
|
+
{:else if feature.winningDecision}
|
|
169
|
+
<div class="text-xs text-base-content/70">
|
|
170
|
+
{feature.winningDecision.reason}
|
|
171
|
+
</div>
|
|
172
|
+
{/if}
|
|
173
|
+
|
|
174
|
+
{#if !compact && feature.allDecisions.length > 1}
|
|
175
|
+
<details class="mt-2">
|
|
176
|
+
<summary class="text-xs cursor-pointer text-base-content/60 hover:text-base-content">
|
|
177
|
+
Show all decisions ({feature.allDecisions.length})
|
|
178
|
+
</summary>
|
|
179
|
+
<div class="ml-4 mt-2 space-y-1">
|
|
180
|
+
{#each feature.allDecisions as decision}
|
|
181
|
+
<div class="text-xs flex items-center gap-2">
|
|
182
|
+
<span class="opacity-50">{decision.step}.</span>
|
|
183
|
+
<span class="badge badge-xs {getRuleBadgeClass(decision.rule)}">
|
|
184
|
+
{decision.rule}
|
|
185
|
+
</span>
|
|
186
|
+
<span>{getActionIcon(decision.action)}</span>
|
|
187
|
+
<span class="opacity-70">{decision.reason}</span>
|
|
188
|
+
</div>
|
|
189
|
+
{/each}
|
|
190
|
+
</div>
|
|
191
|
+
</details>
|
|
192
|
+
{/if}
|
|
193
|
+
</div>
|
|
194
|
+
{/each}
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
{/if}
|
|
199
|
+
</div>
|
|
200
|
+
|
|
201
|
+
<!-- Decision Log (collapsed by default) -->
|
|
202
|
+
{#if !compact && provenance.decisionLog.length > 0}
|
|
203
|
+
<details class="mt-4">
|
|
204
|
+
<summary class="cursor-pointer text-sm font-semibold text-base-content/80 hover:text-base-content">
|
|
205
|
+
Complete Decision Log ({provenance.decisionLog.length} steps)
|
|
206
|
+
</summary>
|
|
207
|
+
<div class="card bg-base-100 shadow-sm mt-2">
|
|
208
|
+
<div class="card-body">
|
|
209
|
+
<div class="space-y-2">
|
|
210
|
+
{#each provenance.decisionLog as decision}
|
|
211
|
+
<div class="flex items-start gap-3 text-xs">
|
|
212
|
+
<div class="font-mono text-base-content/50 w-8">
|
|
213
|
+
{decision.step}.
|
|
214
|
+
</div>
|
|
215
|
+
<div class="flex-1">
|
|
216
|
+
<div class="flex items-center gap-2 mb-1">
|
|
217
|
+
<span class="badge {getRuleBadgeClass(decision.rule)} badge-sm">
|
|
218
|
+
{decision.rule}
|
|
219
|
+
</span>
|
|
220
|
+
<span class="font-semibold">{decision.featureId}</span>
|
|
221
|
+
<span class={decision.action === 'enable' ? 'text-success' : decision.action === 'block' ? 'text-error' : 'text-base-content/50'}>
|
|
222
|
+
{getActionIcon(decision.action)} {decision.action}
|
|
223
|
+
</span>
|
|
224
|
+
</div>
|
|
225
|
+
<div class="text-base-content/70">
|
|
226
|
+
{decision.reason}
|
|
227
|
+
</div>
|
|
228
|
+
<div class="text-base-content/50 mt-1">
|
|
229
|
+
Source: {decision.source.name || decision.source.type}
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
{/each}
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
</details>
|
|
238
|
+
{/if}
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
<style>
|
|
242
|
+
.pnp-provenance-viewer {
|
|
243
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.pnp-provenance-viewer code {
|
|
247
|
+
background-color: hsl(var(--b2));
|
|
248
|
+
padding: 0.125rem 0.375rem;
|
|
249
|
+
border-radius: 0.25rem;
|
|
250
|
+
font-size: 0.875em;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.pnp-provenance-viewer.compact {
|
|
254
|
+
font-size: 0.875rem;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.pnp-provenance-viewer.compact .card-body {
|
|
258
|
+
padding: 1rem;
|
|
259
|
+
}
|
|
260
|
+
</style>
|
|
@@ -15,6 +15,11 @@
|
|
|
15
15
|
highlightCoordinator: { type: 'Object', reflect: false },
|
|
16
16
|
scopeElement: { type: 'Object', reflect: false },
|
|
17
17
|
elementToolStateStore: { type: 'Object', reflect: false },
|
|
18
|
+
toolRegistry: { type: 'Object', reflect: false },
|
|
19
|
+
pnpResolver: { type: 'Object', reflect: false },
|
|
20
|
+
assessment: { type: 'Object', reflect: false },
|
|
21
|
+
itemRef: { type: 'Object', reflect: false },
|
|
22
|
+
item: { type: 'Object', reflect: false },
|
|
18
23
|
|
|
19
24
|
// IDs for element-level state (passed as JS properties, not attributes)
|
|
20
25
|
assessmentId: { type: 'String', reflect: false },
|
|
@@ -26,11 +31,10 @@
|
|
|
26
31
|
<!--
|
|
27
32
|
QuestionToolBar - Inline toolbar for question/passage headers
|
|
28
33
|
|
|
29
|
-
|
|
30
|
-
horizontal layout suitable for embedding in question headers.
|
|
34
|
+
Now uses ToolButtonGroup with registry-based two-pass visibility model.
|
|
31
35
|
|
|
32
|
-
|
|
33
|
-
|
|
36
|
+
If toolRegistry is provided, uses new architecture.
|
|
37
|
+
Otherwise, falls back to legacy hardcoded buttons.
|
|
34
38
|
-->
|
|
35
39
|
<script lang="ts">
|
|
36
40
|
import type {
|
|
@@ -38,13 +42,15 @@
|
|
|
38
42
|
ITTSService,
|
|
39
43
|
IHighlightCoordinator,
|
|
40
44
|
} from '../services/interfaces';
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
import type { ToolRegistry } from '../services/ToolRegistry';
|
|
46
|
+
import type { PNPToolResolver } from '../services/PNPToolResolver';
|
|
47
|
+
import type {
|
|
48
|
+
AssessmentEntity,
|
|
49
|
+
AssessmentItemRef,
|
|
50
|
+
ItemEntity,
|
|
51
|
+
} from '@pie-players/pie-players-shared/types';
|
|
52
|
+
import type { ItemToolContext } from '../services/tool-context';
|
|
53
|
+
import ToolButtonGroup from './ToolButtonGroup.svelte';
|
|
48
54
|
|
|
49
55
|
const isBrowser = typeof window !== 'undefined';
|
|
50
56
|
|
|
@@ -75,12 +81,17 @@
|
|
|
75
81
|
let {
|
|
76
82
|
itemId = '',
|
|
77
83
|
catalogId = '',
|
|
78
|
-
tools = 'calculator,
|
|
84
|
+
tools = 'calculator,textToSpeech,answerEliminator',
|
|
79
85
|
ttsService,
|
|
80
86
|
toolCoordinator,
|
|
81
87
|
highlightCoordinator,
|
|
82
88
|
scopeElement = null,
|
|
83
89
|
elementToolStateStore,
|
|
90
|
+
toolRegistry = null,
|
|
91
|
+
pnpResolver = null,
|
|
92
|
+
assessment = null,
|
|
93
|
+
itemRef = null,
|
|
94
|
+
item = null,
|
|
84
95
|
assessmentId = '',
|
|
85
96
|
sectionId = '',
|
|
86
97
|
class: className = '',
|
|
@@ -95,6 +106,11 @@
|
|
|
95
106
|
highlightCoordinator?: IHighlightCoordinator;
|
|
96
107
|
scopeElement?: HTMLElement | null;
|
|
97
108
|
elementToolStateStore?: any;
|
|
109
|
+
toolRegistry?: ToolRegistry | null;
|
|
110
|
+
pnpResolver?: PNPToolResolver | null;
|
|
111
|
+
assessment?: AssessmentEntity | null;
|
|
112
|
+
itemRef?: AssessmentItemRef | null;
|
|
113
|
+
item?: ItemEntity | null;
|
|
98
114
|
assessmentId?: string;
|
|
99
115
|
sectionId?: string;
|
|
100
116
|
class?: string;
|
|
@@ -109,7 +125,46 @@
|
|
|
109
125
|
// Note: Using itemId as elementId since toolbar is scoped to one item/element
|
|
110
126
|
});
|
|
111
127
|
|
|
112
|
-
//
|
|
128
|
+
// Determine if using new registry-based architecture
|
|
129
|
+
const useRegistryArchitecture = $derived(
|
|
130
|
+
!!(toolRegistry && pnpResolver && assessment && itemRef && item),
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
// NEW ARCHITECTURE: Registry-based rendering
|
|
134
|
+
const allowedToolIds = $derived.by(() => {
|
|
135
|
+
if (!useRegistryArchitecture || !pnpResolver || !assessment || !itemRef) {
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
return pnpResolver.getAllowedToolIds(assessment, itemRef);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const toolContext = $derived.by((): ItemToolContext | null => {
|
|
142
|
+
if (!useRegistryArchitecture || !assessment || !itemRef || !item) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
level: 'item',
|
|
147
|
+
assessment,
|
|
148
|
+
itemRef,
|
|
149
|
+
item,
|
|
150
|
+
};
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Handle tool click (activates tool)
|
|
154
|
+
function handleToolClick(toolId: string) {
|
|
155
|
+
console.log('[QuestionToolBar] Tool clicked:', toolId);
|
|
156
|
+
|
|
157
|
+
if (!toolCoordinator) {
|
|
158
|
+
console.warn('[QuestionToolBar] No toolCoordinator available');
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Toggle tool visibility
|
|
163
|
+
const fullToolId = `${toolId}-${itemId}`;
|
|
164
|
+
toolCoordinator.toggleTool(fullToolId);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// LEGACY ARCHITECTURE: Fallback to old hardcoded rendering
|
|
113
168
|
let enabledTools = $derived(
|
|
114
169
|
tools
|
|
115
170
|
.split(',')
|
|
@@ -121,7 +176,7 @@
|
|
|
121
176
|
let hasCompatibleChoices = $state(false);
|
|
122
177
|
|
|
123
178
|
$effect(() => {
|
|
124
|
-
if (!isBrowser || !scopeElement) {
|
|
179
|
+
if (!isBrowser || !scopeElement || useRegistryArchitecture) {
|
|
125
180
|
hasCompatibleChoices = false;
|
|
126
181
|
return;
|
|
127
182
|
}
|
|
@@ -130,10 +185,6 @@
|
|
|
130
185
|
const checkChoices = () => {
|
|
131
186
|
const hasChoices = hasCompatibleAnswerChoices(scopeElement);
|
|
132
187
|
hasCompatibleChoices = hasChoices;
|
|
133
|
-
console.debug('[QuestionToolBar] Checked choices:', {
|
|
134
|
-
scopeElementTag: scopeElement.tagName,
|
|
135
|
-
hasCompatibleChoices
|
|
136
|
-
});
|
|
137
188
|
};
|
|
138
189
|
|
|
139
190
|
// Initial check
|
|
@@ -152,14 +203,14 @@
|
|
|
152
203
|
return () => observer.disconnect();
|
|
153
204
|
});
|
|
154
205
|
|
|
155
|
-
// Dynamically load tool components based on enabled tools
|
|
206
|
+
// Dynamically load tool components based on enabled tools (LEGACY)
|
|
156
207
|
$effect(() => {
|
|
157
|
-
if (!isBrowser || toolsLoaded) return;
|
|
208
|
+
if (!isBrowser || toolsLoaded || useRegistryArchitecture) return;
|
|
158
209
|
|
|
159
210
|
(async () => {
|
|
160
211
|
const loadPromises = [];
|
|
161
212
|
|
|
162
|
-
if (enabledTools.includes('tts')) {
|
|
213
|
+
if (enabledTools.includes('tts') || enabledTools.includes('textToSpeech')) {
|
|
163
214
|
loadPromises.push(import('@pie-players/pie-tool-tts-inline'));
|
|
164
215
|
}
|
|
165
216
|
if (enabledTools.includes('answerEliminator')) {
|
|
@@ -177,22 +228,33 @@
|
|
|
177
228
|
})();
|
|
178
229
|
});
|
|
179
230
|
|
|
180
|
-
// Should show each tool
|
|
181
|
-
let showTTS = $derived(
|
|
231
|
+
// Should show each tool (LEGACY)
|
|
232
|
+
let showTTS = $derived(
|
|
233
|
+
!useRegistryArchitecture &&
|
|
234
|
+
(enabledTools.includes('tts') || enabledTools.includes('textToSpeech')) &&
|
|
235
|
+
ttsService &&
|
|
236
|
+
toolsLoaded
|
|
237
|
+
);
|
|
182
238
|
let showAnswerEliminator = $derived(
|
|
239
|
+
!useRegistryArchitecture &&
|
|
183
240
|
enabledTools.includes('answerEliminator') &&
|
|
184
241
|
toolCoordinator &&
|
|
185
242
|
hasCompatibleChoices &&
|
|
186
243
|
toolsLoaded
|
|
187
244
|
);
|
|
188
|
-
let showCalculator = $derived(
|
|
245
|
+
let showCalculator = $derived(
|
|
246
|
+
!useRegistryArchitecture &&
|
|
247
|
+
enabledTools.includes('calculator') &&
|
|
248
|
+
toolCoordinator &&
|
|
249
|
+
toolsLoaded
|
|
250
|
+
);
|
|
189
251
|
|
|
190
252
|
// Track answer eliminator visibility state
|
|
191
253
|
let answerEliminatorVisible = $state(false);
|
|
192
254
|
|
|
193
255
|
// Subscribe to tool coordinator to update button state
|
|
194
256
|
$effect(() => {
|
|
195
|
-
if (!isBrowser || !toolCoordinator) return;
|
|
257
|
+
if (!isBrowser || !toolCoordinator || useRegistryArchitecture) return;
|
|
196
258
|
|
|
197
259
|
const unsubscribe = toolCoordinator.subscribe(() => {
|
|
198
260
|
answerEliminatorVisible = toolCoordinator.isToolVisible(
|
|
@@ -272,63 +334,78 @@
|
|
|
272
334
|
|
|
273
335
|
{#if isBrowser}
|
|
274
336
|
<div class="question-toolbar {className} question-toolbar--{size}">
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
<
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
337
|
+
{#if useRegistryArchitecture && toolRegistry && toolContext}
|
|
338
|
+
<!-- NEW ARCHITECTURE: Registry-based rendering -->
|
|
339
|
+
<ToolButtonGroup
|
|
340
|
+
{toolRegistry}
|
|
341
|
+
{allowedToolIds}
|
|
342
|
+
context={toolContext}
|
|
343
|
+
onToolClick={handleToolClick}
|
|
344
|
+
orientation="horizontal"
|
|
345
|
+
compact={size === 'sm'}
|
|
346
|
+
/>
|
|
347
|
+
{:else}
|
|
348
|
+
<!-- LEGACY ARCHITECTURE: Hardcoded tool buttons -->
|
|
349
|
+
<!-- This will be removed in Phase 5 after migration complete -->
|
|
350
|
+
|
|
351
|
+
<!-- Calculator Button (inline tool) -->
|
|
352
|
+
{#if showCalculator}
|
|
353
|
+
<pie-tool-calculator-inline
|
|
354
|
+
bind:this={calculatorInlineElement}
|
|
355
|
+
tool-id="calculator-inline-{itemId}"
|
|
356
|
+
calculator-type="scientific"
|
|
357
|
+
available-types="basic,scientific,graphing"
|
|
358
|
+
size={size}
|
|
359
|
+
></pie-tool-calculator-inline>
|
|
360
|
+
{/if}
|
|
361
|
+
|
|
362
|
+
<!-- TTS Button (inline tool) -->
|
|
363
|
+
{#if showTTS}
|
|
364
|
+
<pie-tool-tts-inline
|
|
365
|
+
bind:this={ttsToolElement}
|
|
366
|
+
tool-id="tts-{itemId}"
|
|
367
|
+
catalog-id={catalogId || itemId}
|
|
368
|
+
language={language}
|
|
369
|
+
size={size}
|
|
370
|
+
></pie-tool-tts-inline>
|
|
371
|
+
{/if}
|
|
372
|
+
|
|
373
|
+
<!-- Answer Eliminator Toggle Button -->
|
|
374
|
+
{#if showAnswerEliminator}
|
|
375
|
+
<button
|
|
376
|
+
type="button"
|
|
377
|
+
class="question-toolbar__button"
|
|
378
|
+
class:question-toolbar__button--active={answerEliminatorVisible}
|
|
379
|
+
onclick={toggleAnswerEliminator}
|
|
380
|
+
aria-label="Answer Eliminator"
|
|
381
|
+
aria-pressed={answerEliminatorVisible}
|
|
382
|
+
title="Toggle answer elimination mode"
|
|
316
383
|
>
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
384
|
+
<!-- X in Circle Icon (matches SchoolCity) -->
|
|
385
|
+
<svg
|
|
386
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
387
|
+
viewBox="0 0 24 24"
|
|
388
|
+
width="20"
|
|
389
|
+
height="20"
|
|
390
|
+
fill="currentColor"
|
|
391
|
+
aria-hidden="true"
|
|
392
|
+
>
|
|
393
|
+
<path
|
|
394
|
+
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"
|
|
395
|
+
/>
|
|
396
|
+
</svg>
|
|
397
|
+
</button>
|
|
398
|
+
|
|
399
|
+
<!-- Answer Eliminator Tool Instance (hidden, manages state) -->
|
|
400
|
+
<pie-tool-answer-eliminator
|
|
401
|
+
bind:this={answerEliminatorElement}
|
|
402
|
+
visible={answerEliminatorVisible}
|
|
403
|
+
tool-id="answerEliminator-{itemId}"
|
|
404
|
+
strategy="strikethrough"
|
|
405
|
+
button-alignment="inline"
|
|
406
|
+
coordinator={toolCoordinator}
|
|
407
|
+
></pie-tool-answer-eliminator>
|
|
408
|
+
{/if}
|
|
332
409
|
{/if}
|
|
333
410
|
</div>
|
|
334
411
|
{/if}
|
|
@@ -356,8 +433,8 @@
|
|
|
356
433
|
}
|
|
357
434
|
|
|
358
435
|
.question-toolbar--sm .question-toolbar__button {
|
|
359
|
-
width:
|
|
360
|
-
height:
|
|
436
|
+
width: 2.75rem; /* 44px - WCAG 2.5.2 Level A minimum */
|
|
437
|
+
height: 2.75rem;
|
|
361
438
|
}
|
|
362
439
|
|
|
363
440
|
.question-toolbar--lg .question-toolbar__button {
|