1- import { type Component , createMemo , Show } from "solid-js" ;
1+ import { type Component , createMemo , onCleanup , Show } from "solid-js" ;
22
33import { Layout } from "./components/layout/layout" ;
44import Sidebar from "./components/layout/sidebar" ;
@@ -12,6 +12,56 @@ export const App: Component = () => {
1212 const [ store , actions ] = useStore ( ) ;
1313 const { toast } = useToast ( ) ;
1414
15+ // Fix iOS Safari WebSocket hanging bug using alternating RAF/setTimeout pattern
16+ // Source - https://stackoverflow.com/a/42036303
17+ // iOS has a bug where rapid WebSocket sends during pointer events can hang the connection
18+ // Solution: alternate between requestAnimationFrame and setTimeout to break the pattern
19+ let scheduledId : number | undefined ;
20+ let pendingLedData : { index : number ; status : number } | null = null ;
21+ let useRaf = true ;
22+ let frametime = 0 ;
23+ let lastframe = Date . now ( ) ;
24+
25+ const deferredLedSend = ( data : { index : number ; status : number } ) => {
26+ pendingLedData = data ;
27+
28+ if ( scheduledId ) return ;
29+
30+ const sendLed = ( ) => {
31+ frametime = Date . now ( ) - lastframe ;
32+ lastframe = Date . now ( ) ;
33+
34+ if ( pendingLedData ) {
35+ actions . send (
36+ JSON . stringify ( {
37+ event : "led" ,
38+ ...pendingLedData ,
39+ } ) ,
40+ ) ;
41+ pendingLedData = null ;
42+ }
43+
44+ scheduledId = undefined ;
45+ useRaf = ! useRaf ;
46+ } ;
47+
48+ if ( useRaf ) {
49+ scheduledId = requestAnimationFrame ( sendLed ) ;
50+ } else {
51+ scheduledId = setTimeout ( sendLed , Math . max ( 0 , frametime ) ) as unknown as number ;
52+ }
53+ } ;
54+
55+ onCleanup ( ( ) => {
56+ if ( scheduledId ) {
57+ if ( useRaf ) {
58+ cancelAnimationFrame ( scheduledId ) ;
59+ } else {
60+ clearTimeout ( scheduledId ) ;
61+ }
62+ }
63+ } ) ;
64+
1565 const rotatedMatrix = createMemo ( ( ) => rotateArray ( store . indexMatrix , store . rotation ) ) ;
1666
1767 const wsMessage = (
@@ -120,18 +170,57 @@ export const App: Component = () => {
120170 </ Show >
121171 }
122172 >
123- < LedMatrix
124- disabled = { store . plugin !== 1 }
125- data = { store . leds || [ ] }
126- indexData = { rotatedMatrix ( ) }
127- brightness = { store . brightness ?? 255 }
128- onSetLed = { ( data ) => {
129- wsMessage ( "led" , data ) ;
130- } }
131- onSetMatrix = { ( data ) => {
132- actions ?. setLeds ( [ ...data ] ) ;
133- } }
134- />
173+ < div class = "flex flex-col items-center gap-6" >
174+ < LedMatrix
175+ disabled = { store . plugin !== 1 }
176+ data = { store . leds || [ ] }
177+ indexData = { rotatedMatrix ( ) }
178+ brightness = { store . brightness ?? 255 }
179+ onSetLed = { ( data ) => {
180+ deferredLedSend ( data ) ;
181+ } }
182+ onSetMatrix = { ( data ) => {
183+ actions ?. setLeds ( [ ...data ] ) ;
184+ } }
185+ />
186+
187+ < div class = "lg:hidden w-full max-w-100 sm:max-w-125" >
188+ < div class = "grid grid-cols-4 gap-2" >
189+ < button
190+ type = "button"
191+ onClick = { handleLoadImage }
192+ class = "flex flex-col items-center justify-center gap-1 bg-gray-700 text-white border-0 p-2 cursor-pointer font-semibold hover:opacity-80 active:-translate-y-px transition-all rounded text-xs"
193+ >
194+ < i class = "fa-solid fa-file-import text-base" />
195+ < span > Import</ span >
196+ </ button >
197+ < button
198+ type = "button"
199+ onClick = { handleClear }
200+ class = "flex flex-col items-center justify-center gap-1 bg-gray-700 text-white border-0 p-2 cursor-pointer font-semibold hover:opacity-80 active:-translate-y-px transition-all rounded hover:bg-red-600 text-xs"
201+ >
202+ < i class = "fa-solid fa-trash text-base" />
203+ < span > Clear</ span >
204+ </ button >
205+ < button
206+ type = "button"
207+ onClick = { handlePersist }
208+ class = "flex flex-col items-center justify-center gap-1 bg-gray-700 text-white border-0 p-2 cursor-pointer font-semibold hover:opacity-80 active:-translate-y-px transition-all rounded text-xs"
209+ >
210+ < i class = "fa-solid fa-floppy-disk text-base" />
211+ < span > Save</ span >
212+ </ button >
213+ < button
214+ type = "button"
215+ onClick = { handleLoad }
216+ class = "flex flex-col items-center justify-center gap-1 bg-gray-700 text-white border-0 p-2 cursor-pointer font-semibold hover:opacity-80 active:-translate-y-px transition-all rounded text-xs"
217+ >
218+ < i class = "fa-solid fa-refresh text-base" />
219+ < span > Load</ span >
220+ </ button >
221+ </ div >
222+ </ div >
223+ </ div >
135224 </ Show >
136225 </ div >
137226 ) ;
0 commit comments