|
42 | 42 | import { pyodideState, simulationState, initPyodide, stopSimulation, continueStreamingSimulation, stageMutations } from '$lib/pyodide/bridge'; |
43 | 43 | import { pendingMutationCount } from '$lib/pyodide/mutationQueue'; |
44 | 44 | import { initBackendFromUrl, autoDetectBackend } from '$lib/pyodide/backend'; |
45 | | - import { runGraphStreamingSimulation, validateGraphSimulation } from '$lib/pyodide/pathsimRunner'; |
| 45 | + import { runGraphStreamingSimulation, validateGraphSimulation, exportToPython } from '$lib/pyodide/pathsimRunner'; |
46 | 46 | import { consoleStore } from '$lib/stores/console'; |
47 | 47 | import { newGraph, saveFile, saveAsFile, setupAutoSave, clearAutoSave, debouncedAutoSave, openImportDialog, importFromUrl, currentFileName } from '$lib/schema/fileOps'; |
48 | 48 | import { confirmationStore } from '$lib/stores/confirmation'; |
|
59 | 59 | let mousePosition = $state({ x: 0, y: 0 }); |
60 | 60 |
|
61 | 61 | // Save feedback animation state |
62 | | - let saveFlash = $state<'save' | 'save-as' | null>(null); |
| 62 | + let saveFlash = $state<'save' | 'save-as' | 'codegen' | null>(null); |
63 | 63 | let saveFlashTimeout: ReturnType<typeof setTimeout> | undefined; |
64 | 64 |
|
65 | | - function flashSaveButton(which: 'save' | 'save-as') { |
| 65 | + function flashSaveButton(which: 'save' | 'save-as' | 'codegen') { |
66 | 66 | clearTimeout(saveFlashTimeout); |
67 | 67 | saveFlash = which; |
68 | 68 | saveFlashTimeout = setTimeout(() => { saveFlash = null; }, 1500); |
|
78 | 78 | if (success) flashSaveButton('save-as'); |
79 | 79 | } |
80 | 80 |
|
| 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 | +
|
81 | 133 | // Panel visibility state |
82 | 134 | let showProperties = $state(false); |
83 | 135 | let showNodeLibrary = $state(false); |
|
1049 | 1101 | <button class="toolbar-btn" onclick={() => exportDialogOpen = true} use:tooltip={{ text: "Python Code", shortcut: "Ctrl+E" }} aria-label="View Python Code"> |
1050 | 1102 | <Icon name="braces" size={16} /> |
1051 | 1103 | </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> |
1052 | 1107 | </div> |
1053 | 1108 |
|
1054 | 1109 | <!-- Help --> |
|
0 commit comments