Skip to content

Commit f6815ec

Browse files
authored
Merge pull request #304 from pathsim/feature/welcome-improvements
Welcome modal: client-side example loading + cleanups
2 parents 41b11ed + 8c5677a commit f6815ec

3 files changed

Lines changed: 49 additions & 11 deletions

File tree

src/lib/components/WelcomeModal.svelte

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@
1717
interface Props {
1818
onNew: () => void;
1919
onClose: () => void;
20+
onLoadExample: (url: string) => void;
2021
}
2122
22-
let { onNew, onClose }: Props = $props();
23+
let { onNew, onClose, onLoadExample }: Props = $props();
2324
2425
const examples: Example[] = [
2526
{ filename: 'feedback-system.json', basename: 'feedback-system', name: 'Feedback System', description: 'Linear feedback system with delayed step excitation' },
@@ -97,7 +98,7 @@
9798

9899
<div class="banner-content">
99100
<div class="version-info">
100-
PathView {PATHVIEW_VERSION} · {Object.entries(EXTRACTED_VERSIONS).map(([pkg, ver]) => `${pkg.replace('_', '-')} ${ver}`).join(' · ')}
101+
pathview {PATHVIEW_VERSION} · {Object.entries(EXTRACTED_VERSIONS).map(([pkg, ver]) => `${pkg.replace('_', '-')} ${ver}`).join(' · ')}
101102
</div>
102103

103104
<div class="header">
@@ -165,12 +166,11 @@
165166
<div class="examples-section">
166167
<div class="examples-grid">
167168
{#each examples as example}
168-
<a
169-
class="example-card"
170-
href="?model={base}/examples/{example.filename}"
171-
data-sveltekit-reload
172-
onclick={onClose}
173-
>
169+
<button
170+
type="button"
171+
class="example-card"
172+
onclick={() => onLoadExample(`${base}/examples/${example.filename}`)}
173+
>
174174
<div class="example-info">
175175
<div class="example-name">{example.name}</div>
176176
<div class="example-description">{example.description}</div>
@@ -184,7 +184,7 @@
184184
onerror={(e) => { (e.currentTarget as HTMLImageElement).style.display = 'none'; }}
185185
/>
186186
</div>
187-
</a>
187+
</button>
188188
{/each}
189189
</div>
190190
</div>

src/lib/pyodide/backend/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,12 @@ export const replState = {
9999
export async function init(): Promise<void> {
100100
const backend = getBackend();
101101

102+
// Idempotent: several callers invoke init() (auto-detect, toolbox installer,
103+
// first run, helper injection). The backend init itself is a no-op once
104+
// ready, but logging/callback setup ran every time, producing repeated
105+
// "Initializing Python REPL..." noise. Bail early when ready.
106+
if (backend.isReady()) return;
107+
102108
// Set up console output callbacks
103109
backend.onStdout((value) => consoleStore.output(value));
104110
backend.onStderr((value) => consoleStore.error(value));

src/routes/+page.svelte

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
import { openNodeDialog } from '$lib/stores/nodeDialog';
4545
import { openEventDialog } from '$lib/stores/eventDialog';
4646
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';
4848
import { pendingMutationCount } from '$lib/pyodide/mutationQueue';
4949
import { initBackendFromUrl, autoDetectBackend } from '$lib/pyodide/backend';
5050
import { runGraphStreamingSimulation, validateGraphSimulation, exportToPython } from '$lib/pyodide/pathsimRunner';
@@ -203,6 +203,11 @@
203203
const urlModelConfig = getUrlModelConfig();
204204
let showWelcomeModal = $state(!urlModelConfig); // Hide if loading from URL
205205
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+
206211
// Track widths directly - initialized on first dual-panel open
207212
let consolePanelWidth = $state<number | undefined>(undefined);
208213
let plotPanelWidth = $state<number | undefined>(undefined);
@@ -581,7 +586,7 @@
581586
// to `registryVersion` bumps, so any (missing) placeholders upgrade
582587
// themselves as soon as their toolbox registers.
583588
seedPreloadedToolboxes();
584-
const backendReady = (async () => {
589+
backendReady = (async () => {
585590
try {
586591
await autoDetectBackend();
587592
await initBackendFromUrl();
@@ -1231,6 +1236,32 @@
12311236
}
12321237
}
12331238
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+
12341265
// Track placement offset for stacking prevention
12351266
let placementOffset = 0;
12361267
let lastPlacementTime = 0;
@@ -1851,6 +1882,7 @@
18511882
<WelcomeModal
18521883
onNew={handleNew}
18531884
onClose={() => showWelcomeModal = false}
1885+
onLoadExample={loadExample}
18541886
/>
18551887
{/if}
18561888
</div>

0 commit comments

Comments
 (0)