@pie-players/pie-players-shared 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/profile.d.ts +15 -0
- package/dist/config/profile.d.ts.map +1 -0
- package/dist/config/profile.js +27 -0
- package/dist/config/profile.js.map +1 -0
- package/dist/i18n/index.d.ts +13 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +12 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n/loader.d.ts +36 -0
- package/dist/i18n/loader.d.ts.map +1 -0
- package/dist/i18n/loader.js +133 -0
- package/dist/i18n/loader.js.map +1 -0
- package/dist/i18n/scripts/check-coverage.d.ts +16 -0
- package/dist/i18n/scripts/check-coverage.d.ts.map +1 -0
- package/dist/i18n/scripts/check-coverage.js +262 -0
- package/dist/i18n/scripts/check-coverage.js.map +1 -0
- package/dist/i18n/scripts/scan-hardcoded.d.ts +16 -0
- package/dist/i18n/scripts/scan-hardcoded.d.ts.map +1 -0
- package/dist/i18n/scripts/scan-hardcoded.js +266 -0
- package/dist/i18n/scripts/scan-hardcoded.js.map +1 -0
- package/dist/i18n/simple-i18n.d.ts +69 -0
- package/dist/i18n/simple-i18n.d.ts.map +1 -0
- package/dist/i18n/simple-i18n.js +199 -0
- package/dist/i18n/simple-i18n.js.map +1 -0
- package/dist/i18n/translations/ar/common.json +36 -0
- package/dist/i18n/translations/ar/toolkit.json +48 -0
- package/dist/i18n/translations/ar/tools.json +109 -0
- package/dist/i18n/translations/en/common.json +36 -0
- package/dist/i18n/translations/en/toolkit.json +48 -0
- package/dist/i18n/translations/en/tools.json +109 -0
- package/dist/i18n/translations/es/common.json +36 -0
- package/dist/i18n/translations/es/toolkit.json +48 -0
- package/dist/i18n/translations/es/tools.json +109 -0
- package/dist/i18n/translations/zh/common.json +36 -0
- package/dist/i18n/translations/zh/toolkit.json +48 -0
- package/dist/i18n/translations/zh/tools.json +109 -0
- package/dist/i18n/types.d.ts +58 -0
- package/dist/i18n/types.d.ts.map +1 -0
- package/dist/i18n/types.js +8 -0
- package/dist/i18n/types.js.map +1 -0
- package/dist/i18n/use-i18n-standalone.svelte.d.ts +87 -0
- package/dist/i18n/use-i18n-standalone.svelte.d.ts.map +1 -0
- package/dist/i18n/use-i18n-standalone.svelte.js +151 -0
- package/dist/i18n/use-i18n-standalone.svelte.js.map +1 -0
- package/dist/i18n/use-i18n.svelte.d.ts +67 -0
- package/dist/i18n/use-i18n.svelte.d.ts.map +1 -0
- package/dist/i18n/use-i18n.svelte.js +144 -0
- package/dist/i18n/use-i18n.svelte.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/instrumentation/index.d.ts +53 -0
- package/dist/instrumentation/index.d.ts.map +1 -0
- package/dist/instrumentation/index.js +53 -0
- package/dist/instrumentation/index.js.map +1 -0
- package/dist/instrumentation/providers/BaseInstrumentationProvider.d.ts +197 -0
- package/dist/instrumentation/providers/BaseInstrumentationProvider.d.ts.map +1 -0
- package/dist/instrumentation/providers/BaseInstrumentationProvider.js +267 -0
- package/dist/instrumentation/providers/BaseInstrumentationProvider.js.map +1 -0
- package/dist/instrumentation/providers/ConsoleInstrumentationProvider.d.ts +106 -0
- package/dist/instrumentation/providers/ConsoleInstrumentationProvider.d.ts.map +1 -0
- package/dist/instrumentation/providers/ConsoleInstrumentationProvider.js +182 -0
- package/dist/instrumentation/providers/ConsoleInstrumentationProvider.js.map +1 -0
- package/dist/instrumentation/providers/DataDogInstrumentationProvider.d.ts +170 -0
- package/dist/instrumentation/providers/DataDogInstrumentationProvider.d.ts.map +1 -0
- package/dist/instrumentation/providers/DataDogInstrumentationProvider.js +183 -0
- package/dist/instrumentation/providers/DataDogInstrumentationProvider.js.map +1 -0
- package/dist/instrumentation/providers/NewRelicInstrumentationProvider.d.ts +86 -0
- package/dist/instrumentation/providers/NewRelicInstrumentationProvider.d.ts.map +1 -0
- package/dist/instrumentation/providers/NewRelicInstrumentationProvider.js +135 -0
- package/dist/instrumentation/providers/NewRelicInstrumentationProvider.js.map +1 -0
- package/dist/instrumentation/providers/index.d.ts +12 -0
- package/dist/instrumentation/providers/index.d.ts.map +1 -0
- package/dist/instrumentation/providers/index.js +12 -0
- package/dist/instrumentation/providers/index.js.map +1 -0
- package/dist/instrumentation/types.d.ts +348 -0
- package/dist/instrumentation/types.d.ts.map +1 -0
- package/dist/instrumentation/types.js +9 -0
- package/dist/instrumentation/types.js.map +1 -0
- package/dist/loader-config.d.ts +76 -0
- package/dist/loader-config.d.ts.map +1 -0
- package/dist/loader-config.js +12 -0
- package/dist/loader-config.js.map +1 -0
- package/dist/loaders/ElementLoader.d.ts +72 -0
- package/dist/loaders/ElementLoader.d.ts.map +1 -0
- package/dist/loaders/ElementLoader.js +52 -0
- package/dist/loaders/ElementLoader.js.map +1 -0
- package/dist/loaders/EsmElementLoader.d.ts +67 -0
- package/dist/loaders/EsmElementLoader.d.ts.map +1 -0
- package/dist/loaders/EsmElementLoader.js +71 -0
- package/dist/loaders/EsmElementLoader.js.map +1 -0
- package/dist/loaders/IifeElementLoader.d.ts +61 -0
- package/dist/loaders/IifeElementLoader.d.ts.map +1 -0
- package/dist/loaders/IifeElementLoader.js +63 -0
- package/dist/loaders/IifeElementLoader.js.map +1 -0
- package/dist/loaders/index.d.ts +28 -0
- package/dist/loaders/index.d.ts.map +1 -0
- package/dist/loaders/index.js +25 -0
- package/dist/loaders/index.js.map +1 -0
- package/dist/object/index.d.ts +12 -0
- package/dist/object/index.d.ts.map +1 -0
- package/dist/object/index.js +40 -0
- package/dist/object/index.js.map +1 -0
- package/dist/pie/asset-handler.d.ts +64 -0
- package/dist/pie/asset-handler.d.ts.map +1 -0
- package/dist/pie/asset-handler.js +238 -0
- package/dist/pie/asset-handler.js.map +1 -0
- package/dist/pie/component-context.d.ts +22 -0
- package/dist/pie/component-context.d.ts.map +1 -0
- package/dist/pie/component-context.js +30 -0
- package/dist/pie/component-context.js.map +1 -0
- package/dist/pie/config.d.ts +39 -0
- package/dist/pie/config.d.ts.map +1 -0
- package/dist/pie/config.js +174 -0
- package/dist/pie/config.js.map +1 -0
- package/dist/pie/configure-initialization.d.ts +35 -0
- package/dist/pie/configure-initialization.d.ts.map +1 -0
- package/dist/pie/configure-initialization.js +141 -0
- package/dist/pie/configure-initialization.js.map +1 -0
- package/dist/pie/esm-loader.d.ts +93 -0
- package/dist/pie/esm-loader.d.ts.map +1 -0
- package/dist/pie/esm-loader.js +308 -0
- package/dist/pie/esm-loader.js.map +1 -0
- package/dist/pie/iife-loader.d.ts +76 -0
- package/dist/pie/iife-loader.d.ts.map +1 -0
- package/dist/pie/iife-loader.js +303 -0
- package/dist/pie/iife-loader.js.map +1 -0
- package/dist/pie/index.d.ts +31 -0
- package/dist/pie/index.d.ts.map +1 -0
- package/dist/pie/index.js +34 -0
- package/dist/pie/index.js.map +1 -0
- package/dist/pie/initialization.d.ts +40 -0
- package/dist/pie/initialization.d.ts.map +1 -0
- package/dist/pie/initialization.js +349 -0
- package/dist/pie/initialization.js.map +1 -0
- package/dist/pie/logger.d.ts +64 -0
- package/dist/pie/logger.d.ts.map +1 -0
- package/dist/pie/logger.js +45 -0
- package/dist/pie/logger.js.map +1 -0
- package/dist/pie/math-rendering.d.ts +69 -0
- package/dist/pie/math-rendering.d.ts.map +1 -0
- package/dist/pie/math-rendering.js +98 -0
- package/dist/pie/math-rendering.js.map +1 -0
- package/dist/pie/overrides.d.ts +43 -0
- package/dist/pie/overrides.d.ts.map +1 -0
- package/dist/pie/overrides.js +146 -0
- package/dist/pie/overrides.js.map +1 -0
- package/dist/pie/player-initializer.d.ts +55 -0
- package/dist/pie/player-initializer.d.ts.map +1 -0
- package/dist/pie/player-initializer.js +123 -0
- package/dist/pie/player-initializer.js.map +1 -0
- package/dist/pie/registry.d.ts +11 -0
- package/dist/pie/registry.d.ts.map +1 -0
- package/dist/pie/registry.js +21 -0
- package/dist/pie/registry.js.map +1 -0
- package/dist/pie/resource-monitor.d.ts +208 -0
- package/dist/pie/resource-monitor.d.ts.map +1 -0
- package/dist/pie/resource-monitor.js +969 -0
- package/dist/pie/resource-monitor.js.map +1 -0
- package/dist/pie/scoring.d.ts +17 -0
- package/dist/pie/scoring.d.ts.map +1 -0
- package/dist/pie/scoring.js +84 -0
- package/dist/pie/scoring.js.map +1 -0
- package/dist/pie/types.d.ts +136 -0
- package/dist/pie/types.d.ts.map +1 -0
- package/dist/pie/types.js +52 -0
- package/dist/pie/types.js.map +1 -0
- package/dist/pie/updates.d.ts +20 -0
- package/dist/pie/updates.d.ts.map +1 -0
- package/dist/pie/updates.js +175 -0
- package/dist/pie/updates.js.map +1 -0
- package/dist/pie/use-resource-monitor.svelte.d.ts +56 -0
- package/dist/pie/use-resource-monitor.svelte.d.ts.map +1 -0
- package/dist/pie/use-resource-monitor.svelte.js +117 -0
- package/dist/pie/use-resource-monitor.svelte.js.map +1 -0
- package/dist/pie/utils.d.ts +44 -0
- package/dist/pie/utils.d.ts.map +1 -0
- package/dist/pie/utils.js +74 -0
- package/dist/pie/utils.js.map +1 -0
- package/dist/types/custom-elements.d.ts +183 -0
- package/dist/types/custom-elements.d.ts.map +1 -0
- package/dist/types/custom-elements.js +8 -0
- package/dist/types/custom-elements.js.map +1 -0
- package/dist/types/index.d.ts +761 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +120 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/search.d.ts +105 -0
- package/dist/types/search.d.ts.map +1 -0
- package/dist/types/search.js +12 -0
- package/dist/types/search.js.map +1 -0
- package/dist/types/transform.d.ts +48 -0
- package/dist/types/transform.d.ts.map +1 -0
- package/dist/types/transform.js +21 -0
- package/dist/types/transform.js.map +1 -0
- package/dist/ui/focus-trap.d.ts +10 -0
- package/dist/ui/focus-trap.d.ts.map +1 -0
- package/dist/ui/focus-trap.js +30 -0
- package/dist/ui/focus-trap.js.map +1 -0
- package/dist/ui/safe-storage.d.ts +3 -0
- package/dist/ui/safe-storage.d.ts.map +1 -0
- package/dist/ui/safe-storage.js +21 -0
- package/dist/ui/safe-storage.js.map +1 -0
- package/package.json +118 -0
- package/src/components/PieItemPlayer.svelte +604 -0
- package/src/components/PiePreviewLayout.svelte +144 -0
- package/src/components/PiePreviewToggle.svelte +110 -0
- package/src/components/PieSpinner.svelte +85 -0
- package/src/components/ToolSettingsButton.svelte +31 -0
- package/src/components/ToolSettingsPanel.svelte +90 -0
- package/src/components/index.ts +6 -0
- package/src/i18n/README.md +223 -0
- package/src/i18n/index.ts +26 -0
- package/src/i18n/loader.ts +156 -0
- package/src/i18n/scripts/check-coverage.ts +345 -0
- package/src/i18n/scripts/scan-hardcoded.ts +342 -0
- package/src/i18n/simple-i18n.ts +236 -0
- package/src/i18n/translations/ar/common.json +36 -0
- package/src/i18n/translations/ar/toolkit.json +48 -0
- package/src/i18n/translations/ar/tools.json +109 -0
- package/src/i18n/translations/en/common.json +36 -0
- package/src/i18n/translations/en/toolkit.json +48 -0
- package/src/i18n/translations/en/tools.json +109 -0
- package/src/i18n/translations/es/common.json +36 -0
- package/src/i18n/translations/es/toolkit.json +48 -0
- package/src/i18n/translations/es/tools.json +109 -0
- package/src/i18n/translations/zh/common.json +36 -0
- package/src/i18n/translations/zh/toolkit.json +48 -0
- package/src/i18n/translations/zh/tools.json +109 -0
- package/src/i18n/types.ts +66 -0
- package/src/i18n/use-i18n-standalone.svelte.ts +184 -0
- package/src/i18n/use-i18n.svelte.ts +163 -0
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
PieItemPlayer - Pure PIE Item Renderer
|
|
3
|
+
|
|
4
|
+
This component renders a PIE item using direct PIE elements (no pie-player wrapper).
|
|
5
|
+
It assumes PIE bundles are already loaded via window.pie.
|
|
6
|
+
|
|
7
|
+
Uses the same reactive pattern as PieItemPreview.svelte (proven to work).
|
|
8
|
+
-->
|
|
9
|
+
<script lang="ts">
|
|
10
|
+
import { onDestroy, tick, untrack } from "svelte";
|
|
11
|
+
import type { LoaderConfig } from "../loader-config";
|
|
12
|
+
import { DEFAULT_LOADER_CONFIG } from "../loader-config";
|
|
13
|
+
import { AssetEventManager } from "../pie/asset-handler";
|
|
14
|
+
import { initializeConfiguresFromLoadedBundle } from "../pie/configure-initialization";
|
|
15
|
+
import { initializePiesFromLoadedBundle } from "../pie/initialization";
|
|
16
|
+
import { createPieLogger, isGlobalDebugEnabled } from "../pie/logger";
|
|
17
|
+
import { findPieController } from "../pie/scoring";
|
|
18
|
+
import type { AuthoringEnv } from "../pie/types";
|
|
19
|
+
import { BundleType } from "../pie/types";
|
|
20
|
+
import { updatePieElements } from "../pie/updates";
|
|
21
|
+
import { useResourceMonitor } from "../pie/use-resource-monitor.svelte";
|
|
22
|
+
import type {
|
|
23
|
+
ConfigEntity,
|
|
24
|
+
Env,
|
|
25
|
+
ImageHandler,
|
|
26
|
+
ModelUpdatedEvent,
|
|
27
|
+
SoundHandler,
|
|
28
|
+
} from "../types";
|
|
29
|
+
|
|
30
|
+
// Create logger (respects global debug flag - pass function for dynamic checking)
|
|
31
|
+
const logger = createPieLogger("pie-item-player", () =>
|
|
32
|
+
isGlobalDebugEnabled()
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
// Use Svelte 5 runes for props
|
|
36
|
+
let {
|
|
37
|
+
itemConfig,
|
|
38
|
+
passageConfig = null,
|
|
39
|
+
env = { mode: "gather", role: "student" } as Env,
|
|
40
|
+
session = [] as any[],
|
|
41
|
+
addCorrectResponse = false,
|
|
42
|
+
customClassname = "",
|
|
43
|
+
passageContainerClass = "",
|
|
44
|
+
containerClass = "",
|
|
45
|
+
bundleType = BundleType.player, // Default to player.js (server-processed models)
|
|
46
|
+
loaderConfig = DEFAULT_LOADER_CONFIG as LoaderConfig,
|
|
47
|
+
// Authoring mode props
|
|
48
|
+
mode = "view" as "view" | "author",
|
|
49
|
+
configuration = {} as Record<string, any>,
|
|
50
|
+
// Toolkit service integration (optional)
|
|
51
|
+
ttsService = null,
|
|
52
|
+
toolCoordinator = null,
|
|
53
|
+
highlightCoordinator = null,
|
|
54
|
+
// Asset handler callbacks
|
|
55
|
+
onInsertImage,
|
|
56
|
+
onDeleteImage,
|
|
57
|
+
onInsertSound,
|
|
58
|
+
onDeleteSound,
|
|
59
|
+
// Event callbacks (Svelte 5 pattern)
|
|
60
|
+
onLoadComplete,
|
|
61
|
+
onPlayerError,
|
|
62
|
+
onSessionChanged,
|
|
63
|
+
onModelUpdated,
|
|
64
|
+
}: {
|
|
65
|
+
itemConfig: ConfigEntity;
|
|
66
|
+
passageConfig?: ConfigEntity | null;
|
|
67
|
+
env?: Env;
|
|
68
|
+
session?: any[];
|
|
69
|
+
addCorrectResponse?: boolean;
|
|
70
|
+
customClassname?: string;
|
|
71
|
+
passageContainerClass?: string;
|
|
72
|
+
containerClass?: string;
|
|
73
|
+
bundleType?: BundleType;
|
|
74
|
+
loaderConfig?: LoaderConfig;
|
|
75
|
+
// Authoring mode props
|
|
76
|
+
mode?: "view" | "author";
|
|
77
|
+
configuration?: Record<string, any>;
|
|
78
|
+
// Toolkit service integration
|
|
79
|
+
ttsService?: any;
|
|
80
|
+
toolCoordinator?: any;
|
|
81
|
+
highlightCoordinator?: any;
|
|
82
|
+
// Asset handlers
|
|
83
|
+
onInsertImage?: (handler: ImageHandler) => void;
|
|
84
|
+
onDeleteImage?: (src: string, done: (err?: Error) => void) => void;
|
|
85
|
+
onInsertSound?: (handler: SoundHandler) => void;
|
|
86
|
+
onDeleteSound?: (src: string, done: (err?: Error) => void) => void;
|
|
87
|
+
// Event callbacks
|
|
88
|
+
onLoadComplete?: (detail?: any) => void;
|
|
89
|
+
onPlayerError?: (detail?: any) => void;
|
|
90
|
+
onSessionChanged?: (detail?: any) => void;
|
|
91
|
+
onModelUpdated?: (detail?: any) => void;
|
|
92
|
+
} = $props();
|
|
93
|
+
|
|
94
|
+
// Track if correct responses have been added
|
|
95
|
+
let correctResponsesAdded = $state(false);
|
|
96
|
+
|
|
97
|
+
// Asset event manager for authoring mode
|
|
98
|
+
let assetEventManager: AssetEventManager | null = $state(null);
|
|
99
|
+
|
|
100
|
+
// Transform markup for authoring mode (append -config suffix)
|
|
101
|
+
function transformMarkupForAuthoring(
|
|
102
|
+
markup: string,
|
|
103
|
+
elements: Record<string, string>
|
|
104
|
+
): string {
|
|
105
|
+
let result = markup;
|
|
106
|
+
for (const elementTag of Object.keys(elements)) {
|
|
107
|
+
// Replace opening tags
|
|
108
|
+
const openRegex = new RegExp(`<${elementTag}(\\s|>)`, "g");
|
|
109
|
+
result = result.replace(openRegex, `<${elementTag}-config$1`);
|
|
110
|
+
// Replace closing tags
|
|
111
|
+
const closeRegex = new RegExp(`</${elementTag}>`, "g");
|
|
112
|
+
result = result.replace(closeRegex, `</${elementTag}-config>`);
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Get appropriate markup based on mode
|
|
118
|
+
const itemMarkup = $derived.by(() => {
|
|
119
|
+
if (!itemConfig?.markup) return "";
|
|
120
|
+
if (mode === "author" && itemConfig.elements) {
|
|
121
|
+
return transformMarkupForAuthoring(
|
|
122
|
+
itemConfig.markup,
|
|
123
|
+
itemConfig.elements
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
return itemConfig.markup;
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const passageMarkup = $derived.by(() => {
|
|
130
|
+
if (!passageConfig?.markup) return "";
|
|
131
|
+
if (mode === "author" && passageConfig.elements) {
|
|
132
|
+
return transformMarkupForAuthoring(
|
|
133
|
+
passageConfig.markup,
|
|
134
|
+
passageConfig.elements
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
return passageConfig.markup;
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Dispatch events (will add more as needed)
|
|
141
|
+
const dispatch = (type: string, detail?: any) => {
|
|
142
|
+
// Call callback prop if provided (Svelte 5 pattern)
|
|
143
|
+
if (type === "load-complete" && onLoadComplete) {
|
|
144
|
+
onLoadComplete(detail);
|
|
145
|
+
} else if (type === "player-error" && onPlayerError) {
|
|
146
|
+
onPlayerError(detail);
|
|
147
|
+
} else if (type === "session-changed" && onSessionChanged) {
|
|
148
|
+
onSessionChanged(detail);
|
|
149
|
+
} else if (type === "model-updated" && onModelUpdated) {
|
|
150
|
+
onModelUpdated(detail);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Also dispatch DOM event for backward compatibility
|
|
154
|
+
const event = new CustomEvent(type, {
|
|
155
|
+
detail,
|
|
156
|
+
bubbles: true,
|
|
157
|
+
composed: true, // Allow events to cross shadow DOM boundaries
|
|
158
|
+
});
|
|
159
|
+
dispatchEvent(event);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// Populate session with correct responses when addCorrectResponse is true
|
|
163
|
+
async function populateCorrectResponses(force = false) {
|
|
164
|
+
// Early return checks
|
|
165
|
+
if (!addCorrectResponse || !itemConfig || (correctResponsesAdded && !force))
|
|
166
|
+
return;
|
|
167
|
+
|
|
168
|
+
// CRITICAL: Only populate if env has instructor role (required for createCorrectResponseSession)
|
|
169
|
+
if (env.role !== "instructor" || env.mode === "evaluate") {
|
|
170
|
+
logger.debug(
|
|
171
|
+
"[PieItemPlayer] Skipping populateCorrectResponses - env not suitable (role=%s, mode=%s)",
|
|
172
|
+
env.role,
|
|
173
|
+
env.mode
|
|
174
|
+
);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const newSession: any[] = [];
|
|
178
|
+
|
|
179
|
+
for (const model of itemConfig.models) {
|
|
180
|
+
const controller = findPieController(model.element);
|
|
181
|
+
logger.debug(
|
|
182
|
+
"[PieItemPlayer] Controller lookup for %s: %s (createCorrectResponseSession=%s)",
|
|
183
|
+
model.element,
|
|
184
|
+
controller ? "FOUND" : "NOT FOUND",
|
|
185
|
+
controller && controller.createCorrectResponseSession ? "YES" : "NO"
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
if (controller && controller.createCorrectResponseSession) {
|
|
189
|
+
try {
|
|
190
|
+
const correctResponse =
|
|
191
|
+
(await controller.createCorrectResponseSession(model, env)) as any;
|
|
192
|
+
|
|
193
|
+
// Check if we got a valid response
|
|
194
|
+
if (!correctResponse) {
|
|
195
|
+
logger.debug(
|
|
196
|
+
"[PieItemPlayer] createCorrectResponseSession returned null for %s (env=%j)",
|
|
197
|
+
model.element,
|
|
198
|
+
env
|
|
199
|
+
);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const { id: _ignoredId, ...sessionData } = correctResponse;
|
|
204
|
+
newSession.push({
|
|
205
|
+
id: model.id,
|
|
206
|
+
element: model.element,
|
|
207
|
+
...sessionData,
|
|
208
|
+
});
|
|
209
|
+
} catch (e) {
|
|
210
|
+
logger.warn(
|
|
211
|
+
"[PieItemPlayer] Failed to create correct response for %s",
|
|
212
|
+
model.element,
|
|
213
|
+
e
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Clear existing session entries first by dispatching clear events for each existing entry
|
|
220
|
+
// This ensures the parent component clears its session state before we populate new responses
|
|
221
|
+
const existingIds = new Set(session.map((s: any) => s.id));
|
|
222
|
+
for (const id of existingIds) {
|
|
223
|
+
// Dispatch a session-changed event with null/empty to signal clearing
|
|
224
|
+
// The parent should handle this by removing the entry
|
|
225
|
+
dispatch("session-changed", { id, clear: true });
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Update session with correct responses
|
|
229
|
+
session.length = 0;
|
|
230
|
+
session.push(...newSession);
|
|
231
|
+
|
|
232
|
+
// Only mark as added if we actually got responses
|
|
233
|
+
if (newSession.length > 0) {
|
|
234
|
+
correctResponsesAdded = true;
|
|
235
|
+
|
|
236
|
+
// IMPORTANT: `session` is a plain array prop that we mutate in place.
|
|
237
|
+
// Svelte reactivity won't necessarily re-run effects on in-place mutation,
|
|
238
|
+
// so we must push the updated session into the PIE elements explicitly.
|
|
239
|
+
try {
|
|
240
|
+
updatePieElements(itemConfig, session, env, rootElement);
|
|
241
|
+
if (passageConfig) {
|
|
242
|
+
updatePieElements(passageConfig, session, env, rootElement);
|
|
243
|
+
}
|
|
244
|
+
} catch (e) {
|
|
245
|
+
logger.warn(
|
|
246
|
+
"[PieItemPlayer] Failed to update PIE elements after populating correct responses",
|
|
247
|
+
e
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
logger.debug(
|
|
252
|
+
"[PieItemPlayer] Correct responses added to session:",
|
|
253
|
+
session
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
// Dispatch session-changed events for each populated response
|
|
257
|
+
// This ensures the parent component can sync its session state
|
|
258
|
+
for (const sessionEntry of newSession) {
|
|
259
|
+
dispatch("session-changed", sessionEntry);
|
|
260
|
+
}
|
|
261
|
+
} else {
|
|
262
|
+
logger.debug(
|
|
263
|
+
"[PieItemPlayer] No correct responses returned (likely wrong env). Will retry if env/addCorrectResponse changes."
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Build CSS classes for containers using $derived
|
|
269
|
+
const passageContainerClassFinal = $derived(
|
|
270
|
+
["pie-passage-container", customClassname, passageContainerClass]
|
|
271
|
+
.filter(Boolean)
|
|
272
|
+
.join(" ")
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
const itemContainerClassFinal = $derived(
|
|
276
|
+
["pie-item-container", customClassname, containerClass]
|
|
277
|
+
.filter(Boolean)
|
|
278
|
+
.join(" ")
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
// Track if we've initialized (to avoid double-initialization)
|
|
282
|
+
let initialized = $state(false);
|
|
283
|
+
|
|
284
|
+
// Set up session-changed listener after DOM is ready
|
|
285
|
+
let sessionListenerAttached = $state(false);
|
|
286
|
+
let detachSessionChangedListener: (() => void) | null = $state(null);
|
|
287
|
+
|
|
288
|
+
// Flag to prevent infinite loop when re-dispatching events
|
|
289
|
+
let isDispatching = $state(false);
|
|
290
|
+
|
|
291
|
+
// Root element reference for resource monitor
|
|
292
|
+
let rootElement: HTMLElement | null = $state(null);
|
|
293
|
+
|
|
294
|
+
// Resource monitor (handles initialization and cleanup automatically)
|
|
295
|
+
const resourceMonitor = useResourceMonitor(
|
|
296
|
+
() => rootElement,
|
|
297
|
+
() => loaderConfig,
|
|
298
|
+
() => isGlobalDebugEnabled(),
|
|
299
|
+
"pie-item-player"
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
// Initialize PIE elements AFTER markup is rendered (reactive pattern like PieItemPreview)
|
|
303
|
+
$effect(() => {
|
|
304
|
+
if (!itemConfig || initialized) return;
|
|
305
|
+
logger.debug(
|
|
306
|
+
"[PieItemPlayer] Item config received, initializing after DOM renders..."
|
|
307
|
+
);
|
|
308
|
+
logger.debug("[PieItemPlayer] Mode:", mode);
|
|
309
|
+
|
|
310
|
+
// Wait for DOM to update (markup to render)
|
|
311
|
+
tick().then(async () => {
|
|
312
|
+
try {
|
|
313
|
+
logger.debug("[PieItemPlayer] DOM ready, initializing PIE elements");
|
|
314
|
+
logger.debug("[PieItemPlayer] Config:", {
|
|
315
|
+
itemElements: Object.keys(itemConfig.elements || {}),
|
|
316
|
+
itemModels: (itemConfig.models || []).length,
|
|
317
|
+
passageElements: passageConfig
|
|
318
|
+
? Object.keys(passageConfig.elements || {})
|
|
319
|
+
: [],
|
|
320
|
+
passageModels: passageConfig
|
|
321
|
+
? (passageConfig.models || []).length
|
|
322
|
+
: 0,
|
|
323
|
+
sessionLength: session.length,
|
|
324
|
+
addCorrectResponse,
|
|
325
|
+
env,
|
|
326
|
+
mode,
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
if (mode === "author") {
|
|
330
|
+
// AUTHORING MODE: Initialize configure elements
|
|
331
|
+
logger.debug("[PieItemPlayer] Initializing in authoring mode");
|
|
332
|
+
|
|
333
|
+
const authoringEnv: AuthoringEnv = {
|
|
334
|
+
...env,
|
|
335
|
+
mode: "author",
|
|
336
|
+
configuration,
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
initializeConfiguresFromLoadedBundle(itemConfig, configuration, {
|
|
340
|
+
env: authoringEnv,
|
|
341
|
+
});
|
|
342
|
+
logger.debug("[PieItemPlayer] Configure elements initialized");
|
|
343
|
+
|
|
344
|
+
if (passageConfig) {
|
|
345
|
+
initializeConfiguresFromLoadedBundle(passageConfig, configuration, {
|
|
346
|
+
env: authoringEnv,
|
|
347
|
+
});
|
|
348
|
+
logger.debug(
|
|
349
|
+
"[PieItemPlayer] Passage configure elements initialized"
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Set up asset event manager if handlers provided
|
|
354
|
+
if (
|
|
355
|
+
rootElement &&
|
|
356
|
+
(onInsertImage || onDeleteImage || onInsertSound || onDeleteSound)
|
|
357
|
+
) {
|
|
358
|
+
assetEventManager = new AssetEventManager(
|
|
359
|
+
rootElement,
|
|
360
|
+
onInsertImage,
|
|
361
|
+
onDeleteImage,
|
|
362
|
+
onInsertSound,
|
|
363
|
+
onDeleteSound
|
|
364
|
+
);
|
|
365
|
+
assetEventManager.attach();
|
|
366
|
+
logger.debug("[PieItemPlayer] Asset event manager attached");
|
|
367
|
+
}
|
|
368
|
+
} else {
|
|
369
|
+
// VIEW MODE: Initialize regular player elements
|
|
370
|
+
logger.debug("[PieItemPlayer] Initializing in view mode");
|
|
371
|
+
|
|
372
|
+
// STEP 1: Initialize bundles and register controllers (don't pass session yet)
|
|
373
|
+
// This registers controllers in the registry so we can call createCorrectResponseSession
|
|
374
|
+
initializePiesFromLoadedBundle(itemConfig, [], { env, bundleType, container: rootElement });
|
|
375
|
+
logger.debug(
|
|
376
|
+
"[PieItemPlayer] Item bundle initialized (bundle type: %s)",
|
|
377
|
+
bundleType
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
if (passageConfig) {
|
|
381
|
+
initializePiesFromLoadedBundle(passageConfig, [], {
|
|
382
|
+
env,
|
|
383
|
+
bundleType,
|
|
384
|
+
container: rootElement,
|
|
385
|
+
});
|
|
386
|
+
logger.debug(
|
|
387
|
+
"[PieItemPlayer] Passage bundle initialized (bundle type: %s)",
|
|
388
|
+
bundleType
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// STEP 2: Don't populate correct responses during initialization
|
|
393
|
+
// Let the reactive effect handle it after initialization when env is ready
|
|
394
|
+
|
|
395
|
+
// STEP 3: Update elements with the correct session
|
|
396
|
+
logger.debug(
|
|
397
|
+
"[PieItemPlayer] Updating elements with session (length=" +
|
|
398
|
+
session.length +
|
|
399
|
+
")"
|
|
400
|
+
);
|
|
401
|
+
updatePieElements(itemConfig, session, env, rootElement);
|
|
402
|
+
|
|
403
|
+
if (passageConfig) {
|
|
404
|
+
updatePieElements(passageConfig, session, env, rootElement);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
initialized = true;
|
|
409
|
+
|
|
410
|
+
// Set up event listeners
|
|
411
|
+
if (!sessionListenerAttached) {
|
|
412
|
+
if (mode === "author") {
|
|
413
|
+
// AUTHORING MODE: Listen for model-updated events
|
|
414
|
+
const handleModelUpdated = (event: Event) => {
|
|
415
|
+
if (isDispatching) return;
|
|
416
|
+
|
|
417
|
+
const customEvent = event as ModelUpdatedEvent;
|
|
418
|
+
logger.debug(
|
|
419
|
+
"[PieItemPlayer] model-updated event received from configure element"
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
isDispatching = true;
|
|
423
|
+
try {
|
|
424
|
+
dispatch("model-updated", customEvent.detail);
|
|
425
|
+
} finally {
|
|
426
|
+
setTimeout(() => {
|
|
427
|
+
isDispatching = false;
|
|
428
|
+
}, 0);
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
if (rootElement) {
|
|
433
|
+
rootElement.addEventListener("model.updated", handleModelUpdated);
|
|
434
|
+
sessionListenerAttached = true;
|
|
435
|
+
detachSessionChangedListener = () => {
|
|
436
|
+
try {
|
|
437
|
+
rootElement?.removeEventListener(
|
|
438
|
+
"model.updated",
|
|
439
|
+
handleModelUpdated
|
|
440
|
+
);
|
|
441
|
+
} catch {}
|
|
442
|
+
};
|
|
443
|
+
logger.debug(
|
|
444
|
+
"[PieItemPlayer] model-updated listener attached to root element"
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
} else {
|
|
448
|
+
// VIEW MODE: Listen for session-changed events from PIE elements
|
|
449
|
+
const handleSessionChanged = (event: Event) => {
|
|
450
|
+
// CRITICAL: Prevent infinite loop
|
|
451
|
+
// When we dispatch, it triggers this listener again
|
|
452
|
+
// Use flag to detect and break the loop
|
|
453
|
+
if (isDispatching) {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const customEvent = event as CustomEvent;
|
|
458
|
+
logger.debug(
|
|
459
|
+
"[PieItemPlayer] session-changed event received from PIE element",
|
|
460
|
+
customEvent.detail
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
// Set flag before dispatching
|
|
464
|
+
isDispatching = true;
|
|
465
|
+
try {
|
|
466
|
+
// Re-dispatch to parent with full session data (not just event detail)
|
|
467
|
+
// The event detail only contains metadata like {complete: boolean, component: string}
|
|
468
|
+
// The actual session data is in the session array prop (it's already the data array)
|
|
469
|
+
dispatch("session-changed", {
|
|
470
|
+
...customEvent.detail,
|
|
471
|
+
session: { id: "", data: session },
|
|
472
|
+
});
|
|
473
|
+
} finally {
|
|
474
|
+
// Reset flag after dispatch (use setTimeout to ensure it happens after event propagation)
|
|
475
|
+
setTimeout(() => {
|
|
476
|
+
isDispatching = false;
|
|
477
|
+
}, 0);
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
// Attach to THIS component instance's root element (critical for stimulus layouts)
|
|
482
|
+
// Using document.querySelector would only attach to the first instance on the page.
|
|
483
|
+
if (rootElement) {
|
|
484
|
+
rootElement.addEventListener(
|
|
485
|
+
"session-changed",
|
|
486
|
+
handleSessionChanged
|
|
487
|
+
);
|
|
488
|
+
sessionListenerAttached = true;
|
|
489
|
+
detachSessionChangedListener = () => {
|
|
490
|
+
try {
|
|
491
|
+
rootElement?.removeEventListener(
|
|
492
|
+
"session-changed",
|
|
493
|
+
handleSessionChanged
|
|
494
|
+
);
|
|
495
|
+
} catch {}
|
|
496
|
+
};
|
|
497
|
+
logger.debug(
|
|
498
|
+
"[PieItemPlayer] session-changed listener attached to root element"
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Note: Resource monitor starts automatically via useResourceMonitor when rootElement is set
|
|
505
|
+
|
|
506
|
+
logger.debug(
|
|
507
|
+
"[PieItemPlayer] Initialization complete, dispatching load-complete event"
|
|
508
|
+
);
|
|
509
|
+
dispatch("load-complete");
|
|
510
|
+
} catch (e: any) {
|
|
511
|
+
logger.error("[PieItemPlayer] Error initializing:", e);
|
|
512
|
+
dispatch("player-error", e.message);
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
onDestroy(() => {
|
|
518
|
+
try {
|
|
519
|
+
detachSessionChangedListener?.();
|
|
520
|
+
assetEventManager?.detach();
|
|
521
|
+
} catch {}
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
// React to addCorrectResponse and env changes to populate/clear correct responses
|
|
525
|
+
$effect(() => {
|
|
526
|
+
if (!initialized) return;
|
|
527
|
+
|
|
528
|
+
// Read env to ensure effect tracks it (so it re-runs when env changes)
|
|
529
|
+
const currentEnv = env;
|
|
530
|
+
|
|
531
|
+
if (addCorrectResponse && !correctResponsesAdded) {
|
|
532
|
+
// Need to populate correct responses
|
|
533
|
+
// populateCorrectResponses will check if env is suitable (role === 'instructor')
|
|
534
|
+
untrack(async () => {
|
|
535
|
+
await populateCorrectResponses();
|
|
536
|
+
// Elements will be updated by the env/session effect below
|
|
537
|
+
});
|
|
538
|
+
} else if (!addCorrectResponse && correctResponsesAdded) {
|
|
539
|
+
// Switching FROM browse mode - clear correct responses
|
|
540
|
+
untrack(() => {
|
|
541
|
+
session.length = 0;
|
|
542
|
+
correctResponsesAdded = false;
|
|
543
|
+
// Elements will be updated by the env/session effect below
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
// Update PIE elements when env or session changes (after initialization) - using $effect
|
|
549
|
+
let isUpdating = false;
|
|
550
|
+
$effect(() => {
|
|
551
|
+
if (!initialized || !env || !itemConfig || !session) return;
|
|
552
|
+
if (isUpdating) return; // Prevent re-entry
|
|
553
|
+
|
|
554
|
+
// Log changes
|
|
555
|
+
logger.debug("[PieItemPlayer] Dependencies changed, updating elements");
|
|
556
|
+
logger.debug("[PieItemPlayer] Env:", env);
|
|
557
|
+
logger.debug(
|
|
558
|
+
"[PieItemPlayer] Session (length=" + session.length + "):",
|
|
559
|
+
session
|
|
560
|
+
);
|
|
561
|
+
|
|
562
|
+
isUpdating = true;
|
|
563
|
+
untrack(() => {
|
|
564
|
+
try {
|
|
565
|
+
updatePieElements(itemConfig, session, env, rootElement);
|
|
566
|
+
|
|
567
|
+
if (passageConfig) {
|
|
568
|
+
updatePieElements(passageConfig, session, env, rootElement);
|
|
569
|
+
}
|
|
570
|
+
} finally {
|
|
571
|
+
isUpdating = false;
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
// Note: Resource monitor cleanup is handled automatically by useResourceMonitor's onDestroy
|
|
577
|
+
</script>
|
|
578
|
+
|
|
579
|
+
<div class="pie-item-player" bind:this={rootElement}>
|
|
580
|
+
{#if passageMarkup}
|
|
581
|
+
<div class={passageContainerClassFinal}>
|
|
582
|
+
{@html passageMarkup}
|
|
583
|
+
</div>
|
|
584
|
+
{/if}
|
|
585
|
+
|
|
586
|
+
{#if itemMarkup}
|
|
587
|
+
<div class={itemContainerClassFinal}>
|
|
588
|
+
{@html itemMarkup}
|
|
589
|
+
</div>
|
|
590
|
+
{/if}
|
|
591
|
+
</div>
|
|
592
|
+
|
|
593
|
+
<style>
|
|
594
|
+
.pie-item-player {
|
|
595
|
+
display: block;
|
|
596
|
+
width: 100%;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
.pie-passage-container,
|
|
600
|
+
.pie-item-container {
|
|
601
|
+
display: block;
|
|
602
|
+
width: 100%;
|
|
603
|
+
}
|
|
604
|
+
</style>
|