1
1
import { withSentry } from "@sentry/remix" ;
2
- import { useEffect , useRef } from "react" ;
2
+ import { FormEvent , useEffect , useRef } from "react" ;
3
3
4
- import {
5
- ActionArgs ,
6
- json ,
7
- LinksFunction ,
8
- LoaderArgs ,
9
- redirect ,
10
- } from "@remix-run/node" ;
4
+ import { json , LinksFunction , redirect } from "@remix-run/node" ;
11
5
import type {
12
6
ShouldRevalidateFunction ,
7
+ SubmitOptions ,
13
8
V2_MetaFunction ,
14
9
} from "@remix-run/react" ;
15
10
import {
@@ -23,9 +18,9 @@ import {
23
18
} from "@remix-run/react" ;
24
19
import path from "path" ;
25
20
import { useDebouncedCallback } from "use-debounce" ;
26
- import { applyTransform , getTransforms , RenderedChildren } from "~/renderer" ;
27
21
import { useEventSourceNullOk } from "~/event-source" ;
28
22
import { handleRedirectResponse } from "~/handleRedirect" ;
23
+ import { applyFormDataTransforms , RenderedChildren } from "~/renderer" ;
29
24
30
25
import { gooeyGuiRouteHeader } from "~/consts" ;
31
26
import appStyles from "~/styles/app.css" ;
@@ -64,53 +59,7 @@ export const links: LinksFunction = () => {
64
59
] ;
65
60
} ;
66
61
67
- export async function loader ( { request } : LoaderArgs ) {
68
- return await callServer ( { request } ) ;
69
- }
70
-
71
- export async function action ( { request } : ActionArgs ) {
72
- // proxy
73
- let contentType = request . headers . get ( "Content-Type" ) ;
74
- if ( ! contentType ?. startsWith ( "application/x-www-form-urlencoded" ) ) {
75
- let body = await request . arrayBuffer ( ) ;
76
- return callServer ( { request, body } ) ;
77
- }
78
- // proxy
79
- let body = await request . text ( ) ;
80
- let formData = new URLSearchParams ( body ) ;
81
- if ( ! formData . has ( "__gooey_gui_request_body" ) ) {
82
- return callServer ( { request, body } ) ;
83
- }
84
-
85
- // parse request body
86
- let { __gooey_gui_request_body, ...inputs } = Object . fromEntries ( formData ) ;
87
- const {
88
- transforms,
89
- state,
90
- ...jsonBody
91
- } : {
92
- transforms : Record < string , string > ;
93
- state : Record < string , any > ;
94
- } & Record < string , any > = JSON . parse ( __gooey_gui_request_body . toString ( ) ) ;
95
- // apply transforms
96
- for ( let [ field , inputType ] of Object . entries ( transforms ) ) {
97
- let toJson = applyTransform [ inputType ] ;
98
- if ( ! toJson ) continue ;
99
- inputs [ field ] = toJson ( inputs [ field ] ) ;
100
- }
101
- // update state with new form data
102
- jsonBody . state = { ...state , ...inputs } ;
103
- request . headers . set ( "Content-Type" , "application/json" ) ;
104
- return callServer ( { request, body : JSON . stringify ( jsonBody ) } ) ;
105
- }
106
-
107
- async function callServer ( {
108
- request,
109
- body,
110
- } : {
111
- request : Request ;
112
- body ?: BodyInit | null ;
113
- } ) {
62
+ export async function loader ( { request } : { request : Request } ) {
114
63
const requestUrl = new URL ( request . url ) ;
115
64
const serverUrl = new URL ( settings . SERVER_HOST ! ) ;
116
65
serverUrl . pathname = path . join ( serverUrl . pathname , requestUrl . pathname ?? "" ) ;
@@ -119,10 +68,15 @@ async function callServer({
119
68
request . headers . delete ( "Host" ) ;
120
69
request . headers . set ( gooeyGuiRouteHeader , "1" ) ;
121
70
71
+ let body ;
72
+ if ( ! [ "GET" , "HEAD" , "OPTIONS" ] . includes ( request . method ) ) {
73
+ body = await request . arrayBuffer ( ) ;
74
+ }
75
+
122
76
let response = await fetch ( serverUrl , {
123
77
method : request . method ,
124
78
redirect : "manual" ,
125
- body : body ,
79
+ body,
126
80
headers : request . headers ,
127
81
} ) ;
128
82
@@ -147,6 +101,8 @@ async function callServer({
147
101
}
148
102
}
149
103
104
+ export const action = loader ;
105
+
150
106
export const shouldRevalidate : ShouldRevalidateFunction = ( args ) => {
151
107
if (
152
108
// don't revalidate if its a form submit with successful response
@@ -200,21 +156,6 @@ function App() {
200
156
const submit = useSubmit ( ) ;
201
157
const navigate = useNavigate ( ) ;
202
158
203
- if ( typeof window !== "undefined" ) {
204
- // @ts -ignore
205
- window . gui = {
206
- session_state : state ,
207
- navigate,
208
- fetcher,
209
- submit ( ) {
210
- if ( formRef . current ) submit ( formRef . current , ...arguments ) ;
211
- } ,
212
- rerun ( ) {
213
- if ( formRef . current ) submit ( formRef . current ) ;
214
- } ,
215
- } ;
216
- }
217
-
218
159
useEffect ( ( ) => {
219
160
if ( ! base64Body ) return ;
220
161
let body = base64Decode ( base64Body ) ;
@@ -225,15 +166,10 @@ function App() {
225
166
226
167
useEffect ( ( ) => {
227
168
if ( realtimeEvent && fetcher . state === "idle" && formRef . current ) {
228
- submit ( formRef . current ) ;
169
+ onSubmit ( ) ;
229
170
}
230
171
} , [ fetcher . state , realtimeEvent , submit ] ) ;
231
172
232
- const debouncedSubmit = useDebouncedCallback ( ( form : HTMLFormElement ) => {
233
- form . removeAttribute ( "debounceInProgress" ) ;
234
- submit ( form ) ;
235
- } , 500 ) ;
236
-
237
173
const onChange : OnChange = ( event ) => {
238
174
const target = event ?. target ;
239
175
const form = event ?. currentTarget || formRef ?. current ;
@@ -267,40 +203,75 @@ function App() {
267
203
"focusout" ,
268
204
function ( ) {
269
205
form . removeAttribute ( "debounceInProgress" ) ;
270
- submit ( form ) ;
206
+ onSubmit ( ) ;
271
207
} ,
272
208
{ once : true }
273
209
) ;
274
210
} else {
275
- submit ( form ) ;
211
+ onSubmit ( ) ;
276
212
}
277
213
} ;
278
214
279
- if ( ! children ) return < > </ > ;
215
+ const debouncedSubmit = useDebouncedCallback ( ( form : HTMLFormElement ) => {
216
+ form . removeAttribute ( "debounceInProgress" ) ;
217
+ onSubmit ( ) ;
218
+ } , 500 ) ;
280
219
281
- const transforms = getTransforms ( { children } ) ;
220
+ let submitOptions : SubmitOptions = {
221
+ method : "post" ,
222
+ action : "?" + searchParams ,
223
+ encType : "application/json" ,
224
+ } ;
225
+
226
+ const onSubmit = ( event ?: FormEvent ) => {
227
+ if ( ! formRef . current ) return ;
228
+ let formData = Object . fromEntries ( new FormData ( formRef . current ) ) ;
229
+ if ( event ) {
230
+ event . preventDefault ( ) ;
231
+ let submitter = ( event . nativeEvent as SubmitEvent )
232
+ . submitter as HTMLFormElement ;
233
+ if ( submitter ) {
234
+ formData [ submitter . name ] = submitter . value ;
235
+ }
236
+ }
237
+ applyFormDataTransforms ( { children, formData } ) ;
238
+ let body = { state : { ...state , ...formData } } ;
239
+ submit ( body , submitOptions ) ;
240
+ } ;
241
+
242
+ if ( typeof window !== "undefined" ) {
243
+ // @ts -ignore
244
+ window . gui = {
245
+ navigate,
246
+ fetcher,
247
+ session_state : state ,
248
+ update_session_state ( newState : Record < string , any > ) {
249
+ submit ( { state : { ...state , ...newState } } , submitOptions ) ;
250
+ } ,
251
+ set_session_state ( newState : Record < string , any > ) {
252
+ submit ( { state : newState } , submitOptions ) ;
253
+ } ,
254
+ rerun : onSubmit ,
255
+ } ;
256
+ }
257
+
258
+ if ( ! children ) return < > </ > ;
282
259
283
260
return (
284
261
< div data-prismjs-copy = "📋 Copy" data-prismjs-copy-success = "✅ Copied!" >
285
- < Form
262
+ < form
286
263
ref = { formRef }
287
264
id = { "gooey-form" }
288
- action = { "?" + searchParams }
289
- method = "POST"
290
265
onChange = { onChange }
266
+ onSubmit = { onSubmit }
291
267
noValidate
292
268
>
293
269
< RenderedChildren
294
270
children = { children }
295
271
onChange = { onChange }
296
272
state = { state }
297
273
/>
298
- < input
299
- type = "hidden"
300
- name = "__gooey_gui_request_body"
301
- value = { JSON . stringify ( { state, transforms } ) }
302
- />
303
- </ Form >
274
+ </ form >
304
275
< script
305
276
async
306
277
defer
0 commit comments