@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.
- package/README.md +200 -67
- package/dist/attempt/TestSession.d.ts +23 -21
- package/dist/attempt/TestSession.d.ts.map +1 -1
- package/dist/attempt/TestSession.js +17 -15
- package/dist/attempt/TestSession.js.map +1 -1
- package/dist/attempt/adapters/activity-to-test-attempt-session.d.ts +50 -0
- package/dist/attempt/adapters/activity-to-test-attempt-session.d.ts.map +1 -0
- package/dist/attempt/adapters/activity-to-test-attempt-session.js +135 -0
- package/dist/attempt/adapters/activity-to-test-attempt-session.js.map +1 -0
- package/dist/components/ItemToolBar.custom-element.js +628 -0
- package/dist/components/PieAssessmentToolkit.custom-element.js +670 -0
- package/dist/components/item-toolbar-element.d.ts +3 -0
- package/dist/components/item-toolbar-element.d.ts.map +1 -0
- package/dist/components/item-toolbar-element.js +2 -0
- package/dist/components/item-toolbar-element.js.map +1 -0
- package/dist/components/pie-assessment-toolkit-element.d.ts +3 -0
- package/dist/components/pie-assessment-toolkit-element.d.ts.map +1 -0
- package/dist/components/pie-assessment-toolkit-element.js +2 -0
- package/dist/components/pie-assessment-toolkit-element.js.map +1 -0
- package/dist/context/assessment-toolkit-context.d.ts +52 -0
- package/dist/context/assessment-toolkit-context.d.ts.map +1 -0
- package/dist/context/assessment-toolkit-context.js +6 -0
- package/dist/context/assessment-toolkit-context.js.map +1 -0
- package/dist/context/runtime-context-consumer.d.ts +14 -0
- package/dist/context/runtime-context-consumer.d.ts.map +1 -0
- package/dist/context/runtime-context-consumer.js +52 -0
- package/dist/context/runtime-context-consumer.js.map +1 -0
- package/dist/index.d.ts +16 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -11
- package/dist/index.js.map +1 -1
- package/dist/runtime/RuntimeRegistry.d.ts +13 -0
- package/dist/runtime/RuntimeRegistry.d.ts.map +1 -0
- package/dist/runtime/RuntimeRegistry.js +50 -0
- package/dist/runtime/RuntimeRegistry.js.map +1 -0
- package/dist/runtime/SectionRuntimeEngine.d.ts +45 -0
- package/dist/runtime/SectionRuntimeEngine.d.ts.map +1 -0
- package/dist/runtime/SectionRuntimeEngine.js +81 -0
- package/dist/runtime/SectionRuntimeEngine.js.map +1 -0
- package/dist/runtime/registration-events.d.ts +19 -0
- package/dist/runtime/registration-events.d.ts.map +1 -0
- package/dist/runtime/registration-events.js +4 -0
- package/dist/runtime/registration-events.js.map +1 -0
- package/dist/runtime/runtime-event-guards.d.ts +3 -0
- package/dist/runtime/runtime-event-guards.d.ts.map +1 -0
- package/dist/runtime/runtime-event-guards.js +9 -0
- package/dist/runtime/runtime-event-guards.js.map +1 -0
- package/dist/runtime/tool-host-contract.d.ts +30 -0
- package/dist/runtime/tool-host-contract.d.ts.map +1 -0
- package/dist/runtime/tool-host-contract.js +48 -0
- package/dist/runtime/tool-host-contract.js.map +1 -0
- package/dist/services/TTSService.d.ts +3 -1
- package/dist/services/TTSService.d.ts.map +1 -1
- package/dist/services/TTSService.js +13 -12
- package/dist/services/TTSService.js.map +1 -1
- package/dist/services/ThemeProvider.d.ts +1 -0
- package/dist/services/ThemeProvider.d.ts.map +1 -1
- package/dist/services/ThemeProvider.js +82 -46
- package/dist/services/ThemeProvider.js.map +1 -1
- package/dist/services/ToolConfigResolver.d.ts +1 -1
- package/dist/services/ToolConfigResolver.js +1 -1
- package/dist/services/ToolCoordinator.d.ts +1 -1
- package/dist/services/ToolCoordinator.d.ts.map +1 -1
- package/dist/services/ToolRegistry.d.ts +64 -67
- package/dist/services/ToolRegistry.d.ts.map +1 -1
- package/dist/services/ToolRegistry.js +72 -22
- package/dist/services/ToolRegistry.js.map +1 -1
- package/dist/services/ToolkitCoordinator.d.ts +106 -8
- package/dist/services/ToolkitCoordinator.d.ts.map +1 -1
- package/dist/services/ToolkitCoordinator.js +502 -71
- package/dist/services/ToolkitCoordinator.js.map +1 -1
- package/dist/services/createDefaultToolRegistry.d.ts +12 -7
- package/dist/services/createDefaultToolRegistry.d.ts.map +1 -1
- package/dist/services/createDefaultToolRegistry.js +6 -10
- package/dist/services/createDefaultToolRegistry.js.map +1 -1
- package/dist/services/defaultPersonalNeedsProfile.d.ts +4 -0
- package/dist/services/defaultPersonalNeedsProfile.d.ts.map +1 -0
- package/dist/services/defaultPersonalNeedsProfile.js +25 -0
- package/dist/services/defaultPersonalNeedsProfile.js.map +1 -0
- package/dist/services/interfaces.d.ts +61 -1
- package/dist/services/interfaces.d.ts.map +1 -1
- package/dist/services/interfaces.js.map +1 -1
- package/dist/services/pnp-standard-features.d.ts +3 -4
- package/dist/services/pnp-standard-features.d.ts.map +1 -1
- package/dist/services/pnp-standard-features.js +0 -3
- package/dist/services/pnp-standard-features.js.map +1 -1
- package/dist/services/section-controller-types.d.ts +32 -0
- package/dist/services/section-controller-types.d.ts.map +1 -0
- package/dist/services/section-controller-types.js +2 -0
- package/dist/services/section-controller-types.js.map +1 -0
- package/dist/services/tool-context.d.ts +4 -4
- package/dist/services/tool-context.d.ts.map +1 -1
- package/dist/services/tool-context.js +106 -17
- package/dist/services/tool-context.js.map +1 -1
- package/dist/services/tool-instance-id.d.ts +17 -0
- package/dist/services/tool-instance-id.d.ts.map +1 -0
- package/dist/services/tool-instance-id.js +61 -0
- package/dist/services/tool-instance-id.js.map +1 -0
- package/dist/services/tool-providers/IToolProvider.d.ts +2 -3
- package/dist/services/tool-providers/IToolProvider.d.ts.map +1 -1
- package/dist/services/tool-providers/IToolProvider.js +1 -2
- package/dist/services/tool-providers/IToolProvider.js.map +1 -1
- package/dist/services/tool-providers/MathJsToolProvider.d.ts +25 -0
- package/dist/services/tool-providers/MathJsToolProvider.d.ts.map +1 -0
- package/dist/services/tool-providers/MathJsToolProvider.js +53 -0
- package/dist/services/tool-providers/MathJsToolProvider.js.map +1 -0
- package/dist/services/tool-providers/index.d.ts +2 -0
- package/dist/services/tool-providers/index.d.ts.map +1 -1
- package/dist/services/tool-providers/index.js +1 -0
- package/dist/services/tool-providers/index.js.map +1 -1
- package/dist/services/tools-config-normalizer.d.ts +26 -0
- package/dist/services/tools-config-normalizer.d.ts.map +1 -0
- package/dist/services/tools-config-normalizer.js +63 -0
- package/dist/services/tools-config-normalizer.js.map +1 -0
- package/dist/tools/calculators/ti-provider.d.ts.map +1 -1
- package/dist/tools/calculators/ti-provider.js +127 -4
- package/dist/tools/calculators/ti-provider.js.map +1 -1
- package/dist/tools/client.d.ts +2 -1
- package/dist/tools/client.d.ts.map +1 -1
- package/dist/tools/client.js +2 -1
- package/dist/tools/client.js.map +1 -1
- package/dist/tools/default-tool-module-loaders.d.ts +9 -0
- package/dist/tools/default-tool-module-loaders.d.ts.map +1 -0
- package/dist/tools/default-tool-module-loaders.js +17 -0
- package/dist/tools/default-tool-module-loaders.js.map +1 -0
- package/dist/tools/index.d.ts +2 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +2 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/library-loader.d.ts +1 -1
- package/dist/tools/library-loader.d.ts.map +1 -1
- package/dist/tools/library-loader.js +7 -2
- package/dist/tools/library-loader.js.map +1 -1
- package/dist/tools/registrations/accessibility-tools.d.ts +1 -9
- package/dist/tools/registrations/accessibility-tools.d.ts.map +1 -1
- package/dist/tools/registrations/accessibility-tools.js +95 -113
- package/dist/tools/registrations/accessibility-tools.js.map +1 -1
- package/dist/tools/registrations/calculator.d.ts +2 -2
- package/dist/tools/registrations/calculator.d.ts.map +1 -1
- package/dist/tools/registrations/calculator.js +34 -55
- package/dist/tools/registrations/calculator.js.map +1 -1
- package/dist/tools/registrations/interaction-tools.d.ts.map +1 -1
- package/dist/tools/registrations/interaction-tools.js +77 -52
- package/dist/tools/registrations/interaction-tools.js.map +1 -1
- package/dist/tools/registrations/measurement-tools.d.ts.map +1 -1
- package/dist/tools/registrations/measurement-tools.js +63 -38
- package/dist/tools/registrations/measurement-tools.js.map +1 -1
- package/dist/tools/registrations/subject-specific-tools.d.ts.map +1 -1
- package/dist/tools/registrations/subject-specific-tools.js +63 -38
- package/dist/tools/registrations/subject-specific-tools.js.map +1 -1
- package/dist/tools/registrations/tts.d.ts +1 -1
- package/dist/tools/registrations/tts.d.ts.map +1 -1
- package/dist/tools/registrations/tts.js +27 -41
- package/dist/tools/registrations/tts.js.map +1 -1
- package/dist/tools/response-discovery.d.ts +12 -14
- package/dist/tools/response-discovery.d.ts.map +1 -1
- package/dist/tools/response-discovery.js +23 -80
- package/dist/tools/response-discovery.js.map +1 -1
- package/dist/tools/tool-tag-map.d.ts +3 -3
- package/dist/tools/tool-tag-map.d.ts.map +1 -1
- package/dist/tools/tool-tag-map.js +2 -3
- package/dist/tools/tool-tag-map.js.map +1 -1
- package/dist/tools/types.d.ts +3 -1
- package/dist/tools/types.d.ts.map +1 -1
- package/package.json +25 -28
- package/src/README.md +16 -32
- package/src/components/ItemToolBar.svelte +489 -0
- package/src/components/PNPProvenanceViewer.svelte +14 -4
- package/src/components/PieAssessmentToolkit.svelte +517 -0
- package/src/components/ToolButton.svelte +11 -11
- package/src/components/ToolButtonGroup.svelte +27 -11
- package/src/tools/README.md +6 -5
- package/src/tools/calculators/README.md +4 -4
- package/dist/player/AssessmentPlayer.d.ts +0 -397
- package/dist/player/AssessmentPlayer.d.ts.map +0 -1
- package/dist/player/AssessmentPlayer.js +0 -974
- package/dist/player/AssessmentPlayer.js.map +0 -1
- package/dist/player/index.d.ts +0 -17
- package/dist/player/index.d.ts.map +0 -1
- package/dist/player/index.js +0 -15
- package/dist/player/index.js.map +0 -1
- package/dist/player/navigation-types.d.ts +0 -63
- package/dist/player/navigation-types.d.ts.map +0 -1
- package/dist/player/navigation-types.js +0 -7
- package/dist/player/navigation-types.js.map +0 -1
- package/dist/player/qti-navigation.d.ts +0 -29
- package/dist/player/qti-navigation.d.ts.map +0 -1
- package/dist/player/qti-navigation.js +0 -160
- package/dist/player/qti-navigation.js.map +0 -1
- package/dist/reference-layout/index.d.ts +0 -19
- package/dist/reference-layout/index.d.ts.map +0 -1
- package/dist/reference-layout/index.js +0 -20
- package/dist/reference-layout/index.js.map +0 -1
- package/dist/services/AnnotationToolbarConfig.d.ts +0 -134
- package/dist/services/AnnotationToolbarConfig.d.ts.map +0 -1
- package/dist/services/AnnotationToolbarConfig.js +0 -100
- package/dist/services/AnnotationToolbarConfig.js.map +0 -1
- package/dist/services/AssessmentAuthoringService.d.ts +0 -60
- package/dist/services/AssessmentAuthoringService.d.ts.map +0 -1
- package/dist/services/AssessmentAuthoringService.js +0 -183
- package/dist/services/AssessmentAuthoringService.js.map +0 -1
- package/dist/services/PNPMapper.d.ts +0 -81
- package/dist/services/PNPMapper.d.ts.map +0 -1
- package/dist/services/PNPMapper.js +0 -108
- package/dist/services/PNPMapper.js.map +0 -1
- package/src/components/PNPProfileTester.example.svelte +0 -188
- package/src/components/PNPProfileTester.svelte +0 -397
- package/src/components/QuestionToolBar.svelte +0 -483
- package/src/player/AssessmentLayout.svelte +0 -68
- package/src/player/README.md +0 -154
- package/src/reference-layout/README.md +0 -135
- package/src/reference-layout/ReferenceLayout.svelte +0 -201
- package/src/reference-layout/components/AssessmentContent.svelte +0 -259
- package/src/reference-layout/components/AssessmentFooter.svelte +0 -89
- package/src/reference-layout/components/AssessmentHeader.svelte +0 -250
- package/src/reference-layout/components/AssessmentNavigation.svelte +0 -134
- package/src/reference-layout/components/AssessmentToolsBar.svelte +0 -176
- package/src/reference-layout/components/ItemPanel.svelte +0 -200
- package/src/reference-layout/components/NotesPanel.svelte +0 -179
- package/src/reference-layout/components/PassagePanel.svelte +0 -76
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
<svelte:options
|
|
2
|
+
customElement={{
|
|
3
|
+
tag: 'pie-item-toolbar',
|
|
4
|
+
shadow: 'open',
|
|
5
|
+
props: {
|
|
6
|
+
itemId: { type: 'String', attribute: 'item-id' },
|
|
7
|
+
catalogId: { type: 'String', attribute: 'catalog-id' },
|
|
8
|
+
tools: { type: 'String', attribute: 'tools' },
|
|
9
|
+
contentKind: { type: 'String', attribute: 'content-kind' },
|
|
10
|
+
size: { type: 'String', attribute: 'size' },
|
|
11
|
+
language: { type: 'String', attribute: 'language' },
|
|
12
|
+
|
|
13
|
+
// Local runtime contracts (passed as JS properties, not attributes)
|
|
14
|
+
scopeElement: { type: 'Object', reflect: false },
|
|
15
|
+
toolRegistry: { type: 'Object', reflect: false },
|
|
16
|
+
pnpResolver: { type: 'Object', reflect: false },
|
|
17
|
+
assessment: { type: 'Object', reflect: false },
|
|
18
|
+
itemRef: { type: 'Object', reflect: false },
|
|
19
|
+
item: { type: 'Object', reflect: false }
|
|
20
|
+
}
|
|
21
|
+
}}
|
|
22
|
+
/>
|
|
23
|
+
|
|
24
|
+
<!--
|
|
25
|
+
ItemToolBar - Inline toolbar for item/passage headers
|
|
26
|
+
|
|
27
|
+
Button visibility is always registry-driven:
|
|
28
|
+
- Pass 1: allowed tool IDs (PNP resolver when available, otherwise explicit tools prop)
|
|
29
|
+
- Pass 2: tool-owned isVisibleInContext(context)
|
|
30
|
+
-->
|
|
31
|
+
<script lang="ts">
|
|
32
|
+
import {
|
|
33
|
+
type AssessmentToolkitShellContext,
|
|
34
|
+
type AssessmentToolkitRuntimeContext
|
|
35
|
+
} from '../context/assessment-toolkit-context.js';
|
|
36
|
+
import {
|
|
37
|
+
connectAssessmentToolkitRuntimeContext,
|
|
38
|
+
connectAssessmentToolkitShellContext,
|
|
39
|
+
} from '../context/runtime-context-consumer.js';
|
|
40
|
+
import type { ToolRegistry, ToolToolbarRenderResult, ToolbarContext } from '../services/ToolRegistry.js';
|
|
41
|
+
import type { PNPToolResolver } from '../services/PNPToolResolver.js';
|
|
42
|
+
import { createDefaultToolRegistry } from '../services/createDefaultToolRegistry.js';
|
|
43
|
+
import { DEFAULT_TOOL_MODULE_LOADERS } from '../tools/default-tool-module-loaders.js';
|
|
44
|
+
import {
|
|
45
|
+
normalizeToolsConfig,
|
|
46
|
+
parseToolList,
|
|
47
|
+
resolveToolsForLevel,
|
|
48
|
+
} from '../services/tools-config-normalizer.js';
|
|
49
|
+
import { createScopedToolId, parseScopedToolId } from '../services/tool-instance-id.js';
|
|
50
|
+
import type { AssessmentEntity, AssessmentItemRef, ItemEntity } from '@pie-players/pie-players-shared/types';
|
|
51
|
+
import type { ElementToolContext, ItemToolContext } from '../services/tool-context.js';
|
|
52
|
+
|
|
53
|
+
const isBrowser = typeof window !== 'undefined';
|
|
54
|
+
const fallbackToolRegistry = createDefaultToolRegistry({
|
|
55
|
+
toolModuleLoaders: DEFAULT_TOOL_MODULE_LOADERS
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
let activeToolState = $state<Record<string, boolean>>({});
|
|
59
|
+
|
|
60
|
+
// Props
|
|
61
|
+
let {
|
|
62
|
+
itemId = '',
|
|
63
|
+
catalogId = '',
|
|
64
|
+
tools = 'calculator,textToSpeech,answerEliminator',
|
|
65
|
+
contentKind = 'assessment-item',
|
|
66
|
+
scopeElement = null,
|
|
67
|
+
toolRegistry = null,
|
|
68
|
+
pnpResolver = null,
|
|
69
|
+
assessment = null,
|
|
70
|
+
itemRef = null,
|
|
71
|
+
item = null,
|
|
72
|
+
class: className = '',
|
|
73
|
+
size = 'md' as 'sm' | 'md' | 'lg',
|
|
74
|
+
language = 'en-US'
|
|
75
|
+
}: {
|
|
76
|
+
itemId?: string;
|
|
77
|
+
catalogId?: string;
|
|
78
|
+
tools?: string;
|
|
79
|
+
contentKind?: string;
|
|
80
|
+
scopeElement?: HTMLElement | null;
|
|
81
|
+
toolRegistry?: ToolRegistry | null;
|
|
82
|
+
pnpResolver?: PNPToolResolver | null;
|
|
83
|
+
assessment?: AssessmentEntity | null;
|
|
84
|
+
itemRef?: AssessmentItemRef | null;
|
|
85
|
+
item?: ItemEntity | null;
|
|
86
|
+
class?: string;
|
|
87
|
+
size?: 'sm' | 'md' | 'lg';
|
|
88
|
+
language?: string;
|
|
89
|
+
} = $props();
|
|
90
|
+
|
|
91
|
+
let toolbarRootElement = $state<HTMLDivElement | null>(null);
|
|
92
|
+
let runtimeContext = $state<AssessmentToolkitRuntimeContext | null>(null);
|
|
93
|
+
let shellContext = $state<AssessmentToolkitShellContext | null>(null);
|
|
94
|
+
let moduleLoadVersion = $state(0);
|
|
95
|
+
|
|
96
|
+
$effect(() => {
|
|
97
|
+
if (!toolbarRootElement) return;
|
|
98
|
+
return connectAssessmentToolkitRuntimeContext(toolbarRootElement, (value) => {
|
|
99
|
+
runtimeContext = value;
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
$effect(() => {
|
|
104
|
+
if (!toolbarRootElement) return;
|
|
105
|
+
return connectAssessmentToolkitShellContext(toolbarRootElement, (value) => {
|
|
106
|
+
shellContext = value;
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const effectiveToolCoordinator = $derived(runtimeContext?.toolCoordinator);
|
|
111
|
+
const effectiveTTSService = $derived(runtimeContext?.ttsService);
|
|
112
|
+
const effectiveElementToolStateStore = $derived(runtimeContext?.elementToolStateStore);
|
|
113
|
+
const effectiveAssessmentId = $derived(runtimeContext?.assessmentId ?? '');
|
|
114
|
+
const effectiveSectionId = $derived(runtimeContext?.sectionId ?? '');
|
|
115
|
+
const effectiveItemId = $derived(itemId || shellContext?.itemId || '');
|
|
116
|
+
const effectiveCanonicalItemId = $derived(shellContext?.canonicalItemId || effectiveItemId);
|
|
117
|
+
const effectiveCatalogId = $derived(catalogId || effectiveItemId);
|
|
118
|
+
const effectiveContentKind = $derived(contentKind || shellContext?.contentKind || 'assessment-item');
|
|
119
|
+
const effectiveItem = $derived((item as ItemEntity | null) || (shellContext?.item as ItemEntity | null) || null);
|
|
120
|
+
|
|
121
|
+
// Effective registry for visibility and metadata ownership.
|
|
122
|
+
const effectiveToolRegistry = $derived(toolRegistry || fallbackToolRegistry);
|
|
123
|
+
|
|
124
|
+
const explicitTools = $derived(parseToolList(tools));
|
|
125
|
+
const normalizedExplicitTools = $derived(
|
|
126
|
+
effectiveToolRegistry.normalizeToolIds(explicitTools).filter(Boolean)
|
|
127
|
+
);
|
|
128
|
+
const effectiveToolsConfig = $derived.by(() => {
|
|
129
|
+
const coordinatorConfig = runtimeContext?.toolkitCoordinator?.config?.tools as any;
|
|
130
|
+
return normalizeToolsConfig(coordinatorConfig || {});
|
|
131
|
+
});
|
|
132
|
+
const placementLevel = $derived.by(() =>
|
|
133
|
+
effectiveContentKind === 'rubric-block-stimulus' ? 'passage' : 'item'
|
|
134
|
+
);
|
|
135
|
+
const placementAllowedToolIds = $derived.by(() => {
|
|
136
|
+
const resolved = resolveToolsForLevel(effectiveToolsConfig, placementLevel);
|
|
137
|
+
return effectiveToolRegistry.normalizeToolIds(resolved).filter(Boolean);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Pass 1: determine allowed tools
|
|
141
|
+
const allowedToolIds = $derived.by(() => {
|
|
142
|
+
const configuredTools =
|
|
143
|
+
normalizedExplicitTools.length > 0 ? normalizedExplicitTools : placementAllowedToolIds;
|
|
144
|
+
if (pnpResolver && assessment && itemRef) {
|
|
145
|
+
const allowedByPnp = pnpResolver.getAllowedToolIds(assessment, itemRef);
|
|
146
|
+
if (configuredTools.length === 0) return allowedByPnp;
|
|
147
|
+
return allowedByPnp.filter((toolId) => configuredTools.includes(toolId));
|
|
148
|
+
}
|
|
149
|
+
return configuredTools;
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const contentReady = $derived.by(() => {
|
|
153
|
+
const config = (effectiveItem as ItemEntity | null)?.config;
|
|
154
|
+
return !!(effectiveItem && config && typeof config === 'object');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const toolContext = $derived.by((): ItemToolContext | null => {
|
|
158
|
+
if (!contentReady || !effectiveItem) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
level: 'item',
|
|
163
|
+
assessment: (assessment || {}) as AssessmentEntity,
|
|
164
|
+
itemRef: (itemRef || ({ id: effectiveCanonicalItemId } as AssessmentItemRef)) as AssessmentItemRef,
|
|
165
|
+
item: effectiveItem as ItemEntity
|
|
166
|
+
};
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const renderContext = $derived.by((): ItemToolContext => {
|
|
170
|
+
if (toolContext) return toolContext;
|
|
171
|
+
return {
|
|
172
|
+
level: 'item',
|
|
173
|
+
assessment: (assessment || {}) as AssessmentEntity,
|
|
174
|
+
itemRef: (itemRef || ({ id: effectiveCanonicalItemId } as AssessmentItemRef)) as AssessmentItemRef,
|
|
175
|
+
item: ((effectiveItem as ItemEntity | null) || ({ id: effectiveCanonicalItemId, config: {} } as ItemEntity)) as ItemEntity
|
|
176
|
+
};
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const elementContexts = $derived.by((): ElementToolContext[] => {
|
|
180
|
+
if (!contentReady || !effectiveItem) return [];
|
|
181
|
+
const modelsRaw = (effectiveItem as any)?.config?.models;
|
|
182
|
+
const models = Array.isArray(modelsRaw)
|
|
183
|
+
? modelsRaw
|
|
184
|
+
: modelsRaw && typeof modelsRaw === 'object'
|
|
185
|
+
? Object.values(modelsRaw)
|
|
186
|
+
: [];
|
|
187
|
+
return models
|
|
188
|
+
.filter((model: any) => model && typeof model === 'object' && typeof model.id === 'string')
|
|
189
|
+
.map((model: any) => ({
|
|
190
|
+
level: 'element' as const,
|
|
191
|
+
assessment: (assessment || {}) as AssessmentEntity,
|
|
192
|
+
itemRef: (itemRef || ({ id: effectiveCanonicalItemId } as AssessmentItemRef)) as AssessmentItemRef,
|
|
193
|
+
item: effectiveItem as ItemEntity,
|
|
194
|
+
elementId: model.id as string
|
|
195
|
+
}));
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Pass 2: tool-owned context filtering (item + element aggregation)
|
|
199
|
+
const visibleToolIds = $derived.by(() => {
|
|
200
|
+
if (!contentReady) {
|
|
201
|
+
// Stage 1: orchestrator-level allow-list only.
|
|
202
|
+
return allowedToolIds;
|
|
203
|
+
}
|
|
204
|
+
if (!toolContext && elementContexts.length === 0) {
|
|
205
|
+
return allowedToolIds;
|
|
206
|
+
}
|
|
207
|
+
const visible = new Set<string>();
|
|
208
|
+
if (toolContext) {
|
|
209
|
+
effectiveToolRegistry.filterVisibleInContext(allowedToolIds, toolContext).forEach((tool) => visible.add(tool.toolId));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
for (const context of elementContexts) {
|
|
213
|
+
effectiveToolRegistry.filterVisibleInContext(allowedToolIds, context).forEach((tool) => visible.add(tool.toolId));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return Array.from(visible);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Dynamically load whatever tools are currently visible.
|
|
220
|
+
// The registry owns module loader configuration by toolId.
|
|
221
|
+
$effect(() => {
|
|
222
|
+
if (!isBrowser) return;
|
|
223
|
+
let cancelled = false;
|
|
224
|
+
void effectiveToolRegistry
|
|
225
|
+
.ensureToolModulesLoaded(visibleToolIds)
|
|
226
|
+
.then(() => {
|
|
227
|
+
if (!cancelled) moduleLoadVersion += 1;
|
|
228
|
+
})
|
|
229
|
+
.catch((error: unknown) => {
|
|
230
|
+
console.error('[ItemToolBar] Failed to load one or more tool modules:', error);
|
|
231
|
+
});
|
|
232
|
+
return () => {
|
|
233
|
+
cancelled = true;
|
|
234
|
+
};
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
function resolveScopeElement(): HTMLElement | null {
|
|
238
|
+
if (scopeElement) return scopeElement;
|
|
239
|
+
return shellContext?.scopeElement || null;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function resolveGlobalElementId(): string | null {
|
|
243
|
+
if (!effectiveElementToolStateStore || !effectiveAssessmentId || !effectiveSectionId || !effectiveCanonicalItemId)
|
|
244
|
+
return null;
|
|
245
|
+
return effectiveElementToolStateStore.getGlobalElementId(
|
|
246
|
+
effectiveAssessmentId,
|
|
247
|
+
effectiveSectionId,
|
|
248
|
+
effectiveCanonicalItemId,
|
|
249
|
+
effectiveCanonicalItemId
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const toolbarContext = $derived.by((): ToolbarContext => {
|
|
254
|
+
const toolkitCoordinator = runtimeContext?.toolkitCoordinator || null;
|
|
255
|
+
const toInstanceToolId = (toolId: string): string => {
|
|
256
|
+
// Accept either base tool IDs (e.g. "calculator") or already-scoped IDs.
|
|
257
|
+
// This keeps toolbar contracts stable across mixed registration implementations.
|
|
258
|
+
return parseScopedToolId(toolId)
|
|
259
|
+
? toolId
|
|
260
|
+
: createScopedToolId(toolId, 'item', effectiveCanonicalItemId);
|
|
261
|
+
};
|
|
262
|
+
return {
|
|
263
|
+
itemId: effectiveCanonicalItemId,
|
|
264
|
+
catalogId: effectiveCatalogId,
|
|
265
|
+
language,
|
|
266
|
+
ui: {
|
|
267
|
+
size
|
|
268
|
+
},
|
|
269
|
+
getScopeElement: resolveScopeElement,
|
|
270
|
+
getGlobalElementId: resolveGlobalElementId,
|
|
271
|
+
toolCoordinator: effectiveToolCoordinator || null,
|
|
272
|
+
toolkitCoordinator,
|
|
273
|
+
ttsService: effectiveTTSService || null,
|
|
274
|
+
elementToolStateStore: effectiveElementToolStateStore || null,
|
|
275
|
+
toggleTool: (toolId: string) => {
|
|
276
|
+
if (!effectiveToolCoordinator) return;
|
|
277
|
+
effectiveToolCoordinator.toggleTool(toInstanceToolId(toolId));
|
|
278
|
+
},
|
|
279
|
+
isToolVisible: (toolId: string) => {
|
|
280
|
+
if (!effectiveToolCoordinator) return false;
|
|
281
|
+
return effectiveToolCoordinator.isToolVisible(toInstanceToolId(toolId));
|
|
282
|
+
},
|
|
283
|
+
subscribeVisibility: effectiveToolCoordinator
|
|
284
|
+
? (listener: () => void) => effectiveToolCoordinator.subscribe(listener)
|
|
285
|
+
: null,
|
|
286
|
+
ensureTTSReady: toolkitCoordinator ? () => toolkitCoordinator.ensureTTSReady() : null
|
|
287
|
+
};
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
let renderedTools = $derived.by((): ToolToolbarRenderResult[] => {
|
|
291
|
+
if (!isBrowser) return [];
|
|
292
|
+
moduleLoadVersion;
|
|
293
|
+
|
|
294
|
+
const rendered: ToolToolbarRenderResult[] = [];
|
|
295
|
+
for (const toolId of visibleToolIds) {
|
|
296
|
+
const result = effectiveToolRegistry.renderForToolbar(toolId, renderContext, toolbarContext);
|
|
297
|
+
if (result) {
|
|
298
|
+
rendered.push(result);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return rendered;
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
function syncRenderedToolsState() {
|
|
305
|
+
const nextActiveState: Record<string, boolean> = {};
|
|
306
|
+
for (const renderedTool of renderedTools) {
|
|
307
|
+
renderedTool.sync?.();
|
|
308
|
+
if (renderedTool.button) {
|
|
309
|
+
nextActiveState[renderedTool.toolId] = renderedTool.button.active ?? false;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
activeToolState = nextActiveState;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
$effect(() => {
|
|
316
|
+
const unsubs: Array<() => void> = [];
|
|
317
|
+
syncRenderedToolsState();
|
|
318
|
+
for (const renderedTool of renderedTools) {
|
|
319
|
+
if (renderedTool.subscribeActive) {
|
|
320
|
+
const unsubscribe = renderedTool.subscribeActive((active) => {
|
|
321
|
+
activeToolState = {
|
|
322
|
+
...activeToolState,
|
|
323
|
+
[renderedTool.toolId]: active
|
|
324
|
+
};
|
|
325
|
+
renderedTool.sync?.();
|
|
326
|
+
});
|
|
327
|
+
unsubs.push(unsubscribe);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return () => {
|
|
332
|
+
unsubs.forEach((unsub) => unsub());
|
|
333
|
+
};
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
$effect(() => {
|
|
337
|
+
if (!toolbarContext.subscribeVisibility) return;
|
|
338
|
+
return toolbarContext.subscribeVisibility(() => {
|
|
339
|
+
syncRenderedToolsState();
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
function mountElement(node: HTMLSpanElement, element: HTMLElement | null) {
|
|
344
|
+
let mountedElement: HTMLElement | null = null;
|
|
345
|
+
const updateMountedElement = (nextElement: HTMLElement | null) => {
|
|
346
|
+
if (mountedElement === nextElement) return;
|
|
347
|
+
if (mountedElement && mountedElement.parentNode === node) {
|
|
348
|
+
node.removeChild(mountedElement);
|
|
349
|
+
}
|
|
350
|
+
mountedElement = nextElement;
|
|
351
|
+
if (mountedElement) {
|
|
352
|
+
if (mountedElement.parentNode && mountedElement.parentNode !== node) {
|
|
353
|
+
mountedElement.parentNode.removeChild(mountedElement);
|
|
354
|
+
}
|
|
355
|
+
node.appendChild(mountedElement);
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
updateMountedElement(element);
|
|
359
|
+
return {
|
|
360
|
+
update(nextElement: HTMLElement | null) {
|
|
361
|
+
updateMountedElement(nextElement);
|
|
362
|
+
},
|
|
363
|
+
destroy() {
|
|
364
|
+
updateMountedElement(null);
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
</script>
|
|
369
|
+
|
|
370
|
+
{#if isBrowser}
|
|
371
|
+
<div
|
|
372
|
+
class="item-toolbar {className} item-toolbar--{size}"
|
|
373
|
+
data-content-kind={effectiveContentKind}
|
|
374
|
+
bind:this={toolbarRootElement}
|
|
375
|
+
>
|
|
376
|
+
{#each renderedTools as renderedTool (renderedTool.toolId)}
|
|
377
|
+
{#if renderedTool.inlineElement}
|
|
378
|
+
<span class="item-toolbar__element-host" use:mountElement={renderedTool.inlineElement}></span>
|
|
379
|
+
{/if}
|
|
380
|
+
|
|
381
|
+
{#if renderedTool.button}
|
|
382
|
+
<button
|
|
383
|
+
type="button"
|
|
384
|
+
class="item-toolbar__button {renderedTool.button.className || ''}"
|
|
385
|
+
class:item-toolbar__button--active={activeToolState[renderedTool.toolId] ?? renderedTool.button.active ?? false}
|
|
386
|
+
onclick={() => {
|
|
387
|
+
renderedTool.button?.onClick();
|
|
388
|
+
syncRenderedToolsState();
|
|
389
|
+
}}
|
|
390
|
+
aria-label={renderedTool.button.ariaLabel || renderedTool.button.label}
|
|
391
|
+
aria-pressed={activeToolState[renderedTool.toolId] ?? renderedTool.button.active ?? false}
|
|
392
|
+
title={renderedTool.button.tooltip || renderedTool.button.label}
|
|
393
|
+
disabled={renderedTool.button.disabled}
|
|
394
|
+
>
|
|
395
|
+
{#if renderedTool.button.icon.startsWith('<svg')}
|
|
396
|
+
{@html renderedTool.button.icon}
|
|
397
|
+
{:else if renderedTool.button.icon.startsWith('http')}
|
|
398
|
+
<img class="item-toolbar__icon-image" src={renderedTool.button.icon} alt="" />
|
|
399
|
+
{:else}
|
|
400
|
+
<i class={`icon icon-${renderedTool.button.icon}`} aria-hidden="true"></i>
|
|
401
|
+
{/if}
|
|
402
|
+
</button>
|
|
403
|
+
{/if}
|
|
404
|
+
|
|
405
|
+
{#if renderedTool.overlayElement}
|
|
406
|
+
<span class="item-toolbar__element-host" use:mountElement={renderedTool.overlayElement}></span>
|
|
407
|
+
{/if}
|
|
408
|
+
{/each}
|
|
409
|
+
</div>
|
|
410
|
+
{/if}
|
|
411
|
+
|
|
412
|
+
<style>
|
|
413
|
+
.item-toolbar {
|
|
414
|
+
display: flex;
|
|
415
|
+
align-items: center;
|
|
416
|
+
gap: 0.5rem;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.item-toolbar__button {
|
|
420
|
+
display: flex;
|
|
421
|
+
align-items: center;
|
|
422
|
+
justify-content: center;
|
|
423
|
+
width: 2rem;
|
|
424
|
+
height: 2rem;
|
|
425
|
+
padding: 0.25rem;
|
|
426
|
+
border: 1px solid var(--pie-border, #ccc);
|
|
427
|
+
border-radius: 0.25rem;
|
|
428
|
+
background-color: var(--pie-background, white);
|
|
429
|
+
color: var(--pie-text, #333);
|
|
430
|
+
cursor: pointer;
|
|
431
|
+
transition: all 0.15s ease;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
.item-toolbar__element-host {
|
|
435
|
+
display: contents;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.item-toolbar--sm .item-toolbar__button {
|
|
439
|
+
width: 2.75rem;
|
|
440
|
+
height: 2.75rem;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
.item-toolbar--lg .item-toolbar__button {
|
|
444
|
+
width: 2.5rem;
|
|
445
|
+
height: 2.5rem;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
.item-toolbar__button:hover:not(:disabled) {
|
|
449
|
+
background-color: var(--pie-secondary-background, #f5f5f5);
|
|
450
|
+
transform: translateY(-1px);
|
|
451
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.item-toolbar__button:active:not(:disabled) {
|
|
455
|
+
transform: translateY(0);
|
|
456
|
+
box-shadow: none;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
.item-toolbar__button--active {
|
|
460
|
+
background-color: var(--pie-primary, #1976d2);
|
|
461
|
+
color: white;
|
|
462
|
+
border-color: var(--pie-primary, #1976d2);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
.item-toolbar__button--active:hover:not(:disabled) {
|
|
466
|
+
background-color: var(--pie-primary-dark, #1565c0);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
.item-toolbar__button:focus-visible {
|
|
470
|
+
outline: 2px solid var(--pie-primary, #1976d2);
|
|
471
|
+
outline-offset: 2px;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
.item-toolbar__button:disabled {
|
|
475
|
+
opacity: 0.5;
|
|
476
|
+
cursor: not-allowed;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.item-toolbar__button :global(svg) {
|
|
480
|
+
width: 100%;
|
|
481
|
+
height: 100%;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
.item-toolbar__icon-image {
|
|
485
|
+
width: 100%;
|
|
486
|
+
height: 100%;
|
|
487
|
+
object-fit: contain;
|
|
488
|
+
}
|
|
489
|
+
</style>
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type { PNPResolutionProvenance } from '
|
|
2
|
+
import type { PNPResolutionProvenance } from '../services/pnp-provenance.js';
|
|
3
3
|
|
|
4
4
|
// PNP Provenance Viewer Component
|
|
5
5
|
// Visualizes how PNP resolution decisions were made.
|
|
6
|
-
// See PNP_TESTING.md for usage documentation.
|
|
7
6
|
|
|
8
7
|
interface Props {
|
|
9
8
|
provenance: PNPResolutionProvenance;
|
|
@@ -37,6 +36,17 @@
|
|
|
37
36
|
};
|
|
38
37
|
return icons[action] || '?';
|
|
39
38
|
}
|
|
39
|
+
|
|
40
|
+
function getSourceName(source: unknown): string | undefined {
|
|
41
|
+
if (!source || typeof source !== 'object') return undefined;
|
|
42
|
+
if ('name' in source && typeof (source as any).name === 'string') {
|
|
43
|
+
return (source as any).name;
|
|
44
|
+
}
|
|
45
|
+
if ('title' in source && typeof (source as any).title === 'string') {
|
|
46
|
+
return (source as any).title;
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
40
50
|
</script>
|
|
41
51
|
|
|
42
52
|
<div class="pnp-provenance-viewer" class:compact>
|
|
@@ -75,7 +85,7 @@
|
|
|
75
85
|
<div class="flex items-center gap-2">
|
|
76
86
|
<div class="badge badge-outline">{type}</div>
|
|
77
87
|
<div class="text-sm">
|
|
78
|
-
{source
|
|
88
|
+
{getSourceName(source) || source.id}
|
|
79
89
|
<span class="text-xs text-base-content/50">({source.id})</span>
|
|
80
90
|
</div>
|
|
81
91
|
</div>
|
|
@@ -226,7 +236,7 @@
|
|
|
226
236
|
{decision.reason}
|
|
227
237
|
</div>
|
|
228
238
|
<div class="text-base-content/50 mt-1">
|
|
229
|
-
Source: {decision.source
|
|
239
|
+
Source: {getSourceName(decision.source) || decision.source.type}
|
|
230
240
|
</div>
|
|
231
241
|
</div>
|
|
232
242
|
</div>
|