44import debug from "debug" ;
55import * as jotai from "jotai" ;
66
7+ import { arrayBufferToBase64 } from "@/util/base64" ;
78import { getOrCreateClientId } from "@/util/clientid" ;
89import { adaptFromReactOrNativeKeyEvent } from "@/util/keyutil" ;
910import { PLATFORM , PlatformMacOS } from "@/util/platformutil" ;
@@ -38,6 +39,28 @@ function isBlank(v: string): boolean {
3839 return v == null || v === "" ;
3940}
4041
42+ async function fileToVDomFileData ( file : File , fieldname : string ) : Promise < VDomFileData > {
43+ const maxSize = 5 * 1024 * 1024 ;
44+ if ( file . size > maxSize ) {
45+ return {
46+ fieldname : fieldname ,
47+ name : file . name ,
48+ size : file . size ,
49+ type : file . type ,
50+ error : "File size exceeds 5MB limit" ,
51+ } ;
52+ }
53+ const buffer = await file . arrayBuffer ( ) ;
54+ const data64 = arrayBufferToBase64 ( buffer ) ;
55+ return {
56+ fieldname : fieldname ,
57+ name : file . name ,
58+ size : file . size ,
59+ type : file . type ,
60+ data64 : data64 ,
61+ } ;
62+ }
63+
4164function annotateEvent ( event : VDomEvent , propName : string , reactEvent : React . SyntheticEvent ) {
4265 if ( reactEvent == null ) {
4366 return ;
@@ -47,7 +70,7 @@ function annotateEvent(event: VDomEvent, propName: string, reactEvent: React.Syn
4770 event . targetvalue = changeEvent . target ?. value ;
4871 event . targetchecked = changeEvent . target ?. checked ;
4972 }
50- if ( propName == "onClick" || propName == "onMouseDown" ) {
73+ if ( propName == "onClick" || propName == "onMouseDown" || propName == "onMouseUp" || propName == "onDoubleClick" ) {
5174 const mouseEvent = reactEvent as React . MouseEvent < any > ;
5275 event . mousedata = {
5376 button : mouseEvent . button ,
@@ -79,6 +102,69 @@ function annotateEvent(event: VDomEvent, propName: string, reactEvent: React.Syn
79102 }
80103}
81104
105+ async function asyncAnnotateEvent ( event : VDomEvent , propName : string , reactEvent : React . SyntheticEvent ) {
106+ if ( propName == "onSubmit" ) {
107+ const formEvent = reactEvent as React . FormEvent < HTMLFormElement > ;
108+ const form = formEvent . currentTarget ;
109+
110+ event . targetname = form . name ;
111+ event . targetid = form . id ;
112+
113+ const formData : VDomFormData = {
114+ method : ( form . method || "get" ) . toUpperCase ( ) ,
115+ enctype : form . enctype || "application/x-www-form-urlencoded" ,
116+ fields : { } ,
117+ files : { } ,
118+ } ;
119+
120+ if ( form . action ) {
121+ formData . action = form . action ;
122+ }
123+ if ( form . id ) {
124+ formData . formid = form . id ;
125+ }
126+ if ( form . name ) {
127+ formData . formname = form . name ;
128+ }
129+
130+ const formDataObj = new FormData ( form ) ;
131+
132+ for ( const [ key , value ] of formDataObj . entries ( ) ) {
133+ if ( value instanceof File ) {
134+ if ( ! value . name && value . size === 0 ) {
135+ continue ;
136+ }
137+ if ( ! formData . files [ key ] ) {
138+ formData . files [ key ] = [ ] ;
139+ }
140+ formData . files [ key ] . push ( await fileToVDomFileData ( value , key ) ) ;
141+ } else {
142+ if ( ! formData . fields [ key ] ) {
143+ formData . fields [ key ] = [ ] ;
144+ }
145+ formData . fields [ key ] . push ( value . toString ( ) ) ;
146+ }
147+ }
148+
149+ event . formdata = formData ;
150+ }
151+ if ( propName == "onChange" ) {
152+ const changeEvent = reactEvent as React . ChangeEvent < HTMLInputElement > ;
153+ if ( changeEvent . target ?. type === "file" && changeEvent . target . files ) {
154+ event . targetname = changeEvent . target . name ;
155+ event . targetid = changeEvent . target . id ;
156+
157+ const files : VDomFileData [ ] = [ ] ;
158+ const fieldname = changeEvent . target . name || changeEvent . target . id || "file" ;
159+ for ( let i = 0 ; i < changeEvent . target . files . length ; i ++ ) {
160+ const file = changeEvent . target . files [ i ] ;
161+ files . push ( await fileToVDomFileData ( file , fieldname ) ) ;
162+ }
163+ event . targetfiles = files ;
164+ }
165+ }
166+ }
167+
82168export class TsunamiModel {
83169 clientId : string ;
84170 serverId : string ;
@@ -109,7 +195,7 @@ export class TsunamiModel {
109195 cachedTitle : string | null = null ;
110196 cachedShortDesc : string | null = null ;
111197 reason : string | null = null ;
112- currentModal : jotai . PrimitiveAtom < ModalConfig | null > = jotai . atom ( null ) ;
198+ currentModal : jotai . PrimitiveAtom < ModalConfig | null > = jotai . atom ( null ) as jotai . PrimitiveAtom < ModalConfig | null > ;
113199
114200 constructor ( ) {
115201 this . clientId = getOrCreateClientId ( ) ;
@@ -631,9 +717,23 @@ export class TsunamiModel {
631717 if ( fnDecl . globalevent ) {
632718 vdomEvent . globaleventtype = fnDecl . globalevent ;
633719 }
634- annotateEvent ( vdomEvent , propName , e ) ;
635- this . batchedEvents . push ( vdomEvent ) ;
636- this . queueUpdate ( true , "event" ) ;
720+ const needsAsync =
721+ propName == "onSubmit" ||
722+ ( propName == "onChange" && ( e . target as HTMLInputElement ) ?. type === "file" ) ;
723+ if ( needsAsync ) {
724+ asyncAnnotateEvent ( vdomEvent , propName , e )
725+ . then ( ( ) => {
726+ this . batchedEvents . push ( vdomEvent ) ;
727+ this . queueUpdate ( true , "event" ) ;
728+ } )
729+ . catch ( ( err ) => {
730+ console . error ( "Error processing event:" , err ) ;
731+ } ) ;
732+ } else {
733+ annotateEvent ( vdomEvent , propName , e ) ;
734+ this . batchedEvents . push ( vdomEvent ) ;
735+ this . queueUpdate ( true , "event" ) ;
736+ }
637737 }
638738
639739 createFeUpdate ( ) : VDomFrontendUpdate {
0 commit comments