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,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component Catalog
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for all UI components (built-in and custom).
|
|
5
|
+
* Uses import.meta.glob for auto-discovery - just drop a file in the folder.
|
|
6
|
+
*
|
|
7
|
+
* Mirrors the interaction-catalog.js pattern for consistency.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const catalog = new Map();
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Register a component type
|
|
14
|
+
* @param {string} type - Component type name (kebab-case, matches data-component value)
|
|
15
|
+
* @param {object} definition - { init?, metadata?, schema, styles? }
|
|
16
|
+
*/
|
|
17
|
+
export function catalogComponent(type, { init, metadata, schema, styles }) {
|
|
18
|
+
if (!schema) {
|
|
19
|
+
throw new Error(`[ComponentCatalog] Cannot register "${type}" without a schema`);
|
|
20
|
+
}
|
|
21
|
+
catalog.set(type, { init, metadata, schema, styles });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get the init function for a component type
|
|
26
|
+
* @param {string} type - Component type name
|
|
27
|
+
* @returns {function|undefined}
|
|
28
|
+
*/
|
|
29
|
+
export function getComponentInit(type) {
|
|
30
|
+
return catalog.get(type)?.init;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get metadata for a component type
|
|
35
|
+
* @param {string} type - Component type name
|
|
36
|
+
* @returns {object|undefined}
|
|
37
|
+
*/
|
|
38
|
+
export function getComponentMetadata(type) {
|
|
39
|
+
return catalog.get(type)?.metadata;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get schema for a component type
|
|
44
|
+
* @param {string} type - Component type name
|
|
45
|
+
* @returns {object|undefined}
|
|
46
|
+
*/
|
|
47
|
+
export function getComponentSchema(type) {
|
|
48
|
+
return catalog.get(type)?.schema;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get styles for a component type (for custom components with CSS-in-JS)
|
|
53
|
+
* @param {string} type - Component type name
|
|
54
|
+
* @returns {string|undefined}
|
|
55
|
+
*/
|
|
56
|
+
export function getComponentStyles(type) {
|
|
57
|
+
return catalog.get(type)?.styles;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get all registered schemas
|
|
62
|
+
* @returns {object} Map of type -> schema
|
|
63
|
+
*/
|
|
64
|
+
export function getAllComponentSchemas() {
|
|
65
|
+
const schemas = {};
|
|
66
|
+
for (const [type, def] of catalog) {
|
|
67
|
+
if (def.schema) {
|
|
68
|
+
schemas[type] = def.schema;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return schemas;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get all registered type names
|
|
76
|
+
* @returns {string[]}
|
|
77
|
+
*/
|
|
78
|
+
export function getRegisteredComponentTypes() {
|
|
79
|
+
return [...catalog.keys()];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Check if a type is registered
|
|
84
|
+
* @param {string} type
|
|
85
|
+
* @returns {boolean}
|
|
86
|
+
*/
|
|
87
|
+
export function isComponentRegistered(type) {
|
|
88
|
+
return catalog.has(type);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// =============================================================================
|
|
92
|
+
// Auto-discover Components (Built-in UI + Custom)
|
|
93
|
+
// =============================================================================
|
|
94
|
+
|
|
95
|
+
// Built-in UI components
|
|
96
|
+
const uiComponentModules = import.meta.glob('../components/ui-components/*.js', { eager: true });
|
|
97
|
+
|
|
98
|
+
// Course-specific custom components
|
|
99
|
+
const customComponentModules = import.meta.glob('../../../course/components/*.js', { eager: true });
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Register all discovered modules.
|
|
103
|
+
* Convention: per-element initializer is exported as `init`.
|
|
104
|
+
*/
|
|
105
|
+
function registerModules(modules) {
|
|
106
|
+
for (const [path, module] of Object.entries(modules)) {
|
|
107
|
+
// Skip index/base files
|
|
108
|
+
if (path.includes('index.js') || path.includes('-base')) continue;
|
|
109
|
+
|
|
110
|
+
// Each component exports: schema (required), metadata (optional), init (optional), styles (optional)
|
|
111
|
+
const { schema, metadata, styles, init } = module;
|
|
112
|
+
|
|
113
|
+
if (!schema?.type) continue;
|
|
114
|
+
|
|
115
|
+
catalogComponent(schema.type, { init, metadata, schema, styles });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Register synchronously - modules are eagerly loaded
|
|
120
|
+
registerModules(uiComponentModules);
|
|
121
|
+
registerModules(customComponentModules);
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { generateId } from '../utilities/utilities.js';
|
|
2
|
+
import { logger } from '../utilities/logger.js';
|
|
3
|
+
|
|
4
|
+
class EventBus {
|
|
5
|
+
constructor() {
|
|
6
|
+
// Event listeners registry
|
|
7
|
+
this.events = {};
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Subscribe to an event
|
|
12
|
+
*
|
|
13
|
+
* @param {string} event - Event name
|
|
14
|
+
* @param {Function} callback - Callback function
|
|
15
|
+
* @param {Object} options - Optional configuration
|
|
16
|
+
* @returns {Function} Unsubscribe function
|
|
17
|
+
*/
|
|
18
|
+
on(event, callback, options = {}) {
|
|
19
|
+
if (!event || typeof callback !== 'function') {
|
|
20
|
+
throw new Error('Event name and callback are required');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!this.events[event]) {
|
|
24
|
+
this.events[event] = [];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const listener = {
|
|
28
|
+
callback,
|
|
29
|
+
once: options.once || false,
|
|
30
|
+
id: generateId('listener')
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
this.events[event].push(listener);
|
|
34
|
+
|
|
35
|
+
// Return unsubscribe function
|
|
36
|
+
return () => this.off(event, listener.id);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Subscribe to an event once (auto-unsubscribe after first trigger)
|
|
41
|
+
*
|
|
42
|
+
* @param {string} event - Event name
|
|
43
|
+
* @param {Function} callback - Callback function
|
|
44
|
+
* @returns {Function} Unsubscribe function
|
|
45
|
+
*/
|
|
46
|
+
once(event, callback) {
|
|
47
|
+
return this.on(event, callback, { once: true });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Unsubscribe from an event
|
|
52
|
+
*
|
|
53
|
+
* @param {string} event - Event name
|
|
54
|
+
* @param {string|Function} listenerIdOrCallback - Listener ID or callback function
|
|
55
|
+
*/
|
|
56
|
+
off(event, listenerIdOrCallback) {
|
|
57
|
+
if (!this.events[event]) return;
|
|
58
|
+
|
|
59
|
+
if (typeof listenerIdOrCallback === 'string') {
|
|
60
|
+
// Remove by ID
|
|
61
|
+
this.events[event] = this.events[event].filter(
|
|
62
|
+
listener => listener.id !== listenerIdOrCallback
|
|
63
|
+
);
|
|
64
|
+
} else if (typeof listenerIdOrCallback === 'function') {
|
|
65
|
+
// Remove by callback reference
|
|
66
|
+
this.events[event] = this.events[event].filter(
|
|
67
|
+
listener => listener.callback !== listenerIdOrCallback
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Clean up empty event arrays
|
|
72
|
+
if (this.events[event].length === 0) {
|
|
73
|
+
delete this.events[event];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Emit an event
|
|
79
|
+
*
|
|
80
|
+
* @param {string} event - Event name
|
|
81
|
+
* @param {*} data - Event data
|
|
82
|
+
* @returns {boolean} True if event had listeners
|
|
83
|
+
*/
|
|
84
|
+
emit(event, data) {
|
|
85
|
+
if (!this.events[event] || this.events[event].length === 0) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Automatically log events that follow the ':error' naming convention
|
|
90
|
+
if (event.endsWith(':error')) {
|
|
91
|
+
logger.error(`[EventBus Error] ${event}:`, JSON.stringify(data, null, 2));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Create a copy of listeners to avoid issues if listeners modify the array
|
|
95
|
+
const listeners = [...this.events[event]];
|
|
96
|
+
const onceListeners = [];
|
|
97
|
+
|
|
98
|
+
listeners.forEach(listener => {
|
|
99
|
+
try {
|
|
100
|
+
listener.callback(data);
|
|
101
|
+
|
|
102
|
+
// Track once listeners for removal
|
|
103
|
+
if (listener.once) {
|
|
104
|
+
onceListeners.push(listener.id);
|
|
105
|
+
}
|
|
106
|
+
} catch (error) {
|
|
107
|
+
// Log the error but don't break other listeners
|
|
108
|
+
logger.error(`[EventBus] Error in listener for '${event}':`, error);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Remove once listeners
|
|
113
|
+
onceListeners.forEach(id => this.off(event, id));
|
|
114
|
+
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Emit an event asynchronously
|
|
120
|
+
*
|
|
121
|
+
* @param {string} event - Event name
|
|
122
|
+
* @param {*} data - Event data
|
|
123
|
+
* @returns {Promise} Resolves when all listeners have been called
|
|
124
|
+
*/
|
|
125
|
+
async emitAsync(event, data) {
|
|
126
|
+
if (!this.events[event] || this.events[event].length === 0) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const listeners = [...this.events[event]];
|
|
131
|
+
const onceListeners = [];
|
|
132
|
+
|
|
133
|
+
for (const listener of listeners) {
|
|
134
|
+
try {
|
|
135
|
+
await listener.callback(data);
|
|
136
|
+
|
|
137
|
+
if (listener.once) {
|
|
138
|
+
onceListeners.push(listener.id);
|
|
139
|
+
}
|
|
140
|
+
} catch (error) {
|
|
141
|
+
logger.error(`[EventBus] Error in async listener for '${event}':`, error);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Remove once listeners
|
|
146
|
+
onceListeners.forEach(id => this.off(event, id));
|
|
147
|
+
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Remove all listeners for an event or all events
|
|
153
|
+
*
|
|
154
|
+
* @param {string} event - Optional event name (if omitted, clears all)
|
|
155
|
+
*/
|
|
156
|
+
clear(event) {
|
|
157
|
+
if (event) {
|
|
158
|
+
delete this.events[event];
|
|
159
|
+
} else {
|
|
160
|
+
this.events = {};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Get listener count for an event
|
|
166
|
+
*
|
|
167
|
+
* @param {string} event - Event name
|
|
168
|
+
* @returns {number} Number of listeners
|
|
169
|
+
*/
|
|
170
|
+
getListenerCount(event) {
|
|
171
|
+
return this.events[event] ? this.events[event].length : 0;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Create global event bus instance
|
|
176
|
+
const eventBus = new EventBus();
|
|
177
|
+
|
|
178
|
+
export { EventBus, eventBus };
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interaction Catalog
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for all interaction types (built-in and custom).
|
|
5
|
+
* Uses import.meta.glob for auto-discovery - just drop a file in the folder.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const catalog = new Map();
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Register an interaction type
|
|
12
|
+
* @param {string} type - Interaction type name (kebab-case)
|
|
13
|
+
* @param {object} definition - { creator, metadata?, schema? }
|
|
14
|
+
*/
|
|
15
|
+
export function catalogInteraction(type, { creator, metadata, schema }) {
|
|
16
|
+
if (!creator) {
|
|
17
|
+
throw new Error(`[InteractionCatalog] Cannot register "${type}" without a creator function`);
|
|
18
|
+
}
|
|
19
|
+
catalog.set(type, { creator, metadata, schema });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get the creator function for an interaction type
|
|
24
|
+
* @param {string} type - Interaction type name
|
|
25
|
+
* @returns {function|undefined}
|
|
26
|
+
*/
|
|
27
|
+
export function getCreator(type) {
|
|
28
|
+
return catalog.get(type)?.creator;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get metadata for an interaction type
|
|
33
|
+
* @param {string} type - Interaction type name
|
|
34
|
+
* @returns {object|undefined}
|
|
35
|
+
*/
|
|
36
|
+
export function getMetadata(type) {
|
|
37
|
+
return catalog.get(type)?.metadata;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get schema for an interaction type
|
|
42
|
+
* @param {string} type - Interaction type name
|
|
43
|
+
* @returns {object|undefined}
|
|
44
|
+
*/
|
|
45
|
+
export function getSchema(type) {
|
|
46
|
+
return catalog.get(type)?.schema;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get all registered schemas
|
|
51
|
+
* @returns {object} Map of type -> schema
|
|
52
|
+
*/
|
|
53
|
+
export function getAllSchemas() {
|
|
54
|
+
const schemas = {};
|
|
55
|
+
for (const [type, def] of catalog) {
|
|
56
|
+
if (def.schema) {
|
|
57
|
+
schemas[type] = def.schema;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return schemas;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get all registered type names
|
|
65
|
+
* @returns {string[]}
|
|
66
|
+
*/
|
|
67
|
+
export function getRegisteredTypes() {
|
|
68
|
+
return [...catalog.keys()];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Check if a type is registered
|
|
73
|
+
* @param {string} type
|
|
74
|
+
* @returns {boolean}
|
|
75
|
+
*/
|
|
76
|
+
export function isRegistered(type) {
|
|
77
|
+
return catalog.has(type);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get merged schema with base properties
|
|
82
|
+
* @param {string} type - Interaction type name
|
|
83
|
+
* @returns {object} Full schema with base properties merged
|
|
84
|
+
*/
|
|
85
|
+
export function getFullSchema(type) {
|
|
86
|
+
const schema = getSchema(type);
|
|
87
|
+
if (!schema) return null;
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
...schema,
|
|
91
|
+
properties: {
|
|
92
|
+
...baseSchema,
|
|
93
|
+
...schema.properties
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// =============================================================================
|
|
99
|
+
// Base Schema (shared properties for all interactions)
|
|
100
|
+
// =============================================================================
|
|
101
|
+
|
|
102
|
+
export const baseSchema = {
|
|
103
|
+
id: { type: 'string', required: true, description: 'Unique identifier for the interaction' },
|
|
104
|
+
prompt: { type: 'string', required: true, description: 'Question or instruction text' },
|
|
105
|
+
controlled: { type: 'boolean', default: false, description: 'If true, interaction is managed externally (e.g., by assessment)' },
|
|
106
|
+
feedback: { type: 'object', description: 'Custom feedback messages' }
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// =============================================================================
|
|
110
|
+
// Auto-discover Interactions (Built-in + Custom)
|
|
111
|
+
// =============================================================================
|
|
112
|
+
|
|
113
|
+
// Built-in framework interactions
|
|
114
|
+
const builtInModules = import.meta.glob('../components/interactions/*.js', { eager: true });
|
|
115
|
+
|
|
116
|
+
// Course-specific custom interactions
|
|
117
|
+
const customModules = import.meta.glob('../../../course/interactions/*.js', { eager: true });
|
|
118
|
+
|
|
119
|
+
// Register all discovered modules
|
|
120
|
+
function registerModules(modules) {
|
|
121
|
+
for (const [path, module] of Object.entries(modules)) {
|
|
122
|
+
// Skip base module
|
|
123
|
+
if (path.includes('interaction-base')) continue;
|
|
124
|
+
|
|
125
|
+
// Each interaction exports: schema, metadata, and a create* function
|
|
126
|
+
const { schema, metadata } = module;
|
|
127
|
+
|
|
128
|
+
if (!schema?.type) continue;
|
|
129
|
+
|
|
130
|
+
// Find the creator function by export key name (not function.name which gets minified)
|
|
131
|
+
const creator = Object.entries(module)
|
|
132
|
+
.filter(([key, v]) => typeof v === 'function' && key.startsWith('create'))
|
|
133
|
+
.map(([, v]) => v)[0];
|
|
134
|
+
|
|
135
|
+
if (!creator) continue;
|
|
136
|
+
|
|
137
|
+
catalogInteraction(schema.type, { creator, metadata, schema });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Register synchronously - modules are eagerly loaded
|
|
142
|
+
registerModules(builtInModules);
|
|
143
|
+
registerModules(customModules);
|
|
144
|
+
|
|
145
|
+
// Multiple-choice-single is an alias for multiple-choice
|
|
146
|
+
if (catalog.has('multiple-choice')) {
|
|
147
|
+
const mc = catalog.get('multiple-choice');
|
|
148
|
+
catalog.set('multiple-choice-single', mc);
|
|
149
|
+
}
|