1
1
import { Button } from "@/components/Button" ;
2
- import {
3
- ResizableHandle ,
4
- ResizablePanel ,
5
- ResizablePanelGroup ,
6
- } from "@/components/Resizable" ;
2
+ import { ResizablePanel } from "@/components/Resizable" ;
7
3
import { ActivityIcon , ExternalLinkIcon , LoaderIcon } from "lucide-react" ;
8
- import { useRef , useState , type FC } from "react" ;
9
- import type { ImperativePanelHandle } from "react-resizable-panels" ;
4
+ import { useEffect , useState , type FC } from "react" ;
10
5
import { useStore } from "@/store" ;
6
+ import { useDebouncedValue } from "@/hooks/debounce" ;
7
+ import { cn } from "@/utils/cn" ;
11
8
12
9
export const Preview : FC = ( ) => {
13
10
const $wasmState = useStore ( ( state ) => state . wasmState ) ;
11
+ const $code = useStore ( ( state ) => state . code ) ;
12
+ const $setError = useStore ( ( state ) => state . setError ) ;
13
+
14
+ const [ debouncedCode , isDebouncing ] = useDebouncedValue ( $code , 1000 ) ;
15
+
16
+ const [ output , setOutput ] = useState < string | null > ( ( ) => null ) ;
17
+
18
+ useEffect ( ( ) => {
19
+ if ( ! window . go_preview ) {
20
+ return ;
21
+ }
22
+
23
+ const getOutput = async ( ) => {
24
+ try {
25
+ const output = await window . go_preview ?.( debouncedCode ) ;
26
+
27
+ if ( output === undefined ) {
28
+ console . error ( "Something went wrong" ) ;
29
+ } else {
30
+ setOutput ( ( ) => output ) ;
31
+ }
32
+ } catch ( e ) {
33
+ console . error ( e ) ;
34
+ if ( e instanceof Error ) {
35
+ $setError ( `${ e . name } : ${ e . message } ` ) ;
36
+ } else {
37
+ $setError ( "Something went wrong" ) ;
38
+ }
39
+ }
40
+ } ;
41
+
42
+ getOutput ( ) ;
43
+ } , [ debouncedCode , $setError ] ) ;
14
44
15
45
return (
16
46
< ResizablePanel className = "relative" >
@@ -31,37 +61,13 @@ export const Preview: FC = () => {
31
61
< Button variant = "destructive" > Reset form</ Button >
32
62
</ div >
33
63
34
- < div className = "flex h-full w-full items-center justify-center overflow-x-clip rounded-xl border p-4" >
35
- < div className = "flex flex-col items-center justify-center gap-3" >
36
- < div className = "flex items-center justify-center rounded-[6px] bg-highlight-sky p-2" >
37
- < ActivityIcon
38
- className = "text-content-invert"
39
- width = { 24 }
40
- height = { 24 }
41
- />
42
- </ div >
43
-
44
- < div className = "flex flex-col items-center gap-2" >
45
- < div className = "flex max-w-[258px] flex-col items-center gap-1" >
46
- < p className = "text-nowrap text-center font-semibold text-2xl text-content-primary" >
47
- Parameters Playground
48
- </ p >
49
- < p className = "text-center font-medium text-content-secondary text-sm" >
50
- Create dynamic parameters here, I need to figure out a better
51
- copy.
52
- </ p >
53
- </ div >
54
- < a
55
- href = "#todo"
56
- className = "flex items-center gap-0.5 text-content-link text-sm"
57
- >
58
- Read the docs
59
- < span className = "inline" >
60
- < ExternalLinkIcon width = { 16 } />
61
- </ span >
62
- </ a >
63
- </ div >
64
- </ div >
64
+ < div
65
+ className = "flex h-full w-full items-center justify-center overflow-x-clip rounded-xl border p-4"
66
+ style = { {
67
+ opacity : isDebouncing ? 0.5 : 1 ,
68
+ } }
69
+ >
70
+ { output ? output : < PreviewEmptyState /> }
65
71
</ div >
66
72
</ div >
67
73
@@ -70,15 +76,39 @@ export const Preview: FC = () => {
70
76
) ;
71
77
} ;
72
78
73
- const ErrorPane = ( ) => {
74
- const $error = useStore ( ( state ) => state . error ) ;
79
+ const PreviewEmptyState = ( ) => {
80
+ return (
81
+ < div className = "flex flex-col items-center justify-center gap-3" >
82
+ < div className = "flex items-center justify-center rounded-[6px] bg-highlight-sky p-2" >
83
+ < ActivityIcon className = "text-content-invert" width = { 24 } height = { 24 } />
84
+ </ div >
75
85
76
- const [ errorPanelSize , setErrorPanelSize ] = useState ( ( ) => 50 ) ;
77
- const errorPanelRef = useRef < ImperativePanelHandle > ( null ) ;
86
+ < div className = "flex flex-col items-center gap-2" >
87
+ < div className = "flex max-w-[258px] flex-col items-center gap-1" >
88
+ < p className = "text-nowrap text-center font-semibold text-2xl text-content-primary" >
89
+ Parameters Playground
90
+ </ p >
91
+ < p className = "text-center font-medium text-content-secondary text-sm" >
92
+ Create dynamic parameters here, I need to figure out a better copy.
93
+ </ p >
94
+ </ div >
95
+ < a
96
+ href = "#todo"
97
+ className = "flex items-center gap-0.5 text-content-link text-sm"
98
+ >
99
+ Read the docs
100
+ < span className = "inline" >
101
+ < ExternalLinkIcon width = { 16 } />
102
+ </ span >
103
+ </ a >
104
+ </ div >
105
+ </ div >
106
+ ) ;
107
+ } ;
78
108
79
- const onCollapseError = ( ) => {
80
- errorPanelRef . current ?. collapse ( ) ;
81
- } ;
109
+ const ErrorPane = ( ) => {
110
+ const $error = useStore ( ( state ) => state . error ) ;
111
+ const $toggleShowError = useStore ( ( state ) => state . toggleShowError ) ;
82
112
83
113
if ( ! $error ) {
84
114
return null ;
@@ -87,34 +117,43 @@ const ErrorPane = () => {
87
117
return (
88
118
< >
89
119
< div
90
- className = "pointer-events-none absolute top-0 left-0 h-full w-full bg-black"
91
- style = { { opacity : errorPanelSize / 100 } }
120
+ aria-hidden = { true }
121
+ className = { cn (
122
+ "pointer-events-none absolute top-0 left-0 h-full w-full transition-all" ,
123
+ $error . show && "bg-black/50" ,
124
+ ) }
92
125
>
93
126
{ /* OVERLAY */ }
94
127
</ div >
95
128
96
- < ResizablePanelGroup
97
- direction = "vertical"
98
- className = "pointer-events-none absolute top-0 left-0"
129
+ < div
130
+ className = { cn (
131
+ "absolute bottom-0 left-0 w-full" ,
132
+ $error . show && "h-2/3" ,
133
+ ) }
99
134
>
100
- < ResizablePanel aria-hidden className = "pointer-events-none" >
101
- { /* EMPTY */ }
102
- </ ResizablePanel >
103
- < ResizableHandle
104
- onClick = { onCollapseError }
135
+ < button
105
136
className = "flex h-4 min-h-4 w-full items-center justify-center rounded-t-xl bg-[#AA5253]"
106
- withHandle = { true }
107
- />
108
- < ResizablePanel
109
- ref = { errorPanelRef }
110
- className = "bg-surface-secondary"
111
- collapsible = { true }
112
- collapsedSize = { 0 }
113
- onResize = { ( size ) => {
114
- setErrorPanelSize ( ( ) => size ) ;
115
- } }
116
- > </ ResizablePanel >
117
- </ ResizablePanelGroup >
137
+ onClick = { $toggleShowError }
138
+ >
139
+ < div className = "h-0.5 w-2/3 max-w-32 rounded-full bg-white/40" > </ div >
140
+ </ button >
141
+
142
+ < div
143
+ aria-hidden = { ! $error . show }
144
+ className = { cn (
145
+ "flex h-full flex-col gap-6 bg-surface-secondary p-6" ,
146
+ ! $error . show && "pointer-events-none h-0 p-0" ,
147
+ ) }
148
+ >
149
+ < p className = "font-semibold text-content-primary text-xl" >
150
+ An error has occurred
151
+ </ p >
152
+ < p className = "rounded-xl bg-surface-tertiary p-3 font-mono text-content-primary text-xs" >
153
+ { $error . message }
154
+ </ p >
155
+ </ div >
156
+ </ div >
118
157
</ >
119
158
) ;
120
159
} ;
0 commit comments