@pie-players/pie-section-player 0.2.13 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/controllers/SectionContentService.d.ts +3 -0
- package/dist/controllers/SectionContentService.d.ts.map +1 -1
- package/dist/controllers/SectionController.d.ts +50 -0
- package/dist/controllers/SectionController.d.ts.map +1 -1
- package/dist/controllers/SectionSessionService.d.ts.map +1 -1
- package/dist/controllers/types.d.ts +69 -5
- package/dist/controllers/types.d.ts.map +1 -1
- package/dist/{pie-item-player-B1iGN63e.js → pie-item-player-DDIEaTcw.js} +1327 -1320
- package/dist/pie-section-player.js +24561 -19488
- package/package.json +18 -17
- package/src/components/ItemShellElement.svelte +110 -3
- package/src/components/PassageShellElement.svelte +49 -0
- package/src/components/PieSectionPlayerBaseElement.svelte +44 -0
- package/src/components/PieSectionPlayerSplitPaneElement.svelte +104 -4
- package/src/components/PieSectionPlayerVerticalElement.svelte +26 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pie-players/pie-section-player",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Web component for rendering QTI 3.0 assessment sections with passages and items",
|
|
6
6
|
"license": "MIT",
|
|
@@ -49,21 +49,22 @@
|
|
|
49
49
|
"svelte": "^5.51.0"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@pie-players/pie-assessment-toolkit": "0.
|
|
53
|
-
"@pie-players/pie-item-player": "0.
|
|
54
|
-
"@pie-players/pie-context": "0.
|
|
55
|
-
"@pie-players/pie-players-shared": "0.
|
|
56
|
-
"@pie-players/pie-toolbars": "0.
|
|
57
|
-
"@pie-players/pie-tool-answer-eliminator": "0.
|
|
58
|
-
"@pie-players/pie-tool-
|
|
59
|
-
"@pie-players/pie-tool-
|
|
60
|
-
"@pie-players/pie-tool-
|
|
61
|
-
"@pie-players/pie-tool-
|
|
62
|
-
"@pie-players/pie-tool-
|
|
63
|
-
"@pie-players/pie-tool-
|
|
64
|
-
"@pie-players/pie-tool-
|
|
65
|
-
"@pie-players/pie-tool-
|
|
66
|
-
"@pie-players/
|
|
52
|
+
"@pie-players/pie-assessment-toolkit": "0.3.0",
|
|
53
|
+
"@pie-players/pie-item-player": "0.3.0",
|
|
54
|
+
"@pie-players/pie-context": "0.3.0",
|
|
55
|
+
"@pie-players/pie-players-shared": "0.3.0",
|
|
56
|
+
"@pie-players/pie-toolbars": "0.3.0",
|
|
57
|
+
"@pie-players/pie-tool-answer-eliminator": "0.3.0",
|
|
58
|
+
"@pie-players/pie-tool-annotation-toolbar": "0.3.0",
|
|
59
|
+
"@pie-players/pie-tool-calculator": "0.3.0",
|
|
60
|
+
"@pie-players/pie-tool-theme": "0.3.0",
|
|
61
|
+
"@pie-players/pie-tool-graph": "0.3.0",
|
|
62
|
+
"@pie-players/pie-tool-line-reader": "0.3.0",
|
|
63
|
+
"@pie-players/pie-tool-periodic-table": "0.3.0",
|
|
64
|
+
"@pie-players/pie-tool-protractor": "0.3.0",
|
|
65
|
+
"@pie-players/pie-tool-ruler": "0.3.0",
|
|
66
|
+
"@pie-players/pie-tool-text-to-speech": "0.3.0",
|
|
67
|
+
"@pie-players/tts-client-server": "0.3.0",
|
|
67
68
|
"daisyui": "^5.5.18"
|
|
68
69
|
},
|
|
69
70
|
"devDependencies": {
|
|
@@ -83,7 +84,7 @@
|
|
|
83
84
|
"url": "https://github.com/pie-framework/pie-players/issues"
|
|
84
85
|
},
|
|
85
86
|
"engines": {
|
|
86
|
-
"node": ">=
|
|
87
|
+
"node": ">=20.0.0"
|
|
87
88
|
},
|
|
88
89
|
"sideEffects": true
|
|
89
90
|
}
|
|
@@ -13,8 +13,16 @@
|
|
|
13
13
|
}}
|
|
14
14
|
/>
|
|
15
15
|
|
|
16
|
+
<script module lang="ts">
|
|
17
|
+
const crossShellSessionDedupe = new Map<
|
|
18
|
+
string,
|
|
19
|
+
{ fingerprint: string; timestamp: number }
|
|
20
|
+
>();
|
|
21
|
+
</script>
|
|
22
|
+
|
|
16
23
|
<script lang="ts">
|
|
17
24
|
import {
|
|
25
|
+
PIE_INTERNAL_ITEM_SESSION_CHANGED_EVENT,
|
|
18
26
|
PIE_ITEM_SESSION_CHANGED_EVENT,
|
|
19
27
|
PIE_REGISTER_EVENT,
|
|
20
28
|
PIE_UNREGISTER_EVENT,
|
|
@@ -23,11 +31,28 @@
|
|
|
23
31
|
dispatchCrossBoundaryEvent,
|
|
24
32
|
type AssessmentToolkitRegionScopeContext,
|
|
25
33
|
type AssessmentToolkitShellContext,
|
|
34
|
+
type InternalItemSessionChangedDetail,
|
|
26
35
|
type ItemSessionChangedDetail,
|
|
27
36
|
type RuntimeRegistrationDetail,
|
|
28
37
|
} from "@pie-players/pie-assessment-toolkit";
|
|
38
|
+
import { normalizeItemSessionChange } from "@pie-players/pie-players-shared";
|
|
29
39
|
import { ContextProvider, ContextRoot } from "@pie-players/pie-context";
|
|
30
40
|
|
|
41
|
+
const PIE_INTERNAL_CONTENT_LOADED_EVENT = "pie-content-loaded";
|
|
42
|
+
const PIE_INTERNAL_ITEM_PLAYER_ERROR_EVENT = "pie-item-player-error";
|
|
43
|
+
type InternalContentLoadedDetail = {
|
|
44
|
+
itemId: string;
|
|
45
|
+
canonicalItemId?: string;
|
|
46
|
+
contentKind?: string;
|
|
47
|
+
detail?: unknown;
|
|
48
|
+
};
|
|
49
|
+
type InternalItemPlayerErrorDetail = {
|
|
50
|
+
itemId: string;
|
|
51
|
+
canonicalItemId?: string;
|
|
52
|
+
contentKind?: string;
|
|
53
|
+
error: unknown;
|
|
54
|
+
};
|
|
55
|
+
|
|
31
56
|
let {
|
|
32
57
|
itemId = "",
|
|
33
58
|
canonicalItemId = "",
|
|
@@ -100,13 +125,64 @@
|
|
|
100
125
|
function normalizeAndDispatchSession(event: Event): void {
|
|
101
126
|
if (!host || !itemId) return;
|
|
102
127
|
const detail = (event as CustomEvent).detail;
|
|
128
|
+
const internalPayload: InternalItemSessionChangedDetail = {
|
|
129
|
+
itemId,
|
|
130
|
+
session: detail,
|
|
131
|
+
};
|
|
132
|
+
// Internal runtime session wiring must always continue to keep item UI/state synchronized.
|
|
133
|
+
dispatchCrossBoundaryEvent(host, PIE_INTERNAL_ITEM_SESSION_CHANGED_EVENT, internalPayload);
|
|
134
|
+
// Keep public item stream response-focused; metadata-only belongs on canonical session-changed.
|
|
135
|
+
const normalized = normalizeItemSessionChange({
|
|
136
|
+
itemId,
|
|
137
|
+
sessionDetail: detail,
|
|
138
|
+
});
|
|
139
|
+
if (normalized.intent === "metadata-only" || !normalized.session) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
103
142
|
const payload: ItemSessionChangedDetail = {
|
|
104
143
|
itemId,
|
|
105
144
|
canonicalItemId: canonicalItemId || itemId,
|
|
106
|
-
session:
|
|
145
|
+
session: normalized.session,
|
|
107
146
|
};
|
|
108
147
|
dispatchCrossBoundaryEvent(host, PIE_ITEM_SESSION_CHANGED_EVENT, payload);
|
|
109
|
-
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function dispatchLoaded(detail: unknown): void {
|
|
151
|
+
if (!host || !itemId) return;
|
|
152
|
+
const payload: InternalContentLoadedDetail = {
|
|
153
|
+
itemId,
|
|
154
|
+
canonicalItemId: canonicalItemId || itemId,
|
|
155
|
+
contentKind,
|
|
156
|
+
detail,
|
|
157
|
+
};
|
|
158
|
+
dispatchCrossBoundaryEvent(host, PIE_INTERNAL_CONTENT_LOADED_EVENT, payload);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function dispatchPlayerError(error: unknown): void {
|
|
162
|
+
if (!host || !itemId) return;
|
|
163
|
+
const payload: InternalItemPlayerErrorDetail = {
|
|
164
|
+
itemId,
|
|
165
|
+
canonicalItemId: canonicalItemId || itemId,
|
|
166
|
+
contentKind,
|
|
167
|
+
error,
|
|
168
|
+
};
|
|
169
|
+
dispatchCrossBoundaryEvent(host, PIE_INTERNAL_ITEM_PLAYER_ERROR_EVENT, payload);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function createSessionEventFingerprint(detail: unknown): string {
|
|
173
|
+
const semanticDetail =
|
|
174
|
+
detail && typeof detail === "object"
|
|
175
|
+
? { ...(detail as Record<string, unknown>) }
|
|
176
|
+
: detail;
|
|
177
|
+
if (semanticDetail && typeof semanticDetail === "object") {
|
|
178
|
+
delete (semanticDetail as Record<string, unknown>).timestamp;
|
|
179
|
+
delete (semanticDetail as Record<string, unknown>).sourceRuntimeId;
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
182
|
+
return JSON.stringify(semanticDetail);
|
|
183
|
+
} catch {
|
|
184
|
+
return String(semanticDetail);
|
|
185
|
+
}
|
|
110
186
|
}
|
|
111
187
|
|
|
112
188
|
$effect(() => {
|
|
@@ -114,19 +190,50 @@
|
|
|
114
190
|
dispatchRegistration(PIE_REGISTER_EVENT);
|
|
115
191
|
|
|
116
192
|
const seenSessionEvents = new WeakSet<Event>();
|
|
193
|
+
let lastForwardedFingerprint = "";
|
|
194
|
+
const CROSS_SHELL_DEDUPE_WINDOW_MS = 500;
|
|
117
195
|
const onSessionChanged = (event: Event) => {
|
|
196
|
+
// Keep framework surface minimal: normalize these descendant events and do not
|
|
197
|
+
// leak raw item-player session events to external listeners.
|
|
198
|
+
event.stopPropagation();
|
|
118
199
|
// Some players emit `session-changed`, others emit `sessionchanged`.
|
|
119
|
-
// Guard against duplicate forwarding when both fire for the same
|
|
200
|
+
// Guard against duplicate forwarding when both fire for the same payload.
|
|
120
201
|
if (seenSessionEvents.has(event)) return;
|
|
121
202
|
seenSessionEvents.add(event);
|
|
203
|
+
const fingerprint = createSessionEventFingerprint((event as CustomEvent).detail);
|
|
204
|
+
if (fingerprint === lastForwardedFingerprint) return;
|
|
205
|
+
const dedupeKey = itemId || canonicalItemId || "__unknown-item__";
|
|
206
|
+
const now = Date.now();
|
|
207
|
+
const lastCrossShell = crossShellSessionDedupe.get(dedupeKey);
|
|
208
|
+
if (
|
|
209
|
+
lastCrossShell &&
|
|
210
|
+
lastCrossShell.fingerprint === fingerprint &&
|
|
211
|
+
now - lastCrossShell.timestamp < CROSS_SHELL_DEDUPE_WINDOW_MS
|
|
212
|
+
) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
crossShellSessionDedupe.set(dedupeKey, { fingerprint, timestamp: now });
|
|
216
|
+
lastForwardedFingerprint = fingerprint;
|
|
122
217
|
normalizeAndDispatchSession(event);
|
|
123
218
|
};
|
|
124
219
|
host.addEventListener("sessionchanged", onSessionChanged);
|
|
125
220
|
host.addEventListener("session-changed", onSessionChanged);
|
|
221
|
+
const onLoadComplete = (event: Event) => {
|
|
222
|
+
event.stopPropagation();
|
|
223
|
+
dispatchLoaded((event as CustomEvent).detail);
|
|
224
|
+
};
|
|
225
|
+
const onPlayerError = (event: Event) => {
|
|
226
|
+
event.stopPropagation();
|
|
227
|
+
dispatchPlayerError((event as CustomEvent).detail);
|
|
228
|
+
};
|
|
229
|
+
host.addEventListener("load-complete", onLoadComplete);
|
|
230
|
+
host.addEventListener("player-error", onPlayerError);
|
|
126
231
|
|
|
127
232
|
return () => {
|
|
128
233
|
host?.removeEventListener("sessionchanged", onSessionChanged);
|
|
129
234
|
host?.removeEventListener("session-changed", onSessionChanged);
|
|
235
|
+
host?.removeEventListener("load-complete", onLoadComplete);
|
|
236
|
+
host?.removeEventListener("player-error", onPlayerError);
|
|
130
237
|
dispatchRegistration(PIE_UNREGISTER_EVENT);
|
|
131
238
|
};
|
|
132
239
|
});
|
|
@@ -26,6 +26,21 @@
|
|
|
26
26
|
} from "@pie-players/pie-assessment-toolkit";
|
|
27
27
|
import { ContextProvider, ContextRoot } from "@pie-players/pie-context";
|
|
28
28
|
|
|
29
|
+
const PIE_INTERNAL_CONTENT_LOADED_EVENT = "pie-content-loaded";
|
|
30
|
+
const PIE_INTERNAL_ITEM_PLAYER_ERROR_EVENT = "pie-item-player-error";
|
|
31
|
+
type InternalContentLoadedDetail = {
|
|
32
|
+
itemId: string;
|
|
33
|
+
canonicalItemId?: string;
|
|
34
|
+
contentKind?: string;
|
|
35
|
+
detail?: unknown;
|
|
36
|
+
};
|
|
37
|
+
type InternalItemPlayerErrorDetail = {
|
|
38
|
+
itemId: string;
|
|
39
|
+
canonicalItemId?: string;
|
|
40
|
+
contentKind?: string;
|
|
41
|
+
error: unknown;
|
|
42
|
+
};
|
|
43
|
+
|
|
29
44
|
let {
|
|
30
45
|
itemId = "",
|
|
31
46
|
canonicalItemId = "",
|
|
@@ -95,11 +110,45 @@
|
|
|
95
110
|
dispatchCrossBoundaryEvent(host, eventName, detail);
|
|
96
111
|
}
|
|
97
112
|
|
|
113
|
+
function dispatchLoaded(detail: unknown): void {
|
|
114
|
+
if (!host || !itemId) return;
|
|
115
|
+
const payload: InternalContentLoadedDetail = {
|
|
116
|
+
itemId,
|
|
117
|
+
canonicalItemId: canonicalItemId || itemId,
|
|
118
|
+
contentKind,
|
|
119
|
+
detail,
|
|
120
|
+
};
|
|
121
|
+
dispatchCrossBoundaryEvent(host, PIE_INTERNAL_CONTENT_LOADED_EVENT, payload);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function dispatchPlayerError(error: unknown): void {
|
|
125
|
+
if (!host || !itemId) return;
|
|
126
|
+
const payload: InternalItemPlayerErrorDetail = {
|
|
127
|
+
itemId,
|
|
128
|
+
canonicalItemId: canonicalItemId || itemId,
|
|
129
|
+
contentKind,
|
|
130
|
+
error,
|
|
131
|
+
};
|
|
132
|
+
dispatchCrossBoundaryEvent(host, PIE_INTERNAL_ITEM_PLAYER_ERROR_EVENT, payload);
|
|
133
|
+
}
|
|
134
|
+
|
|
98
135
|
$effect(() => {
|
|
99
136
|
if (!host) return;
|
|
100
137
|
dispatchRegistration(PIE_REGISTER_EVENT);
|
|
138
|
+
const onLoadComplete = (event: Event) => {
|
|
139
|
+
event.stopPropagation();
|
|
140
|
+
dispatchLoaded((event as CustomEvent).detail);
|
|
141
|
+
};
|
|
142
|
+
const onPlayerError = (event: Event) => {
|
|
143
|
+
event.stopPropagation();
|
|
144
|
+
dispatchPlayerError((event as CustomEvent).detail);
|
|
145
|
+
};
|
|
146
|
+
host.addEventListener("load-complete", onLoadComplete);
|
|
147
|
+
host.addEventListener("player-error", onPlayerError);
|
|
101
148
|
|
|
102
149
|
return () => {
|
|
150
|
+
host?.removeEventListener("load-complete", onLoadComplete);
|
|
151
|
+
host?.removeEventListener("player-error", onPlayerError);
|
|
103
152
|
dispatchRegistration(PIE_UNREGISTER_EVENT);
|
|
104
153
|
};
|
|
105
154
|
});
|
|
@@ -23,9 +23,14 @@
|
|
|
23
23
|
|
|
24
24
|
<script lang="ts">
|
|
25
25
|
import "@pie-players/pie-assessment-toolkit/components/pie-assessment-toolkit-element";
|
|
26
|
+
import "@pie-players/pie-tool-annotation-toolbar";
|
|
26
27
|
import {
|
|
27
28
|
createDefaultPersonalNeedsProfile,
|
|
28
29
|
} from "@pie-players/pie-assessment-toolkit";
|
|
30
|
+
import {
|
|
31
|
+
normalizeToolsConfig,
|
|
32
|
+
resolveToolsForLevel,
|
|
33
|
+
} from "@pie-players/pie-assessment-toolkit";
|
|
29
34
|
import { createEventDispatcher } from "svelte";
|
|
30
35
|
import { SectionController } from "../controllers/SectionController.js";
|
|
31
36
|
import type { SectionCompositionModel } from "../controllers/types.js";
|
|
@@ -57,6 +62,7 @@
|
|
|
57
62
|
} = $props();
|
|
58
63
|
|
|
59
64
|
let toolkitElement = $state<any>(null);
|
|
65
|
+
let activeToolkitCoordinator = $state<any>(null);
|
|
60
66
|
let lastCompositionVersion = $state(-1);
|
|
61
67
|
type BaseSectionPlayerEvents = {
|
|
62
68
|
"composition-changed": { composition: SectionCompositionModel };
|
|
@@ -124,9 +130,40 @@
|
|
|
124
130
|
eventName: Exclude<keyof BaseSectionPlayerEvents, "composition-changed">,
|
|
125
131
|
): void {
|
|
126
132
|
const detail = (event as CustomEvent).detail as Record<string, unknown>;
|
|
133
|
+
if (eventName === "toolkit-ready" && detail?.coordinator) {
|
|
134
|
+
activeToolkitCoordinator = detail.coordinator;
|
|
135
|
+
}
|
|
127
136
|
emit(eventName, detail || ({} as Record<string, unknown>));
|
|
128
137
|
}
|
|
129
138
|
|
|
139
|
+
const normalizedToolsConfig = $derived.by(() =>
|
|
140
|
+
normalizeToolsConfig((effectiveTools || {}) as any),
|
|
141
|
+
);
|
|
142
|
+
const annotationToolbarPlacementEnabled = $derived.by(() => {
|
|
143
|
+
const levels: Array<"section" | "item" | "passage"> = [
|
|
144
|
+
"section",
|
|
145
|
+
"item",
|
|
146
|
+
"passage",
|
|
147
|
+
];
|
|
148
|
+
return levels.some((level) =>
|
|
149
|
+
resolveToolsForLevel(normalizedToolsConfig as any, level).includes(
|
|
150
|
+
"annotationToolbar",
|
|
151
|
+
),
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
const annotationToolbarProviderEnabled = $derived.by(() =>
|
|
155
|
+
activeToolkitCoordinator?.isToolEnabled?.("annotationToolbar") ??
|
|
156
|
+
((normalizedToolsConfig as any)?.providers?.annotationToolbar?.enabled !==
|
|
157
|
+
false),
|
|
158
|
+
);
|
|
159
|
+
const shouldRenderAnnotationToolbar = $derived(
|
|
160
|
+
Boolean(
|
|
161
|
+
activeToolkitCoordinator &&
|
|
162
|
+
annotationToolbarPlacementEnabled &&
|
|
163
|
+
annotationToolbarProviderEnabled,
|
|
164
|
+
),
|
|
165
|
+
);
|
|
166
|
+
|
|
130
167
|
$effect(() => {
|
|
131
168
|
if (!toolkitElement) return;
|
|
132
169
|
toolkitElement.createSectionController =
|
|
@@ -157,6 +194,13 @@
|
|
|
157
194
|
onruntime-owned={(event: Event) => handleToolkitEvent(event, "runtime-owned")}
|
|
158
195
|
onruntime-inherited={(event: Event) => handleToolkitEvent(event, "runtime-inherited")}
|
|
159
196
|
>
|
|
197
|
+
{#if shouldRenderAnnotationToolbar}
|
|
198
|
+
<pie-tool-annotation-toolbar
|
|
199
|
+
enabled={true}
|
|
200
|
+
ttsService={activeToolkitCoordinator.ttsService}
|
|
201
|
+
highlightCoordinator={activeToolkitCoordinator.highlightCoordinator}
|
|
202
|
+
></pie-tool-annotation-toolbar>
|
|
203
|
+
{/if}
|
|
160
204
|
<slot></slot>
|
|
161
205
|
</pie-assessment-toolkit>
|
|
162
206
|
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
isolation: { attribute: "isolation", type: "String" },
|
|
20
20
|
env: { type: "Object", reflect: false },
|
|
21
21
|
iifeBundleHost: { attribute: "iife-bundle-host", type: "String" },
|
|
22
|
-
showToolbar: { attribute: "show-toolbar", type: "
|
|
22
|
+
showToolbar: { attribute: "show-toolbar", type: "String" },
|
|
23
23
|
toolbarPosition: { attribute: "toolbar-position", type: "String" },
|
|
24
24
|
enabledTools: { attribute: "enabled-tools", type: "String" },
|
|
25
25
|
itemToolbarTools: { attribute: "item-toolbar-tools", type: "String" },
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
/>
|
|
30
30
|
|
|
31
31
|
<script lang="ts">
|
|
32
|
+
import { onMount } from "svelte";
|
|
32
33
|
import "./section-player-base-element.js";
|
|
33
34
|
import * as SectionItemCardModule from "./shared/SectionItemCard.svelte";
|
|
34
35
|
import * as SectionPassageCardModule from "./shared/SectionPassageCard.svelte";
|
|
@@ -91,13 +92,36 @@
|
|
|
91
92
|
isolation,
|
|
92
93
|
env,
|
|
93
94
|
iifeBundleHost,
|
|
94
|
-
showToolbar = true,
|
|
95
|
+
showToolbar = "true" as boolean | string | null | undefined,
|
|
95
96
|
toolbarPosition = "right",
|
|
96
97
|
enabledTools = "",
|
|
97
98
|
itemToolbarTools = "",
|
|
98
99
|
passageToolbarTools = "",
|
|
99
100
|
} = $props();
|
|
100
101
|
|
|
102
|
+
function resolveToolbarVisibility(value: boolean | string | null | undefined): boolean {
|
|
103
|
+
if (typeof value === "boolean") {
|
|
104
|
+
return value;
|
|
105
|
+
}
|
|
106
|
+
if (value === null || value === undefined) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
const normalizedValue = String(value).trim().toLowerCase();
|
|
110
|
+
if (normalizedValue === "") {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
if (["false", "0", "off", "no"].includes(normalizedValue)) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
if (["true", "1", "on", "yes"].includes(normalizedValue)) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
return Boolean(normalizedValue);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const MANAGED_OUTER_SCROLL_CLASS = "pie-outer-scrollbars-managed";
|
|
123
|
+
const ACTIVE_OUTER_SCROLL_CLASS = "pie-outer-scrolling";
|
|
124
|
+
|
|
101
125
|
let compositionModel = $state<SectionCompositionModel>(EMPTY_COMPOSITION);
|
|
102
126
|
let leftPanelWidth = $state(50);
|
|
103
127
|
let isDragging = $state(false);
|
|
@@ -109,7 +133,9 @@
|
|
|
109
133
|
const passages = $derived(compositionModel.passages || []);
|
|
110
134
|
const items = $derived(compositionModel.items || []);
|
|
111
135
|
const hasPassages = $derived(passages.length > 0);
|
|
112
|
-
const shouldRenderToolbar = $derived(
|
|
136
|
+
const shouldRenderToolbar = $derived(
|
|
137
|
+
resolveToolbarVisibility(showToolbar) && toolbarPosition !== "none",
|
|
138
|
+
);
|
|
113
139
|
const toolbarBeforeContent = $derived(
|
|
114
140
|
toolbarPosition === "top" || toolbarPosition === "left",
|
|
115
141
|
);
|
|
@@ -237,6 +263,39 @@
|
|
|
237
263
|
});
|
|
238
264
|
});
|
|
239
265
|
|
|
266
|
+
onMount(() => {
|
|
267
|
+
let scrollTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
268
|
+
const html = document.documentElement;
|
|
269
|
+
const body = document.body;
|
|
270
|
+
|
|
271
|
+
html.classList.add(MANAGED_OUTER_SCROLL_CLASS);
|
|
272
|
+
body.classList.add(MANAGED_OUTER_SCROLL_CLASS);
|
|
273
|
+
|
|
274
|
+
const showOuterScrollbars = () => {
|
|
275
|
+
html.classList.add(ACTIVE_OUTER_SCROLL_CLASS);
|
|
276
|
+
body.classList.add(ACTIVE_OUTER_SCROLL_CLASS);
|
|
277
|
+
if (scrollTimeout) {
|
|
278
|
+
clearTimeout(scrollTimeout);
|
|
279
|
+
}
|
|
280
|
+
scrollTimeout = setTimeout(() => {
|
|
281
|
+
html.classList.remove(ACTIVE_OUTER_SCROLL_CLASS);
|
|
282
|
+
body.classList.remove(ACTIVE_OUTER_SCROLL_CLASS);
|
|
283
|
+
}, 900);
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
window.addEventListener("scroll", showOuterScrollbars, { passive: true });
|
|
287
|
+
return () => {
|
|
288
|
+
window.removeEventListener("scroll", showOuterScrollbars);
|
|
289
|
+
html.classList.remove(ACTIVE_OUTER_SCROLL_CLASS);
|
|
290
|
+
body.classList.remove(ACTIVE_OUTER_SCROLL_CLASS);
|
|
291
|
+
html.classList.remove(MANAGED_OUTER_SCROLL_CLASS);
|
|
292
|
+
body.classList.remove(MANAGED_OUTER_SCROLL_CLASS);
|
|
293
|
+
if (scrollTimeout) {
|
|
294
|
+
clearTimeout(scrollTimeout);
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
});
|
|
298
|
+
|
|
240
299
|
</script>
|
|
241
300
|
|
|
242
301
|
<pie-section-player-base
|
|
@@ -258,7 +317,7 @@
|
|
|
258
317
|
{/if}
|
|
259
318
|
|
|
260
319
|
<div
|
|
261
|
-
class={`pie-section-player-layout-body ${toolbarInline ? "pie-section-player-layout-body--inline" : ""}`}
|
|
320
|
+
class={`pie-section-player-layout-body ${shouldRenderToolbar && toolbarInline ? "pie-section-player-layout-body--inline" : ""}`}
|
|
262
321
|
>
|
|
263
322
|
<div
|
|
264
323
|
class={`pie-section-player-split-content ${!hasPassages ? "pie-section-player-split-content--no-passages" : ""}`}
|
|
@@ -555,6 +614,47 @@
|
|
|
555
614
|
padding: 1rem;
|
|
556
615
|
}
|
|
557
616
|
|
|
617
|
+
:global(html.pie-outer-scrollbars-managed),
|
|
618
|
+
:global(body.pie-outer-scrollbars-managed) {
|
|
619
|
+
scrollbar-width: auto;
|
|
620
|
+
scrollbar-color: transparent transparent;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
:global(html.pie-outer-scrollbars-managed.pie-outer-scrolling),
|
|
624
|
+
:global(body.pie-outer-scrollbars-managed.pie-outer-scrolling) {
|
|
625
|
+
scrollbar-color: #c1c1c1 #f1f1f1;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
:global(html.pie-outer-scrollbars-managed::-webkit-scrollbar),
|
|
629
|
+
:global(body.pie-outer-scrollbars-managed::-webkit-scrollbar) {
|
|
630
|
+
width: 0;
|
|
631
|
+
height: 0;
|
|
632
|
+
background: transparent;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
:global(html.pie-outer-scrollbars-managed.pie-outer-scrolling::-webkit-scrollbar),
|
|
636
|
+
:global(body.pie-outer-scrollbars-managed.pie-outer-scrolling::-webkit-scrollbar) {
|
|
637
|
+
width: 8px;
|
|
638
|
+
height: 8px;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
:global(html.pie-outer-scrollbars-managed.pie-outer-scrolling::-webkit-scrollbar-track),
|
|
642
|
+
:global(body.pie-outer-scrollbars-managed.pie-outer-scrolling::-webkit-scrollbar-track) {
|
|
643
|
+
background: #f1f1f1;
|
|
644
|
+
border-radius: 4px;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
:global(html.pie-outer-scrollbars-managed.pie-outer-scrolling::-webkit-scrollbar-thumb),
|
|
648
|
+
:global(body.pie-outer-scrollbars-managed.pie-outer-scrolling::-webkit-scrollbar-thumb) {
|
|
649
|
+
background: #c1c1c1;
|
|
650
|
+
border-radius: 4px;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
:global(html.pie-outer-scrollbars-managed.pie-outer-scrolling::-webkit-scrollbar-thumb:hover),
|
|
654
|
+
:global(body.pie-outer-scrollbars-managed.pie-outer-scrolling::-webkit-scrollbar-thumb:hover) {
|
|
655
|
+
background: #a1a1a1;
|
|
656
|
+
}
|
|
657
|
+
|
|
558
658
|
@media (max-width: 1100px) {
|
|
559
659
|
.pie-section-player-shell--left,
|
|
560
660
|
.pie-section-player-shell--right {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
isolation: { attribute: "isolation", type: "String" },
|
|
20
20
|
env: { type: "Object", reflect: false },
|
|
21
21
|
iifeBundleHost: { attribute: "iife-bundle-host", type: "String" },
|
|
22
|
-
showToolbar: { attribute: "show-toolbar", type: "
|
|
22
|
+
showToolbar: { attribute: "show-toolbar", type: "String" },
|
|
23
23
|
toolbarPosition: { attribute: "toolbar-position", type: "String" },
|
|
24
24
|
enabledTools: { attribute: "enabled-tools", type: "String" },
|
|
25
25
|
itemToolbarTools: { attribute: "item-toolbar-tools", type: "String" },
|
|
@@ -89,13 +89,33 @@
|
|
|
89
89
|
isolation,
|
|
90
90
|
env,
|
|
91
91
|
iifeBundleHost,
|
|
92
|
-
showToolbar = true,
|
|
92
|
+
showToolbar = "true" as boolean | string | null | undefined,
|
|
93
93
|
toolbarPosition = "right",
|
|
94
94
|
enabledTools = "",
|
|
95
95
|
itemToolbarTools = "",
|
|
96
96
|
passageToolbarTools = "",
|
|
97
97
|
} = $props();
|
|
98
98
|
|
|
99
|
+
function resolveToolbarVisibility(value: boolean | string | null | undefined): boolean {
|
|
100
|
+
if (typeof value === "boolean") {
|
|
101
|
+
return value;
|
|
102
|
+
}
|
|
103
|
+
if (value === null || value === undefined) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
const normalizedValue = String(value).trim().toLowerCase();
|
|
107
|
+
if (normalizedValue === "") {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
if (["false", "0", "off", "no"].includes(normalizedValue)) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
if (["true", "1", "on", "yes"].includes(normalizedValue)) {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
return Boolean(normalizedValue);
|
|
117
|
+
}
|
|
118
|
+
|
|
99
119
|
let compositionModel = $state<SectionCompositionModel>(EMPTY_COMPOSITION);
|
|
100
120
|
let elementsLoaded = $state(false);
|
|
101
121
|
let lastPreloadSignature = $state("");
|
|
@@ -103,7 +123,9 @@
|
|
|
103
123
|
|
|
104
124
|
const passages = $derived(compositionModel.passages || []);
|
|
105
125
|
const items = $derived(compositionModel.items || []);
|
|
106
|
-
const shouldRenderToolbar = $derived(
|
|
126
|
+
const shouldRenderToolbar = $derived(
|
|
127
|
+
resolveToolbarVisibility(showToolbar) && toolbarPosition !== "none",
|
|
128
|
+
);
|
|
107
129
|
const toolbarBeforeContent = $derived(
|
|
108
130
|
toolbarPosition === "top" || toolbarPosition === "left",
|
|
109
131
|
);
|
|
@@ -205,7 +227,7 @@
|
|
|
205
227
|
{/if}
|
|
206
228
|
|
|
207
229
|
<div
|
|
208
|
-
class={`pie-section-player-layout-body ${toolbarInline ? "pie-section-player-layout-body--inline" : ""}`}
|
|
230
|
+
class={`pie-section-player-layout-body ${shouldRenderToolbar && toolbarInline ? "pie-section-player-layout-body--inline" : ""}`}
|
|
209
231
|
>
|
|
210
232
|
<div class="pie-section-player-vertical-content">
|
|
211
233
|
{#if !elementsLoaded}
|