@pie-players/pie-section-player 0.2.12 → 0.2.13
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 +28 -568
- package/dist/component-definitions.d.ts +0 -3
- package/dist/component-definitions.d.ts.map +1 -1
- package/dist/components/section-player-vertical-element.d.ts +2 -0
- package/dist/components/section-player-vertical-element.d.ts.map +1 -0
- package/dist/components/shared/composition.d.ts +9 -0
- package/dist/components/shared/composition.d.ts.map +1 -0
- package/dist/components/shared/player-action.d.ts +18 -0
- package/dist/components/shared/player-action.d.ts.map +1 -0
- package/dist/components/shared/player-preload.d.ts +37 -0
- package/dist/components/shared/player-preload.d.ts.map +1 -0
- package/dist/components/shared/section-player-runtime.d.ts +104 -0
- package/dist/components/shared/section-player-runtime.d.ts.map +1 -0
- package/dist/components/shared/section-player-view-state.d.ts +24 -0
- package/dist/components/shared/section-player-view-state.d.ts.map +1 -0
- package/dist/controllers/SectionContentService.d.ts.map +1 -1
- package/dist/controllers/SectionController.d.ts +5 -1
- package/dist/controllers/SectionController.d.ts.map +1 -1
- package/dist/controllers/SectionSessionService.d.ts +0 -1
- package/dist/controllers/SectionSessionService.d.ts.map +1 -1
- package/dist/controllers/toolkit-section-contracts.d.ts +2 -28
- package/dist/controllers/toolkit-section-contracts.d.ts.map +1 -1
- package/dist/controllers/types.d.ts +28 -1
- package/dist/controllers/types.d.ts.map +1 -1
- package/dist/pie-item-player-B1iGN63e.js +6189 -0
- package/dist/pie-section-player.d.ts +0 -8
- package/dist/pie-section-player.d.ts.map +1 -1
- package/dist/pie-section-player.js +56558 -11
- package/dist/player-preload-CQVG0Bih.js +705 -0
- package/dist/utils/player-preload.d.ts +2 -0
- package/dist/utils/player-preload.d.ts.map +1 -0
- package/dist/utils/player-preload.js +8 -0
- package/package.json +23 -32
- package/src/components/ItemShellElement.svelte +10 -1
- package/src/components/PieSectionPlayerBaseElement.svelte +21 -78
- package/src/components/PieSectionPlayerSplitPaneElement.svelte +236 -295
- package/src/components/PieSectionPlayerVerticalElement.svelte +424 -0
- package/src/components/shared/SectionItemCard.svelte +92 -0
- package/src/components/shared/SectionPassageCard.svelte +88 -0
- package/dist/ItemRenderer-MsjF_Beu.js +0 -467
- package/dist/PieItemModeLayoutElement-D7oTzA9T.js +0 -316
- package/dist/PieSplitPanelLayoutElement-GUtJ_NlF.js +0 -246
- package/dist/PieVerticalLayoutElement-BoA3FO5g.js +0 -194
- package/dist/controllers/SectionToolkitService.d.ts +0 -24
- package/dist/controllers/SectionToolkitService.d.ts.map +0 -1
- package/dist/controllers/SessionPersistenceStrategy.d.ts +0 -15
- package/dist/controllers/SessionPersistenceStrategy.d.ts.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/pie-section-player-DJ5NcwdT.js +0 -17078
- package/dist/runtime/runtime-event-guards.d.ts +0 -4
- package/dist/runtime/runtime-event-guards.d.ts.map +0 -1
- package/src/PieSectionPlayer.svelte +0 -826
- package/src/components/ItemModeLayout.svelte +0 -172
- package/src/components/ItemNavigation.svelte +0 -96
- package/src/components/ItemPlayerBridge.svelte +0 -110
- package/src/components/ItemRenderer.svelte +0 -248
- package/src/components/ItemShell.svelte +0 -86
- package/src/components/layout-elements/PieItemModeLayoutElement.svelte +0 -47
- package/src/components/layout-elements/PieSplitPanelLayoutElement.svelte +0 -62
- package/src/components/layout-elements/PieVerticalLayoutElement.svelte +0 -41
- package/src/components/layouts/SplitPanelLayout.svelte +0 -385
- package/src/components/layouts/VerticalLayout.svelte +0 -193
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
ItemModeLayout - Internal Component
|
|
3
|
-
|
|
4
|
-
Renders item mode (keepTogether: false) - one item at a time with navigation.
|
|
5
|
-
Not exposed as a web component - used internally in PieSectionPlayer.
|
|
6
|
-
-->
|
|
7
|
-
<script lang="ts">
|
|
8
|
-
import type { SectionCompositionModel } from '../controllers/types.js';
|
|
9
|
-
import ItemNavigation from './ItemNavigation.svelte';
|
|
10
|
-
import ItemRenderer from './ItemRenderer.svelte';
|
|
11
|
-
|
|
12
|
-
let {
|
|
13
|
-
composition,
|
|
14
|
-
env = { mode: 'gather', role: 'student' },
|
|
15
|
-
toolbarPosition = 'right',
|
|
16
|
-
showToolbar = true,
|
|
17
|
-
onprevious,
|
|
18
|
-
onnext
|
|
19
|
-
}: {
|
|
20
|
-
composition: SectionCompositionModel;
|
|
21
|
-
env?: { mode: 'gather' | 'view' | 'evaluate' | 'author'; role: 'student' | 'instructor' };
|
|
22
|
-
toolbarPosition?: 'top' | 'right' | 'bottom' | 'left' | 'none';
|
|
23
|
-
showToolbar?: boolean;
|
|
24
|
-
onprevious?: () => void;
|
|
25
|
-
onnext?: () => void;
|
|
26
|
-
} = $props();
|
|
27
|
-
|
|
28
|
-
let passages = $derived(composition?.passages || []);
|
|
29
|
-
let items = $derived(composition?.items || []);
|
|
30
|
-
let currentIndex = $derived(composition?.currentItemIndex || 0);
|
|
31
|
-
let totalItems = $derived(items.length);
|
|
32
|
-
let currentItem = $derived(composition?.currentItem || items[currentIndex] || null);
|
|
33
|
-
let itemSessionsByItemId = $derived(composition?.itemSessionsByItemId || {});
|
|
34
|
-
let itemSession = $derived(currentItem?.id ? itemSessionsByItemId[currentItem.id] : undefined);
|
|
35
|
-
let canPrevious = $derived(currentIndex > 0);
|
|
36
|
-
let canNext = $derived(currentIndex < totalItems - 1);
|
|
37
|
-
let shouldRenderToolbar = $derived(showToolbar && toolbarPosition !== 'none');
|
|
38
|
-
let isToolbarBeforeContent = $derived(
|
|
39
|
-
toolbarPosition === 'top' || toolbarPosition === 'left'
|
|
40
|
-
);
|
|
41
|
-
</script>
|
|
42
|
-
|
|
43
|
-
<div class={`pie-section-player__layout-shell pie-section-player__layout-shell--${toolbarPosition}`}>
|
|
44
|
-
{#if shouldRenderToolbar && isToolbarBeforeContent}
|
|
45
|
-
<pie-section-tools-toolbar
|
|
46
|
-
position={toolbarPosition}
|
|
47
|
-
enabled-tools=""
|
|
48
|
-
></pie-section-tools-toolbar>
|
|
49
|
-
{/if}
|
|
50
|
-
<div class="pie-section-player__item-mode-layout">
|
|
51
|
-
<!-- Passages (visible for all items) -->
|
|
52
|
-
{#if passages.length > 0}
|
|
53
|
-
<div class="pie-section-player__passages-section">
|
|
54
|
-
{#each passages as passage (passage.id)}
|
|
55
|
-
<div class="pie-section-player__passage-wrapper">
|
|
56
|
-
<ItemRenderer
|
|
57
|
-
item={passage}
|
|
58
|
-
contentKind="rubric-block-stimulus"
|
|
59
|
-
env={{ mode: 'view', role: env.role }}
|
|
60
|
-
customClassName="pie-section-player__passage-item"
|
|
61
|
-
/>
|
|
62
|
-
</div>
|
|
63
|
-
{/each}
|
|
64
|
-
</div>
|
|
65
|
-
{/if}
|
|
66
|
-
|
|
67
|
-
<!-- Current Item -->
|
|
68
|
-
{#if currentItem}
|
|
69
|
-
<div class="pie-section-player__current-item-section">
|
|
70
|
-
<ItemRenderer
|
|
71
|
-
item={currentItem}
|
|
72
|
-
contentKind="assessment-item"
|
|
73
|
-
{env}
|
|
74
|
-
session={itemSession}
|
|
75
|
-
customClassName="pie-section-player__item-content"
|
|
76
|
-
/>
|
|
77
|
-
</div>
|
|
78
|
-
{:else}
|
|
79
|
-
<div class="pie-section-player__no-item">
|
|
80
|
-
<p>No item to display</p>
|
|
81
|
-
</div>
|
|
82
|
-
{/if}
|
|
83
|
-
|
|
84
|
-
<!-- Navigation -->
|
|
85
|
-
<ItemNavigation
|
|
86
|
-
{currentIndex}
|
|
87
|
-
{totalItems}
|
|
88
|
-
{canNext}
|
|
89
|
-
{canPrevious}
|
|
90
|
-
{onprevious}
|
|
91
|
-
{onnext}
|
|
92
|
-
/>
|
|
93
|
-
</div>
|
|
94
|
-
{#if shouldRenderToolbar && !isToolbarBeforeContent}
|
|
95
|
-
<pie-section-tools-toolbar
|
|
96
|
-
position={toolbarPosition}
|
|
97
|
-
enabled-tools=""
|
|
98
|
-
></pie-section-tools-toolbar>
|
|
99
|
-
{/if}
|
|
100
|
-
</div>
|
|
101
|
-
|
|
102
|
-
<style>
|
|
103
|
-
.pie-section-player__layout-shell {
|
|
104
|
-
display: flex;
|
|
105
|
-
width: 100%;
|
|
106
|
-
height: 100%;
|
|
107
|
-
min-height: 0;
|
|
108
|
-
overflow: hidden;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
.pie-section-player__layout-shell--top,
|
|
112
|
-
.pie-section-player__layout-shell--bottom,
|
|
113
|
-
.pie-section-player__layout-shell--none {
|
|
114
|
-
flex-direction: column;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
.pie-section-player__layout-shell--left,
|
|
118
|
-
.pie-section-player__layout-shell--right {
|
|
119
|
-
flex-direction: row;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
.pie-section-player__item-mode-layout {
|
|
123
|
-
flex: 1;
|
|
124
|
-
display: flex;
|
|
125
|
-
flex-direction: column;
|
|
126
|
-
gap: 1.5rem;
|
|
127
|
-
padding: 1rem;
|
|
128
|
-
overflow-y: auto;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
.pie-section-player__passages-section {
|
|
132
|
-
display: flex;
|
|
133
|
-
flex-direction: column;
|
|
134
|
-
gap: 1rem;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
.pie-section-player__passages-section :global(.pie-section-player__passage-item) {
|
|
138
|
-
padding: 0;
|
|
139
|
-
background: transparent;
|
|
140
|
-
border: 0;
|
|
141
|
-
border-radius: 0;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
.pie-section-player__passage-wrapper {
|
|
145
|
-
flex-shrink: 0;
|
|
146
|
-
padding: 0.25rem;
|
|
147
|
-
background: var(--pie-white, white);
|
|
148
|
-
border: 1px solid var(--pie-border-light, #e5e7eb);
|
|
149
|
-
border-radius: 6px;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
.pie-section-player__current-item-section {
|
|
153
|
-
padding: 0.25rem;
|
|
154
|
-
background: var(--pie-white, white);
|
|
155
|
-
border: 1px solid var(--pie-border-light, #e5e7eb);
|
|
156
|
-
border-radius: 6px;
|
|
157
|
-
min-height: 300px;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
.pie-section-player__no-item {
|
|
161
|
-
padding: 2rem;
|
|
162
|
-
text-align: center;
|
|
163
|
-
color: var(--pie-disabled-secondary, #999);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/* Responsive */
|
|
167
|
-
@media (max-width: 768px) {
|
|
168
|
-
.pie-section-player__item-mode-layout {
|
|
169
|
-
gap: 1rem;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
</style>
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
ItemNavigation - Internal Component
|
|
3
|
-
|
|
4
|
-
Navigation controls for item mode (keepTogether: false).
|
|
5
|
-
Not exposed as a web component - used internally in PieSectionPlayer.
|
|
6
|
-
-->
|
|
7
|
-
<script lang="ts">
|
|
8
|
-
let {
|
|
9
|
-
currentIndex,
|
|
10
|
-
totalItems,
|
|
11
|
-
canNext,
|
|
12
|
-
canPrevious,
|
|
13
|
-
onprevious,
|
|
14
|
-
onnext
|
|
15
|
-
}: {
|
|
16
|
-
currentIndex: number;
|
|
17
|
-
totalItems: number;
|
|
18
|
-
canNext: boolean;
|
|
19
|
-
canPrevious: boolean;
|
|
20
|
-
onprevious?: () => void;
|
|
21
|
-
onnext?: () => void;
|
|
22
|
-
} = $props();
|
|
23
|
-
</script>
|
|
24
|
-
|
|
25
|
-
<div class="pie-section-player__item-navigation">
|
|
26
|
-
<button
|
|
27
|
-
type="button"
|
|
28
|
-
class="pie-section-player__nav-button pie-section-player__nav-button--previous"
|
|
29
|
-
disabled={!canPrevious}
|
|
30
|
-
onclick={onprevious}
|
|
31
|
-
>
|
|
32
|
-
Previous
|
|
33
|
-
</button>
|
|
34
|
-
<span class="pie-section-player__item-counter">
|
|
35
|
-
{currentIndex + 1} / {totalItems}
|
|
36
|
-
</span>
|
|
37
|
-
<button
|
|
38
|
-
type="button"
|
|
39
|
-
class="pie-section-player__nav-button pie-section-player__nav-button--next"
|
|
40
|
-
disabled={!canNext}
|
|
41
|
-
onclick={onnext}
|
|
42
|
-
>
|
|
43
|
-
Next
|
|
44
|
-
</button>
|
|
45
|
-
</div>
|
|
46
|
-
|
|
47
|
-
<style>
|
|
48
|
-
.pie-section-player__item-navigation {
|
|
49
|
-
display: flex;
|
|
50
|
-
justify-content: space-between;
|
|
51
|
-
align-items: center;
|
|
52
|
-
padding: 1rem;
|
|
53
|
-
background: var(--pie-secondary-background, #f9f9f9);
|
|
54
|
-
border-top: 1px solid var(--pie-border-light, #e0e0e0);
|
|
55
|
-
gap: 1rem;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
.pie-section-player__nav-button {
|
|
59
|
-
padding: 0.5rem 1rem;
|
|
60
|
-
background: var(--pie-primary, #007bff);
|
|
61
|
-
color: var(--pie-white, white);
|
|
62
|
-
border: none;
|
|
63
|
-
border-radius: 4px;
|
|
64
|
-
cursor: pointer;
|
|
65
|
-
font-size: 0.9rem;
|
|
66
|
-
transition: background 0.2s;
|
|
67
|
-
min-width: 100px;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
.pie-section-player__nav-button:hover:not(:disabled) {
|
|
71
|
-
background: var(--pie-primary-dark, #0056b3);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
.pie-section-player__nav-button:disabled {
|
|
75
|
-
background: var(--pie-disabled-secondary, #ccc);
|
|
76
|
-
cursor: not-allowed;
|
|
77
|
-
opacity: 0.6;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
.pie-section-player__item-counter {
|
|
81
|
-
font-size: 0.9rem;
|
|
82
|
-
color: var(--pie-disabled, #666);
|
|
83
|
-
font-weight: 500;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/* Responsive */
|
|
87
|
-
@media (max-width: 768px) {
|
|
88
|
-
.pie-section-player__item-navigation {
|
|
89
|
-
flex-direction: column;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
.pie-section-player__nav-button {
|
|
93
|
-
width: 100%;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
</style>
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { ComponentDefinition } from "../component-definitions.js";
|
|
3
|
-
import type { ItemEntity, PassageEntity } from "@pie-players/pie-players-shared";
|
|
4
|
-
import { onMount, untrack } from "svelte";
|
|
5
|
-
|
|
6
|
-
let {
|
|
7
|
-
item,
|
|
8
|
-
env,
|
|
9
|
-
session,
|
|
10
|
-
hasElements,
|
|
11
|
-
resolvedPlayerTag,
|
|
12
|
-
resolvedPlayerDefinition,
|
|
13
|
-
skipElementLoading = true,
|
|
14
|
-
onsessionchanged,
|
|
15
|
-
}: {
|
|
16
|
-
item: ItemEntity | PassageEntity;
|
|
17
|
-
env: {
|
|
18
|
-
mode: "gather" | "view" | "evaluate" | "author";
|
|
19
|
-
role: "student" | "instructor";
|
|
20
|
-
};
|
|
21
|
-
session: any;
|
|
22
|
-
hasElements: boolean;
|
|
23
|
-
resolvedPlayerTag: string;
|
|
24
|
-
resolvedPlayerDefinition?: ComponentDefinition;
|
|
25
|
-
skipElementLoading?: boolean;
|
|
26
|
-
onsessionchanged?: (event: CustomEvent) => void;
|
|
27
|
-
} = $props();
|
|
28
|
-
|
|
29
|
-
let playerElement: any = $state(null);
|
|
30
|
-
let lastConfig: any = null;
|
|
31
|
-
let lastEnv: any = null;
|
|
32
|
-
|
|
33
|
-
onMount(() => {
|
|
34
|
-
(async () => {
|
|
35
|
-
if (hasElements) {
|
|
36
|
-
await resolvedPlayerDefinition?.ensureDefined?.();
|
|
37
|
-
}
|
|
38
|
-
})();
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
$effect(() => {
|
|
42
|
-
const currentConfig = item.config;
|
|
43
|
-
const currentEnv = env;
|
|
44
|
-
const currentSession = session;
|
|
45
|
-
|
|
46
|
-
if (!playerElement || !currentConfig || !hasElements) return;
|
|
47
|
-
|
|
48
|
-
const envChanged =
|
|
49
|
-
!lastEnv ||
|
|
50
|
-
lastEnv.mode !== currentEnv.mode ||
|
|
51
|
-
lastEnv.role !== currentEnv.role;
|
|
52
|
-
if (currentConfig === lastConfig && !envChanged) return;
|
|
53
|
-
|
|
54
|
-
untrack(() => {
|
|
55
|
-
playerElement.config = currentConfig;
|
|
56
|
-
playerElement.session = currentSession;
|
|
57
|
-
playerElement.env = currentEnv;
|
|
58
|
-
if (resolvedPlayerDefinition?.attributes) {
|
|
59
|
-
for (const [name, value] of Object.entries(
|
|
60
|
-
resolvedPlayerDefinition.attributes,
|
|
61
|
-
)) {
|
|
62
|
-
playerElement.setAttribute(name, value);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
if (resolvedPlayerDefinition?.props) {
|
|
66
|
-
for (const [name, value] of Object.entries(resolvedPlayerDefinition.props)) {
|
|
67
|
-
(playerElement as any)[name] = value;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
if (skipElementLoading) {
|
|
71
|
-
playerElement.setAttribute("skip-element-loading", "true");
|
|
72
|
-
(playerElement as any).skipElementLoading = true;
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
lastConfig = currentConfig;
|
|
77
|
-
lastEnv = currentEnv;
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
$effect(() => {
|
|
81
|
-
if (!playerElement || !onsessionchanged) return;
|
|
82
|
-
|
|
83
|
-
const handler = (event: Event) => {
|
|
84
|
-
const customEvent = event as CustomEvent;
|
|
85
|
-
console.debug('[ItemPlayerBridge][SessionTrace] session-changed received', {
|
|
86
|
-
itemId: item?.id || null,
|
|
87
|
-
envMode: env?.mode,
|
|
88
|
-
envRole: env?.role,
|
|
89
|
-
detailKeys:
|
|
90
|
-
customEvent?.detail && typeof customEvent.detail === 'object'
|
|
91
|
-
? Object.keys(customEvent.detail)
|
|
92
|
-
: []
|
|
93
|
-
});
|
|
94
|
-
onsessionchanged(event as CustomEvent);
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
playerElement.addEventListener("session-changed", handler);
|
|
98
|
-
return () => {
|
|
99
|
-
playerElement.removeEventListener("session-changed", handler);
|
|
100
|
-
};
|
|
101
|
-
});
|
|
102
|
-
</script>
|
|
103
|
-
|
|
104
|
-
{#if hasElements}
|
|
105
|
-
{#key resolvedPlayerTag}
|
|
106
|
-
<svelte:element this={resolvedPlayerTag} bind:this={playerElement}></svelte:element>
|
|
107
|
-
{/key}
|
|
108
|
-
{:else}
|
|
109
|
-
{@html item.config.markup}
|
|
110
|
-
{/if}
|
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
ItemRenderer - Internal Component
|
|
3
|
-
|
|
4
|
-
Renders a single item using the IIFE player.
|
|
5
|
-
Handles SSML extraction, TTS service binding, and player lifecycle.
|
|
6
|
-
-->
|
|
7
|
-
<script lang="ts">
|
|
8
|
-
import {
|
|
9
|
-
SSMLExtractor,
|
|
10
|
-
assessmentToolkitRuntimeContext,
|
|
11
|
-
createScopedToolId,
|
|
12
|
-
type AssessmentToolkitRuntimeContext,
|
|
13
|
-
} from "@pie-players/pie-assessment-toolkit";
|
|
14
|
-
import { ContextConsumer } from "@pie-players/pie-context";
|
|
15
|
-
import "@pie-players/pie-assessment-toolkit/components/item-toolbar-element";
|
|
16
|
-
import {
|
|
17
|
-
DEFAULT_PLAYER_DEFINITIONS,
|
|
18
|
-
} from "../component-definitions.js";
|
|
19
|
-
import ItemPlayerBridge from "./ItemPlayerBridge.svelte";
|
|
20
|
-
import ItemShell, { type QtiContentKind } from "./ItemShell.svelte";
|
|
21
|
-
import type { ItemEntity, PassageEntity } from "@pie-players/pie-players-shared";
|
|
22
|
-
import { onMount, untrack } from "svelte";
|
|
23
|
-
|
|
24
|
-
type SectionPlayerRuntimeContext = AssessmentToolkitRuntimeContext & {
|
|
25
|
-
reportSessionChanged?: (itemId: string, detail: unknown) => void;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
let {
|
|
29
|
-
item,
|
|
30
|
-
env = { mode: "gather", role: "student" },
|
|
31
|
-
session = { id: "", data: [] },
|
|
32
|
-
contentKind = "assessment-item" as QtiContentKind,
|
|
33
|
-
skipElementLoading = true,
|
|
34
|
-
customClassName = "",
|
|
35
|
-
onsessionchanged,
|
|
36
|
-
}: {
|
|
37
|
-
item: ItemEntity | PassageEntity;
|
|
38
|
-
env?: {
|
|
39
|
-
mode: "gather" | "view" | "evaluate" | "author";
|
|
40
|
-
role: "student" | "instructor";
|
|
41
|
-
};
|
|
42
|
-
session?: any;
|
|
43
|
-
contentKind?: QtiContentKind;
|
|
44
|
-
skipElementLoading?: boolean;
|
|
45
|
-
playerVersion?: string;
|
|
46
|
-
customClassName?: string;
|
|
47
|
-
onsessionchanged?: (event: CustomEvent) => void;
|
|
48
|
-
} = $props();
|
|
49
|
-
|
|
50
|
-
function handlePlayerSessionChanged(event: CustomEvent) {
|
|
51
|
-
console.debug("[ItemRenderer][SessionTrace] handlePlayerSessionChanged", {
|
|
52
|
-
itemId: item?.id || null,
|
|
53
|
-
contentKind,
|
|
54
|
-
hasRuntimeReporter: !!runtimeContext?.reportSessionChanged,
|
|
55
|
-
hasOnSessionChangedProp: !!onsessionchanged,
|
|
56
|
-
detailKeys:
|
|
57
|
-
event?.detail && typeof event.detail === "object"
|
|
58
|
-
? Object.keys(event.detail)
|
|
59
|
-
: [],
|
|
60
|
-
});
|
|
61
|
-
// Section item sessions are reported through runtime context to avoid
|
|
62
|
-
// callback prop-drilling across internal layout components.
|
|
63
|
-
if (contentKind === "assessment-item") {
|
|
64
|
-
const itemId = item.id || "";
|
|
65
|
-
if (itemId && runtimeContext?.reportSessionChanged) {
|
|
66
|
-
console.debug("[ItemRenderer][SessionTrace] forwarding via runtimeContext", {
|
|
67
|
-
itemId,
|
|
68
|
-
});
|
|
69
|
-
event.stopPropagation();
|
|
70
|
-
runtimeContext.reportSessionChanged(itemId, event.detail);
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
if (onsessionchanged) {
|
|
75
|
-
console.debug("[ItemRenderer][SessionTrace] forwarding via onsessionchanged prop", {
|
|
76
|
-
itemId: item?.id || null,
|
|
77
|
-
});
|
|
78
|
-
event.stopPropagation();
|
|
79
|
-
onsessionchanged(event);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Get the DOM element reference for service binding
|
|
84
|
-
let contextHostElement: HTMLElement | null = $state(null);
|
|
85
|
-
let itemContentElement: HTMLElement | null = $state(null);
|
|
86
|
-
let itemToolbarElement: HTMLElement | null = $state(null);
|
|
87
|
-
let runtimeContext = $state<SectionPlayerRuntimeContext | null>(null);
|
|
88
|
-
let runtimeContextConsumer: ContextConsumer<
|
|
89
|
-
typeof assessmentToolkitRuntimeContext
|
|
90
|
-
> | null = null;
|
|
91
|
-
|
|
92
|
-
// Runtime dependencies come from assessment toolkit context.
|
|
93
|
-
const effectiveToolkitCoordinator = $derived(runtimeContext?.toolkitCoordinator);
|
|
94
|
-
const toolCoordinator = $derived(
|
|
95
|
-
effectiveToolkitCoordinator?.toolCoordinator,
|
|
96
|
-
);
|
|
97
|
-
const catalogResolver = $derived(
|
|
98
|
-
effectiveToolkitCoordinator?.catalogResolver,
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
// Consume runtime context from the section-player provider tree.
|
|
102
|
-
$effect(() => {
|
|
103
|
-
if (!contextHostElement) return;
|
|
104
|
-
runtimeContextConsumer = new ContextConsumer(contextHostElement, {
|
|
105
|
-
context: assessmentToolkitRuntimeContext,
|
|
106
|
-
subscribe: true,
|
|
107
|
-
onValue: (value: AssessmentToolkitRuntimeContext) => {
|
|
108
|
-
runtimeContext = value as SectionPlayerRuntimeContext;
|
|
109
|
-
},
|
|
110
|
-
});
|
|
111
|
-
runtimeContextConsumer.connect();
|
|
112
|
-
return () => {
|
|
113
|
-
runtimeContextConsumer?.disconnect();
|
|
114
|
-
runtimeContextConsumer = null;
|
|
115
|
-
};
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
let calculatorVisible = $state(false);
|
|
119
|
-
|
|
120
|
-
let hasElements = $derived(
|
|
121
|
-
!!(item?.config?.elements && Object.keys(item.config.elements).length > 0),
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
let resolvedPlayerDefinition = $derived.by(
|
|
125
|
-
() => DEFAULT_PLAYER_DEFINITIONS["iife"],
|
|
126
|
-
);
|
|
127
|
-
let resolvedPlayerTag = $derived(resolvedPlayerDefinition?.tagName || "pie-iife-player");
|
|
128
|
-
|
|
129
|
-
onMount(() => {
|
|
130
|
-
// Cleanup: clear this item's catalogs on unmount.
|
|
131
|
-
return () => {
|
|
132
|
-
if (catalogResolver) {
|
|
133
|
-
catalogResolver.clearItemCatalogs();
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
// Extract SSML from item config when item changes
|
|
139
|
-
$effect(() => {
|
|
140
|
-
if (item?.config && catalogResolver) {
|
|
141
|
-
// Skip if already extracted
|
|
142
|
-
if (item.config.extractedCatalogs) {
|
|
143
|
-
catalogResolver.clearItemCatalogs();
|
|
144
|
-
catalogResolver.addItemCatalogs(item.config.extractedCatalogs);
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const extractor = new SSMLExtractor();
|
|
149
|
-
const result = extractor.extractFromItemConfig(item.config);
|
|
150
|
-
|
|
151
|
-
// Update config with cleaned content (SSML removed, catalog IDs added)
|
|
152
|
-
untrack(() => {
|
|
153
|
-
item.config = result.cleanedConfig;
|
|
154
|
-
item.config.extractedCatalogs = result.catalogs;
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
// Register catalogs with resolver for TTS lookup
|
|
158
|
-
if (result.catalogs.length > 0) {
|
|
159
|
-
catalogResolver.clearItemCatalogs(); // Clear previous item's catalogs
|
|
160
|
-
catalogResolver.addItemCatalogs(result.catalogs);
|
|
161
|
-
console.debug(
|
|
162
|
-
`[ItemRenderer] Extracted ${result.catalogs.length} SSML catalogs for item ${item.id}`,
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
// Bind direct item contracts to item toolbar.
|
|
169
|
-
$effect(() => {
|
|
170
|
-
if (!itemToolbarElement) return;
|
|
171
|
-
if (itemContentElement) {
|
|
172
|
-
(itemToolbarElement as any).scopeElement = itemContentElement;
|
|
173
|
-
}
|
|
174
|
-
if (item) {
|
|
175
|
-
(itemToolbarElement as any).item = item;
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
// Subscribe to calculator visibility changes
|
|
181
|
-
$effect(() => {
|
|
182
|
-
if (!toolCoordinator || !item) return;
|
|
183
|
-
const scopedCalculatorId = createScopedToolId(
|
|
184
|
-
"calculator",
|
|
185
|
-
"item",
|
|
186
|
-
item.id || "unknown-item",
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
const unsubscribe = toolCoordinator.subscribe(() => {
|
|
190
|
-
calculatorVisible = toolCoordinator.isToolVisible(scopedCalculatorId);
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
// Initial update
|
|
194
|
-
calculatorVisible = toolCoordinator.isToolVisible(scopedCalculatorId);
|
|
195
|
-
|
|
196
|
-
return unsubscribe;
|
|
197
|
-
});
|
|
198
|
-
</script>
|
|
199
|
-
|
|
200
|
-
<div bind:this={contextHostElement} data-item-id={item.id || ""}>
|
|
201
|
-
{#if item.config}
|
|
202
|
-
<ItemShell
|
|
203
|
-
{item}
|
|
204
|
-
{contentKind}
|
|
205
|
-
{customClassName}
|
|
206
|
-
>
|
|
207
|
-
<pie-item-toolbar
|
|
208
|
-
slot="toolbar"
|
|
209
|
-
bind:this={itemToolbarElement}
|
|
210
|
-
item-id={item.id}
|
|
211
|
-
catalog-id={item.id}
|
|
212
|
-
tools="calculator,tts,answerEliminator"
|
|
213
|
-
content-kind={contentKind}
|
|
214
|
-
size="md"
|
|
215
|
-
language="en-US"
|
|
216
|
-
></pie-item-toolbar>
|
|
217
|
-
|
|
218
|
-
<div class="pie-section-player__item-content" bind:this={itemContentElement}>
|
|
219
|
-
<ItemPlayerBridge
|
|
220
|
-
{item}
|
|
221
|
-
{env}
|
|
222
|
-
{session}
|
|
223
|
-
{hasElements}
|
|
224
|
-
resolvedPlayerTag={resolvedPlayerTag}
|
|
225
|
-
resolvedPlayerDefinition={resolvedPlayerDefinition}
|
|
226
|
-
{skipElementLoading}
|
|
227
|
-
onsessionchanged={handlePlayerSessionChanged}
|
|
228
|
-
/>
|
|
229
|
-
</div>
|
|
230
|
-
</ItemShell>
|
|
231
|
-
|
|
232
|
-
<!-- Calculator Tool Instance (rendered outside panel for floating overlay) -->
|
|
233
|
-
{#if item}
|
|
234
|
-
<pie-tool-calculator
|
|
235
|
-
visible={calculatorVisible}
|
|
236
|
-
tool-id={createScopedToolId("calculator", "item", item.id || "unknown-item")}
|
|
237
|
-
></pie-tool-calculator>
|
|
238
|
-
{/if}
|
|
239
|
-
{/if}
|
|
240
|
-
</div>
|
|
241
|
-
|
|
242
|
-
<style>
|
|
243
|
-
.pie-section-player__item-content {
|
|
244
|
-
padding: 1rem;
|
|
245
|
-
border: 1px solid var(--pie-border-light, #e5e7eb);
|
|
246
|
-
border-radius: 4px;
|
|
247
|
-
}
|
|
248
|
-
</style>
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
assessmentToolkitRuntimeContext,
|
|
4
|
-
type AssessmentToolkitRuntimeContext,
|
|
5
|
-
} from "@pie-players/pie-assessment-toolkit";
|
|
6
|
-
import { ContextConsumer } from "@pie-players/pie-context";
|
|
7
|
-
import type { ItemEntity, PassageEntity } from "@pie-players/pie-players-shared";
|
|
8
|
-
|
|
9
|
-
export type QtiContentKind =
|
|
10
|
-
| "assessment-item"
|
|
11
|
-
| "rubric-block-stimulus"
|
|
12
|
-
| "rubric-block-instructions"
|
|
13
|
-
| "rubric-block-rubric";
|
|
14
|
-
|
|
15
|
-
let {
|
|
16
|
-
item,
|
|
17
|
-
contentKind = "assessment-item" as QtiContentKind,
|
|
18
|
-
customClassName = "",
|
|
19
|
-
}: {
|
|
20
|
-
item: ItemEntity | PassageEntity;
|
|
21
|
-
contentKind?: QtiContentKind;
|
|
22
|
-
customClassName?: string;
|
|
23
|
-
} = $props();
|
|
24
|
-
|
|
25
|
-
let contextHostElement = $state<HTMLElement | null>(null);
|
|
26
|
-
let runtimeContext = $state<AssessmentToolkitRuntimeContext | null>(null);
|
|
27
|
-
let runtimeContextConsumer: ContextConsumer<
|
|
28
|
-
typeof assessmentToolkitRuntimeContext
|
|
29
|
-
> | null = null;
|
|
30
|
-
|
|
31
|
-
const effectiveAssessmentId = $derived(runtimeContext?.assessmentId ?? "");
|
|
32
|
-
const effectiveSectionId = $derived(runtimeContext?.sectionId ?? "");
|
|
33
|
-
|
|
34
|
-
$effect(() => {
|
|
35
|
-
if (!contextHostElement) return;
|
|
36
|
-
runtimeContextConsumer = new ContextConsumer(contextHostElement, {
|
|
37
|
-
context: assessmentToolkitRuntimeContext,
|
|
38
|
-
subscribe: true,
|
|
39
|
-
onValue: (value: AssessmentToolkitRuntimeContext) => {
|
|
40
|
-
runtimeContext = value;
|
|
41
|
-
},
|
|
42
|
-
});
|
|
43
|
-
runtimeContextConsumer.connect();
|
|
44
|
-
return () => {
|
|
45
|
-
runtimeContextConsumer?.disconnect();
|
|
46
|
-
runtimeContextConsumer = null;
|
|
47
|
-
};
|
|
48
|
-
});
|
|
49
|
-
</script>
|
|
50
|
-
|
|
51
|
-
<div
|
|
52
|
-
bind:this={contextHostElement}
|
|
53
|
-
class="pie-section-player__item-renderer {customClassName}"
|
|
54
|
-
data-assessment-id={effectiveAssessmentId}
|
|
55
|
-
data-section-id={effectiveSectionId}
|
|
56
|
-
data-item-id={item.id}
|
|
57
|
-
data-content-kind={contentKind}
|
|
58
|
-
>
|
|
59
|
-
<div class="pie-section-player__item-header">
|
|
60
|
-
<h4 class="pie-section-player__item-title">{item.name || "Question"}</h4>
|
|
61
|
-
<slot name="toolbar"></slot>
|
|
62
|
-
</div>
|
|
63
|
-
<slot></slot>
|
|
64
|
-
</div>
|
|
65
|
-
|
|
66
|
-
<style>
|
|
67
|
-
.pie-section-player__item-renderer {
|
|
68
|
-
display: block;
|
|
69
|
-
margin-bottom: 0;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
.pie-section-player__item-header {
|
|
73
|
-
display: flex;
|
|
74
|
-
align-items: center;
|
|
75
|
-
justify-content: space-between;
|
|
76
|
-
padding: 0.75rem 0;
|
|
77
|
-
margin-bottom: 0.5rem;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
.pie-section-player__item-title {
|
|
81
|
-
margin: 0;
|
|
82
|
-
font-size: 0.95rem;
|
|
83
|
-
font-weight: 600;
|
|
84
|
-
color: var(--pie-primary, #1976d2);
|
|
85
|
-
}
|
|
86
|
-
</style>
|