diff --git a/src/lib/tours/scripts/simulation.ts b/src/lib/tours/scripts/simulation.ts index f093d48b..d2ea3a5b 100644 --- a/src/lib/tours/scripts/simulation.ts +++ b/src/lib/tours/scripts/simulation.ts @@ -180,9 +180,9 @@ export const simulationTour: TourScript = { openerPosition: { side: 'bottom', align: 'end' }, contentTitle: 'Python Export', contentBody: ` -

The exported script is self-contained — copy, save or send to the Codegen tool. Useful for:

+

The exported script is self-contained — copy, save or run it anywhere. Useful for:

diff --git a/src/lib/tours/scripts/start.ts b/src/lib/tours/scripts/start.ts index af73aaa1..b373edca 100644 --- a/src/lib/tours/scripts/start.ts +++ b/src/lib/tours/scripts/start.ts @@ -191,7 +191,7 @@ export const startTour: TourScript = { element: '[data-tour="toolbar-files"]', title: 'Files & Sharing', body: ` -

Save and load .pvm files. Buttons left to right: New, Open, Save, Save As, View Python Code, Send to Codegen.

+

Save and load .pvm files. Buttons left to right: New, Open, Save, Save As, View Python Code.

diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 5436e217..b10a989d 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -99,10 +99,10 @@ let mousePosition = $state({ x: 0, y: 0 }); // Save feedback animation state - let saveFlash = $state<'save' | 'save-as' | 'codegen' | null>(null); + let saveFlash = $state<'save' | 'save-as' | null>(null); let saveFlashTimeout: ReturnType | undefined; - function flashSaveButton(which: 'save' | 'save-as' | 'codegen') { + function flashSaveButton(which: 'save' | 'save-as') { clearTimeout(saveFlashTimeout); saveFlash = which; saveFlashTimeout = setTimeout(() => { saveFlash = null; }, 1500); @@ -118,58 +118,6 @@ if (success) flashSaveButton('save-as'); } - // Codegen export — compress Python code into URL hash and open codegen - const CODEGEN_URL = import.meta.env.VITE_CODEGEN_URL ?? 'https://code.pathsim.org/app'; - const CODEGEN_MAX_BYTES = 100_000; // 100 KB raw Python limit - - async function compressAndEncode(text: string): Promise { - const data = new TextEncoder().encode(text); - const cs = new CompressionStream('deflate-raw'); - const writer = cs.writable.getWriter(); - writer.write(data); - writer.close(); - const compressed = new Uint8Array(await new Response(cs.readable).arrayBuffer()); - let binary = ''; - for (const b of compressed) binary += String.fromCharCode(b); - return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); - } - - async function handleSendToCodegen() { - const { nodes, connections } = graphStore.toJSON(); - - if (nodes.length === 0) { - await confirmationStore.show({ - title: 'No Model', - message: 'There are no blocks in the current graph. Add blocks to your simulation before exporting to Codegen.', - confirmText: 'OK', - cancelText: 'Cancel' - }); - return; - } - - const settings = settingsStore.get(); - const codeContext = codeContextStore.getCode(); - const events = eventStore.toJSON(); - const pythonCode = exportToPython(nodes, connections, settings, codeContext, events); - - const rawBytes = new TextEncoder().encode(pythonCode).length; - if (rawBytes > CODEGEN_MAX_BYTES) { - const sizeKB = Math.round(rawBytes / 1024); - const openExport = await confirmationStore.show({ - title: 'Model Too Large', - message: `The generated Python code is ${sizeKB} KB, which exceeds the transfer limit. Use the Python Code export (Ctrl+E) to copy the code and paste it into Codegen manually.`, - confirmText: 'Open Python Export', - cancelText: 'Cancel' - }); - if (openExport) exportDialogOpen = true; - return; - } - - const encoded = await compressAndEncode(pythonCode); - window.open(`${CODEGEN_URL}?code=${encoded}`, '_blank'); - flashSaveButton('codegen'); - } - // Panel visibility state let showProperties = $state(false); let showNodeLibrary = $state(false); @@ -1319,9 +1267,70 @@ -
- - + +
+ + {#if recentFilesSupported && recentFilesMenuOpen} + + {/if} +
+ + + +{/snippet} + +{#snippet viewActions(btn: string)} + + +{/snippet} + +
+ + + + + @@ -1338,113 +1347,7 @@
triggerClearSelection()}> - -
- {#if simRunning} - - {:else} -
- -
- {/if} - - {#if hasRunSimulation && $pendingMutationCount > 0} - - {/if} -
- - -
- - -
- - {#if recentFilesSupported && recentFilesMenuOpen} - - {/if} -
- - - - -
+ {#if hiddenNodes.length > 0} @@ -1485,39 +1388,63 @@ {/if}
{/if} +
- -
+ + +
triggerClearSelection()}> + + +
+ {#if simRunning} + + {:else} + + {/if} + {#if hasRunSimulation && $pendingMutationCount > 0} + + {/if} -
-
- - -
triggerClearSelection()}> -
- -
- -
-
@@ -1916,6 +1829,49 @@ z-index: 0; } + /* Top nav bar — fixed chrome over the canvas. */ + .editor-nav { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 200; + display: flex; + align-items: center; + justify-content: space-between; + height: var(--header-height); + padding: 0 var(--space-md); + background: var(--surface-raised); + border-bottom: 1px solid var(--border); + } + .editor-nav .nav-side { display: flex; align-items: center; gap: var(--space-xs); } + /* Home button relies on .icon-btn for sizing/hover; small separator from file-ops. */ + .editor-nav .brand { margin-right: var(--space-xs); } + /* Push the top-anchored floating overlays below the fixed nav. */ + .app.has-nav .toolbar-container { top: calc(var(--space-md) + var(--header-height)); } + .app.has-nav .subsystem-breadcrumb { top: calc(var(--space-md) + var(--header-height)); } + .app.has-nav .logo-overlay { top: calc(var(--space-md) + var(--header-height)); } + + /* Logo overlay — stays over the canvas, below the fixed nav. */ + .logo-overlay { + position: fixed; + top: var(--space-md); + left: var(--space-md); + z-index: 100; + background: none; + border: none; + padding: 0; + cursor: pointer; + } + .logo-overlay img { + height: 44px; + width: auto; + transition: opacity var(--transition-fast); + } + .logo-overlay:hover img { + opacity: 0.8; + } + /* Toolbar */ .toolbar-container { position: fixed; @@ -2130,27 +2086,6 @@ } /* Logo overlay */ - .logo-overlay { - position: fixed; - top: var(--space-md); - left: var(--space-md); - z-index: 100; - background: none; - border: none; - padding: 0; - cursor: pointer; - } - - .logo-overlay img { - height: 44px; - width: auto; - transition: opacity var(--transition-fast); - } - - .logo-overlay:hover img { - opacity: 0.8; - } - /* Subsystem breadcrumb navigation */ .subsystem-breadcrumb { position: fixed;
OpenCtrl/Cmd+O
SaveCtrl/Cmd+S