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,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file NavigationState.js
|
|
3
|
+
* @description Manages the state for course navigation, including slide progression
|
|
4
|
+
* and visited status. It persists its state through the StateManager.
|
|
5
|
+
*
|
|
6
|
+
* NOTE: currentSlideIndex is NOT persisted - cmi.location is the authoritative bookmark.
|
|
7
|
+
*/
|
|
8
|
+
import stateManager from '../state/index.js';
|
|
9
|
+
import { deepClone as _deepClone } from '../utilities/utilities.js';
|
|
10
|
+
import { eventBus } from '../core/event-bus.js';
|
|
11
|
+
import { logger } from '../utilities/logger.js';
|
|
12
|
+
|
|
13
|
+
const DOMAIN_KEY = 'navigation';
|
|
14
|
+
const SOURCE = 'navigation-state';
|
|
15
|
+
|
|
16
|
+
let isInitialized = false;
|
|
17
|
+
|
|
18
|
+
// Internal state
|
|
19
|
+
const state = {
|
|
20
|
+
currentSlideIndex: 0, // Runtime only, not persisted - cmi.location is authoritative
|
|
21
|
+
visitedSlides: [], // Array of slide IDs (persisted)
|
|
22
|
+
resumeSlideId: null, // Runtime only, cleared after processing
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Stores reference to slides array for ID lookup
|
|
26
|
+
let slidesList = [];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Initializes the navigation state from the StateManager.
|
|
30
|
+
* According to SCORM spec, on resume the course should check cmi.location
|
|
31
|
+
* to determine where to start, then use suspend_data for additional state.
|
|
32
|
+
* @throws {Error} If already initialized
|
|
33
|
+
*/
|
|
34
|
+
export function initializeNavigationState() {
|
|
35
|
+
if (isInitialized) {
|
|
36
|
+
throw new Error('NavigationState: Already initialized. Do not call initializeNavigationState() more than once.');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const persistedState = stateManager.getDomainState(DOMAIN_KEY);
|
|
40
|
+
|
|
41
|
+
// Check resume status via semantic driver reads
|
|
42
|
+
let resumeSlideId = null;
|
|
43
|
+
|
|
44
|
+
// Read entry mode from driver (ab-initio, resume, etc.)
|
|
45
|
+
const entry = stateManager.getEntryMode();
|
|
46
|
+
|
|
47
|
+
if (entry === 'resume') {
|
|
48
|
+
// Read bookmark from driver to determine starting point
|
|
49
|
+
try {
|
|
50
|
+
resumeSlideId = stateManager.getBookmark();
|
|
51
|
+
logger.debug('[NavigationState] Resume detected. Bookmark:', resumeSlideId || '(empty)');
|
|
52
|
+
|
|
53
|
+
// Handle empty cmi.location on resume
|
|
54
|
+
// This can happen due to LMS bugs, network issues during previous session,
|
|
55
|
+
// or data corruption. The course itself always sets cmi.location on navigation.
|
|
56
|
+
if (!resumeSlideId) {
|
|
57
|
+
const errorMessage =
|
|
58
|
+
'NavigationState: Resume session detected but cmi.location is empty. ' +
|
|
59
|
+
'This indicates the course failed to set bookmarks during navigation, ' +
|
|
60
|
+
'or the LMS failed to persist the bookmark. ' +
|
|
61
|
+
'Check that NavigationActions properly sets cmi.location on every slide change.';
|
|
62
|
+
const errorContext = { key: 'cmi.location', entry, value: resumeSlideId };
|
|
63
|
+
|
|
64
|
+
if (import.meta.env.DEV) {
|
|
65
|
+
// Dev mode: FAIL FAST to help developers identify bookmark issues
|
|
66
|
+
logger.fatal(errorMessage, { domain: 'state', operation: 'resume', ...errorContext });
|
|
67
|
+
} else {
|
|
68
|
+
// Production mode: Gracefully recover by treating as first launch
|
|
69
|
+
// This prevents users from being blocked by LMS data issues
|
|
70
|
+
logger.warn(`[NavigationState] ${errorMessage} Recovering by starting from beginning.`);
|
|
71
|
+
eventBus.emit('state:recovered', {
|
|
72
|
+
domain: 'state',
|
|
73
|
+
operation: 'resume',
|
|
74
|
+
message: errorMessage,
|
|
75
|
+
context: errorContext,
|
|
76
|
+
action: 'treated_as_first_launch'
|
|
77
|
+
});
|
|
78
|
+
// resumeSlideId stays null, which will trigger first-launch behavior
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
} catch (locationError) {
|
|
82
|
+
// Handle errors reading cmi.location
|
|
83
|
+
const errorMessage = `Failed to read cmi.location on resume: ${locationError.message}`;
|
|
84
|
+
const errorContext = { key: 'cmi.location', entry };
|
|
85
|
+
|
|
86
|
+
if (import.meta.env.DEV) {
|
|
87
|
+
// Dev mode: FAIL FAST
|
|
88
|
+
logger.fatal(errorMessage, { domain: 'state', operation: 'getValue', ...errorContext });
|
|
89
|
+
} else {
|
|
90
|
+
// Production mode: Gracefully recover
|
|
91
|
+
logger.warn(`[NavigationState] ${errorMessage} Recovering by starting from beginning.`);
|
|
92
|
+
eventBus.emit('state:recovered', {
|
|
93
|
+
domain: 'state',
|
|
94
|
+
operation: 'getValue',
|
|
95
|
+
message: errorMessage,
|
|
96
|
+
context: errorContext,
|
|
97
|
+
action: 'treated_as_first_launch'
|
|
98
|
+
});
|
|
99
|
+
// resumeSlideId stays null, which will trigger first-launch behavior
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
// First launch: cmi.location will be empty, use default (slide 0)
|
|
104
|
+
logger.debug('[NavigationState] First launch detected (cmi.entry is not "resume"). Starting from beginning.');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (persistedState) {
|
|
108
|
+
logger.debug('[NavigationState] Restoring state from LMS suspend_data:', persistedState);
|
|
109
|
+
|
|
110
|
+
// Restore visitedSlides as array of IDs
|
|
111
|
+
// NOTE: currentSlideIndex is NOT restored from suspend_data - cmi.location is authoritative
|
|
112
|
+
if (Array.isArray(persistedState.visitedSlides)) {
|
|
113
|
+
state.visitedSlides = [...persistedState.visitedSlides];
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
logger.debug('[NavigationState] No persisted state found in suspend_data.');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Store resumeSlideId for later resolution (needs slides array which isn't available yet)
|
|
120
|
+
// NavigationActions.init() will handle converting slideId to index
|
|
121
|
+
state.resumeSlideId = resumeSlideId;
|
|
122
|
+
|
|
123
|
+
isInitialized = true;
|
|
124
|
+
logger.debug('[NavigationState] Initialized:', state);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Gets the current navigation state.
|
|
129
|
+
* @returns {object} A deep clone of the current state.
|
|
130
|
+
*/
|
|
131
|
+
export function getNavigationState() {
|
|
132
|
+
return {
|
|
133
|
+
currentSlideIndex: state.currentSlideIndex,
|
|
134
|
+
visitedSlides: [...state.visitedSlides],
|
|
135
|
+
resumeSlideId: state.resumeSlideId
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Gets the current slide index.
|
|
141
|
+
* @returns {number} The index of the current slide.
|
|
142
|
+
*/
|
|
143
|
+
export function getCurrentSlideIndex() {
|
|
144
|
+
return state.currentSlideIndex;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Stores the slides array for ID lookup.
|
|
149
|
+
* Called by NavigationActions.init()
|
|
150
|
+
* @param {Array} slides - The course slides array
|
|
151
|
+
* @throws {Error} If slides is not a valid array
|
|
152
|
+
*/
|
|
153
|
+
export function setSlidesReference(slides) {
|
|
154
|
+
if (!Array.isArray(slides) || slides.length === 0) {
|
|
155
|
+
throw new Error('NavigationState: Invalid slides array. Must be non-empty array.');
|
|
156
|
+
}
|
|
157
|
+
slidesList = slides;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Gets the current slide ID.
|
|
162
|
+
* @returns {string|null} The ID of the current slide, or null if not available.
|
|
163
|
+
*/
|
|
164
|
+
export function getCurrentSlideId() {
|
|
165
|
+
const slide = slidesList[state.currentSlideIndex];
|
|
166
|
+
return slide ? slide.id : null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Gets the resume slide ID from cmi.location (if course was resumed).
|
|
171
|
+
* This should be checked during initialization to determine starting slide.
|
|
172
|
+
* @returns {string|null} The slide ID to resume to, or null if not resuming.
|
|
173
|
+
*/
|
|
174
|
+
export function getResumeSlideId() {
|
|
175
|
+
return state.resumeSlideId || null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Clears the resume slide ID after it has been processed.
|
|
180
|
+
* @private
|
|
181
|
+
*/
|
|
182
|
+
export function clearResumeSlideId() {
|
|
183
|
+
state.resumeSlideId = null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Sets the current slide index and persists the change.
|
|
189
|
+
* @param {number} index - The new slide index.
|
|
190
|
+
* @throws {Error} If index is invalid or out of bounds
|
|
191
|
+
*/
|
|
192
|
+
export function setCurrentSlideIndex(index) {
|
|
193
|
+
if (typeof index !== 'number' || index < 0) {
|
|
194
|
+
throw new Error(`NavigationState: Invalid slide index provided: ${index}`);
|
|
195
|
+
}
|
|
196
|
+
if (slidesList.length > 0 && index >= slidesList.length) {
|
|
197
|
+
throw new Error(`NavigationState: Slide index ${index} exceeds bounds (max: ${slidesList.length - 1})`);
|
|
198
|
+
}
|
|
199
|
+
state.currentSlideIndex = index;
|
|
200
|
+
_persistState();
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Gets a copy of the array of visited slide IDs.
|
|
205
|
+
* @returns {string[]} An array of slide IDs.
|
|
206
|
+
*/
|
|
207
|
+
export function getVisitedSlides() {
|
|
208
|
+
return [...state.visitedSlides];
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Adds a slide ID to the list of visited slides and persists the change.
|
|
213
|
+
* @param {string} slideId - The ID of the slide to mark as visited.
|
|
214
|
+
*/
|
|
215
|
+
export function addVisitedSlide(slideId) {
|
|
216
|
+
if (!slideId) return;
|
|
217
|
+
|
|
218
|
+
if (!state.visitedSlides.includes(slideId)) {
|
|
219
|
+
state.visitedSlides.push(slideId);
|
|
220
|
+
_persistState();
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Clears all visited slides from the state.
|
|
226
|
+
*/
|
|
227
|
+
export function clearVisitedSlides() {
|
|
228
|
+
state.visitedSlides = [];
|
|
229
|
+
_persistState();
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Checks if a slide has been visited.
|
|
234
|
+
* @param {string} slideId - The slide ID to check
|
|
235
|
+
* @returns {boolean} True if the slide has been visited
|
|
236
|
+
*/
|
|
237
|
+
export function isSlideVisited(slideId) {
|
|
238
|
+
return state.visitedSlides.includes(slideId);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Gets the total number of slides in the course.
|
|
243
|
+
* @returns {number} The slide count
|
|
244
|
+
*/
|
|
245
|
+
export function getSlideCount() {
|
|
246
|
+
return slidesList.length;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Checks if currently on the first slide.
|
|
251
|
+
* @returns {boolean} True if on first slide
|
|
252
|
+
*/
|
|
253
|
+
export function isFirstSlide() {
|
|
254
|
+
return state.currentSlideIndex === 0;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Checks if currently on the last slide.
|
|
259
|
+
* @returns {boolean} True if on last slide
|
|
260
|
+
*/
|
|
261
|
+
export function isLastSlide() {
|
|
262
|
+
return state.currentSlideIndex === slidesList.length - 1;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Persists the current state to the StateManager.
|
|
267
|
+
* Only visitedSlides is persisted - currentSlideIndex uses cmi.location.
|
|
268
|
+
* @private
|
|
269
|
+
*/
|
|
270
|
+
function _persistState() {
|
|
271
|
+
// Only persist visitedSlides - cmi.location handles bookmark
|
|
272
|
+
if (state.visitedSlides.length > 0) {
|
|
273
|
+
stateManager.setDomainState(DOMAIN_KEY, { visitedSlides: state.visitedSlides }, { source: SOURCE });
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|