Spaces:
Sleeping
Sleeping
File size: 4,042 Bytes
7596726 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | import { buildAnalysisBody } from './schedule/analysis-modal.mjs';
import { createAppShell } from './shell/app-shell.mjs';
import { createAppState } from './shell/app-state.mjs';
import { loadAppConfig } from './shell/config-loader.mjs';
import { renderDataTables } from './shell/data-panel.mjs';
import { createSolverController } from './shell/solver-controller.mjs';
import { createViewRegistry } from './views/registry.mjs';
// Browser entrypoint that wires together config loading, the shared UI shell,
// hospital-specific view renderers, and the retained-job controller.
export async function bootApp(root = globalThis) {
const document = root.document;
const sf = root.SF;
const appElement = document && document.getElementById('sf-app');
if (!document || !appElement) return null;
if (!sf) {
throw new Error('SolverForge UI must be loaded before bootApp()');
}
const { config, uiModel, backend, demoId } = await loadAppConfig(root);
const state = createAppState(uiModel.views[0].id);
const statusBar = sf.createStatusBar({ constraints: uiModel.constraints });
const shell = createAppShell({
root,
sf,
appElement,
config,
uiModel,
demoId,
statusBar,
activeTab: state.activeTab,
actions: {
onSolve: () => startSolve(),
onPause: () => controller.pause(),
onResume: () => controller.resume(),
onCancel: () => controller.cancel(),
onAnalyze: () => openAnalysis(),
onTabChange(tabId) {
state.activeTab = tabId;
},
},
});
const views = createViewRegistry();
const controller = createSolverController({
sf,
backend,
statusBar,
onPlan(plan) {
renderAll(plan);
},
onAnalysis() {},
onMeta() {},
onLifecycle(markers) {
shell.syncLifecycleMarkers(markers);
},
onError(error) {
root.console.error('Solver lifecycle failed:', error);
},
});
try {
const demoData = await backend.getDemoData(demoId);
renderAll(demoData);
} catch (error) {
root.console.error('Initial demo load failed:', error);
}
return {
backend,
controller,
shell,
state,
uiModel,
};
// Re-renders both schedule tabs and the raw data tables from the latest plan.
function renderAll(data) {
state.currentPlan = clonePlan(data);
renderViews(data);
renderDataTables({ sf, container: shell.dataRoot, uiModel, data });
}
// Dispatches each configured view to the renderer registered for its `kind`.
function renderViews(data) {
uiModel.views.forEach((view) => {
const container = shell.viewRoots[view.id];
const renderView = views[view.kind];
if (!container) return;
container.innerHTML = '';
if (!renderView) {
container.appendChild(sf.el('p', null, `No renderer is registered for ${view.kind}.`));
return;
}
renderView({ sf, container, data, view });
});
}
// Starts solving from the last rendered plan snapshot.
async function startSolve() {
const plan = await resolvePlanForSolve();
await controller.start(() => Promise.resolve(clonePlan(plan)));
}
// Lazily loads demo data if the user clicks Solve before the first fetch finishes.
async function resolvePlanForSolve() {
if (state.currentPlan) {
return state.currentPlan;
}
const demoData = await backend.getDemoData(demoId);
renderAll(demoData);
return state.currentPlan;
}
// Fetches exact retained-snapshot analysis and opens it in the shared modal.
async function openAnalysis() {
if (!controller.getJobId()) return Promise.resolve();
try {
const currentAnalysis = await controller.analyzeSnapshot();
shell.openAnalysis(buildAnalysisBody(document, currentAnalysis, uiModel.constraints));
} catch (error) {
root.console.error('Analysis failed:', error);
}
}
}
// Defensive deep clone so the UI never mutates the last backend payload in place.
function clonePlan(data) {
return JSON.parse(JSON.stringify(data));
}
|