/**
* 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: `
IEC 63339 / SMIM
{{ t.ontoTitle }}
IEC 63339
ποΈ {{ t.viewTitle }}
π¨ {{ t.engineTitle }}
T = {{ state.currentTime }}
β
{{ currentSubtitleText }}
{{ formatTime(audioCurrentTime) }} / {{ formatTime(audioDuration) }}
`
};
createApp(App).mount('#app');