Skip to content

Commit 3430847

Browse files
authored
Merge pull request #271 from pathsim/feature/codegen-export
Add Send to Codegen button with compressed URL hash transfer
2 parents 51c97fa + 1b73718 commit 3430847

File tree

2 files changed

+64
-3
lines changed

2 files changed

+64
-3
lines changed

src/lib/components/icons/Icon.svelte

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,12 @@
427427
<line x1="12" y1="15" x2="12" y2="3"/>
428428
<line x1="4" y1="21" x2="20" y2="21"/>
429429
</svg>
430+
{:else if name === 'codegen'}
431+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
432+
<path d="M7 4H6a2 2 0 0 0-2 2v4a2 2 0 0 1-2 2 2 2 0 0 1 2 2v4a2 2 0 0 0 2 2h1"/>
433+
<path d="M17 4h1a2 2 0 0 1 2 2v4a2 2 0 0 0 2 2 2 2 0 0 0-2 2v4a2 2 0 0 1-2 2h-1"/>
434+
<text x="12" y="16" text-anchor="middle" fill="currentColor" stroke="none" font-size="12" font-weight="700" font-family="var(--font-mono), monospace">C</text>
435+
</svg>
430436
{:else if name === 'font-size-increase'}
431437
<svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor" stroke="none">
432438
<text x="2" y="18" font-size="16" font-weight="700" font-family="system-ui, sans-serif">A</text>

src/routes/+page.svelte

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
import { pyodideState, simulationState, initPyodide, stopSimulation, continueStreamingSimulation, stageMutations } from '$lib/pyodide/bridge';
4343
import { pendingMutationCount } from '$lib/pyodide/mutationQueue';
4444
import { initBackendFromUrl, autoDetectBackend } from '$lib/pyodide/backend';
45-
import { runGraphStreamingSimulation, validateGraphSimulation } from '$lib/pyodide/pathsimRunner';
45+
import { runGraphStreamingSimulation, validateGraphSimulation, exportToPython } from '$lib/pyodide/pathsimRunner';
4646
import { consoleStore } from '$lib/stores/console';
4747
import { newGraph, saveFile, saveAsFile, setupAutoSave, clearAutoSave, debouncedAutoSave, openImportDialog, importFromUrl, currentFileName } from '$lib/schema/fileOps';
4848
import { confirmationStore } from '$lib/stores/confirmation';
@@ -59,10 +59,10 @@
5959
let mousePosition = $state({ x: 0, y: 0 });
6060
6161
// Save feedback animation state
62-
let saveFlash = $state<'save' | 'save-as' | null>(null);
62+
let saveFlash = $state<'save' | 'save-as' | 'codegen' | null>(null);
6363
let saveFlashTimeout: ReturnType<typeof setTimeout> | undefined;
6464
65-
function flashSaveButton(which: 'save' | 'save-as') {
65+
function flashSaveButton(which: 'save' | 'save-as' | 'codegen') {
6666
clearTimeout(saveFlashTimeout);
6767
saveFlash = which;
6868
saveFlashTimeout = setTimeout(() => { saveFlash = null; }, 1500);
@@ -78,6 +78,58 @@
7878
if (success) flashSaveButton('save-as');
7979
}
8080
81+
// Codegen export — compress Python code into URL hash and open codegen
82+
const CODEGEN_URL = import.meta.env.VITE_CODEGEN_URL ?? 'https://code.pathsim.org/app';
83+
const CODEGEN_MAX_BYTES = 100_000; // 100 KB raw Python limit
84+
85+
async function compressAndEncode(text: string): Promise<string> {
86+
const data = new TextEncoder().encode(text);
87+
const cs = new CompressionStream('deflate-raw');
88+
const writer = cs.writable.getWriter();
89+
writer.write(data);
90+
writer.close();
91+
const compressed = new Uint8Array(await new Response(cs.readable).arrayBuffer());
92+
let binary = '';
93+
for (const b of compressed) binary += String.fromCharCode(b);
94+
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
95+
}
96+
97+
async function handleSendToCodegen() {
98+
const { nodes, connections } = graphStore.toJSON();
99+
100+
if (nodes.length === 0) {
101+
await confirmationStore.show({
102+
title: 'No Model',
103+
message: 'There are no blocks in the current graph. Add blocks to your simulation before exporting to Codegen.',
104+
confirmText: 'OK',
105+
cancelText: 'Cancel'
106+
});
107+
return;
108+
}
109+
110+
const settings = settingsStore.get();
111+
const codeContext = codeContextStore.getCode();
112+
const events = eventStore.toJSON();
113+
const pythonCode = exportToPython(nodes, connections, settings, codeContext, events);
114+
115+
const rawBytes = new TextEncoder().encode(pythonCode).length;
116+
if (rawBytes > CODEGEN_MAX_BYTES) {
117+
const sizeKB = Math.round(rawBytes / 1024);
118+
const openExport = await confirmationStore.show({
119+
title: 'Model Too Large',
120+
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.`,
121+
confirmText: 'Open Python Export',
122+
cancelText: 'Cancel'
123+
});
124+
if (openExport) exportDialogOpen = true;
125+
return;
126+
}
127+
128+
const encoded = await compressAndEncode(pythonCode);
129+
window.open(`${CODEGEN_URL}#code=${encoded}`, '_blank');
130+
flashSaveButton('codegen');
131+
}
132+
81133
// Panel visibility state
82134
let showProperties = $state(false);
83135
let showNodeLibrary = $state(false);
@@ -1049,6 +1101,9 @@
10491101
<button class="toolbar-btn" onclick={() => exportDialogOpen = true} use:tooltip={{ text: "Python Code", shortcut: "Ctrl+E" }} aria-label="View Python Code">
10501102
<Icon name="braces" size={16} />
10511103
</button>
1104+
<button class="toolbar-btn" onclick={handleSendToCodegen} use:tooltip={"Send to Codegen"} aria-label="Send to Codegen">
1105+
<Icon name={saveFlash === 'codegen' ? 'check' : 'codegen'} size={16} />
1106+
</button>
10521107
</div>
10531108

10541109
<!-- Help -->

0 commit comments

Comments
 (0)