|
44 | 44 | import { openNodeDialog } from '$lib/stores/nodeDialog'; |
45 | 45 | import { openEventDialog } from '$lib/stores/eventDialog'; |
46 | 46 | import type { MenuItemType } from '$lib/components/ContextMenu.svelte'; |
47 | | - import { pyodideState, simulationState, initPyodide, stopSimulation, continueStreamingSimulation, stageMutations } from '$lib/pyodide/bridge'; |
| 47 | + import { pyodideState, simulationState, initPyodide, stopSimulation, continueStreamingSimulation, stageMutations, resetSimulation } from '$lib/pyodide/bridge'; |
48 | 48 | import { pendingMutationCount } from '$lib/pyodide/mutationQueue'; |
49 | 49 | import { initBackendFromUrl, autoDetectBackend } from '$lib/pyodide/backend'; |
50 | 50 | import { runGraphStreamingSimulation, validateGraphSimulation, exportToPython } from '$lib/pyodide/pathsimRunner'; |
|
203 | 203 | const urlModelConfig = getUrlModelConfig(); |
204 | 204 | let showWelcomeModal = $state(!urlModelConfig); // Hide if loading from URL |
205 | 205 |
|
| 206 | + // Backend-ready promise (assigned in onMount). Component-scoped so client- |
| 207 | + // side example loading can gate its toolbox install on the running worker |
| 208 | + // instead of forcing a full reload + Pyodide reinit. |
| 209 | + let backendReady: Promise<unknown> | undefined = $state(undefined); |
| 210 | +
|
206 | 211 | // Track widths directly - initialized on first dual-panel open |
207 | 212 | let consolePanelWidth = $state<number | undefined>(undefined); |
208 | 213 | let plotPanelWidth = $state<number | undefined>(undefined); |
|
581 | 586 | // to `registryVersion` bumps, so any (missing) placeholders upgrade |
582 | 587 | // themselves as soon as their toolbox registers. |
583 | 588 | seedPreloadedToolboxes(); |
584 | | - const backendReady = (async () => { |
| 589 | + backendReady = (async () => { |
585 | 590 | try { |
586 | 591 | await autoDetectBackend(); |
587 | 592 | await initBackendFromUrl(); |
|
1231 | 1236 | } |
1232 | 1237 | } |
1233 | 1238 |
|
| 1239 | + // Load an example/model client-side — no page reload, so the running |
| 1240 | + // Pyodide worker is reused (no reinit). Reflects the model in the URL via |
| 1241 | + // replaceState, so deep-links (?model=) still work and the URL stays |
| 1242 | + // shareable. Used by the welcome modal's example cards. |
| 1243 | + async function loadExample(url: string): Promise<void> { |
| 1244 | + showWelcomeModal = false; |
| 1245 | + // Clear previous results / REPL state; the worker stays up. |
| 1246 | + await resetSimulation(); |
| 1247 | + const result = await importFromUrl(url, { |
| 1248 | + deferToolboxInstall: true, |
| 1249 | + backendReady: backendReady ?? Promise.resolve() |
| 1250 | + }); |
| 1251 | + if (result.success) { |
| 1252 | + try { |
| 1253 | + history.replaceState(history.state, '', `?model=${encodeURIComponent(url)}`); |
| 1254 | + } catch { |
| 1255 | + /* replaceState can throw in odd embedding contexts; non-fatal */ |
| 1256 | + } |
| 1257 | + setTimeout(() => triggerFitView(), 100); |
| 1258 | + } else if (result.error) { |
| 1259 | + consoleStore.error(`Failed to load example: ${url}`); |
| 1260 | + consoleStore.error(result.error); |
| 1261 | + showConsole = true; |
| 1262 | + } |
| 1263 | + } |
| 1264 | +
|
1234 | 1265 | // Track placement offset for stacking prevention |
1235 | 1266 | let placementOffset = 0; |
1236 | 1267 | let lastPlacementTime = 0; |
|
1851 | 1882 | <WelcomeModal |
1852 | 1883 | onNew={handleNew} |
1853 | 1884 | onClose={() => showWelcomeModal = false} |
| 1885 | + onLoadExample={loadExample} |
1854 | 1886 | /> |
1855 | 1887 | {/if} |
1856 | 1888 | </div> |
|
0 commit comments