@@ -5,22 +5,14 @@ import {
55 ResizableHandle ,
66 ResizablePanelGroup ,
77} from "@/client/components/Resizable" ;
8- import { useStore } from "@/client/store" ;
98import {
109 DropdownMenu ,
1110 DropdownMenuContent ,
1211 DropdownMenuItem ,
1312 DropdownMenuPortal ,
1413 DropdownMenuTrigger ,
1514} from "@/client/components/DropdownMenu" ;
16- import {
17- type FC ,
18- useCallback ,
19- useEffect ,
20- useMemo ,
21- useRef ,
22- useState ,
23- } from "react" ;
15+ import { type FC , useEffect , useMemo , useRef , useState } from "react" ;
2416import { useTheme } from "@/client/contexts/theme" ;
2517import { MoonIcon , ShareIcon , SunIcon , SunMoonIcon } from "lucide-react" ;
2618import { Button } from "@/client/components/Button" ;
@@ -31,7 +23,14 @@ import {
3123} from "@/client/components/Tooltip" ;
3224import { rpc } from "@/utils/rpc" ;
3325import { useLoaderData , type LoaderFunctionArgs } from "react-router" ;
34- import { initWasm , type WasmLoadState } from "@/utils/wasm" ;
26+ import {
27+ getDynamicParametersOutput ,
28+ initWasm ,
29+ type WasmLoadState ,
30+ } from "@/utils/wasm" ;
31+ import { defaultCode } from "@/client/snippets" ;
32+ import type { PreviewOutput } from "@/gen/types" ;
33+ import { useDebouncedValue } from "./hooks/debounce" ;
3534
3635/**
3736 * Load the shared code if present.
@@ -61,16 +60,35 @@ export const App = () => {
6160 }
6261 return "loading" ;
6362 } ) ;
64- const $setCode = useStore ( ( store ) => store . setCode ) ;
65- const code = useLoaderData < typeof loader > ( ) ;
63+ const loadedCode = useLoaderData < typeof loader > ( ) ;
64+ const [ code , setCode ] = useState ( loadedCode ?? defaultCode ) ;
65+ const [ debouncedCode , isDebouncing ] = useDebouncedValue ( code , 1000 ) ;
66+ const [ parameterValues , setParameterValues ] = useState <
67+ Record < string , string >
68+ > ( { } ) ;
69+ const [ output , setOutput ] = useState < PreviewOutput | null > ( null ) ;
6670
67- useEffect ( ( ) => {
68- if ( ! code ) {
69- return ;
70- }
71+ const onDownloadOutput = ( ) => {
72+ const blob = new Blob ( [ JSON . stringify ( output , null , 2 ) ] , {
73+ type : "application/json" ,
74+ } ) ;
75+
76+ const url = URL . createObjectURL ( blob ) ;
7177
72- $setCode ( code ) ;
73- } , [ code , $setCode ] ) ;
78+ const link = document . createElement ( "a" ) ;
79+ link . href = url ;
80+ link . download = "output.json" ;
81+ document . body . appendChild ( link ) ;
82+ link . click ( ) ;
83+ document . body . removeChild ( link ) ;
84+
85+ // Give the click event enough time to fire and then revoke the URL.
86+ // This method of doing it doesn't seem great but I'm not sure if there is a
87+ // better way.
88+ setTimeout ( ( ) => {
89+ URL . revokeObjectURL ( url ) ;
90+ } , 100 ) ;
91+ } ;
7492
7593 useEffect ( ( ) => {
7694 if ( ! window . go_preview ) {
@@ -84,6 +102,19 @@ export const App = () => {
84102 }
85103 } , [ ] ) ;
86104
105+ useEffect ( ( ) => {
106+ getDynamicParametersOutput ( debouncedCode , parameterValues )
107+ . catch ( ( e ) => {
108+ console . error ( e ) ;
109+ setWasmLoadingState ( "error" ) ;
110+
111+ return null ;
112+ } )
113+ . then ( ( output ) => {
114+ setOutput ( output ) ;
115+ } ) ;
116+ } , [ debouncedCode , parameterValues ] ) ;
117+
87118 return (
88119 < main className = "flex h-dvh w-screen flex-col items-center bg-surface-primary" >
89120 { /* NAV BAR */ }
@@ -96,7 +127,7 @@ export const App = () => {
96127 </ p >
97128 </ div >
98129
99- < ShareButton />
130+ < ShareButton code = { code } />
100131 </ div >
101132
102133 < div className = "flex items-center gap-3" >
@@ -130,12 +161,17 @@ export const App = () => {
130161
131162 < ResizablePanelGroup direction = { "horizontal" } >
132163 { /* EDITOR */ }
133- < Editor />
164+ < Editor code = { code } setCode = { setCode } />
134165
135166 < ResizableHandle className = "bg-surface-quaternary" />
136167
137168 { /* PREVIEW */ }
138- < Preview wasmLoadState = { wasmLoadState } />
169+ < Preview
170+ wasmLoadState = { wasmLoadState }
171+ isDebouncing = { isDebouncing }
172+ onDownloadOutput = { onDownloadOutput }
173+ output = { output }
174+ />
139175 </ ResizablePanelGroup >
140176 </ main >
141177 ) ;
@@ -180,15 +216,17 @@ const ThemeSelector: FC = () => {
180216 ) ;
181217} ;
182218
183- const ShareButton : FC = ( ) => {
184- const $code = useStore ( ( state ) => state . code ) ;
219+ type ShareButtonProps = {
220+ code : string ;
221+ } ;
222+ const ShareButton : FC < ShareButtonProps > = ( { code } ) => {
185223 const [ isCopied , setIsCopied ] = useState ( ( ) => false ) ;
186224 const timeoutId = useRef < ReturnType < typeof setTimeout > > ( undefined ) ;
187225
188- const onShare = useCallback ( async ( ) => {
226+ const onShare = async ( ) => {
189227 try {
190228 const { id } = await rpc . parameters
191- . $post ( { json : { code : $code } } )
229+ . $post ( { json : { code } } )
192230 . then ( ( res ) => res . json ( ) ) ;
193231
194232 const { protocol, host } = window . location ;
@@ -200,7 +238,7 @@ const ShareButton: FC = () => {
200238 } catch ( e ) {
201239 console . error ( e ) ;
202240 }
203- } , [ $code ] ) ;
241+ } ;
204242
205243 useEffect ( ( ) => {
206244 if ( ! isCopied ) {
0 commit comments