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,565 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stub-player/interactions-panel.js - Interactions panel component and handlers
|
|
3
|
+
*
|
|
4
|
+
* Shows standalone interaction definitions from slide files (NOT assessment questions).
|
|
5
|
+
* Provides viewing and inline editing of interaction properties.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { escapeHtml, renderEditForm, saveItemEdits } from './edit-utils.js';
|
|
9
|
+
|
|
10
|
+
const PANEL_ICON_PATHS = {
|
|
11
|
+
interactions: '<path d="M12 7v14"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/>',
|
|
12
|
+
list: '<path d="M3 5h.01"/><path d="M3 12h.01"/><path d="M3 19h.01"/><path d="M8 5h13"/><path d="M8 12h13"/><path d="M8 19h13"/>',
|
|
13
|
+
target: '<circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/>',
|
|
14
|
+
slide: '<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M3 9h18"/><path d="M9 21V9"/>',
|
|
15
|
+
navigate: '<path d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"/><circle cx="12" cy="10" r="3"/>',
|
|
16
|
+
edit: '<path d="M13 21h8"/><path d="m15 5 4 4"/><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"/>',
|
|
17
|
+
close: '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>'
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
function renderPanelIcon(name, className = 'panel-icon') {
|
|
21
|
+
const path = PANEL_ICON_PATHS[name];
|
|
22
|
+
if (!path) return '';
|
|
23
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="${className}" aria-hidden="true">${path}</svg>`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Generate the Interactions Panel HTML container
|
|
29
|
+
*/
|
|
30
|
+
export function generateInteractionsPanel() {
|
|
31
|
+
return `
|
|
32
|
+
<div id="stub-player-interactions-panel">
|
|
33
|
+
<div id="stub-player-interactions-panel-header">
|
|
34
|
+
<h3>${renderPanelIcon('interactions')} Interactions</h3>
|
|
35
|
+
<button id="stub-player-interactions-panel-close">×</button>
|
|
36
|
+
</div>
|
|
37
|
+
<div id="stub-player-interactions-tabs">
|
|
38
|
+
<button class="active" data-tab="all">${renderPanelIcon('list', 'tab-icon')} <span>Interactions</span></button>
|
|
39
|
+
<button data-tab="assessments">${renderPanelIcon('target', 'tab-icon')} <span>Assessments</span></button>
|
|
40
|
+
<button data-tab="slide">${renderPanelIcon('slide', 'tab-icon')} <span>This Slide</span></button>
|
|
41
|
+
</div>
|
|
42
|
+
<div id="stub-player-interactions-body">
|
|
43
|
+
<div class="interactions-loading">Loading...</div>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Create interactions panel handlers
|
|
51
|
+
* @param {Object} context - Shared context containing cmiData, navigateToSlide, etc.
|
|
52
|
+
* @returns {Object} Panel handler methods
|
|
53
|
+
*/
|
|
54
|
+
export function createInteractionsPanelHandlers(context) {
|
|
55
|
+
const { getCmiData, navigateToSlide } = context;
|
|
56
|
+
|
|
57
|
+
let interactionsData = null;
|
|
58
|
+
let assessmentsData = null;
|
|
59
|
+
let interactionSchemas = {};
|
|
60
|
+
let currentTab = 'all';
|
|
61
|
+
let editingInteractionId = null;
|
|
62
|
+
let currentAssessmentId = null;
|
|
63
|
+
|
|
64
|
+
const interactionsPanel = document.getElementById('stub-player-interactions-panel');
|
|
65
|
+
const interactionsTabs = document.getElementById('stub-player-interactions-tabs');
|
|
66
|
+
const interactionsBody = document.getElementById('stub-player-interactions-body');
|
|
67
|
+
const closeBtn = document.getElementById('stub-player-interactions-panel-close');
|
|
68
|
+
|
|
69
|
+
// Setup tab handlers
|
|
70
|
+
function setupTabs() {
|
|
71
|
+
if (!interactionsTabs) return;
|
|
72
|
+
interactionsTabs.querySelectorAll('button').forEach(btn => {
|
|
73
|
+
btn.addEventListener('click', () => {
|
|
74
|
+
interactionsTabs.querySelectorAll('button').forEach(b => b.classList.remove('active'));
|
|
75
|
+
btn.classList.add('active');
|
|
76
|
+
setTab(btn.dataset.tab);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Close button handler
|
|
82
|
+
if (closeBtn) {
|
|
83
|
+
closeBtn.addEventListener('click', () => {
|
|
84
|
+
interactionsPanel?.classList.remove('visible');
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Initialize tabs on creation
|
|
89
|
+
setupTabs();
|
|
90
|
+
|
|
91
|
+
async function loadInteractions() {
|
|
92
|
+
if (!interactionsBody) return;
|
|
93
|
+
interactionsBody.innerHTML = '<div class="interactions-loading">Loading...</div>';
|
|
94
|
+
try {
|
|
95
|
+
// Load manifest (contains slides & interactions) and assessments
|
|
96
|
+
const [manifestResponse, assessResponse, schemaResponse] = await Promise.all([
|
|
97
|
+
fetch('/_content-manifest.json'),
|
|
98
|
+
fetch('/__assessments'),
|
|
99
|
+
fetch('/__interaction-schemas')
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
if (schemaResponse.ok) {
|
|
103
|
+
const schemaData = await schemaResponse.json();
|
|
104
|
+
interactionSchemas = schemaData?.schemas || {};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (manifestResponse.ok) {
|
|
108
|
+
const manifest = await manifestResponse.json();
|
|
109
|
+
|
|
110
|
+
// Flatten interactions from slides structure
|
|
111
|
+
const interactions = [];
|
|
112
|
+
if (manifest && manifest.slides) {
|
|
113
|
+
for (const [slideId, slideData] of Object.entries(manifest.slides)) {
|
|
114
|
+
if (slideData.interactions) {
|
|
115
|
+
for (const interaction of slideData.interactions) {
|
|
116
|
+
interactions.push({
|
|
117
|
+
...interaction,
|
|
118
|
+
schema: interaction.schema || interactionSchemas[interaction.type] || null,
|
|
119
|
+
slideId // Ensure slideId is attached
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
interactionsData = { interactions };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (assessResponse.ok) {
|
|
130
|
+
const data = await assessResponse.json();
|
|
131
|
+
assessmentsData = data.assessments || [];
|
|
132
|
+
}
|
|
133
|
+
render();
|
|
134
|
+
} catch (err) {
|
|
135
|
+
interactionsBody.innerHTML = '<div class="interactions-empty">Error: ' + err.message + '</div>';
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function setTab(tab) {
|
|
140
|
+
currentTab = tab;
|
|
141
|
+
editingInteractionId = null;
|
|
142
|
+
render();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function render() {
|
|
146
|
+
if (!interactionsBody) return;
|
|
147
|
+
|
|
148
|
+
if (currentTab === 'slide') {
|
|
149
|
+
renderSlideTab();
|
|
150
|
+
} else if (currentTab === 'all') {
|
|
151
|
+
renderInteractionsTab();
|
|
152
|
+
} else if (currentTab === 'assessments') {
|
|
153
|
+
renderAssessmentsTab();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// =========================================================================
|
|
158
|
+
// THIS SLIDE TAB - Context-aware
|
|
159
|
+
// =========================================================================
|
|
160
|
+
function renderSlideTab() {
|
|
161
|
+
const cmiData = getCmiData();
|
|
162
|
+
const currentSlide = cmiData['cmi.location'] || '';
|
|
163
|
+
|
|
164
|
+
// Check if current slide is an assessment
|
|
165
|
+
const assessment = assessmentsData?.find(a => a.id === currentSlide);
|
|
166
|
+
|
|
167
|
+
if (assessment) {
|
|
168
|
+
// Show assessment details for this slide
|
|
169
|
+
renderSingleAssessment(assessment);
|
|
170
|
+
} else {
|
|
171
|
+
// Show interactions for this slide
|
|
172
|
+
const interactions = (interactionsData?.interactions || []).filter(i => i.slideId === currentSlide);
|
|
173
|
+
|
|
174
|
+
if (interactions.length === 0) {
|
|
175
|
+
interactionsBody.innerHTML = '<div class="interactions-empty">No interactions on current slide</div>';
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
renderInteractionsList(interactions);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// =========================================================================
|
|
184
|
+
// INTERACTIONS TAB - All interactions
|
|
185
|
+
// =========================================================================
|
|
186
|
+
function renderInteractionsTab() {
|
|
187
|
+
const interactions = interactionsData?.interactions || [];
|
|
188
|
+
|
|
189
|
+
if (interactions.length === 0) {
|
|
190
|
+
interactionsBody.innerHTML = '<div class="interactions-empty">No standalone interactions found in course</div>';
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const grouped = new Map();
|
|
195
|
+
for (const interaction of interactions) {
|
|
196
|
+
const key = interaction.slideId || 'unknown-slide';
|
|
197
|
+
if (!grouped.has(key)) grouped.set(key, []);
|
|
198
|
+
grouped.get(key).push(interaction);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const slideIds = Array.from(grouped.keys()).sort((a, b) => a.localeCompare(b));
|
|
202
|
+
let html = '<div class="interaction-groups">';
|
|
203
|
+
|
|
204
|
+
for (const slideId of slideIds) {
|
|
205
|
+
const groupInteractions = grouped.get(slideId);
|
|
206
|
+
html += `
|
|
207
|
+
<section class="interaction-group">
|
|
208
|
+
<div class="interaction-group-header">
|
|
209
|
+
<div class="interaction-group-title-wrap">
|
|
210
|
+
${renderPanelIcon('slide', 'group-icon')}
|
|
211
|
+
<h4 class="interaction-group-title">${escapeHtml(slideId)}</h4>
|
|
212
|
+
<span class="interaction-group-count">${groupInteractions.length}</span>
|
|
213
|
+
</div>
|
|
214
|
+
<button class="interaction-nav-btn with-label" data-slide="${escapeHtml(slideId)}" title="Go to slide">${renderPanelIcon('navigate', 'action-icon')} <span>Open</span></button>
|
|
215
|
+
</div>
|
|
216
|
+
<div class="interactions-list">
|
|
217
|
+
${groupInteractions.map(interaction => renderInteractionCard(interaction)).join('')}
|
|
218
|
+
</div>
|
|
219
|
+
</section>
|
|
220
|
+
`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
html += '</div>';
|
|
224
|
+
interactionsBody.innerHTML = html;
|
|
225
|
+
attachInteractionHandlers();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function renderInteractionsList(interactions) {
|
|
229
|
+
let html = '<div class="interactions-list">';
|
|
230
|
+
|
|
231
|
+
for (const interaction of interactions) {
|
|
232
|
+
html += renderInteractionCard(interaction);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
html += '</div>';
|
|
236
|
+
interactionsBody.innerHTML = html;
|
|
237
|
+
attachInteractionHandlers();
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function attachInteractionHandlers() {
|
|
241
|
+
interactionsBody.querySelectorAll('.interaction-nav-btn').forEach(btn => {
|
|
242
|
+
btn.addEventListener('click', (e) => {
|
|
243
|
+
e.stopPropagation();
|
|
244
|
+
navigateToSlide(btn.dataset.slide);
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
interactionsBody.querySelectorAll('.interaction-edit-btn').forEach(btn => {
|
|
249
|
+
btn.addEventListener('click', (e) => {
|
|
250
|
+
e.stopPropagation();
|
|
251
|
+
const id = btn.dataset.id;
|
|
252
|
+
editingInteractionId = (editingInteractionId === id) ? null : id;
|
|
253
|
+
render();
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
interactionsBody.querySelectorAll('.edit-save-btn').forEach(btn => {
|
|
258
|
+
btn.addEventListener('click', async (e) => {
|
|
259
|
+
e.stopPropagation();
|
|
260
|
+
const card = btn.closest('.interaction-card');
|
|
261
|
+
const form = card.querySelector('.edit-form');
|
|
262
|
+
const interactionId = card.dataset.interactionId;
|
|
263
|
+
const slideId = card.dataset.slideId;
|
|
264
|
+
await saveItemEdits('/__edit-interaction', form, slideId, interactionId);
|
|
265
|
+
editingInteractionId = null;
|
|
266
|
+
await loadInteractions();
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
interactionsBody.querySelectorAll('.edit-cancel-btn').forEach(btn => {
|
|
271
|
+
btn.addEventListener('click', (e) => {
|
|
272
|
+
e.stopPropagation();
|
|
273
|
+
editingInteractionId = null;
|
|
274
|
+
render();
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// =========================================================================
|
|
280
|
+
// ASSESSMENTS TAB - All assessments
|
|
281
|
+
// =========================================================================
|
|
282
|
+
function renderAssessmentsTab() {
|
|
283
|
+
if (!assessmentsData || assessmentsData.length === 0) {
|
|
284
|
+
interactionsBody.innerHTML = '<div class="interactions-empty">No assessments found</div>';
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Ensure current selection is valid
|
|
289
|
+
if (!currentAssessmentId || !assessmentsData.find(a => a.id === currentAssessmentId)) {
|
|
290
|
+
currentAssessmentId = assessmentsData[0].id;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Render assessment sub-tabs
|
|
294
|
+
let tabsHtml = '<div class="assessment-sub-tabs">';
|
|
295
|
+
for (const assessment of assessmentsData) {
|
|
296
|
+
const active = assessment.id === currentAssessmentId ? 'active' : '';
|
|
297
|
+
const title = assessment.title || assessment.id;
|
|
298
|
+
tabsHtml += `<button class="${active}" data-assessment-id="${escapeHtml(assessment.id)}">${escapeHtml(title)}</button>`;
|
|
299
|
+
}
|
|
300
|
+
tabsHtml += '</div>';
|
|
301
|
+
|
|
302
|
+
const assessment = assessmentsData.find(a => a.id === currentAssessmentId);
|
|
303
|
+
let bodyHtml = '';
|
|
304
|
+
if (assessment) {
|
|
305
|
+
bodyHtml = renderAssessmentDetails(assessment);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
interactionsBody.innerHTML = tabsHtml + bodyHtml;
|
|
309
|
+
|
|
310
|
+
// Attach assessment sub-tab handlers
|
|
311
|
+
interactionsBody.querySelectorAll('.assessment-sub-tabs button').forEach(btn => {
|
|
312
|
+
btn.addEventListener('click', () => {
|
|
313
|
+
currentAssessmentId = btn.dataset.assessmentId;
|
|
314
|
+
editingInteractionId = null; // Clear editing state when switching tabs
|
|
315
|
+
renderAssessmentsTab();
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// Attach all assessment handlers (nav, settings)
|
|
320
|
+
if (assessment) {
|
|
321
|
+
attachAssessmentHandlers(assessment);
|
|
322
|
+
attachInteractionHandlers(); // For question editing
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function renderSingleAssessment(assessment) {
|
|
327
|
+
interactionsBody.innerHTML = renderAssessmentDetails(assessment);
|
|
328
|
+
attachAssessmentHandlers(assessment);
|
|
329
|
+
attachInteractionHandlers(); // For question editing
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function attachAssessmentHandlers(assessment) {
|
|
333
|
+
const assessmentId = assessment.id;
|
|
334
|
+
|
|
335
|
+
// Attach navigation handler
|
|
336
|
+
interactionsBody.querySelectorAll('.interaction-nav-btn').forEach(btn => {
|
|
337
|
+
btn.addEventListener('click', () => {
|
|
338
|
+
navigateToSlide(btn.dataset.slide);
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// Toggle click handlers (save immediately)
|
|
343
|
+
interactionsBody.querySelectorAll('.assessment-config-toggle[data-field]').forEach(toggle => {
|
|
344
|
+
toggle.addEventListener('click', async function () {
|
|
345
|
+
const isOn = this.classList.contains('on');
|
|
346
|
+
this.classList.toggle('on');
|
|
347
|
+
const field = this.dataset.field;
|
|
348
|
+
await saveAssessmentValue(assessmentId, field, !isOn);
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// Input handlers (save with debounce)
|
|
353
|
+
interactionsBody.querySelectorAll('.assessment-config-input[data-field]').forEach(input => {
|
|
354
|
+
let timeout;
|
|
355
|
+
input.addEventListener('input', function () {
|
|
356
|
+
clearTimeout(timeout);
|
|
357
|
+
timeout = setTimeout(async () => {
|
|
358
|
+
const field = this.dataset.field;
|
|
359
|
+
const isArray = this.dataset.type === 'array';
|
|
360
|
+
let value;
|
|
361
|
+
|
|
362
|
+
if (isArray) {
|
|
363
|
+
const trimmed = this.value.trim();
|
|
364
|
+
value = trimmed ? trimmed.split(',').map(s => s.trim()).filter(Boolean) : [];
|
|
365
|
+
} else if (this.type === 'number') {
|
|
366
|
+
if (this.value === '' || this.value === 'null') {
|
|
367
|
+
value = null;
|
|
368
|
+
} else {
|
|
369
|
+
value = parseInt(this.value, 10);
|
|
370
|
+
}
|
|
371
|
+
} else {
|
|
372
|
+
value = this.value;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
await saveAssessmentValue(assessmentId, field, value);
|
|
376
|
+
}, 500);
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async function saveAssessmentValue(assessmentId, field, value) {
|
|
382
|
+
try {
|
|
383
|
+
const response = await fetch('/__edit-assessment', {
|
|
384
|
+
method: 'POST',
|
|
385
|
+
headers: { 'Content-Type': 'application/json' },
|
|
386
|
+
body: JSON.stringify({ assessmentId, field, value })
|
|
387
|
+
});
|
|
388
|
+
if (!response.ok) {
|
|
389
|
+
const error = await response.json();
|
|
390
|
+
console.error('Assessment save error:', error);
|
|
391
|
+
}
|
|
392
|
+
} catch (err) {
|
|
393
|
+
console.error('Failed to save assessment setting:', err);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function renderAssessmentDetails(assessment) {
|
|
398
|
+
const settings = assessment.settings || assessment.config || {};
|
|
399
|
+
const questions = assessment.questions || [];
|
|
400
|
+
|
|
401
|
+
let html = `
|
|
402
|
+
<div class="assessment-info" data-assessment-id="${escapeHtml(assessment.id)}">
|
|
403
|
+
<h4>${escapeHtml(assessment.title || assessment.id)}</h4>
|
|
404
|
+
<div class="assessment-meta">
|
|
405
|
+
<span class="meta-item">ID: ${escapeHtml(assessment.id)}</span>
|
|
406
|
+
<button class="interaction-nav-btn with-label" data-slide="${escapeHtml(assessment.id)}" title="Go to start slide">${renderPanelIcon('navigate', 'action-icon')} <span>Go to Slide</span></button>
|
|
407
|
+
</div>
|
|
408
|
+
`;
|
|
409
|
+
|
|
410
|
+
// Settings section - always editable (inline pattern)
|
|
411
|
+
html += `<div class="config-section">
|
|
412
|
+
<div class="config-section-header">Settings</div>
|
|
413
|
+
<div class="config-row">
|
|
414
|
+
<span class="config-label">Passing Score</span>
|
|
415
|
+
<input type="number" class="config-input assessment-config-input assessment-config-score-input" data-field="passingScore" value="${settings.passingScore ?? 80}" min="0" max="100">
|
|
416
|
+
<span class="config-hint-inline">%</span>
|
|
417
|
+
</div>
|
|
418
|
+
<div class="config-row">
|
|
419
|
+
<span class="config-label">Allow Retake</span>
|
|
420
|
+
<div class="config-toggle assessment-config-toggle ${settings.allowRetake !== false ? 'on' : ''}" data-field="allowRetake"></div>
|
|
421
|
+
</div>
|
|
422
|
+
<div class="config-row">
|
|
423
|
+
<span class="config-label">Allow Review</span>
|
|
424
|
+
<div class="config-toggle assessment-config-toggle ${settings.allowReview !== false ? 'on' : ''}" data-field="allowReview"></div>
|
|
425
|
+
</div>
|
|
426
|
+
<div class="config-row">
|
|
427
|
+
<span class="config-label">Randomize Questions</span>
|
|
428
|
+
<div class="config-toggle assessment-config-toggle ${settings.randomizeQuestions ? 'on' : ''}" data-field="randomizeQuestions"></div>
|
|
429
|
+
</div>
|
|
430
|
+
<div class="config-row">
|
|
431
|
+
<span class="config-label">Randomize on Retake</span>
|
|
432
|
+
<div class="config-toggle assessment-config-toggle ${settings.randomizeOnRetake !== false ? 'on' : ''}" data-field="randomizeOnRetake"></div>
|
|
433
|
+
</div>
|
|
434
|
+
<div class="config-row">
|
|
435
|
+
<span class="config-label">Show Progress</span>
|
|
436
|
+
<div class="config-toggle assessment-config-toggle ${settings.showProgress !== false ? 'on' : ''}" data-field="showProgress"></div>
|
|
437
|
+
</div>
|
|
438
|
+
<div class="config-row">
|
|
439
|
+
<span class="config-label">Attempts Before Remedial</span>
|
|
440
|
+
<input type="number" class="config-input assessment-config-input assessment-config-attempts-input" data-field="attemptsBeforeRemedial" value="${settings.attemptsBeforeRemedial ?? ''}" min="1" placeholder="unlimited">
|
|
441
|
+
</div>
|
|
442
|
+
<div class="config-row">
|
|
443
|
+
<span class="config-label">Attempts Before Restart</span>
|
|
444
|
+
<input type="number" class="config-input assessment-config-input assessment-config-attempts-input" data-field="attemptsBeforeRestart" value="${settings.attemptsBeforeRestart ?? ''}" min="1" placeholder="unlimited">
|
|
445
|
+
</div>
|
|
446
|
+
<div class="config-row">
|
|
447
|
+
<span class="config-label">Remedial Slides</span>
|
|
448
|
+
<input type="text" class="config-input assessment-config-input assessment-config-remedial-input" data-field="remedialSlideIds" data-type="array" value="${settings.remedialSlideIds ? settings.remedialSlideIds.join(', ') : ''}" placeholder="slide-1, slide-2">
|
|
449
|
+
</div>
|
|
450
|
+
</div>`;
|
|
451
|
+
|
|
452
|
+
// Questions List - reuse interaction card components
|
|
453
|
+
if (questions.length > 0) {
|
|
454
|
+
html += `
|
|
455
|
+
<section class="interaction-group assessment-interactions-section">
|
|
456
|
+
<div class="interaction-group-header">
|
|
457
|
+
<div class="interaction-group-title-wrap">
|
|
458
|
+
${renderPanelIcon('list', 'group-icon')}
|
|
459
|
+
<h4 class="interaction-group-title">Questions</h4>
|
|
460
|
+
<span class="interaction-group-count">${questions.length}</span>
|
|
461
|
+
</div>
|
|
462
|
+
</div>
|
|
463
|
+
<div class="interactions-list assessment-interactions-list">
|
|
464
|
+
`;
|
|
465
|
+
|
|
466
|
+
for (const q of questions) {
|
|
467
|
+
// Preserve full question payload so schema editors can bind correctly.
|
|
468
|
+
const interaction = {
|
|
469
|
+
...q,
|
|
470
|
+
schema: q.schema || interactionSchemas[q.type] || null,
|
|
471
|
+
slideId: assessment.id,
|
|
472
|
+
};
|
|
473
|
+
html += renderInteractionCard(interaction, q.weight);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
html += '</div></section>';
|
|
477
|
+
} else {
|
|
478
|
+
html += '<div class="interactions-empty">No questions defined</div>';
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
html += '</div>'; // End assessment-info
|
|
482
|
+
return html;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Shared helper for rendering a single interaction/question card
|
|
486
|
+
function renderInteractionCard(interaction, weight = null) {
|
|
487
|
+
const isEditing = editingInteractionId === interaction.id;
|
|
488
|
+
|
|
489
|
+
let html = `<div class="interaction-card${isEditing ? ' editing' : ''}" data-interaction-id="${interaction.id}" data-slide-id="${interaction.slideId}">
|
|
490
|
+
<div class="interaction-header">
|
|
491
|
+
<div class="interaction-header-left">
|
|
492
|
+
<span class="interaction-type">${interaction.type}</span>
|
|
493
|
+
<span class="interaction-id">${interaction.id}</span>
|
|
494
|
+
${weight !== null ? `<span class="interaction-weight">Weight: ${weight}</span>` : ''}
|
|
495
|
+
</div>
|
|
496
|
+
<div class="interaction-header-right">
|
|
497
|
+
<button class="interaction-edit-btn icon-btn" data-id="${interaction.id}" title="${isEditing ? 'Cancel edit' : 'Edit interaction'}">${isEditing ? renderPanelIcon('close', 'action-icon') : renderPanelIcon('edit', 'action-icon')}</button>
|
|
498
|
+
<button class="interaction-nav-btn icon-btn" data-slide="${interaction.slideId}" title="Go to slide">${renderPanelIcon('navigate', 'action-icon')}</button>
|
|
499
|
+
</div>
|
|
500
|
+
</div>`;
|
|
501
|
+
|
|
502
|
+
if (isEditing) {
|
|
503
|
+
const schema = interaction.schema;
|
|
504
|
+
if (!schema) {
|
|
505
|
+
console.warn(
|
|
506
|
+
'[InteractionsPanel] Schema not found for interaction type "' + interaction.type + '". ' +
|
|
507
|
+
'Falling back to legacy editor for interaction "' + interaction.id + '" on slide "' + interaction.slideId + '".'
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
html += renderEditForm(interaction, schema || null);
|
|
511
|
+
} else {
|
|
512
|
+
if (interaction.label) {
|
|
513
|
+
html += `<div class="interaction-label">${escapeHtml(interaction.label)}</div>`;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (interaction.prompt) {
|
|
517
|
+
html += `<div class="interaction-prompt">${escapeHtml(interaction.prompt)}</div>`;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (interaction.correctAnswer !== undefined && interaction.correctAnswer !== null) {
|
|
521
|
+
html += `<div class="interaction-detail">
|
|
522
|
+
<span class="detail-label">Correct Answer:</span>
|
|
523
|
+
<span class="detail-value">${escapeHtml(String(interaction.correctAnswer))}</span>
|
|
524
|
+
</div>`;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (interaction.choices && interaction.choices.length > 0) {
|
|
528
|
+
html += '<div class="interaction-choices">';
|
|
529
|
+
html += '<span class="detail-label">Choices:</span><ul>';
|
|
530
|
+
for (const c of interaction.choices) {
|
|
531
|
+
const marker = c.correct ? ' ✓' : '';
|
|
532
|
+
html += `<li>${escapeHtml(c.value)}: ${escapeHtml(c.text)}${marker}</li>`;
|
|
533
|
+
}
|
|
534
|
+
html += '</ul></div>';
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (interaction.pairs && interaction.pairs.length > 0) {
|
|
538
|
+
html += '<div class="interaction-pairs">';
|
|
539
|
+
html += '<span class="detail-label">Pairs:</span><ul>';
|
|
540
|
+
for (const p of interaction.pairs) {
|
|
541
|
+
html += `<li>${escapeHtml(p.text)} → ${escapeHtml(p.match)}</li>`;
|
|
542
|
+
}
|
|
543
|
+
html += '</ul></div>';
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
html += '</div>';
|
|
548
|
+
return html;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function onLocationChange() {
|
|
552
|
+
// Re-render if on "This Slide" tab
|
|
553
|
+
if (currentTab === 'slide' && (interactionsData || assessmentsData)) {
|
|
554
|
+
render();
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
return {
|
|
559
|
+
loadInteractions,
|
|
560
|
+
render,
|
|
561
|
+
setTab,
|
|
562
|
+
onLocationChange,
|
|
563
|
+
get data() { return interactionsData; }
|
|
564
|
+
};
|
|
565
|
+
}
|