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,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file engagement-trackers.js
|
|
3
|
+
* @description Component registration and engagement tracking methods.
|
|
4
|
+
* These are mixed into EngagementManager's prototype to keep the manager slim.
|
|
5
|
+
*
|
|
6
|
+
* Uses factory functions to eliminate boilerplate — all generated methods
|
|
7
|
+
* follow the same validate → getState → update → save → emit pattern.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { logger } from '../utilities/logger.js';
|
|
11
|
+
|
|
12
|
+
// =========================================================================
|
|
13
|
+
// Factory Functions
|
|
14
|
+
// =========================================================================
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Creates a batch registration function that sets tracked[totalField] = ids.length.
|
|
18
|
+
*/
|
|
19
|
+
function makeRegister(totalField, label) {
|
|
20
|
+
// Derive the viewed field name from the total field (e.g., 'tabsTotal' → 'tabsViewed')
|
|
21
|
+
const viewedField = totalField.replace('Total', 'Viewed');
|
|
22
|
+
return function (slideId, ids) {
|
|
23
|
+
if (!slideId || !Array.isArray(ids)) return;
|
|
24
|
+
const state = this._getState();
|
|
25
|
+
if (!state[slideId]) return;
|
|
26
|
+
state[slideId].tracked[totalField] = ids.length;
|
|
27
|
+
// Reset viewed array on re-registration to prevent viewed > total inconsistency
|
|
28
|
+
state[slideId].tracked[viewedField] = [];
|
|
29
|
+
this._setState(state);
|
|
30
|
+
logger.debug(`[EngagementManager] Registered ${ids.length} ${label}: ${slideId}`);
|
|
31
|
+
this._checkAndEmitProgress(slideId);
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Creates an incremental registration function that adds to total and inits viewed array.
|
|
37
|
+
*/
|
|
38
|
+
function makeRegisterIncremental(totalField, viewedField, label) {
|
|
39
|
+
return function (slideId, ids) {
|
|
40
|
+
if (!slideId || !Array.isArray(ids)) return;
|
|
41
|
+
const state = this._getState();
|
|
42
|
+
if (!state[slideId]) return;
|
|
43
|
+
state[slideId].tracked[totalField] = (state[slideId].tracked[totalField] || 0) + ids.length;
|
|
44
|
+
if (!state[slideId].tracked[viewedField]) {
|
|
45
|
+
state[slideId].tracked[viewedField] = [];
|
|
46
|
+
}
|
|
47
|
+
this._setState(state);
|
|
48
|
+
logger.debug(`[EngagementManager] Registered ${ids.length} ${label}: ${slideId}`);
|
|
49
|
+
this._checkAndEmitProgress(slideId);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Creates an array-push tracker that deduplicates and logs progress.
|
|
55
|
+
* @param {string} viewedField - tracked[viewedField] array to push into
|
|
56
|
+
* @param {string} [totalField] - tracked[totalField] for progress logging (optional)
|
|
57
|
+
* @param {string} label - human-readable label for debug logs
|
|
58
|
+
*/
|
|
59
|
+
function makeArrayTracker(viewedField, totalField, label) {
|
|
60
|
+
return function (slideId, itemId) {
|
|
61
|
+
if (!slideId || !itemId) return;
|
|
62
|
+
const state = this._getState();
|
|
63
|
+
if (!state[slideId]) return;
|
|
64
|
+
const tracked = state[slideId].tracked;
|
|
65
|
+
if (!tracked[viewedField]) tracked[viewedField] = [];
|
|
66
|
+
if (!tracked[viewedField].includes(itemId)) {
|
|
67
|
+
tracked[viewedField].push(itemId);
|
|
68
|
+
this._setState(state);
|
|
69
|
+
const progress = totalField ? ` (${tracked[viewedField].length}/${tracked[totalField]})` : '';
|
|
70
|
+
logger.debug(`[EngagementManager] ${label}: ${itemId}${progress}`);
|
|
71
|
+
this._checkAndEmitProgress(slideId);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Creates a boolean-flag tracker that sets tracked[field] = true once.
|
|
78
|
+
*/
|
|
79
|
+
function makeBoolTracker(field, label) {
|
|
80
|
+
return function (slideId) {
|
|
81
|
+
if (!slideId) return;
|
|
82
|
+
const state = this._getState();
|
|
83
|
+
if (!state[slideId]) return;
|
|
84
|
+
if (!state[slideId].tracked[field]) {
|
|
85
|
+
state[slideId].tracked[field] = true;
|
|
86
|
+
this._setState(state);
|
|
87
|
+
logger.debug(`[EngagementManager] ${label}: ${slideId}`);
|
|
88
|
+
this._checkAndEmitProgress(slideId);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// =========================================================================
|
|
94
|
+
// Generated Registration Methods
|
|
95
|
+
// =========================================================================
|
|
96
|
+
|
|
97
|
+
export const registerTabs = makeRegister('tabsTotal', 'tabs');
|
|
98
|
+
export const registerAccordion = makeRegister('accordionPanelsTotal', 'accordion panels');
|
|
99
|
+
export const registerFlipCards = makeRegister('flipCardsTotal', 'flip cards');
|
|
100
|
+
export const registerTimeline = makeRegister('timelineEventsTotal', 'timeline events');
|
|
101
|
+
export const registerModals = makeRegister('modalsTotal', 'modals');
|
|
102
|
+
|
|
103
|
+
export const registerInteractiveImage = makeRegisterIncremental(
|
|
104
|
+
'interactiveImageHotspotsTotal', 'interactiveImageHotspotsViewed', 'hotspots'
|
|
105
|
+
);
|
|
106
|
+
export const registerLightbox = makeRegisterIncremental(
|
|
107
|
+
'lightboxesTotal', 'lightboxesViewed', 'lightboxes'
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// =========================================================================
|
|
111
|
+
// Special-Case Registration
|
|
112
|
+
// =========================================================================
|
|
113
|
+
|
|
114
|
+
/** Single flip-card registration (incremental, updates total from registered list). */
|
|
115
|
+
export function registerFlipCard(slideId, cardId) {
|
|
116
|
+
if (!slideId || !cardId) return;
|
|
117
|
+
const state = this._getState();
|
|
118
|
+
if (!state[slideId]) return;
|
|
119
|
+
const tracked = state[slideId].tracked;
|
|
120
|
+
if (!tracked.flipCardsRegistered) tracked.flipCardsRegistered = [];
|
|
121
|
+
if (!tracked.flipCardsRegistered.includes(cardId)) {
|
|
122
|
+
tracked.flipCardsRegistered.push(cardId);
|
|
123
|
+
// Use whichever is larger: batch-registered total or incremental count
|
|
124
|
+
tracked.flipCardsTotal = Math.max(tracked.flipCardsTotal || 0, tracked.flipCardsRegistered.length);
|
|
125
|
+
this._setState(state);
|
|
126
|
+
logger.debug(`[EngagementManager] Registered flip card: ${cardId} (total: ${tracked.flipCardsTotal})`);
|
|
127
|
+
this._checkAndEmitProgress(slideId);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// =========================================================================
|
|
132
|
+
// Generated Tracking Methods
|
|
133
|
+
// =========================================================================
|
|
134
|
+
|
|
135
|
+
export const trackTabView = makeArrayTracker('tabsViewed', 'tabsTotal', 'Tab viewed');
|
|
136
|
+
export const trackAccordionPanel = makeArrayTracker('accordionPanelsViewed', 'accordionPanelsTotal', 'Panel viewed');
|
|
137
|
+
export const trackFlipCardView = makeArrayTracker('flipCardsViewed', 'flipCardsTotal', 'Flip card viewed');
|
|
138
|
+
export const trackTimelineView = makeArrayTracker('timelineEventsViewed', 'timelineEventsTotal', 'Timeline event viewed');
|
|
139
|
+
export const trackInteractiveImageView = makeArrayTracker('interactiveImageHotspotsViewed', 'interactiveImageHotspotsTotal', 'Hotspot viewed');
|
|
140
|
+
export const trackLightboxView = makeArrayTracker('lightboxesViewed', 'lightboxesTotal', 'Lightbox viewed');
|
|
141
|
+
export const trackModalView = makeArrayTracker('modalsViewed', 'modalsTotal', 'Modal viewed');
|
|
142
|
+
export const trackStandaloneAudioComplete = makeArrayTracker('standaloneAudioComplete', null, 'Standalone audio completed');
|
|
143
|
+
export const trackStandaloneVideoComplete = makeArrayTracker('standaloneVideoComplete', null, 'Standalone video completed');
|
|
144
|
+
export const trackModalAudioComplete = makeArrayTracker('modalsAudioComplete', null, 'Modal audio completed');
|
|
145
|
+
|
|
146
|
+
export const trackSlideAudioComplete = makeBoolTracker('audioComplete', 'Slide audio completed');
|
|
147
|
+
export const trackSlideVideoComplete = makeBoolTracker('videoComplete', 'Slide video completed');
|
|
148
|
+
|
|
149
|
+
// =========================================================================
|
|
150
|
+
// Special-Case Tracking
|
|
151
|
+
// =========================================================================
|
|
152
|
+
|
|
153
|
+
/** Interaction tracking — uses object map keyed by interactionId, not array. */
|
|
154
|
+
export function trackInteraction(slideId, interactionId, completed, correct) {
|
|
155
|
+
if (!slideId || !interactionId) return;
|
|
156
|
+
const state = this._getState();
|
|
157
|
+
if (!state[slideId]) return;
|
|
158
|
+
state[slideId].tracked.interactionsCompleted[interactionId] = { completed, correct };
|
|
159
|
+
this._setState(state);
|
|
160
|
+
logger.debug(`[EngagementManager] Interaction: ${interactionId} (completed: ${completed}, correct: ${correct})`);
|
|
161
|
+
this._checkAndEmitProgress(slideId);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Scroll depth — numeric high-water mark, not array/boolean. */
|
|
165
|
+
export function trackScrollDepth(slideId, percentage) {
|
|
166
|
+
if (!slideId || typeof percentage !== 'number') return;
|
|
167
|
+
const state = this._getState();
|
|
168
|
+
if (!state[slideId]) return;
|
|
169
|
+
const currentDepth = state[slideId].tracked.scrollDepth;
|
|
170
|
+
if (percentage > currentDepth) {
|
|
171
|
+
state[slideId].tracked.scrollDepth = Math.min(100, Math.max(0, percentage));
|
|
172
|
+
this._setState(state);
|
|
173
|
+
logger.debug(`[EngagementManager] Scroll: ${percentage}% for ${slideId}`);
|
|
174
|
+
this._checkAndEmitProgress(slideId);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// =========================================================================
|
|
179
|
+
// Active Selection Persistence
|
|
180
|
+
// =========================================================================
|
|
181
|
+
|
|
182
|
+
/** Save the currently active tab so it can be restored on revisit. */
|
|
183
|
+
export function saveActiveTab(slideId, tabId) {
|
|
184
|
+
if (!slideId || !tabId) return;
|
|
185
|
+
const state = this._getState();
|
|
186
|
+
if (!state[slideId]) return;
|
|
187
|
+
state[slideId].tracked.activeTab = tabId;
|
|
188
|
+
this._setState(state);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Get the last active tab for a slide, or null if none saved. */
|
|
192
|
+
export function getActiveTab(slideId) {
|
|
193
|
+
if (!slideId) return null;
|
|
194
|
+
const state = this._getState();
|
|
195
|
+
if (!state[slideId]) return null;
|
|
196
|
+
return state[slideId].tracked.activeTab ?? null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// =========================================================================
|
|
200
|
+
// Queries
|
|
201
|
+
// =========================================================================
|
|
202
|
+
|
|
203
|
+
export function isSlideVideoComplete(slideId) {
|
|
204
|
+
if (!slideId) return false;
|
|
205
|
+
const state = this._getState();
|
|
206
|
+
if (!state[slideId]) return true;
|
|
207
|
+
return state[slideId].tracked?.videoComplete || false;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function isSlideAudioComplete(slideId) {
|
|
211
|
+
if (!slideId) return false;
|
|
212
|
+
const state = this._getState();
|
|
213
|
+
if (!state[slideId]) return true;
|
|
214
|
+
return state[slideId].tracked?.audioComplete || false;
|
|
215
|
+
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file requirement-strategies.js
|
|
3
|
+
* @description Strategy map for engagement requirement types.
|
|
4
|
+
* Each strategy encapsulates evaluate, progress, label, and fields logic
|
|
5
|
+
* for a single requirement type.
|
|
6
|
+
*
|
|
7
|
+
* Strategy interface:
|
|
8
|
+
* fields: { key: defaultValue } — tracked state fields this strategy reads
|
|
9
|
+
* evaluate(req, tracked, ctx) → { met, type, message, ...extra }
|
|
10
|
+
* progress(req, tracked, result, ctx) → number (0–1)
|
|
11
|
+
* label(req, tracked, result, ctx) → string
|
|
12
|
+
*
|
|
13
|
+
* ctx: { slideId, stateManager, interactionRegistry, formatTime, formatInteractionId }
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// ── Helpers for "view all" pattern ──────────────────────────────────
|
|
17
|
+
function viewAllStrategy(trackedKey, totalKey, defaultLabel) {
|
|
18
|
+
return {
|
|
19
|
+
fields: { [trackedKey]: [], [totalKey]: 0 },
|
|
20
|
+
evaluate(req, tracked) {
|
|
21
|
+
const total = tracked[totalKey] || 0;
|
|
22
|
+
const viewed = tracked[trackedKey] || [];
|
|
23
|
+
return {
|
|
24
|
+
met: total > 0 && viewed.length >= total,
|
|
25
|
+
type: req.type,
|
|
26
|
+
message: req.message,
|
|
27
|
+
viewed: viewed.length,
|
|
28
|
+
total
|
|
29
|
+
};
|
|
30
|
+
},
|
|
31
|
+
progress(req, tracked) {
|
|
32
|
+
const total = tracked[totalKey] || 0;
|
|
33
|
+
return total > 0 ? (tracked[trackedKey]?.length || 0) / total : 0;
|
|
34
|
+
},
|
|
35
|
+
label(req, tracked) {
|
|
36
|
+
const total = tracked[totalKey] || 0;
|
|
37
|
+
const viewed = tracked[trackedKey]?.length || 0;
|
|
38
|
+
return `${defaultLabel} (${viewed}/${total})`;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** @type {Record<string, {fields?, evaluate, progress, label}>} */
|
|
44
|
+
const strategies = {
|
|
45
|
+
viewAllTabs: viewAllStrategy('tabsViewed', 'tabsTotal', 'View all tabs'),
|
|
46
|
+
viewAllPanels: viewAllStrategy('accordionPanelsViewed', 'accordionPanelsTotal', 'View all sections'),
|
|
47
|
+
viewAllFlipCards: viewAllStrategy('flipCardsViewed', 'flipCardsTotal', 'Flip all cards'),
|
|
48
|
+
viewAllHotspots: viewAllStrategy('interactiveImageHotspotsViewed', 'interactiveImageHotspotsTotal', 'View all hotspots'),
|
|
49
|
+
viewAllModals: viewAllStrategy('modalsViewed', 'modalsTotal', 'View all modals'),
|
|
50
|
+
viewAllLightboxes: viewAllStrategy('lightboxesViewed', 'lightboxesTotal', 'View all lightboxes'),
|
|
51
|
+
viewAllTimelineEvents: viewAllStrategy('timelineEventsViewed', 'timelineEventsTotal', 'View all events'),
|
|
52
|
+
|
|
53
|
+
interactionComplete: {
|
|
54
|
+
fields: { interactionsCompleted: {} },
|
|
55
|
+
evaluate(req, tracked) {
|
|
56
|
+
const interaction = tracked.interactionsCompleted[req.interactionId];
|
|
57
|
+
const completed = interaction?.completed || false;
|
|
58
|
+
const correct = interaction?.correct || false;
|
|
59
|
+
const met = req.requireCorrect ? (completed && correct) : completed;
|
|
60
|
+
return { met, type: req.type, message: req.message, interactionId: req.interactionId, completed, correct };
|
|
61
|
+
},
|
|
62
|
+
progress(req, tracked, result) {
|
|
63
|
+
return result.met ? 1 : 0;
|
|
64
|
+
},
|
|
65
|
+
label(req, _tracked, _result, ctx) {
|
|
66
|
+
const registered = ctx.interactionRegistry?.getAll().find(i => i.id === req.interactionId);
|
|
67
|
+
const interactionLabel = req.label || registered?.config?.label || ctx.formatInteractionId(req.interactionId);
|
|
68
|
+
return `Complete: ${interactionLabel}`;
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
allInteractionsComplete: {
|
|
73
|
+
// shares interactionsCompleted field with interactionComplete
|
|
74
|
+
evaluate(req, tracked, ctx) {
|
|
75
|
+
const completedCount = Object.values(tracked.interactionsCompleted)
|
|
76
|
+
.filter(i => req.requireCorrect ? (i.completed && i.correct) : i.completed).length;
|
|
77
|
+
const registered = ctx.interactionRegistry?.getAll() || [];
|
|
78
|
+
const total = registered.length;
|
|
79
|
+
return { met: total > 0 && completedCount >= total, type: req.type, message: req.message, completed: completedCount, total };
|
|
80
|
+
},
|
|
81
|
+
progress(req, tracked, result, ctx) {
|
|
82
|
+
const registered = ctx.interactionRegistry?.getAll() || [];
|
|
83
|
+
const total = registered.length;
|
|
84
|
+
if (total <= 0) return 0;
|
|
85
|
+
const completedCount = Object.values(tracked.interactionsCompleted)
|
|
86
|
+
.filter(i => req.requireCorrect ? (i.completed && i.correct) : i.completed).length;
|
|
87
|
+
return completedCount / total;
|
|
88
|
+
},
|
|
89
|
+
label(_req, _tracked, result) {
|
|
90
|
+
return `Complete all interactions (${result.completed}/${result.total})`;
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
scrollDepth: {
|
|
95
|
+
fields: { scrollDepth: 0 },
|
|
96
|
+
evaluate(req, tracked) {
|
|
97
|
+
const required = req.percentage || req.minPercentage || 95;
|
|
98
|
+
return { met: tracked.scrollDepth >= required, type: req.type, message: req.message, current: tracked.scrollDepth, required };
|
|
99
|
+
},
|
|
100
|
+
progress(req, tracked) {
|
|
101
|
+
const required = req.percentage || req.minPercentage || 95;
|
|
102
|
+
return Math.min(1, tracked.scrollDepth / required);
|
|
103
|
+
},
|
|
104
|
+
label(_req, _tracked, result) {
|
|
105
|
+
return `Scroll: ${result.current}% / ${result.required}%`;
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
timeOnSlide: {
|
|
110
|
+
// No tracked fields — reads from sessionData domain, not engagement tracked state
|
|
111
|
+
evaluate(req, _tracked, ctx) {
|
|
112
|
+
const sessionData = ctx.stateManager.getDomainState('sessionData') || {};
|
|
113
|
+
const slideDurations = sessionData.slideDurations || {};
|
|
114
|
+
const slideStartTimes = sessionData.slideStartTimes || {};
|
|
115
|
+
let timeSpentMs = slideDurations[ctx.slideId] || 0;
|
|
116
|
+
const activeStartTime = slideStartTimes[ctx.slideId];
|
|
117
|
+
if (activeStartTime) timeSpentMs += Date.now() - activeStartTime;
|
|
118
|
+
const current = Math.floor(timeSpentMs / 1000);
|
|
119
|
+
const required = req.minSeconds || 0;
|
|
120
|
+
return { met: current >= required, type: req.type, message: req.message, current, required };
|
|
121
|
+
},
|
|
122
|
+
progress(req, _tracked, _result, ctx) {
|
|
123
|
+
const sessionData = ctx.stateManager.getDomainState('sessionData') || {};
|
|
124
|
+
const slideDurations = sessionData.slideDurations || {};
|
|
125
|
+
const slideStartTimes = sessionData.slideStartTimes || {};
|
|
126
|
+
let timeSpentMs = slideDurations[ctx.slideId] || 0;
|
|
127
|
+
const activeStartTime = slideStartTimes[ctx.slideId];
|
|
128
|
+
if (activeStartTime) timeSpentMs += Date.now() - activeStartTime;
|
|
129
|
+
const minMs = (req.minSeconds || 0) * 1000;
|
|
130
|
+
return minMs > 0 ? Math.min(1, timeSpentMs / minMs) : 0;
|
|
131
|
+
},
|
|
132
|
+
label(_req, _tracked, result, ctx) {
|
|
133
|
+
return `Please spend ${ctx.formatTime(result.required)} on this slide (${ctx.formatTime(result.current)} so far)`;
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
flag: {
|
|
138
|
+
// No tracked fields — reads from flags domain via ctx.stateManager
|
|
139
|
+
evaluate(req, _tracked, ctx) {
|
|
140
|
+
const flags = ctx.stateManager.getDomainState('flags') || {};
|
|
141
|
+
const flagValue = flags[req.key];
|
|
142
|
+
const met = req.equals !== undefined ? flagValue === req.equals : !!flagValue;
|
|
143
|
+
return { met, type: req.type, message: req.message || `Flag "${req.key}" must be set`, flagKey: req.key, currentValue: flagValue, requiredValue: req.equals };
|
|
144
|
+
},
|
|
145
|
+
progress(_req, _tracked, result) {
|
|
146
|
+
return result.met ? 1 : 0;
|
|
147
|
+
},
|
|
148
|
+
label(req) {
|
|
149
|
+
return req.message || 'Complete required step';
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
allFlags: {
|
|
154
|
+
// No tracked fields — reads from flags domain via ctx.stateManager
|
|
155
|
+
evaluate(req, _tracked, ctx) {
|
|
156
|
+
const flags = ctx.stateManager.getDomainState('flags') || {};
|
|
157
|
+
const requiredFlags = req.flags || [];
|
|
158
|
+
const results = requiredFlags.map(flagConfig => {
|
|
159
|
+
const key = typeof flagConfig === 'string' ? flagConfig : flagConfig.key;
|
|
160
|
+
const value = flags[key];
|
|
161
|
+
const isMet = (typeof flagConfig === 'object' && flagConfig.equals !== undefined) ? value === flagConfig.equals : !!value;
|
|
162
|
+
return { key, met: isMet, value };
|
|
163
|
+
});
|
|
164
|
+
const metCount = results.filter(r => r.met).length;
|
|
165
|
+
return { met: metCount === requiredFlags.length, type: req.type, message: req.message || 'Complete all required steps', flags: results, completed: metCount, total: requiredFlags.length };
|
|
166
|
+
},
|
|
167
|
+
progress(_req, _tracked, result) {
|
|
168
|
+
return result.total > 0 ? result.completed / result.total : 0;
|
|
169
|
+
},
|
|
170
|
+
label(req) {
|
|
171
|
+
return req.message || 'Complete all required steps';
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
slideAudioComplete: {
|
|
176
|
+
fields: { audioComplete: false },
|
|
177
|
+
evaluate(req, tracked) {
|
|
178
|
+
return { met: tracked.audioComplete || false, type: req.type, message: req.message || 'Listen to the audio narration' };
|
|
179
|
+
},
|
|
180
|
+
progress(_req, _tracked, result) {
|
|
181
|
+
return result.met ? 1 : 0;
|
|
182
|
+
},
|
|
183
|
+
label(req) {
|
|
184
|
+
return req.message || 'Listen to the slide narration';
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
audioComplete: {
|
|
189
|
+
fields: { standaloneAudioComplete: [] },
|
|
190
|
+
evaluate(req, tracked) {
|
|
191
|
+
if (!req.audioId) throw new Error("[EngagementManager] audioComplete requirement requires 'audioId' property. For slide-level audio, use 'slideAudioComplete' instead.");
|
|
192
|
+
const complete = (tracked.standaloneAudioComplete || []).includes(req.audioId);
|
|
193
|
+
return { met: complete, type: req.type, audioId: req.audioId, message: req.message || `Listen to the audio: ${req.audioId}` };
|
|
194
|
+
},
|
|
195
|
+
progress(_req, _tracked, result) {
|
|
196
|
+
return result.met ? 1 : 0;
|
|
197
|
+
},
|
|
198
|
+
label(req) {
|
|
199
|
+
return req.message || 'Listen to the audio';
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
modalAudioComplete: {
|
|
204
|
+
fields: { modalsAudioComplete: [] },
|
|
205
|
+
evaluate(req, tracked) {
|
|
206
|
+
if (!req.modalId) throw new Error("[EngagementManager] modalAudioComplete requirement requires 'modalId' property.");
|
|
207
|
+
const complete = (tracked.modalsAudioComplete || []).includes(req.modalId);
|
|
208
|
+
return { met: complete, type: req.type, modalId: req.modalId, message: req.message || `Listen to the modal audio: ${req.modalId}` };
|
|
209
|
+
},
|
|
210
|
+
progress(_req, _tracked, result) {
|
|
211
|
+
return result.met ? 1 : 0;
|
|
212
|
+
},
|
|
213
|
+
label(req) {
|
|
214
|
+
return req.message || 'Listen to the modal audio';
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
slideVideoComplete: {
|
|
219
|
+
fields: { videoComplete: false },
|
|
220
|
+
evaluate(req, tracked) {
|
|
221
|
+
return { met: tracked.videoComplete || false, type: req.type, message: req.message || 'Watch the video' };
|
|
222
|
+
},
|
|
223
|
+
progress(_req, _tracked, result) {
|
|
224
|
+
return result.met ? 1 : 0;
|
|
225
|
+
},
|
|
226
|
+
label(req) {
|
|
227
|
+
return req.message || 'Watch the video';
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
videoComplete: {
|
|
232
|
+
fields: { standaloneVideoComplete: [] },
|
|
233
|
+
evaluate(req, tracked) {
|
|
234
|
+
if (!req.videoId) throw new Error("[EngagementManager] videoComplete requirement requires 'videoId' property. For slide-level video, use 'slideVideoComplete' instead.");
|
|
235
|
+
const complete = (tracked.standaloneVideoComplete || []).includes(req.videoId);
|
|
236
|
+
return { met: complete, type: req.type, videoId: req.videoId, message: req.message || `Watch the video: ${req.videoId}` };
|
|
237
|
+
},
|
|
238
|
+
progress(_req, _tracked, result) {
|
|
239
|
+
return result.met ? 1 : 0;
|
|
240
|
+
},
|
|
241
|
+
label(req) {
|
|
242
|
+
return req.message || 'Watch the video';
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
export default strategies;
|
|
248
|
+
|
|
249
|
+
/** All valid requirement type names */
|
|
250
|
+
export const validTypes = Object.keys(strategies);
|
|
251
|
+
|
|
252
|
+
// ── Core fields not owned by any single strategy ────────────────────
|
|
253
|
+
const CORE_FIELDS = { flipCardsRegistered: [] };
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Aggregate all tracked field defaults from strategy declarations.
|
|
257
|
+
* Used by engagement-manager (initSlide) and engagement-progress (merge/strip).
|
|
258
|
+
* @returns {Record<string, any>} field name → default value
|
|
259
|
+
*/
|
|
260
|
+
export function getTrackedFieldDefaults() {
|
|
261
|
+
const defaults = { ...CORE_FIELDS };
|
|
262
|
+
for (const strategy of Object.values(strategies)) {
|
|
263
|
+
if (strategy.fields) {
|
|
264
|
+
Object.assign(defaults, strategy.fields);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return defaults;
|
|
268
|
+
}
|