/** * APPLICATION ORCHESTRATOR (Vue 3) * -------------------------------- * Binds the single reactive state to the DOM, handles user inputs, and routes * data between the Event Log, Domain Actions, Ontologies (SMIM), Storage (SMS), * Viewpoints (URMSM), and Renderers. */ import { createApp, computed, watch, onMounted, ref, reactive, nextTick } from 'vue'; import { state, advanceTime, loadState, setPerspective, nextSequence } from './state.js'; import { i18n } from './i18n.js'; import { createRegisterPartEvent, createAssembleEvent, createReportDefectEvent, createRevokeDefectEvent, createDismantleEvent } from './smrm/actions.js'; import { ENTITY_CLASSES, RELATION_TYPES, INTERNAL_PROPERTY_KEYS, inferEntityClass } from './smrm/definitions.js'; import * as bfoAdapter from './smim/bfo.js'; import * as isoAdapter from './smim/iso15926.js'; import * as naiveAdapter from './smim/naive.js'; import { initVisNetwork, updateVisNetwork } from './renderers/vis-adapter.js'; import { updateMermaidNetwork } from './renderers/mermaid-adapter.js'; import { generateBomTrees } from './urmsm/bom-view.js'; import { reduceGraphForSMRM } from './urmsm/smrm-view.js'; import { generateLpgNodeStorage, generateLpgStorage, generateLpgQuery, evaluateLiveQuery } from './sms/lpg-generator.js'; import { generateRdfStorage, generateRdfQuery } from './sms/rdf-generator.js'; import { generatePhysicalGraph } from './sms/sms-graph-generator.js'; import { vanishingCrackScenario, onTimeUpdate as dispatchScenarioTimeline, resetTimeline } from './scenarios/scenario-vanishing-crack.js'; /** * RECURSIVE BOM TREE NODE COMPONENT * --------------------------------- * Renders hierarchical Bill of Materials nodes with entity class badges * and surviving defect indicators at time T. */ const BomNode = { name: 'BomNode', props: { node: { type: Object, required: true } }, template: `
  • {{ node.id }} {{ node.entityClass }} ⚠️ {{ def }}
  • ` }; const App = { components: { BomNode }, setup() { // DOM References for renderers & file operations const visContainer = ref(null); const mermaidContainer = ref(null); const fileInput = ref(null); // Universal Engineer Business UI inputs const bizRegType = ref('Part'); const bizRegId = ref(''); const bizInstSource = ref(''); const bizInstTarget = ref(''); const bizDefTarget = ref(''); const bizDefType = ref(''); const bizDefVal = ref(''); const bizRevDefect = ref(''); const bizDismPart = ref(''); const bizStorePart = ref(''); const bizStoreTarget = ref(''); // Scenario Player State const scenarioAudio = ref(null); const isPlaying = ref(false); const audioCurrentTime = ref(0); const audioDuration = ref(0); const showSubtitles = ref(true); const currentSubtitleText = ref(''); // Layout State (Flex percentages for resizers) const layout = reactive({ leftWidth: 64, bomHeight: 24, storageHeight: 42 }); // Active dictionary based on state.language const t = computed(() => i18n[state.language] || i18n.en); // Reconstruct base registered entities up to state.currentTime const baseNodes = computed(() => { const nodeMap = new Map(); state.eventLog.forEach(event => { if (event.valid_from !== undefined && event.valid_from > state.currentTime) return; if (event.type === 'REGISTER_PART' && event.partId) { const cls = inferEntityClass(event.partId, event.entityClass); nodeMap.set(event.partId, { id: event.partId, label: event.partId, type: cls, entityClass: cls }); } else if (event.type === 'ASSEMBLE') { if (event.parentId && !nodeMap.has(event.parentId)) { const pCls = inferEntityClass(event.parentId); nodeMap.set(event.parentId, { id: event.parentId, label: event.parentId, type: pCls, entityClass: pCls }); } if (event.childId && !nodeMap.has(event.childId)) { const cCls = inferEntityClass(event.childId); nodeMap.set(event.childId, { id: event.childId, label: event.childId, type: cCls, entityClass: cCls }); } } }); return Array.from(nodeMap.values()); }); const availableNodes = computed(() => baseNodes.value.map(n => n.id)); const activeParts = computed(() => baseNodes.value.filter(n => n.entityClass !== 'Slot' && n.entityClass !== 'Warehouse').map(n => n.id) ); const activeWarehouses = computed(() => baseNodes.value.filter(n => n.entityClass === 'Warehouse').map(n => n.id) ); const bizInstallSources = computed(() => baseNodes.value.filter(n => n.entityClass === 'Part').map(n => n.id) ); const bizInstallTargets = computed(() => baseNodes.value.filter(n => n.entityClass === 'Vehicle' || n.entityClass === 'Part').map(n => n.id) ); /** * Central Event Dispatcher (Pure Event Sourcing) * Appends immutable business facts to the event log. */ function dispatch(event) { if (!event) return; state.eventLog.push(event); } // Core Transformation Pipeline: Event Log -> SMIM Canonical Semantic Graph const semanticGraph = computed(() => { const opts = { ontology: state.ontology, showHistory: state.showHistory }; if (state.ontology === 'BFO') { return bfoAdapter.generateSemanticGraph(state.eventLog, baseNodes.value, state.currentTime, opts); } if (state.ontology === 'ISO15926') { return isoAdapter.generateSemanticGraph(state.eventLog, baseNodes.value, state.currentTime, opts); } return naiveAdapter.generateSemanticGraph(state.eventLog, baseNodes.value, state.currentTime, opts); }); // Engineer SMRM reduced graph (used for BOM, SMRM view, and installed component discovery) const smrmReducedGraph = computed(() => reduceGraphForSMRM(semanticGraph.value)); const installedComponents = computed(() => { const comps = new Set(); smrmReducedGraph.value.edges.forEach(e => { if (e.properties?.isCurrent === false) return; if (e.label === 'has_part') { comps.add(e.to); } else if (e.label === 'mounted_in' || e.label === 'stored_at') { comps.add(e.from); } }); return Array.from(comps).filter(id => !id.startsWith('Slot_')); }); // Active Defects List (respecting REVOKE_DEFECT events) const activeDefectsList = computed(() => { const activeMap = new Map(); state.eventLog.forEach((e, index) => { if (e.valid_from !== undefined && e.valid_from > state.currentTime) return; if (e.type === 'REPORT_DEFECT') { if (e.valid_to !== null && e.valid_to !== undefined && e.valid_to <= state.currentTime) return; const key = `${e.targetId}::${e.defectName}`; activeMap.set(key, { id: e.id || `def_${index}_${e.targetId}_${e.defectName}`, logIndex: index, targetId: e.targetId, defectName: e.defectName, defectValue: e.defectValue, label: `${e.targetId} β€’ ${e.defectName}=${e.defectValue}` }); } else if (e.type === 'REVOKE_DEFECT') { const key = `${e.targetId}::${e.defectName}`; activeMap.delete(key); } }); return Array.from(activeMap.values()); }); // Keep select dropdown defaults synchronised with available options watch(bizInstallSources, (sources) => { if (!sources.includes(bizInstSource.value)) bizInstSource.value = sources[0] || ''; }, { immediate: true }); watch(bizInstallTargets, (targets) => { if (!targets.includes(bizInstTarget.value)) bizInstTarget.value = targets[0] || ''; }, { immediate: true }); watch(activeParts, (parts) => { if (!parts.includes(bizStorePart.value)) bizStorePart.value = parts[0] || ''; }, { immediate: true }); watch(activeWarehouses, (warehouses) => { if (!warehouses.includes(bizStoreTarget.value)) bizStoreTarget.value = warehouses[0] || ''; }, { immediate: true }); watch(installedComponents, (comps) => { if (!comps.includes(bizDismPart.value)) bizDismPart.value = comps[0] || ''; }, { immediate: true }); watch(activeDefectsList, (defects) => { if (!defects.some(d => d.id === bizRevDefect.value)) bizRevDefect.value = defects[0]?.id || ''; }, { immediate: true }); watch(availableNodes, (nodes) => { if (!nodes.includes(bizDefTarget.value)) bizDefTarget.value = nodes[0] || ''; }, { immediate: true }); // Derived Views for Sidebar (URMSM & SMS) const bomTrees = computed(() => generateBomTrees(semanticGraph.value)); const lpgNodeRecords = computed(() => { if (state.storage === 'LPG') { return generateLpgNodeStorage(semanticGraph.value); } return []; }); const lpgRecords = computed(() => { if (state.storage === 'LPG') { return generateLpgStorage(semanticGraph.value); } return []; }); const rdfTtl = computed(() => { if (state.storage === 'RDF') { return generateRdfStorage(semanticGraph.value); } return ''; }); function formatProperties(props) { if (!props) return ''; const entries = Object.entries(props).filter( ([key, val]) => !INTERNAL_PROPERTY_KEYS.includes(key) || key === 'validFrom' || key === 'validTo' || key === 'atTime' ? (key !== 'isCurrent' && val !== undefined) : false ); if (entries.length === 0) return ''; return entries.map(([k, v]) => { const valStr = v === null ? 'null' : (typeof v === 'number' ? `T${v}` : `'${v}'`); return `${k}: ${valStr}`; }).join(', '); } const queryCode = computed(() => { if (state.storage === 'LPG') { return generateLpgQuery(semanticGraph.value, state.currentTime, state.ontology); } return generateRdfQuery(semanticGraph.value, state.currentTime, state.ontology); }); const liveQueryResult = computed(() => evaluateLiveQuery(semanticGraph.value, state.currentTime, state.ontology) ); // Route active graph to canvas based on the 4 unified Perspectives const activeGraph = computed(() => { if (state.perspective === 'SMS_LPG') { return generatePhysicalGraph(semanticGraph.value, 'LPG'); } if (state.perspective === 'SMS_RDF') { return generatePhysicalGraph(semanticGraph.value, 'RDF'); } if (state.perspective === 'SMRM') { return smrmReducedGraph.value; } return semanticGraph.value; }); // Reactive Graph Caption (truthful to the active perspective) const graphCaption = computed(() => { let ontoText = 'BFO (3D)'; if (state.ontology === 'ISO15926') ontoText = 'ISO 15926 (4D)'; if (state.ontology === 'NAIVE_NODE') ontoText = t.value.ontoNaiveNode; if (state.ontology === 'NAIVE_EDGE') ontoText = t.value.ontoNaiveEdge; let viewText = t.value.viewLogicalCaption; if (state.perspective === 'SMRM') viewText = t.value.viewSmrmCaption; if (state.perspective === 'SMS_LPG') viewText = t.value.viewPhysicalLpgCaption; if (state.perspective === 'SMS_RDF') viewText = t.value.viewPhysicalRdfCaption; return `${ontoText} | ${viewText}`; }); // Contextual Dynamic Legend matching the rendered canvas const graphLegendItems = computed(() => { const nodes = []; const edges = []; const dict = t.value.legend; // In Engineer (SMRM) Perspective or Naive ontologies, domain entities are shown if (state.perspective === 'SMRM' || state.ontology === 'NAIVE_NODE' || state.ontology === 'NAIVE_EDGE') { nodes.push({ id: 'naiveVehicle', label: dict.naiveVehicle, swatch: 'swatch-box-vehicle' }); if (state.perspective !== 'SMRM') { nodes.push({ id: 'naiveSlot', label: dict.naiveSlot, swatch: 'swatch-box-slot' }); } nodes.push({ id: 'naivePart', label: dict.naivePart, swatch: 'swatch-box-part' }); nodes.push({ id: 'naiveDefect', label: dict.naiveDefect, swatch: 'swatch-diamond-defect' }); nodes.push({ id: 'naiveWarehouse', label: dict.naiveWarehouse, swatch: 'swatch-box-warehouse' }); if (state.perspective === 'SMS_RDF') { nodes.push({ id: 'rdfStmt', label: dict.rdfStmt, swatch: 'swatch-diamond' }); nodes.push({ id: 'rdfLit', label: dict.rdfLit, swatch: 'swatch-literal' }); edges.push({ id: 'rdfPred', label: dict.rdfPred, swatch: 'swatch-line-past' }); } else { if (state.perspective !== 'SMRM') { edges.push({ id: 'relHasSlot', label: dict.relHasSlot, swatch: 'swatch-line-slot' }); } edges.push({ id: 'relMountedIn', label: dict.relMountedIn, swatch: 'swatch-line-mount' }); edges.push({ id: 'relStoredAt', label: dict.relStoredAt, swatch: 'swatch-line-store' }); edges.push({ id: 'relHasDefect', label: dict.relHasDefect, swatch: 'swatch-line-defect' }); } } else if (state.ontology === 'BFO') { nodes.push({ id: 'bfoSys', label: dict.bfoSys, swatch: 'swatch-box-sys' }); nodes.push({ id: 'bfoElem', label: dict.bfoElem, swatch: 'swatch-box-elem' }); nodes.push({ id: 'bfoQuality', label: dict.bfoQuality, swatch: 'swatch-diamond' }); if (state.perspective === 'SMS_RDF') { nodes.push({ id: 'rdfStmt', label: dict.rdfStmt, swatch: 'swatch-diamond' }); nodes.push({ id: 'rdfLit', label: dict.rdfLit, swatch: 'swatch-literal' }); edges.push({ id: 'rdfPred', label: dict.rdfPred, swatch: 'swatch-line-past' }); } else { edges.push({ id: 'bfoActive', label: dict.bfoActiveEdge, swatch: 'swatch-line-active' }); edges.push({ id: 'bfoInheres', label: dict.bfoInheres, swatch: 'swatch-line-transition' }); if (state.showHistory) { edges.push({ id: 'bfoPast', label: dict.bfoPastEdge, swatch: 'swatch-line-past' }); } } } else { // ISO 15926 (4D) in SMIM / SMS_LPG / SMS_RDF nodes.push({ id: 'iso4d', label: dict.iso4d, swatch: 'swatch-box-4d' }); nodes.push({ id: 'isoState', label: dict.isoState, swatch: 'swatch-box-state' }); nodes.push({ id: 'isoDefect', label: dict.isoDefect, swatch: 'swatch-diamond-defect' }); if (state.perspective === 'SMS_RDF') { nodes.push({ id: 'rdfLit', label: dict.rdfLit, swatch: 'swatch-literal' }); } edges.push({ id: 'isoPart', label: dict.isoPartRel, swatch: 'swatch-line-active' }); edges.push({ id: 'isoSlice', label: dict.isoSliceRel, swatch: 'swatch-line-past' }); edges.push({ id: 'isoDefectRel', label: dict.isoDefectRel, swatch: 'swatch-line-defect' }); if (state.perspective === 'SMS_LPG') { edges.push({ id: 'isoTrans', label: dict.isoTransitionRel, swatch: 'swatch-line-transition' }); } } return { nodes, edges }; }); // Re-render canvas when activeGraph or engine changes watch([activeGraph, () => state.graphEngine], () => { renderGraph(); }, { deep: true }); function renderGraph() { if (state.currentView === 'MENU') return; const graphToRender = activeGraph.value; if (state.graphEngine === 'VIS') { const isComplex = state.ontology === 'ISO15926' || state.perspective.startsWith('SMS_'); updateVisNetwork(graphToRender, isComplex); } else if (mermaidContainer.value) { updateMermaidNetwork(graphToRender, mermaidContainer.value); } } function seedInitialSandboxData() { state.eventLog.splice(0, state.eventLog.length); state.currentTime = 0; state.edgeSequence = 1; dispatch(createRegisterPartEvent('System_A', 0, 'Vehicle', nextSequence())); dispatch(createRegisterPartEvent('SubAssembly_1', 0, 'Part', nextSequence())); dispatch(createAssembleEvent('System_A', 'SubAssembly_1', 'mounted_in', 0, nextSequence())); dispatch(createRegisterPartEvent('Part_A', 0, 'Part', nextSequence())); dispatch(createAssembleEvent('System_A', 'Part_A', 'mounted_in', 0, nextSequence())); dispatch(createRegisterPartEvent('Sensor_X', 0, 'Part', nextSequence())); dispatch(createAssembleEvent('SubAssembly_1', 'Sensor_X', 'mounted_in', 0, nextSequence())); dispatch(createRegisterPartEvent('Warehouse_1', 0, 'Warehouse', nextSequence())); } // Watch for view transitions (MENU vs SANDBOX vs SCENARIO) watch(() => state.currentView, async (newView) => { if (newView !== 'SCENARIO') { stopAudio(false); } if (newView === 'SCENARIO') { state.userRole = 'UNI_ENGINEER'; state.eventLog.splice(0, state.eventLog.length); resetTimeline(0); state.currentTime = 0; state.ontology = 'NAIVE_NODE'; setPerspective('SMIM'); state.graphEngine = 'MERMAID'; } else if (newView === 'SANDBOX' && state.eventLog.length === 0) { seedInitialSandboxData(); } if (newView !== 'MENU') { await nextTick(); if (visContainer.value) { initVisNetwork(visContainer.value); } renderGraph(); } }); onMounted(() => { seedInitialSandboxData(); if (state.currentView !== 'MENU' && visContainer.value) { initVisNetwork(visContainer.value); renderGraph(); } }); function goHome() { stopAudio(false); state.currentView = 'MENU'; state.eventLog.splice(0, state.eventLog.length); state.currentTime = 0; state.edgeSequence = 1; } function playAudio() { if (scenarioAudio.value) { scenarioAudio.value.play().catch(err => { console.warn('Audio playback was prevented or failed:', err); }); } } function pauseAudio() { if (scenarioAudio.value) { scenarioAudio.value.pause(); } } function togglePlayAudio() { if (isPlaying.value) { pauseAudio(); } else { playAudio(); } } function stopAudio(resetScenarioGraph = true) { if (scenarioAudio.value) { scenarioAudio.value.pause(); scenarioAudio.value.currentTime = 0; } isPlaying.value = false; audioCurrentTime.value = 0; currentSubtitleText.value = ''; if (resetScenarioGraph) { state.eventLog.splice(0, state.eventLog.length); resetTimeline(0); state.currentTime = 0; state.ontology = 'NAIVE_NODE'; setPerspective('SMIM'); state.graphEngine = 'MERMAID'; nextTick(() => { renderGraph(); }); } } function onTimeUpdate() { if (scenarioAudio.value) { audioCurrentTime.value = scenarioAudio.value.currentTime; } const currentTime = audioCurrentTime.value; const activeSubtitle = vanishingCrackScenario.subtitles.find( sub => currentTime >= sub.start && currentTime <= sub.end ); currentSubtitleText.value = activeSubtitle ? activeSubtitle.text : ''; dispatchScenarioTimeline(currentTime, state, renderGraph); } function onLoadedMetadata() { if (scenarioAudio.value) { audioDuration.value = scenarioAudio.value.duration || 0; } } function onAudioEnded() { isPlaying.value = false; currentSubtitleText.value = ''; } function onSeek(event) { const targetTime = parseFloat(event.target.value); if (scenarioAudio.value) { scenarioAudio.value.currentTime = targetTime; } audioCurrentTime.value = targetTime; state.eventLog.splice(0, state.eventLog.length); resetTimeline(0); state.currentTime = 0; state.ontology = 'NAIVE_NODE'; setPerspective('SMIM'); state.graphEngine = 'MERMAID'; onTimeUpdate(); } function formatTime(seconds) { if (!seconds || isNaN(seconds) || seconds < 0) return '00:00'; const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; } // Engineer Domain Actions function handleBizRegister() { if (!bizRegId.value) return; const event = createRegisterPartEvent( bizRegId.value, state.currentTime, bizRegType.value, nextSequence() ); if (event) { dispatch(event); bizRegId.value = ''; } } function handleBizInstall() { if (!bizInstSource.value || !bizInstTarget.value) return; if (bizInstSource.value === bizInstTarget.value) return; const event = createAssembleEvent( bizInstTarget.value, bizInstSource.value, 'mounted_in', state.currentTime, nextSequence() ); if (event) dispatch(event); } function handleBizReportDefect() { if (!bizDefTarget.value || !bizDefType.value) return; const event = createReportDefectEvent( bizDefTarget.value, bizDefType.value, bizDefVal.value || 'present', state.currentTime, nextSequence() ); if (event) { dispatch(event); bizDefType.value = ''; bizDefVal.value = ''; } } function handleBizRevokeDefect() { if (!bizRevDefect.value) return; const item = activeDefectsList.value.find(d => d.id === bizRevDefect.value); if (item) { const event = createRevokeDefectEvent( item.targetId, item.defectName, state.currentTime, item.id, nextSequence() ); if (event) { dispatch(event); bizRevDefect.value = ''; } } } function handleBizDismantle() { if (!bizDismPart.value) return; const event = createDismantleEvent( bizDismPart.value, state.currentTime, nextSequence() ); if (event) { dispatch(event); bizDismPart.value = ''; } } function handleBizStore() { if (!bizStorePart.value || !bizStoreTarget.value) return; const event = createAssembleEvent( bizStoreTarget.value, bizStorePart.value, 'stored_at', state.currentTime, nextSequence() ); if (event) dispatch(event); } // Resizer Handlers with active .dragging visual feedback function startResizeH(e) { const resizerEl = e.currentTarget; resizerEl?.classList.add('dragging'); const startX = e.clientX; const startWidth = layout.leftWidth; const containerWidth = document.querySelector('.main-container').getBoundingClientRect().width; const onMouseMove = (evt) => { const newWidth = startWidth + ((evt.clientX - startX) / containerWidth) * 100; if (newWidth > 25 && newWidth < 78) layout.leftWidth = newWidth; }; const onMouseUp = () => { resizerEl?.classList.remove('dragging'); document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); document.body.classList.remove('no-select'); }; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); document.body.classList.add('no-select'); } function startResizeV(e, property, containerSelector) { const resizerEl = e.currentTarget; resizerEl?.classList.add('dragging'); const startY = e.clientY; const startHeight = layout[property]; const containerHeight = document.querySelector(containerSelector).getBoundingClientRect().height; const onMouseMove = (evt) => { const newHeight = startHeight + ((evt.clientY - startY) / containerHeight) * 100; if (newHeight > 12 && newHeight < 75) layout[property] = newHeight; }; const onMouseUp = () => { resizerEl?.classList.remove('dragging'); document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); document.body.classList.remove('no-select'); }; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); document.body.classList.add('no-select'); } // Full JSON State Export & Import function saveJson() { const exportData = { version: '2.0', savedAt: new Date().toISOString(), userRole: state.userRole, ontology: state.ontology, perspective: state.perspective, storage: state.storage, graphEngine: state.graphEngine, language: state.language, showHistory: state.showHistory, currentTime: state.currentTime, edgeSequence: state.edgeSequence, eventLog: JSON.parse(JSON.stringify(state.eventLog)) }; const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `workbench-state-t${state.currentTime}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } function triggerLoadJson() { if (fileInput.value) { fileInput.value.value = ''; fileInput.value.click(); } } function onFileLoaded(e) { const file = e.target.files?.[0]; if (!file) return; const reader = new FileReader(); reader.onload = (event) => { try { const parsed = JSON.parse(event.target.result); if (Array.isArray(parsed)) { loadState({ eventLog: parsed }); } else if (parsed && typeof parsed === 'object') { loadState(parsed); } nextTick(() => { renderGraph(); }); } catch (err) { console.error('Failed to parse state JSON:', err); alert('Invalid JSON file format.'); } }; reader.readAsText(file); } return { state, t, layout, advanceTime, setPerspective, dispatch, fileInput, saveJson, triggerLoadJson, onFileLoaded, entityClasses: ENTITY_CLASSES, relationTypes: RELATION_TYPES, availableNodes, goHome, activeParts, activeWarehouses, bizRegType, bizRegId, handleBizRegister, bizInstSource, bizInstTarget, handleBizInstall, bizInstallSources, bizInstallTargets, bizDefTarget, bizDefType, bizDefVal, handleBizReportDefect, bizRevDefect, handleBizRevokeDefect, activeDefectsList, bizDismPart, handleBizDismantle, installedComponents, bizStorePart, bizStoreTarget, handleBizStore, bomTrees, lpgNodeRecords, lpgRecords, rdfTtl, formatProperties, queryCode, liveQueryResult, visContainer, mermaidContainer, graphCaption, graphLegendItems, startResizeH, startResizeV, scenarioAudio, isPlaying, audioCurrentTime, audioDuration, showSubtitles, currentSubtitleText, playAudio, pauseAudio, togglePlayAudio, stopAudio, onTimeUpdate, onLoadedMetadata, onAudioEnded, onSeek, formatTime }; }, template: `
    πŸ“Œ {{ graphCaption }}
    {{ t.legend.title }}
    {{ t.legend.nodes }}
    {{ item.label }}
    {{ t.legend.edges }}
    {{ item.label }}
    {{ currentSubtitleText }}
    {{ formatTime(audioCurrentTime) }} / {{ formatTime(audioDuration) }}
    ` }; createApp(App).mount('#app');