coursecode 0.1.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/LICENSE +21 -0
- package/README.md +322 -0
- package/THIRD_PARTY_NOTICES.md +22 -0
- package/bin/cli.js +331 -0
- package/framework/assets/logo-coursecode-black.svg +14 -0
- package/framework/assets/logo-coursecode-white.svg +14 -0
- package/framework/assets/logo-coursecode.svg +14 -0
- package/framework/css/01-base.css +160 -0
- package/framework/css/02-layout.css +499 -0
- package/framework/css/accessibility.css +834 -0
- package/framework/css/components/accordions.css +710 -0
- package/framework/css/components/assessments.css +520 -0
- package/framework/css/components/audio-player.css +570 -0
- package/framework/css/components/badges.css +80 -0
- package/framework/css/components/breadcrumbs.css +87 -0
- package/framework/css/components/buttons.css +707 -0
- package/framework/css/components/callouts.css +1280 -0
- package/framework/css/components/cards.css +475 -0
- package/framework/css/components/carousel.css +193 -0
- package/framework/css/components/checkbox-group.css +123 -0
- package/framework/css/components/checklist.css +203 -0
- package/framework/css/components/collapse.css +96 -0
- package/framework/css/components/comparison.css +33 -0
- package/framework/css/components/content-image.css +36 -0
- package/framework/css/components/document-gallery.css +425 -0
- package/framework/css/components/dropdown.css +115 -0
- package/framework/css/components/embed-frame.css +142 -0
- package/framework/css/components/engagement.css +412 -0
- package/framework/css/components/features.css +35 -0
- package/framework/css/components/flip-cards.css +253 -0
- package/framework/css/components/footer.css +353 -0
- package/framework/css/components/forms.css +294 -0
- package/framework/css/components/hero.css +216 -0
- package/framework/css/components/images.css +528 -0
- package/framework/css/components/interactive-timeline.css +274 -0
- package/framework/css/components/intro-cards.css +30 -0
- package/framework/css/components/lightbox.css +666 -0
- package/framework/css/components/loading.css +65 -0
- package/framework/css/components/modals.css +235 -0
- package/framework/css/components/notifications.css +107 -0
- package/framework/css/components/quote.css +150 -0
- package/framework/css/components/sidebar.css +684 -0
- package/framework/css/components/slide-header.css +52 -0
- package/framework/css/components/spinner.css +62 -0
- package/framework/css/components/stats.css +44 -0
- package/framework/css/components/steps.css +232 -0
- package/framework/css/components/tables.css +90 -0
- package/framework/css/components/tabs.css +347 -0
- package/framework/css/components/timeline.css +154 -0
- package/framework/css/components/toggle.css +95 -0
- package/framework/css/components/tooltip.css +226 -0
- package/framework/css/components/video-player.css +438 -0
- package/framework/css/design-tokens.css +707 -0
- package/framework/css/framework.css +86 -0
- package/framework/css/interactions/accessibility.css +75 -0
- package/framework/css/interactions/base.css +92 -0
- package/framework/css/interactions/drag-drop.css +295 -0
- package/framework/css/interactions/fill-in-the-blank.css +236 -0
- package/framework/css/interactions/hotspots.css +69 -0
- package/framework/css/interactions/index.css +45 -0
- package/framework/css/interactions/interactive-image.css +359 -0
- package/framework/css/interactions/likert.css +126 -0
- package/framework/css/interactions/matching.css +354 -0
- package/framework/css/interactions/numeric-input.css +78 -0
- package/framework/css/interactions/sequencing.css +378 -0
- package/framework/css/interactions/true-false.css +177 -0
- package/framework/css/layouts/article.css +258 -0
- package/framework/css/layouts/base.css +30 -0
- package/framework/css/layouts/canvas.css +38 -0
- package/framework/css/layouts/focused.css +236 -0
- package/framework/css/layouts/index.css +29 -0
- package/framework/css/layouts/presentation.css +191 -0
- package/framework/css/layouts/traditional.css +52 -0
- package/framework/css/responsive.css +439 -0
- package/framework/css/utilities/accessibility-utils.css +59 -0
- package/framework/css/utilities/animations.css +419 -0
- package/framework/css/utilities/borders.css +72 -0
- package/framework/css/utilities/colors.css +76 -0
- package/framework/css/utilities/container.css +46 -0
- package/framework/css/utilities/decorative.css +442 -0
- package/framework/css/utilities/display.css +257 -0
- package/framework/css/utilities/flexbox.css +80 -0
- package/framework/css/utilities/grid.css +69 -0
- package/framework/css/utilities/icons.css +534 -0
- package/framework/css/utilities/lists.css +190 -0
- package/framework/css/utilities/spacing.css +167 -0
- package/framework/css/utilities/tables.css +81 -0
- package/framework/css/utilities/typography.css +159 -0
- package/framework/css/utilities/visibility.css +117 -0
- package/framework/docs/COURSE_AUTHORING_GUIDE.md +1773 -0
- package/framework/docs/COURSE_OUTLINE_GUIDE.md +725 -0
- package/framework/docs/COURSE_OUTLINE_TEMPLATE.md +161 -0
- package/framework/docs/DATA_MODEL.md +409 -0
- package/framework/docs/FRAMEWORK_GUIDE.md +1088 -0
- package/framework/docs/USER_GUIDE.md +583 -0
- package/framework/docs/examples/cloudflare-channel-relay.js +169 -0
- package/framework/docs/examples/cloudflare-data-worker.js +102 -0
- package/framework/docs/examples/cloudflare-error-worker.js +228 -0
- package/framework/index.html +175 -0
- package/framework/js/app/AppActions.js +410 -0
- package/framework/js/app/AppState.js +225 -0
- package/framework/js/app/AppUI.js +616 -0
- package/framework/js/assessment/AssessmentActions.js +615 -0
- package/framework/js/assessment/AssessmentFactory.js +471 -0
- package/framework/js/assessment/AssessmentState.js +322 -0
- package/framework/js/assessment/AssessmentUI.js +451 -0
- package/framework/js/automation/api-engagement.js +196 -0
- package/framework/js/automation/api-interactions.js +167 -0
- package/framework/js/automation/api.js +242 -0
- package/framework/js/automation/index.js +41 -0
- package/framework/js/components/interactions/drag-drop.js +884 -0
- package/framework/js/components/interactions/fill-in.js +535 -0
- package/framework/js/components/interactions/hotspot.js +702 -0
- package/framework/js/components/interactions/interaction-base.js +511 -0
- package/framework/js/components/interactions/likert.js +301 -0
- package/framework/js/components/interactions/matching.js +699 -0
- package/framework/js/components/interactions/multiple-choice.js +377 -0
- package/framework/js/components/interactions/numeric.js +271 -0
- package/framework/js/components/interactions/sequencing.js +423 -0
- package/framework/js/components/interactions/true-false.js +241 -0
- package/framework/js/components/ui-components/accordion.js +442 -0
- package/framework/js/components/ui-components/alert.js +88 -0
- package/framework/js/components/ui-components/audio-player.js +1193 -0
- package/framework/js/components/ui-components/callout.js +121 -0
- package/framework/js/components/ui-components/carousel.js +145 -0
- package/framework/js/components/ui-components/checkbox-group.js +87 -0
- package/framework/js/components/ui-components/checklist.js +40 -0
- package/framework/js/components/ui-components/collapse.js +114 -0
- package/framework/js/components/ui-components/comparison.js +30 -0
- package/framework/js/components/ui-components/conditional-display.js +150 -0
- package/framework/js/components/ui-components/content-image.js +41 -0
- package/framework/js/components/ui-components/dropdown.js +262 -0
- package/framework/js/components/ui-components/embed-frame.js +274 -0
- package/framework/js/components/ui-components/features.js +33 -0
- package/framework/js/components/ui-components/flip-card.js +230 -0
- package/framework/js/components/ui-components/form-validator.js +76 -0
- package/framework/js/components/ui-components/hero.js +49 -0
- package/framework/js/components/ui-components/index.js +12 -0
- package/framework/js/components/ui-components/interactive-image.js +235 -0
- package/framework/js/components/ui-components/interactive-timeline.js +285 -0
- package/framework/js/components/ui-components/intro-cards.js +35 -0
- package/framework/js/components/ui-components/lightbox.js +652 -0
- package/framework/js/components/ui-components/modal.js +386 -0
- package/framework/js/components/ui-components/notifications.js +145 -0
- package/framework/js/components/ui-components/progress.js +88 -0
- package/framework/js/components/ui-components/quote.js +41 -0
- package/framework/js/components/ui-components/stats.js +33 -0
- package/framework/js/components/ui-components/steps.js +41 -0
- package/framework/js/components/ui-components/tabs.js +255 -0
- package/framework/js/components/ui-components/timeline.js +42 -0
- package/framework/js/components/ui-components/toggle-group.js +73 -0
- package/framework/js/components/ui-components/tooltip.js +458 -0
- package/framework/js/components/ui-components/value-display.js +133 -0
- package/framework/js/components/ui-components/video-player.js +686 -0
- package/framework/js/core/component-catalog.js +121 -0
- package/framework/js/core/event-bus.js +178 -0
- package/framework/js/core/interaction-catalog.js +149 -0
- package/framework/js/dev/runtime-linter.js +1725 -0
- package/framework/js/drivers/cmi5-driver.js +768 -0
- package/framework/js/drivers/driver-factory.js +77 -0
- package/framework/js/drivers/driver-interface.js +110 -0
- package/framework/js/drivers/http-driver-base.js +241 -0
- package/framework/js/drivers/lti-driver.js +508 -0
- package/framework/js/drivers/proxy-driver.js +444 -0
- package/framework/js/drivers/scorm-12-driver.js +560 -0
- package/framework/js/drivers/scorm-2004-driver.js +775 -0
- package/framework/js/drivers/scorm-driver-base.js +112 -0
- package/framework/js/engagement/engagement-manager.js +404 -0
- package/framework/js/engagement/engagement-progress.js +191 -0
- package/framework/js/engagement/engagement-trackers.js +215 -0
- package/framework/js/engagement/requirement-strategies.js +268 -0
- package/framework/js/main.js +727 -0
- package/framework/js/managers/accessibility-manager.js +499 -0
- package/framework/js/managers/assessment-manager.js +230 -0
- package/framework/js/managers/audio-manager.js +944 -0
- package/framework/js/managers/comment-manager.js +88 -0
- package/framework/js/managers/flag-manager.js +86 -0
- package/framework/js/managers/interaction-manager.js +254 -0
- package/framework/js/managers/interaction-registry.js +96 -0
- package/framework/js/managers/objective-manager.js +423 -0
- package/framework/js/managers/score-manager.js +441 -0
- package/framework/js/managers/video-manager.js +536 -0
- package/framework/js/navigation/Breadcrumbs.js +234 -0
- package/framework/js/navigation/NavigationActions.js +1132 -0
- package/framework/js/navigation/NavigationState.js +276 -0
- package/framework/js/navigation/NavigationUI.js +574 -0
- package/framework/js/navigation/document-gallery.js +357 -0
- package/framework/js/navigation/navigation-helpers.js +175 -0
- package/framework/js/navigation/navigation-validators.js +174 -0
- package/framework/js/state/index.js +8 -0
- package/framework/js/state/lms-connection.js +482 -0
- package/framework/js/state/lms-error-utils.js +58 -0
- package/framework/js/state/state-commits.js +200 -0
- package/framework/js/state/state-domains.js +86 -0
- package/framework/js/state/state-manager.js +502 -0
- package/framework/js/state/state-validation.js +311 -0
- package/framework/js/state/transaction-log.js +41 -0
- package/framework/js/state/xapi-statement-service.js +325 -0
- package/framework/js/utilities/access-control.js +99 -0
- package/framework/js/utilities/breakpoint-manager.js +315 -0
- package/framework/js/utilities/canvas-slide.js +35 -0
- package/framework/js/utilities/conditional-display.js +388 -0
- package/framework/js/utilities/course-channel.js +214 -0
- package/framework/js/utilities/course-helpers.js +420 -0
- package/framework/js/utilities/data-reporter.js +273 -0
- package/framework/js/utilities/error-reporter.js +313 -0
- package/framework/js/utilities/hotspot-helper.js +341 -0
- package/framework/js/utilities/icons.js +348 -0
- package/framework/js/utilities/logger.js +92 -0
- package/framework/js/utilities/markdown-renderer.js +45 -0
- package/framework/js/utilities/scroll-tracker.js +68 -0
- package/framework/js/utilities/ui-initializer.js +146 -0
- package/framework/js/utilities/utilities.js +293 -0
- package/framework/js/utilities/view-manager.js +227 -0
- package/framework/js/validation/html-validators.js +422 -0
- package/framework/js/validation/scorm-validators.js +438 -0
- package/framework/js/vendor/pipwerks.js +931 -0
- package/framework/scripts/generate-narration.js +629 -0
- package/framework/scripts/tts-providers/azure-provider.js +178 -0
- package/framework/scripts/tts-providers/base-provider.js +81 -0
- package/framework/scripts/tts-providers/deepgram-provider.js +135 -0
- package/framework/scripts/tts-providers/elevenlabs-provider.js +148 -0
- package/framework/scripts/tts-providers/google-provider.js +272 -0
- package/framework/scripts/tts-providers/index.js +158 -0
- package/framework/scripts/tts-providers/openai-provider.js +143 -0
- package/framework/version.json +63 -0
- package/lib/authoring-api.js +919 -0
- package/lib/build-linter.js +450 -0
- package/lib/build-packaging.js +186 -0
- package/lib/build.js +88 -0
- package/lib/cloud.js +691 -0
- package/lib/convert.js +341 -0
- package/lib/course-parser.js +936 -0
- package/lib/course-writer.js +258 -0
- package/lib/create.js +248 -0
- package/lib/css-index.js +237 -0
- package/lib/dev.js +51 -0
- package/lib/export-content.js +1246 -0
- package/lib/headless-browser.js +413 -0
- package/lib/import.js +377 -0
- package/lib/index.js +80 -0
- package/lib/info.js +79 -0
- package/lib/interaction-formatters.js +568 -0
- package/lib/manifest/cmi5-manifest.js +63 -0
- package/lib/manifest/lti-tool-config.js +53 -0
- package/lib/manifest/manifest-factory.js +99 -0
- package/lib/manifest/scorm-12-manifest.js +61 -0
- package/lib/manifest/scorm-2004-manifest.js +94 -0
- package/lib/manifest/scorm-proxy-manifest.js +104 -0
- package/lib/manifest-parser.js +96 -0
- package/lib/mcp-prompts.js +753 -0
- package/lib/mcp-server.js +316 -0
- package/lib/narration.js +53 -0
- package/lib/pdf-structure.js +142 -0
- package/lib/preview-export.js +231 -0
- package/lib/preview-routes-api.js +662 -0
- package/lib/preview-routes-editing.js +159 -0
- package/lib/preview-routes-lms.js +230 -0
- package/lib/preview-server.js +564 -0
- package/lib/project-utils.js +269 -0
- package/lib/proxy-templates/proxy.html +68 -0
- package/lib/proxy-templates/scorm-bridge.js +112 -0
- package/lib/scaffold.js +193 -0
- package/lib/schema-extractor.js +361 -0
- package/lib/slide-source-editor.js +586 -0
- package/lib/stub-player/app-viewer.js +195 -0
- package/lib/stub-player/app.js +370 -0
- package/lib/stub-player/catalog-panel.js +312 -0
- package/lib/stub-player/config-panel.js +1303 -0
- package/lib/stub-player/content-generator.js +586 -0
- package/lib/stub-player/content-viewer.js +173 -0
- package/lib/stub-player/debug-panel.js +420 -0
- package/lib/stub-player/edit-mode.js +922 -0
- package/lib/stub-player/edit-utils.js +400 -0
- package/lib/stub-player/header-bar.js +354 -0
- package/lib/stub-player/interaction-editor.js +210 -0
- package/lib/stub-player/interactions-panel.js +565 -0
- package/lib/stub-player/lms-api.js +1094 -0
- package/lib/stub-player/login-screen.js +74 -0
- package/lib/stub-player/outline-mode.js +689 -0
- package/lib/stub-player/styles/_assessments-panel.css +245 -0
- package/lib/stub-player/styles/_base.css +89 -0
- package/lib/stub-player/styles/_catalog-icons.css +96 -0
- package/lib/stub-player/styles/_catalog-panel.css +291 -0
- package/lib/stub-player/styles/_config-panel.css +636 -0
- package/lib/stub-player/styles/_content-viewer.css +834 -0
- package/lib/stub-player/styles/_debug-panel.css +576 -0
- package/lib/stub-player/styles/_edit-mode.css +128 -0
- package/lib/stub-player/styles/_header-bar.css +343 -0
- package/lib/stub-player/styles/_interaction-editor.css +140 -0
- package/lib/stub-player/styles/_interactions-panel.css +1038 -0
- package/lib/stub-player/styles/_login-screen.css +102 -0
- package/lib/stub-player/styles/_outline-mode.css +752 -0
- package/lib/stub-player/styles.css +15 -0
- package/lib/stub-player.js +160 -0
- package/lib/test-data-reporting.js +176 -0
- package/lib/test-error-reporting.js +146 -0
- package/lib/token.js +86 -0
- package/lib/upgrade.js +257 -0
- package/lib/validation-rules.js +517 -0
- package/lib/vite-plugin-content-discovery.js +296 -0
- package/package.json +108 -0
- package/schemas/XMLSchema.dtd +402 -0
- package/schemas/adlcp_v1p3.xsd +111 -0
- package/schemas/adlnav_v1p3.xsd +61 -0
- package/schemas/adlseq_v1p3.xsd +93 -0
- package/schemas/common/anyElement.xsd +27 -0
- package/schemas/common/dataTypes.xsd +138 -0
- package/schemas/common/elementNames.xsd +767 -0
- package/schemas/common/elementTypes.xsd +786 -0
- package/schemas/common/rootElement.xsd +31 -0
- package/schemas/common/vocabTypes.xsd +345 -0
- package/schemas/common/vocabValues.xsd +257 -0
- package/schemas/datatypes.dtd +203 -0
- package/schemas/ims_xml.xsd +35 -0
- package/schemas/imscp_v1p1.xsd +368 -0
- package/schemas/imsss_v1p0.xsd +67 -0
- package/schemas/imsss_v1p0auxresource.xsd +19 -0
- package/schemas/imsss_v1p0control.xsd +20 -0
- package/schemas/imsss_v1p0delivery.xsd +17 -0
- package/schemas/imsss_v1p0limit.xsd +47 -0
- package/schemas/imsss_v1p0objective.xsd +67 -0
- package/schemas/imsss_v1p0random.xsd +16 -0
- package/schemas/imsss_v1p0rollup.xsd +46 -0
- package/schemas/imsss_v1p0seqrule.xsd +108 -0
- package/schemas/imsss_v1p0util.xsd +94 -0
- package/schemas/license.txt +17 -0
- package/schemas/lom.xsd +102 -0
- package/schemas/lomCustom.xsd +62 -0
- package/schemas/lomLoose.xsd +62 -0
- package/schemas/lomStrict.xsd +62 -0
- package/schemas/xml.xsd +81 -0
- package/template/.env.example +92 -0
- package/template/course/assets/audio/example-intro.mp3 +0 -0
- package/template/course/assets/audio/example-ui-demo--compact-player.mp3 +0 -0
- package/template/course/assets/audio/example-ui-demo--demo-modal.mp3 +0 -0
- package/template/course/assets/audio/example-ui-demo--full-player.mp3 +0 -0
- package/template/course/assets/docs/example_md_1.md +39 -0
- package/template/course/assets/docs/example_md_2.md +41 -0
- package/template/course/assets/docs/example_pdf_1_thumbnail.png +0 -0
- package/template/course/assets/docs/example_pdf_2.pdf +0 -0
- package/template/course/assets/images/course-architecture.svg +36 -0
- package/template/course/assets/images/logo.svg +14 -0
- package/template/course/assets/widgets/counter-demo.html +190 -0
- package/template/course/assets/widgets/gravity-painter.html +384 -0
- package/template/course/course-config.js +539 -0
- package/template/course/icons.js +19 -0
- package/template/course/interactions/PLUGIN_GUIDE.md +97 -0
- package/template/course/slides/example-course-structure.js +138 -0
- package/template/course/slides/example-final-exam.js +144 -0
- package/template/course/slides/example-finishing.js +127 -0
- package/template/course/slides/example-interactions-showcase.js +615 -0
- package/template/course/slides/example-preview-tour.js +129 -0
- package/template/course/slides/example-remedial.js +143 -0
- package/template/course/slides/example-summary.js +103 -0
- package/template/course/slides/example-ui-showcase.js +1805 -0
- package/template/course/slides/example-welcome.js +123 -0
- package/template/course/slides/example-workflow.js +140 -0
- package/template/course/theme.css +165 -0
- package/template/eslint.config.js +47 -0
- package/template/package.json +28 -0
- package/template/vite.config.js +339 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stub-player/app-viewer.js - Viewer-only Entry Point
|
|
3
|
+
*
|
|
4
|
+
* Lightweight stub player for export previews and cloud previews.
|
|
5
|
+
* Excludes all authoring/edit functionality: debug panel, config panel,
|
|
6
|
+
* interactions panel, catalog, outline mode, edit mode.
|
|
7
|
+
*
|
|
8
|
+
* Only imports: lms-api, header-bar, content-viewer, login-screen.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
initializeLMS
|
|
13
|
+
} from './lms-api.js';
|
|
14
|
+
|
|
15
|
+
import { createHeaderBarHandlers } from './header-bar.js';
|
|
16
|
+
import { createContentViewerHandlers } from './content-viewer.js';
|
|
17
|
+
import { createLoginHandlers } from './login-screen.js';
|
|
18
|
+
|
|
19
|
+
// Global Configuration (injected by server into window.STUB_CONFIG)
|
|
20
|
+
const config = window.STUB_CONFIG || {};
|
|
21
|
+
const LAUNCH_URL = config.launchUrl || '/';
|
|
22
|
+
const START_SLIDE = config.startSlide || null;
|
|
23
|
+
|
|
24
|
+
// State
|
|
25
|
+
let isInitialized = false;
|
|
26
|
+
let contentLoaded = false;
|
|
27
|
+
|
|
28
|
+
// =============================================================================
|
|
29
|
+
// INITIALIZATION
|
|
30
|
+
// =============================================================================
|
|
31
|
+
|
|
32
|
+
function init() {
|
|
33
|
+
// Initialize LMS APIs on window (SCORM 1.2, 2004, cmi5)
|
|
34
|
+
initializeLMS();
|
|
35
|
+
|
|
36
|
+
// Login Screen
|
|
37
|
+
createLoginHandlers({
|
|
38
|
+
onLogin: () => loadCourse()
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Content (Review) Panel
|
|
42
|
+
const contentPanel = document.getElementById('stub-player-content-panel');
|
|
43
|
+
const contentHandlers = contentPanel
|
|
44
|
+
? createContentViewerHandlers({ initialContent: config.courseContent || null })
|
|
45
|
+
: null;
|
|
46
|
+
|
|
47
|
+
// Header Bar — viewer mode: Review + More menu (skip gating, reset)
|
|
48
|
+
createHeaderBarHandlers({
|
|
49
|
+
onToggle: () => {},
|
|
50
|
+
onContent: () => {
|
|
51
|
+
if (contentPanel) {
|
|
52
|
+
contentPanel.classList.toggle('visible');
|
|
53
|
+
if (contentPanel.classList.contains('visible') && !contentLoaded && contentHandlers) {
|
|
54
|
+
contentHandlers.loadContent();
|
|
55
|
+
contentLoaded = true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
onReset: () => doReset(),
|
|
60
|
+
onSkipGating: (enabled) => {
|
|
61
|
+
if (enabled) {
|
|
62
|
+
loadCourse();
|
|
63
|
+
} else {
|
|
64
|
+
doReset();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Load course if no login screen active
|
|
70
|
+
if (!document.getElementById('stub-player-login-screen')?.classList.contains('visible')) {
|
|
71
|
+
loadCourse();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Outside click to close panels
|
|
75
|
+
setupOutsideClickListener(contentPanel);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// =============================================================================
|
|
79
|
+
// COURSE LOADING & NAVIGATION
|
|
80
|
+
// =============================================================================
|
|
81
|
+
|
|
82
|
+
function loadCourse() {
|
|
83
|
+
const frame = document.getElementById('stub-player-course-frame');
|
|
84
|
+
const skipGating = document.getElementById('stub-player-skip-gating')?.checked;
|
|
85
|
+
let url = LAUNCH_URL;
|
|
86
|
+
if (skipGating) url += (url.includes('?') ? '&' : '?') + 'skipGating=true';
|
|
87
|
+
frame.src = url;
|
|
88
|
+
frame.addEventListener('load', handleFrameLoad);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function handleFrameLoad() {
|
|
92
|
+
try {
|
|
93
|
+
const frame = document.getElementById('stub-player-course-frame');
|
|
94
|
+
const win = frame.contentWindow;
|
|
95
|
+
|
|
96
|
+
// Inject LMS APIs
|
|
97
|
+
win.API_1484_11 = window.API_1484_11;
|
|
98
|
+
win.API = window.API;
|
|
99
|
+
win.cmi5 = window.cmi5;
|
|
100
|
+
win.lti = window.lti;
|
|
101
|
+
|
|
102
|
+
if (document.getElementById('stub-player-skip-gating')?.checked) {
|
|
103
|
+
win.__SCORM_PREVIEW_SKIP_GATING = true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Initial navigation (URL param or config)
|
|
107
|
+
if (!isInitialized) {
|
|
108
|
+
const urlSlide = new URLSearchParams(window.location.search).get('slide');
|
|
109
|
+
const target = urlSlide || START_SLIDE;
|
|
110
|
+
if (target) navigateToSlide(win, target);
|
|
111
|
+
isInitialized = true;
|
|
112
|
+
}
|
|
113
|
+
} catch (e) {
|
|
114
|
+
console.error('[Viewer] Failed to inject API:', e);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function navigateToSlide(contentWindow, slideIdOrIndex) {
|
|
119
|
+
const maxAttempts = 50;
|
|
120
|
+
let attempts = 0;
|
|
121
|
+
|
|
122
|
+
function tryNavigate() {
|
|
123
|
+
attempts++;
|
|
124
|
+
try {
|
|
125
|
+
const fw = contentWindow.CourseCode;
|
|
126
|
+
const nav = fw && fw.NavigationActions;
|
|
127
|
+
|
|
128
|
+
if (nav && typeof nav.isReady === 'function' && nav.isReady() && typeof nav.goToSlide === 'function') {
|
|
129
|
+
const slideId = resolveSlideId(contentWindow, slideIdOrIndex);
|
|
130
|
+
if (slideId) {
|
|
131
|
+
nav.goToSlide(slideId, { source: 'preview-url' });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (attempts < maxAttempts) setTimeout(tryNavigate, 100);
|
|
137
|
+
} catch (_e) {
|
|
138
|
+
if (attempts < maxAttempts) setTimeout(tryNavigate, 100);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
setTimeout(tryNavigate, 300);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function resolveSlideId(contentWindow, slideIdOrIndex) {
|
|
145
|
+
const asNumber = Number(slideIdOrIndex);
|
|
146
|
+
if (!isNaN(asNumber) && Number.isInteger(asNumber) && asNumber >= 0) {
|
|
147
|
+
if (contentWindow.CourseCode?.NavigationActions?.getAllSlides) {
|
|
148
|
+
const slides = contentWindow.CourseCode.NavigationActions.getAllSlides();
|
|
149
|
+
if (slides && slides[asNumber]) return slides[asNumber].id;
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
return slideIdOrIndex;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// =============================================================================
|
|
157
|
+
// UTILS
|
|
158
|
+
// =============================================================================
|
|
159
|
+
|
|
160
|
+
function doReset() {
|
|
161
|
+
// Static/cloud export: clear LMS localStorage and reload (no server route)
|
|
162
|
+
const storageKey = config.storageKey;
|
|
163
|
+
if (storageKey) {
|
|
164
|
+
try { localStorage.removeItem(storageKey); } catch { /* ignore */ }
|
|
165
|
+
}
|
|
166
|
+
window.location.reload();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function setupOutsideClickListener(contentPanel) {
|
|
170
|
+
document.addEventListener('click', (e) => {
|
|
171
|
+
if (contentPanel?.classList.contains('visible')) {
|
|
172
|
+
const clickedInPanel = e.target.closest('#stub-player-content-panel');
|
|
173
|
+
const clickedToggle = e.target.closest('button[id*="-btn"]');
|
|
174
|
+
const clickedHeader = e.target.closest('#stub-player-header');
|
|
175
|
+
|
|
176
|
+
if (!clickedInPanel && !clickedToggle && !clickedHeader) {
|
|
177
|
+
contentPanel.classList.remove('visible');
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const iframe = document.getElementById('stub-player-course-frame');
|
|
183
|
+
if (iframe) {
|
|
184
|
+
window.addEventListener('blur', () => {
|
|
185
|
+
setTimeout(() => {
|
|
186
|
+
if (document.activeElement === iframe) {
|
|
187
|
+
contentPanel?.classList.remove('visible');
|
|
188
|
+
}
|
|
189
|
+
}, 0);
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Start
|
|
195
|
+
init();
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stub-player/app.js - Main Application Entry Point
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates all stub player components, initializes LMS API,
|
|
5
|
+
* and handles global state/events.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
cmiData,
|
|
10
|
+
logError,
|
|
11
|
+
logXapiStatement,
|
|
12
|
+
setUiCallbacks,
|
|
13
|
+
initializeLMS
|
|
14
|
+
} from './lms-api.js';
|
|
15
|
+
|
|
16
|
+
import { createHeaderBarHandlers, updateSlideId } from './header-bar.js';
|
|
17
|
+
import { createDebugPanelHandlers } from './debug-panel.js';
|
|
18
|
+
import { createConfigPanelHandlers } from './config-panel.js';
|
|
19
|
+
import { createContentViewerHandlers } from './content-viewer.js';
|
|
20
|
+
import { createInteractionsPanelHandlers } from './interactions-panel.js';
|
|
21
|
+
import { createCatalogPanelHandlers } from './catalog-panel.js';
|
|
22
|
+
import { createOutlineModeHandlers } from './outline-mode.js';
|
|
23
|
+
import { createLoginHandlers } from './login-screen.js';
|
|
24
|
+
import { createEditModeHandlers } from './edit-mode.js';
|
|
25
|
+
import { initInteractionEditor } from './interaction-editor.js';
|
|
26
|
+
|
|
27
|
+
// Global Configuration (injected by server into window.STUB_CONFIG)
|
|
28
|
+
const config = window.STUB_CONFIG || {};
|
|
29
|
+
const IS_LIVE = config.isLive || false;
|
|
30
|
+
const LAUNCH_URL = config.launchUrl || '/';
|
|
31
|
+
const START_SLIDE = config.startSlide || null;
|
|
32
|
+
// State
|
|
33
|
+
let isInitialized = false;
|
|
34
|
+
|
|
35
|
+
// =============================================================================
|
|
36
|
+
// INITIALIZATION
|
|
37
|
+
// =============================================================================
|
|
38
|
+
|
|
39
|
+
function init() {
|
|
40
|
+
console.log('[App] Initializing Stub Player...');
|
|
41
|
+
|
|
42
|
+
// Initialize LMS APIs on window (SCORM 1.2, 2004, cmi5)
|
|
43
|
+
initializeLMS();
|
|
44
|
+
|
|
45
|
+
// Login Screen
|
|
46
|
+
createLoginHandlers({
|
|
47
|
+
onLogin: () => {
|
|
48
|
+
loadCourse();
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// If no login required (or already handled), load course immediately handled by login-screen logic normally
|
|
53
|
+
// But login-screen.js might need to be checked if it auto-hides.
|
|
54
|
+
// Actually, stub-player.js logic was: if login needed, show it, else loadCourse.
|
|
55
|
+
// For now, let's assume login handlers handle the "Start" flow.
|
|
56
|
+
// Check if we need to auto-start if no login required?
|
|
57
|
+
// login-screen.js handles this check usually.
|
|
58
|
+
// Wait, createLoginHandlers usually returns success check?
|
|
59
|
+
// Let's rely on callback.
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
// UI Panels & Exclusivity Management
|
|
63
|
+
const panels = {
|
|
64
|
+
debug: { el: document.getElementById('stub-player-debug-panel'), load: null },
|
|
65
|
+
config: { el: document.getElementById('stub-player-config-panel'), load: () => handlers.config.loadConfig() },
|
|
66
|
+
content: { el: document.getElementById('stub-player-content-panel'), load: () => handlers.content.loadContent() },
|
|
67
|
+
interactions: { el: document.getElementById('stub-player-interactions-panel'), load: () => handlers.interactions.loadInteractions() },
|
|
68
|
+
catalog: { el: document.getElementById('stub-player-catalog-panel'), load: () => handlers.catalog.loadCatalog() }
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
function closeAllPanels(except = null) {
|
|
72
|
+
for (const [key, panel] of Object.entries(panels)) {
|
|
73
|
+
if (key !== except && panel.el) {
|
|
74
|
+
panel.el.classList.remove('visible');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function togglePanel(name) {
|
|
80
|
+
closeAllPanels(name);
|
|
81
|
+
if (panels[name] && panels[name].el) {
|
|
82
|
+
panels[name].el.classList.toggle('visible');
|
|
83
|
+
if (panels[name].el.classList.contains('visible') && panels[name].load) {
|
|
84
|
+
panels[name].load();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Context for child modules
|
|
90
|
+
const context = {
|
|
91
|
+
getCmiData: () => cmiData,
|
|
92
|
+
navigateToSlide: (id) => {
|
|
93
|
+
const frame = document.getElementById('stub-player-course-frame');
|
|
94
|
+
if (frame && frame.contentWindow) {
|
|
95
|
+
navigateToSlide(frame.contentWindow, id);
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
isLive: IS_LIVE,
|
|
99
|
+
loadCourse: () => loadCourse()
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// Initialize Module Handlers
|
|
103
|
+
const handlers = {
|
|
104
|
+
debug: createDebugPanelHandlers(), // Debug panel handles its own internal logic, we trigger visibility via header
|
|
105
|
+
config: createConfigPanelHandlers ? createConfigPanelHandlers(context) : null,
|
|
106
|
+
content: createContentViewerHandlers ? createContentViewerHandlers(context) : null,
|
|
107
|
+
interactions: createInteractionsPanelHandlers ? createInteractionsPanelHandlers(context) : null,
|
|
108
|
+
catalog: createCatalogPanelHandlers ? createCatalogPanelHandlers(context) : null,
|
|
109
|
+
outline: createOutlineModeHandlers ? createOutlineModeHandlers(context) : null
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// Stage-aware: check if we should show dashboard instead of course
|
|
113
|
+
if (IS_LIVE && handlers.outline) {
|
|
114
|
+
handlers.outline.checkStage().then(active => {
|
|
115
|
+
// If dashboard didn't activate, load course now
|
|
116
|
+
if (!active && !document.getElementById('stub-player-login-screen')?.classList.contains('visible')) {
|
|
117
|
+
loadCourse();
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Header Bar
|
|
123
|
+
createHeaderBarHandlers({
|
|
124
|
+
onToggle: (isCollapsed) => {
|
|
125
|
+
if (isCollapsed) closeAllPanels();
|
|
126
|
+
},
|
|
127
|
+
onDebug: () => togglePanel('debug'),
|
|
128
|
+
onConfig: () => togglePanel('config'),
|
|
129
|
+
onContent: () => togglePanel('content'),
|
|
130
|
+
onInteract: () => togglePanel('interactions'),
|
|
131
|
+
onCatalog: () => togglePanel('catalog'),
|
|
132
|
+
onEdit: () => { /* Edit logic internal to stub-player.js? Or moved? */ },
|
|
133
|
+
onReset: () => doReset(),
|
|
134
|
+
onSkipGating: (enabled) => {
|
|
135
|
+
if (enabled) {
|
|
136
|
+
loadCourse(); // Reload to apply skip
|
|
137
|
+
} else {
|
|
138
|
+
doReset(); // Reset to enforce gating
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
onStatus: () => {
|
|
142
|
+
if (handlers.outline) handlers.outline.toggle();
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Register LMS API Callbacks to update UI
|
|
147
|
+
setUiCallbacks({
|
|
148
|
+
onSlideNavigation: (slideId) => {
|
|
149
|
+
// Update header badge when cmi.location changes
|
|
150
|
+
updateSlideId(slideId);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Initial Course Load (if no login screen or dashboard active)
|
|
155
|
+
if (!IS_LIVE && !document.getElementById('stub-player-login-screen')?.classList.contains('visible')) {
|
|
156
|
+
loadCourse();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Setup outside click listener
|
|
160
|
+
setupOutsideClickListener(panels);
|
|
161
|
+
|
|
162
|
+
// Setup Edit Mode logic (if live)
|
|
163
|
+
if (IS_LIVE) setupEditMode();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// =============================================================================
|
|
167
|
+
// COURSE LOADING & NAVIGATION
|
|
168
|
+
// =============================================================================
|
|
169
|
+
|
|
170
|
+
function loadCourse() {
|
|
171
|
+
const frame = document.getElementById('stub-player-course-frame');
|
|
172
|
+
const skipGating = document.getElementById('stub-player-skip-gating')?.checked;
|
|
173
|
+
let url = LAUNCH_URL;
|
|
174
|
+
if (skipGating) url += (url.includes('?') ? '&' : '?') + 'skipGating=true';
|
|
175
|
+
frame.src = url;
|
|
176
|
+
|
|
177
|
+
// Inject API on load
|
|
178
|
+
frame.addEventListener('load', handleFrameLoad);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function handleFrameLoad() {
|
|
182
|
+
try {
|
|
183
|
+
const frame = document.getElementById('stub-player-course-frame');
|
|
184
|
+
const win = frame.contentWindow;
|
|
185
|
+
|
|
186
|
+
// Inject APIs (attached to window.* by initializeLMS() in init())
|
|
187
|
+
win.API_1484_11 = window.API_1484_11;
|
|
188
|
+
win.API = window.API;
|
|
189
|
+
win.cmi5 = window.cmi5;
|
|
190
|
+
win.lti = window.lti;
|
|
191
|
+
|
|
192
|
+
if (document.getElementById('stub-player-skip-gating')?.checked) {
|
|
193
|
+
win.__SCORM_PREVIEW_SKIP_GATING = true;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Attach console interceptors
|
|
197
|
+
attachConsoleInterceptors(win);
|
|
198
|
+
|
|
199
|
+
// Attach xAPI listener
|
|
200
|
+
attachXapiListener(win);
|
|
201
|
+
|
|
202
|
+
// Initial Nav
|
|
203
|
+
if (!isInitialized) {
|
|
204
|
+
const urlSlide = new URLSearchParams(window.location.search).get('slide');
|
|
205
|
+
const target = urlSlide || START_SLIDE;
|
|
206
|
+
if (target) {
|
|
207
|
+
navigateToSlide(win, target);
|
|
208
|
+
}
|
|
209
|
+
isInitialized = true;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Listen for slide changes to update header
|
|
213
|
+
// We can poll or hook into framework?
|
|
214
|
+
// Framework events are best.
|
|
215
|
+
// See attachXapiListener for pattern.
|
|
216
|
+
|
|
217
|
+
} catch (e) {
|
|
218
|
+
console.error('[App] Failed to inject API:', e);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ... Navigation helpers (navigateToSlide, resolveSlideId) ...
|
|
223
|
+
function navigateToSlide(contentWindow, slideIdOrIndex) {
|
|
224
|
+
const maxAttempts = 50;
|
|
225
|
+
let attempts = 0;
|
|
226
|
+
|
|
227
|
+
function tryNavigate() {
|
|
228
|
+
attempts++;
|
|
229
|
+
try {
|
|
230
|
+
const fw = contentWindow.CourseCode;
|
|
231
|
+
const nav = fw && fw.NavigationActions;
|
|
232
|
+
|
|
233
|
+
if (nav && typeof nav.isReady === 'function' && nav.isReady() && typeof nav.goToSlide === 'function') {
|
|
234
|
+
const slideId = resolveSlideId(contentWindow, slideIdOrIndex);
|
|
235
|
+
if (slideId) {
|
|
236
|
+
nav.goToSlide(slideId, { source: 'preview-url' });
|
|
237
|
+
// Update header badge
|
|
238
|
+
updateSlideId(slideId);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (attempts < maxAttempts) setTimeout(tryNavigate, 100);
|
|
244
|
+
} catch (_e) {
|
|
245
|
+
if (attempts < maxAttempts) setTimeout(tryNavigate, 100);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
setTimeout(tryNavigate, 300);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function resolveSlideId(contentWindow, slideIdOrIndex) {
|
|
252
|
+
// ... same implementation as before ...
|
|
253
|
+
const asNumber = Number(slideIdOrIndex);
|
|
254
|
+
if (!isNaN(asNumber) && Number.isInteger(asNumber) && asNumber >= 0) {
|
|
255
|
+
if (contentWindow.CourseCode?.NavigationActions?.getAllSlides) {
|
|
256
|
+
const slides = contentWindow.CourseCode.NavigationActions.getAllSlides();
|
|
257
|
+
if (slides && slides[asNumber]) return slides[asNumber].id;
|
|
258
|
+
}
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
return slideIdOrIndex;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// =============================================================================
|
|
265
|
+
// UTILS
|
|
266
|
+
// =============================================================================
|
|
267
|
+
|
|
268
|
+
function doReset() {
|
|
269
|
+
window.location.href = '/__reset';
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function setupOutsideClickListener(panels) {
|
|
273
|
+
// Standard click-outside for clicks in parent document
|
|
274
|
+
document.addEventListener('click', (e) => {
|
|
275
|
+
const visiblePanel = Object.values(panels).find(p => p.el && p.el.classList.contains('visible'));
|
|
276
|
+
if (visiblePanel) {
|
|
277
|
+
const clickedInPanel = e.target.closest('#' + visiblePanel.el.id);
|
|
278
|
+
const clickedToggle = e.target.closest('button[id*="-btn"]');
|
|
279
|
+
const clickedHeader = e.target.closest('#stub-player-header');
|
|
280
|
+
|
|
281
|
+
if (!clickedInPanel && !clickedToggle && !clickedHeader) {
|
|
282
|
+
visiblePanel.el.classList.remove('visible');
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// Iframe focus detection - when user clicks inside iframe, it gets focus
|
|
288
|
+
// This handles the case where the iframe covers most of the viewport
|
|
289
|
+
const iframe = document.getElementById('stub-player-course-frame');
|
|
290
|
+
if (iframe) {
|
|
291
|
+
// Use focusin on window to detect when iframe gets focus
|
|
292
|
+
window.addEventListener('blur', () => {
|
|
293
|
+
// Window loses focus when iframe gains it
|
|
294
|
+
setTimeout(() => {
|
|
295
|
+
if (document.activeElement === iframe) {
|
|
296
|
+
// User clicked inside iframe - close all panels
|
|
297
|
+
Object.values(panels).forEach(p => {
|
|
298
|
+
if (p.el) p.el.classList.remove('visible');
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}, 0);
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function setupEditMode() {
|
|
307
|
+
createEditModeHandlers({
|
|
308
|
+
getCmiData: () => cmiData
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// Initialize the popup interaction editor
|
|
312
|
+
initInteractionEditor({
|
|
313
|
+
onSave: () => {
|
|
314
|
+
// Refresh the course iframe after saving an interaction
|
|
315
|
+
const frame = document.getElementById('stub-player-course-frame');
|
|
316
|
+
if (frame && frame.contentWindow) {
|
|
317
|
+
frame.contentWindow.location.reload();
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function attachConsoleInterceptors(win) {
|
|
324
|
+
const output = win.console;
|
|
325
|
+
const origError = output.error;
|
|
326
|
+
const origWarn = output.warn;
|
|
327
|
+
|
|
328
|
+
output.error = function (...args) {
|
|
329
|
+
const msg = args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ');
|
|
330
|
+
logError('Course Error', msg.substring(0, 500), '', false);
|
|
331
|
+
origError.apply(output, args);
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
output.warn = function (...args) {
|
|
335
|
+
const msg = args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ');
|
|
336
|
+
const isCV = msg.includes('COURSE VALIDATION');
|
|
337
|
+
logError('Course Warning', msg.substring(0, isCV ? 10000 : 500), '', true);
|
|
338
|
+
origWarn.apply(output, args);
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
win.addEventListener('error', function (e) {
|
|
342
|
+
logError('Course Uncaught', e.message || 'Unknown', e.filename ? e.filename.split('/').pop() + ':' + e.lineno : '', false);
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function attachXapiListener(win) {
|
|
347
|
+
function tryAttach() {
|
|
348
|
+
const fw = win.CourseCode;
|
|
349
|
+
if (fw && fw.eventBus) {
|
|
350
|
+
fw.eventBus.on('xapi:statement', logXapiStatement);
|
|
351
|
+
// Also listen for navigation to trigger config panel update
|
|
352
|
+
fw.eventBus.on('navigation:change', (data) => {
|
|
353
|
+
if (data.to && window.__refreshSlideTab) window.__refreshSlideTab();
|
|
354
|
+
});
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (!tryAttach()) {
|
|
361
|
+
let retries = 0;
|
|
362
|
+
const iv = setInterval(() => {
|
|
363
|
+
if (tryAttach() || ++retries > 20) clearInterval(iv);
|
|
364
|
+
}, 200);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
// Start
|
|
370
|
+
init();
|