reflex 0.4.2a1__py3-none-any.whl → 0.4.3a2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of reflex might be problematic. Click here for more details.
- reflex/.templates/apps/blank/code/blank.py +1 -1
- reflex/.templates/apps/sidebar/README.md +3 -2
- reflex/.templates/apps/sidebar/assets/reflex_white.svg +8 -0
- reflex/.templates/apps/sidebar/code/components/sidebar.py +26 -22
- reflex/.templates/apps/sidebar/code/pages/dashboard.py +6 -5
- reflex/.templates/apps/sidebar/code/pages/settings.py +45 -6
- reflex/.templates/apps/sidebar/code/styles.py +15 -17
- reflex/.templates/apps/sidebar/code/templates/__init__.py +1 -1
- reflex/.templates/apps/sidebar/code/templates/template.py +54 -40
- reflex/.templates/jinja/custom_components/README.md.jinja2 +9 -0
- reflex/.templates/jinja/custom_components/__init__.py.jinja2 +1 -0
- reflex/.templates/jinja/custom_components/demo_app.py.jinja2 +36 -0
- reflex/.templates/jinja/custom_components/pyproject.toml.jinja2 +35 -0
- reflex/.templates/jinja/custom_components/src.py.jinja2 +57 -0
- reflex/.templates/jinja/web/utils/context.js.jinja2 +26 -6
- reflex/.templates/web/utils/state.js +206 -146
- reflex/app.py +21 -18
- reflex/compiler/compiler.py +6 -2
- reflex/compiler/templates.py +17 -0
- reflex/compiler/utils.py +2 -2
- reflex/components/core/__init__.py +2 -1
- reflex/components/core/banner.py +99 -11
- reflex/components/core/banner.pyi +215 -2
- reflex/components/el/elements/__init__.py +1 -0
- reflex/components/el/elements/forms.py +6 -0
- reflex/components/el/elements/forms.pyi +4 -0
- reflex/components/markdown/markdown.py +13 -25
- reflex/components/markdown/markdown.pyi +5 -5
- reflex/components/plotly/plotly.py +3 -0
- reflex/components/plotly/plotly.pyi +2 -0
- reflex/components/radix/primitives/drawer.py +3 -7
- reflex/components/radix/themes/components/select.py +4 -4
- reflex/components/radix/themes/components/text_field.pyi +4 -0
- reflex/constants/__init__.py +4 -0
- reflex/constants/colors.py +1 -0
- reflex/constants/compiler.py +4 -3
- reflex/constants/custom_components.py +30 -0
- reflex/custom_components/__init__.py +1 -0
- reflex/custom_components/custom_components.py +565 -0
- reflex/reflex.py +11 -2
- reflex/route.py +4 -0
- reflex/state.py +594 -124
- reflex/testing.py +6 -0
- reflex/utils/exec.py +9 -0
- reflex/utils/prerequisites.py +28 -2
- reflex/utils/telemetry.py +3 -1
- reflex/utils/types.py +23 -0
- reflex/vars.py +48 -17
- reflex/vars.pyi +8 -3
- {reflex-0.4.2a1.dist-info → reflex-0.4.3a2.dist-info}/METADATA +4 -2
- {reflex-0.4.2a1.dist-info → reflex-0.4.3a2.dist-info}/RECORD +55 -51
- {reflex-0.4.2a1.dist-info → reflex-0.4.3a2.dist-info}/WHEEL +1 -1
- reflex/components/base/bare.pyi +0 -84
- reflex/constants/base.pyi +0 -94
- reflex/constants/event.pyi +0 -59
- reflex/constants/route.pyi +0 -50
- reflex/constants/style.pyi +0 -20
- /reflex/.templates/apps/sidebar/assets/{icon.svg → reflex_black.svg} +0 -0
- {reflex-0.4.2a1.dist-info → reflex-0.4.3a2.dist-info}/LICENSE +0 -0
- {reflex-0.4.2a1.dist-info → reflex-0.4.3a2.dist-info}/entry_points.txt +0 -0
|
@@ -6,14 +6,19 @@ import env from "/env.json";
|
|
|
6
6
|
import Cookies from "universal-cookie";
|
|
7
7
|
import { useEffect, useReducer, useRef, useState } from "react";
|
|
8
8
|
import Router, { useRouter } from "next/router";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
initialEvents,
|
|
11
|
+
initialState,
|
|
12
|
+
onLoadInternalEvent,
|
|
13
|
+
state_name,
|
|
14
|
+
} from "utils/context.js";
|
|
10
15
|
|
|
11
16
|
// Endpoint URLs.
|
|
12
|
-
const EVENTURL = env.EVENT
|
|
13
|
-
const UPLOADURL = env.UPLOAD
|
|
17
|
+
const EVENTURL = env.EVENT;
|
|
18
|
+
const UPLOADURL = env.UPLOAD;
|
|
14
19
|
|
|
15
20
|
// These hostnames indicate that the backend and frontend are reachable via the same domain.
|
|
16
|
-
const SAME_DOMAIN_HOSTNAMES = ["localhost", "0.0.0.0", "::", "0:0:0:0:0:0:0:0"]
|
|
21
|
+
const SAME_DOMAIN_HOSTNAMES = ["localhost", "0.0.0.0", "::", "0:0:0:0:0:0:0:0"];
|
|
17
22
|
|
|
18
23
|
// Global variable to hold the token.
|
|
19
24
|
let token;
|
|
@@ -28,7 +33,7 @@ const cookies = new Cookies();
|
|
|
28
33
|
export const refs = {};
|
|
29
34
|
|
|
30
35
|
// Flag ensures that only one event is processing on the backend concurrently.
|
|
31
|
-
let event_processing = false
|
|
36
|
+
let event_processing = false;
|
|
32
37
|
// Array holding pending events to be processed.
|
|
33
38
|
const event_queue = [];
|
|
34
39
|
|
|
@@ -64,7 +69,7 @@ export const getToken = () => {
|
|
|
64
69
|
if (token) {
|
|
65
70
|
return token;
|
|
66
71
|
}
|
|
67
|
-
if (typeof window !==
|
|
72
|
+
if (typeof window !== "undefined") {
|
|
68
73
|
if (!window.sessionStorage.getItem(TOKEN_KEY)) {
|
|
69
74
|
window.sessionStorage.setItem(TOKEN_KEY, generateUUID());
|
|
70
75
|
}
|
|
@@ -81,7 +86,10 @@ export const getToken = () => {
|
|
|
81
86
|
export const getBackendURL = (url_str) => {
|
|
82
87
|
// Get backend URL object from the endpoint.
|
|
83
88
|
const endpoint = new URL(url_str);
|
|
84
|
-
if (
|
|
89
|
+
if (
|
|
90
|
+
typeof window !== "undefined" &&
|
|
91
|
+
SAME_DOMAIN_HOSTNAMES.includes(endpoint.hostname)
|
|
92
|
+
) {
|
|
85
93
|
// Use the frontend domain to access the backend
|
|
86
94
|
const frontend_hostname = window.location.hostname;
|
|
87
95
|
endpoint.hostname = frontend_hostname;
|
|
@@ -91,11 +99,11 @@ export const getBackendURL = (url_str) => {
|
|
|
91
99
|
} else if (endpoint.protocol === "http:") {
|
|
92
100
|
endpoint.protocol = "https:";
|
|
93
101
|
}
|
|
94
|
-
endpoint.port = "";
|
|
102
|
+
endpoint.port = ""; // Assume websocket is on https port via load balancer.
|
|
95
103
|
}
|
|
96
104
|
}
|
|
97
|
-
return endpoint
|
|
98
|
-
}
|
|
105
|
+
return endpoint;
|
|
106
|
+
};
|
|
99
107
|
|
|
100
108
|
/**
|
|
101
109
|
* Apply a delta to the state.
|
|
@@ -103,10 +111,9 @@ export const getBackendURL = (url_str) => {
|
|
|
103
111
|
* @param delta The delta to apply.
|
|
104
112
|
*/
|
|
105
113
|
export const applyDelta = (state, delta) => {
|
|
106
|
-
return { ...state, ...delta }
|
|
114
|
+
return { ...state, ...delta };
|
|
107
115
|
};
|
|
108
116
|
|
|
109
|
-
|
|
110
117
|
/**
|
|
111
118
|
* Handle frontend event or send the event to the backend via Websocket.
|
|
112
119
|
* @param event The event to send.
|
|
@@ -117,10 +124,8 @@ export const applyDelta = (state, delta) => {
|
|
|
117
124
|
export const applyEvent = async (event, socket) => {
|
|
118
125
|
// Handle special events
|
|
119
126
|
if (event.name == "_redirect") {
|
|
120
|
-
if (event.payload.external)
|
|
121
|
-
|
|
122
|
-
else
|
|
123
|
-
Router.push(event.payload.path);
|
|
127
|
+
if (event.payload.external) window.open(event.payload.path, "_blank");
|
|
128
|
+
else Router.push(event.payload.path);
|
|
124
129
|
return false;
|
|
125
130
|
}
|
|
126
131
|
|
|
@@ -130,20 +135,20 @@ export const applyEvent = async (event, socket) => {
|
|
|
130
135
|
}
|
|
131
136
|
|
|
132
137
|
if (event.name == "_remove_cookie") {
|
|
133
|
-
cookies.remove(event.payload.key, { ...event.payload.options })
|
|
134
|
-
queueEvents(initialEvents(), socket)
|
|
138
|
+
cookies.remove(event.payload.key, { ...event.payload.options });
|
|
139
|
+
queueEvents(initialEvents(), socket);
|
|
135
140
|
return false;
|
|
136
141
|
}
|
|
137
142
|
|
|
138
143
|
if (event.name == "_clear_local_storage") {
|
|
139
144
|
localStorage.clear();
|
|
140
|
-
queueEvents(initialEvents(), socket)
|
|
145
|
+
queueEvents(initialEvents(), socket);
|
|
141
146
|
return false;
|
|
142
147
|
}
|
|
143
148
|
|
|
144
149
|
if (event.name == "_remove_local_storage") {
|
|
145
150
|
localStorage.removeItem(event.payload.key);
|
|
146
|
-
queueEvents(initialEvents(), socket)
|
|
151
|
+
queueEvents(initialEvents(), socket);
|
|
147
152
|
return false;
|
|
148
153
|
}
|
|
149
154
|
|
|
@@ -154,9 +159,9 @@ export const applyEvent = async (event, socket) => {
|
|
|
154
159
|
}
|
|
155
160
|
|
|
156
161
|
if (event.name == "_download") {
|
|
157
|
-
const a = document.createElement(
|
|
162
|
+
const a = document.createElement("a");
|
|
158
163
|
a.hidden = true;
|
|
159
|
-
a.href = event.payload.url
|
|
164
|
+
a.href = event.payload.url;
|
|
160
165
|
a.download = event.payload.filename;
|
|
161
166
|
a.click();
|
|
162
167
|
a.remove();
|
|
@@ -178,7 +183,9 @@ export const applyEvent = async (event, socket) => {
|
|
|
178
183
|
if (event.name == "_set_value") {
|
|
179
184
|
const ref =
|
|
180
185
|
event.payload.ref in refs ? refs[event.payload.ref] : event.payload.ref;
|
|
181
|
-
ref.current
|
|
186
|
+
if (ref.current) {
|
|
187
|
+
ref.current.value = event.payload.value;
|
|
188
|
+
}
|
|
182
189
|
return false;
|
|
183
190
|
}
|
|
184
191
|
|
|
@@ -186,10 +193,10 @@ export const applyEvent = async (event, socket) => {
|
|
|
186
193
|
try {
|
|
187
194
|
const eval_result = eval(event.payload.javascript_code);
|
|
188
195
|
if (event.payload.callback) {
|
|
189
|
-
if (!!eval_result && typeof eval_result.then ===
|
|
190
|
-
eval(event.payload.callback)(await eval_result)
|
|
196
|
+
if (!!eval_result && typeof eval_result.then === "function") {
|
|
197
|
+
eval(event.payload.callback)(await eval_result);
|
|
191
198
|
} else {
|
|
192
|
-
eval(event.payload.callback)(eval_result)
|
|
199
|
+
eval(event.payload.callback)(eval_result);
|
|
193
200
|
}
|
|
194
201
|
}
|
|
195
202
|
} catch (e) {
|
|
@@ -199,14 +206,24 @@ export const applyEvent = async (event, socket) => {
|
|
|
199
206
|
}
|
|
200
207
|
|
|
201
208
|
// Update token and router data (if missing).
|
|
202
|
-
event.token = getToken()
|
|
203
|
-
if (
|
|
204
|
-
event.router_data
|
|
209
|
+
event.token = getToken();
|
|
210
|
+
if (
|
|
211
|
+
event.router_data === undefined ||
|
|
212
|
+
Object.keys(event.router_data).length === 0
|
|
213
|
+
) {
|
|
214
|
+
event.router_data = (({ pathname, query, asPath }) => ({
|
|
215
|
+
pathname,
|
|
216
|
+
query,
|
|
217
|
+
asPath,
|
|
218
|
+
}))(Router);
|
|
205
219
|
}
|
|
206
220
|
|
|
207
221
|
// Send the event to the server.
|
|
208
222
|
if (socket) {
|
|
209
|
-
socket.emit(
|
|
223
|
+
socket.emit(
|
|
224
|
+
"event",
|
|
225
|
+
JSON.stringify(event, (k, v) => (v === undefined ? null : v))
|
|
226
|
+
);
|
|
210
227
|
return true;
|
|
211
228
|
}
|
|
212
229
|
|
|
@@ -242,17 +259,15 @@ export const applyRestEvent = async (event, socket) => {
|
|
|
242
259
|
* @param socket The socket object to send the event on.
|
|
243
260
|
*/
|
|
244
261
|
export const queueEvents = async (events, socket) => {
|
|
245
|
-
event_queue.push(...events)
|
|
246
|
-
await processEvent(socket.current)
|
|
247
|
-
}
|
|
262
|
+
event_queue.push(...events);
|
|
263
|
+
await processEvent(socket.current);
|
|
264
|
+
};
|
|
248
265
|
|
|
249
266
|
/**
|
|
250
267
|
* Process an event off the event queue.
|
|
251
268
|
* @param socket The socket object to send the event on.
|
|
252
269
|
*/
|
|
253
|
-
export const processEvent = async (
|
|
254
|
-
socket
|
|
255
|
-
) => {
|
|
270
|
+
export const processEvent = async (socket) => {
|
|
256
271
|
// Only proceed if the socket is up, otherwise we throw the event into the void
|
|
257
272
|
if (!socket) {
|
|
258
273
|
return;
|
|
@@ -264,12 +279,12 @@ export const processEvent = async (
|
|
|
264
279
|
}
|
|
265
280
|
|
|
266
281
|
// Set processing to true to block other events from being processed.
|
|
267
|
-
event_processing = true
|
|
282
|
+
event_processing = true;
|
|
268
283
|
|
|
269
284
|
// Apply the next event in the queue.
|
|
270
285
|
const event = event_queue.shift();
|
|
271
286
|
|
|
272
|
-
let eventSent = false
|
|
287
|
+
let eventSent = false;
|
|
273
288
|
// Process events with handlers via REST and all others via websockets.
|
|
274
289
|
if (event.handler) {
|
|
275
290
|
eventSent = await applyRestEvent(event, socket);
|
|
@@ -281,27 +296,27 @@ export const processEvent = async (
|
|
|
281
296
|
event_processing = false;
|
|
282
297
|
// recursively call processEvent to drain the queue, since there is
|
|
283
298
|
// no state update to trigger the useEffect event loop.
|
|
284
|
-
await processEvent(socket)
|
|
299
|
+
await processEvent(socket);
|
|
285
300
|
}
|
|
286
|
-
}
|
|
301
|
+
};
|
|
287
302
|
|
|
288
303
|
/**
|
|
289
304
|
* Connect to a websocket and set the handlers.
|
|
290
305
|
* @param socket The socket object to connect.
|
|
291
306
|
* @param dispatch The function to queue state update
|
|
292
307
|
* @param transports The transports to use.
|
|
293
|
-
* @param
|
|
308
|
+
* @param setConnectErrors The function to update connection error value.
|
|
294
309
|
* @param client_storage The client storage object from context.js
|
|
295
310
|
*/
|
|
296
311
|
export const connect = async (
|
|
297
312
|
socket,
|
|
298
313
|
dispatch,
|
|
299
314
|
transports,
|
|
300
|
-
|
|
301
|
-
client_storage = {}
|
|
315
|
+
setConnectErrors,
|
|
316
|
+
client_storage = {}
|
|
302
317
|
) => {
|
|
303
318
|
// Get backend URL object from the endpoint.
|
|
304
|
-
const endpoint = getBackendURL(EVENTURL)
|
|
319
|
+
const endpoint = getBackendURL(EVENTURL);
|
|
305
320
|
|
|
306
321
|
// Create the socket.
|
|
307
322
|
socket.current = io(endpoint.href, {
|
|
@@ -310,27 +325,39 @@ export const connect = async (
|
|
|
310
325
|
autoUnref: false,
|
|
311
326
|
});
|
|
312
327
|
|
|
328
|
+
function checkVisibility() {
|
|
329
|
+
if (document.visibilityState === "visible") {
|
|
330
|
+
if (!socket.current.connected) {
|
|
331
|
+
console.log("Socket is disconnected, attempting to reconnect ");
|
|
332
|
+
socket.current.connect();
|
|
333
|
+
} else {
|
|
334
|
+
console.log("Socket is reconnected ");
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
313
339
|
// Once the socket is open, hydrate the page.
|
|
314
340
|
socket.current.on("connect", () => {
|
|
315
|
-
|
|
341
|
+
setConnectErrors([]);
|
|
316
342
|
});
|
|
317
343
|
|
|
318
|
-
socket.current.on(
|
|
319
|
-
|
|
344
|
+
socket.current.on("connect_error", (error) => {
|
|
345
|
+
setConnectErrors((connectErrors) => [connectErrors.slice(-9), error]);
|
|
320
346
|
});
|
|
321
|
-
|
|
322
347
|
// On each received message, queue the updates and events.
|
|
323
|
-
socket.current.on("event", message => {
|
|
324
|
-
const update = JSON5.parse(message)
|
|
348
|
+
socket.current.on("event", (message) => {
|
|
349
|
+
const update = JSON5.parse(message);
|
|
325
350
|
for (const substate in update.delta) {
|
|
326
|
-
dispatch[substate](update.delta[substate])
|
|
351
|
+
dispatch[substate](update.delta[substate]);
|
|
327
352
|
}
|
|
328
|
-
applyClientStorageDelta(client_storage, update.delta)
|
|
329
|
-
event_processing = !update.final
|
|
353
|
+
applyClientStorageDelta(client_storage, update.delta);
|
|
354
|
+
event_processing = !update.final;
|
|
330
355
|
if (update.events) {
|
|
331
|
-
queueEvents(update.events, socket)
|
|
356
|
+
queueEvents(update.events, socket);
|
|
332
357
|
}
|
|
333
358
|
});
|
|
359
|
+
|
|
360
|
+
document.addEventListener("visibilitychange", checkVisibility);
|
|
334
361
|
};
|
|
335
362
|
|
|
336
363
|
/**
|
|
@@ -344,38 +371,44 @@ export const connect = async (
|
|
|
344
371
|
*
|
|
345
372
|
* @returns The response from posting to the UPLOADURL endpoint.
|
|
346
373
|
*/
|
|
347
|
-
export const uploadFiles = async (
|
|
374
|
+
export const uploadFiles = async (
|
|
375
|
+
handler,
|
|
376
|
+
files,
|
|
377
|
+
upload_id,
|
|
378
|
+
on_upload_progress,
|
|
379
|
+
socket
|
|
380
|
+
) => {
|
|
348
381
|
// return if there's no file to upload
|
|
349
382
|
if (files === undefined || files.length === 0) {
|
|
350
383
|
return false;
|
|
351
384
|
}
|
|
352
385
|
|
|
353
386
|
if (upload_controllers[upload_id]) {
|
|
354
|
-
console.log("Upload already in progress for ", upload_id)
|
|
387
|
+
console.log("Upload already in progress for ", upload_id);
|
|
355
388
|
return false;
|
|
356
389
|
}
|
|
357
390
|
|
|
358
391
|
let resp_idx = 0;
|
|
359
392
|
const eventHandler = (progressEvent) => {
|
|
360
393
|
// handle any delta / event streamed from the upload event handler
|
|
361
|
-
const chunks = progressEvent.event.target.responseText.trim().split("\n")
|
|
394
|
+
const chunks = progressEvent.event.target.responseText.trim().split("\n");
|
|
362
395
|
chunks.slice(resp_idx).map((chunk) => {
|
|
363
396
|
try {
|
|
364
397
|
socket._callbacks.$event.map((f) => {
|
|
365
|
-
f(chunk)
|
|
366
|
-
})
|
|
367
|
-
resp_idx += 1
|
|
398
|
+
f(chunk);
|
|
399
|
+
});
|
|
400
|
+
resp_idx += 1;
|
|
368
401
|
} catch (e) {
|
|
369
402
|
if (progressEvent.progress === 1) {
|
|
370
403
|
// Chunk may be incomplete, so only report errors when full response is available.
|
|
371
|
-
console.log("Error parsing chunk", chunk, e)
|
|
404
|
+
console.log("Error parsing chunk", chunk, e);
|
|
372
405
|
}
|
|
373
|
-
return
|
|
406
|
+
return;
|
|
374
407
|
}
|
|
375
|
-
})
|
|
376
|
-
}
|
|
408
|
+
});
|
|
409
|
+
};
|
|
377
410
|
|
|
378
|
-
const controller = new AbortController()
|
|
411
|
+
const controller = new AbortController();
|
|
379
412
|
const config = {
|
|
380
413
|
headers: {
|
|
381
414
|
"Reflex-Client-Token": getToken(),
|
|
@@ -383,26 +416,22 @@ export const uploadFiles = async (handler, files, upload_id, on_upload_progress,
|
|
|
383
416
|
},
|
|
384
417
|
signal: controller.signal,
|
|
385
418
|
onDownloadProgress: eventHandler,
|
|
386
|
-
}
|
|
419
|
+
};
|
|
387
420
|
if (on_upload_progress) {
|
|
388
|
-
config["onUploadProgress"] = on_upload_progress
|
|
421
|
+
config["onUploadProgress"] = on_upload_progress;
|
|
389
422
|
}
|
|
390
423
|
const formdata = new FormData();
|
|
391
424
|
|
|
392
425
|
// Add the token and handler to the file name.
|
|
393
426
|
files.forEach((file) => {
|
|
394
|
-
formdata.append(
|
|
395
|
-
|
|
396
|
-
file,
|
|
397
|
-
file.path || file.name
|
|
398
|
-
);
|
|
399
|
-
})
|
|
427
|
+
formdata.append("files", file, file.path || file.name);
|
|
428
|
+
});
|
|
400
429
|
|
|
401
430
|
// Send the file to the server.
|
|
402
|
-
upload_controllers[upload_id] = controller
|
|
431
|
+
upload_controllers[upload_id] = controller;
|
|
403
432
|
|
|
404
433
|
try {
|
|
405
|
-
return await axios.post(getBackendURL(UPLOADURL), formdata, config)
|
|
434
|
+
return await axios.post(getBackendURL(UPLOADURL), formdata, config);
|
|
406
435
|
} catch (error) {
|
|
407
436
|
if (error.response) {
|
|
408
437
|
// The request was made and the server responded with a status code
|
|
@@ -419,7 +448,7 @@ export const uploadFiles = async (handler, files, upload_id, on_upload_progress,
|
|
|
419
448
|
}
|
|
420
449
|
return false;
|
|
421
450
|
} finally {
|
|
422
|
-
delete upload_controllers[upload_id]
|
|
451
|
+
delete upload_controllers[upload_id];
|
|
423
452
|
}
|
|
424
453
|
};
|
|
425
454
|
|
|
@@ -441,30 +470,32 @@ export const Event = (name, payload = {}, handler = null) => {
|
|
|
441
470
|
* @returns payload dict of client storage values
|
|
442
471
|
*/
|
|
443
472
|
export const hydrateClientStorage = (client_storage) => {
|
|
444
|
-
const client_storage_values = {}
|
|
473
|
+
const client_storage_values = {};
|
|
445
474
|
if (client_storage.cookies) {
|
|
446
475
|
for (const state_key in client_storage.cookies) {
|
|
447
|
-
const cookie_options = client_storage.cookies[state_key]
|
|
448
|
-
const cookie_name = cookie_options.name || state_key
|
|
449
|
-
const cookie_value = cookies.get(cookie_name)
|
|
476
|
+
const cookie_options = client_storage.cookies[state_key];
|
|
477
|
+
const cookie_name = cookie_options.name || state_key;
|
|
478
|
+
const cookie_value = cookies.get(cookie_name);
|
|
450
479
|
if (cookie_value !== undefined) {
|
|
451
|
-
client_storage_values[state_key] = cookies.get(cookie_name)
|
|
480
|
+
client_storage_values[state_key] = cookies.get(cookie_name);
|
|
452
481
|
}
|
|
453
482
|
}
|
|
454
483
|
}
|
|
455
|
-
if (client_storage.local_storage &&
|
|
484
|
+
if (client_storage.local_storage && typeof window !== "undefined") {
|
|
456
485
|
for (const state_key in client_storage.local_storage) {
|
|
457
|
-
const options = client_storage.local_storage[state_key]
|
|
458
|
-
const local_storage_value = localStorage.getItem(
|
|
486
|
+
const options = client_storage.local_storage[state_key];
|
|
487
|
+
const local_storage_value = localStorage.getItem(
|
|
488
|
+
options.name || state_key
|
|
489
|
+
);
|
|
459
490
|
if (local_storage_value !== null) {
|
|
460
|
-
client_storage_values[state_key] = local_storage_value
|
|
491
|
+
client_storage_values[state_key] = local_storage_value;
|
|
461
492
|
}
|
|
462
493
|
}
|
|
463
494
|
}
|
|
464
495
|
if (client_storage.cookies || client_storage.local_storage) {
|
|
465
|
-
return client_storage_values
|
|
496
|
+
return client_storage_values;
|
|
466
497
|
}
|
|
467
|
-
return {}
|
|
498
|
+
return {};
|
|
468
499
|
};
|
|
469
500
|
|
|
470
501
|
/**
|
|
@@ -474,9 +505,11 @@ export const hydrateClientStorage = (client_storage) => {
|
|
|
474
505
|
*/
|
|
475
506
|
const applyClientStorageDelta = (client_storage, delta) => {
|
|
476
507
|
// find the main state and check for is_hydrated
|
|
477
|
-
const unqualified_states = Object.keys(delta).filter(
|
|
508
|
+
const unqualified_states = Object.keys(delta).filter(
|
|
509
|
+
(key) => key.split(".").length === 1
|
|
510
|
+
);
|
|
478
511
|
if (unqualified_states.length === 1) {
|
|
479
|
-
const main_state = delta[unqualified_states[0]]
|
|
512
|
+
const main_state = delta[unqualified_states[0]];
|
|
480
513
|
if (main_state.is_hydrated !== undefined && !main_state.is_hydrated) {
|
|
481
514
|
// skip if the state is not hydrated yet, since all client storage
|
|
482
515
|
// values are sent in the hydrate event
|
|
@@ -486,19 +519,23 @@ const applyClientStorageDelta = (client_storage, delta) => {
|
|
|
486
519
|
// Save known client storage values to cookies and localStorage.
|
|
487
520
|
for (const substate in delta) {
|
|
488
521
|
for (const key in delta[substate]) {
|
|
489
|
-
const state_key = `${substate}.${key}
|
|
522
|
+
const state_key = `${substate}.${key}`;
|
|
490
523
|
if (client_storage.cookies && state_key in client_storage.cookies) {
|
|
491
|
-
const cookie_options = { ...client_storage.cookies[state_key] }
|
|
492
|
-
const cookie_name = cookie_options.name || state_key
|
|
493
|
-
delete cookie_options.name
|
|
524
|
+
const cookie_options = { ...client_storage.cookies[state_key] };
|
|
525
|
+
const cookie_name = cookie_options.name || state_key;
|
|
526
|
+
delete cookie_options.name; // name is not a valid cookie option
|
|
494
527
|
cookies.set(cookie_name, delta[substate][key], cookie_options);
|
|
495
|
-
} else if (
|
|
496
|
-
|
|
528
|
+
} else if (
|
|
529
|
+
client_storage.local_storage &&
|
|
530
|
+
state_key in client_storage.local_storage &&
|
|
531
|
+
typeof window !== "undefined"
|
|
532
|
+
) {
|
|
533
|
+
const options = client_storage.local_storage[state_key];
|
|
497
534
|
localStorage.setItem(options.name || state_key, delta[substate][key]);
|
|
498
535
|
}
|
|
499
536
|
}
|
|
500
537
|
}
|
|
501
|
-
}
|
|
538
|
+
};
|
|
502
539
|
|
|
503
540
|
/**
|
|
504
541
|
* Establish websocket event loop for a NextJS page.
|
|
@@ -506,18 +543,18 @@ const applyClientStorageDelta = (client_storage, delta) => {
|
|
|
506
543
|
* @param initial_events The initial app events.
|
|
507
544
|
* @param client_storage The client storage object from context.js
|
|
508
545
|
*
|
|
509
|
-
* @returns [addEvents,
|
|
546
|
+
* @returns [addEvents, connectErrors] -
|
|
510
547
|
* addEvents is used to queue an event, and
|
|
511
|
-
*
|
|
548
|
+
* connectErrors is an array of reactive js error from the websocket connection (or null if connected).
|
|
512
549
|
*/
|
|
513
550
|
export const useEventLoop = (
|
|
514
551
|
dispatch,
|
|
515
552
|
initial_events = () => [],
|
|
516
|
-
client_storage = {}
|
|
553
|
+
client_storage = {}
|
|
517
554
|
) => {
|
|
518
|
-
const socket = useRef(null)
|
|
519
|
-
const router = useRouter()
|
|
520
|
-
const [
|
|
555
|
+
const socket = useRef(null);
|
|
556
|
+
const router = useRouter();
|
|
557
|
+
const [connectErrors, setConnectErrors] = useState([]);
|
|
521
558
|
|
|
522
559
|
// Function to add new events to the event queue.
|
|
523
560
|
const addEvents = (events, _e, event_actions) => {
|
|
@@ -527,22 +564,26 @@ export const useEventLoop = (
|
|
|
527
564
|
if (event_actions?.stopPropagation && _e?.stopPropagation) {
|
|
528
565
|
_e.stopPropagation();
|
|
529
566
|
}
|
|
530
|
-
queueEvents(events, socket)
|
|
531
|
-
}
|
|
567
|
+
queueEvents(events, socket);
|
|
568
|
+
};
|
|
532
569
|
|
|
533
|
-
const sentHydrate = useRef(false);
|
|
570
|
+
const sentHydrate = useRef(false); // Avoid double-hydrate due to React strict-mode
|
|
534
571
|
useEffect(() => {
|
|
535
572
|
if (router.isReady && !sentHydrate.current) {
|
|
536
|
-
|
|
537
|
-
addEvents(
|
|
538
|
-
{
|
|
573
|
+
const events = initial_events();
|
|
574
|
+
addEvents(
|
|
575
|
+
events.map((e) => ({
|
|
539
576
|
...e,
|
|
540
|
-
router_data: (({ pathname, query, asPath }) => ({
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
577
|
+
router_data: (({ pathname, query, asPath }) => ({
|
|
578
|
+
pathname,
|
|
579
|
+
query,
|
|
580
|
+
asPath,
|
|
581
|
+
}))(router),
|
|
582
|
+
}))
|
|
583
|
+
);
|
|
584
|
+
sentHydrate.current = true;
|
|
544
585
|
}
|
|
545
|
-
}, [router.isReady])
|
|
586
|
+
}, [router.isReady]);
|
|
546
587
|
|
|
547
588
|
// Main event loop.
|
|
548
589
|
useEffect(() => {
|
|
@@ -554,17 +595,22 @@ export const useEventLoop = (
|
|
|
554
595
|
if (Object.keys(initialState).length > 1) {
|
|
555
596
|
// Initialize the websocket connection.
|
|
556
597
|
if (!socket.current) {
|
|
557
|
-
connect(
|
|
598
|
+
connect(
|
|
599
|
+
socket,
|
|
600
|
+
dispatch,
|
|
601
|
+
["websocket", "polling"],
|
|
602
|
+
setConnectErrors,
|
|
603
|
+
client_storage
|
|
604
|
+
);
|
|
558
605
|
}
|
|
559
606
|
(async () => {
|
|
560
607
|
// Process all outstanding events.
|
|
561
608
|
while (event_queue.length > 0 && !event_processing) {
|
|
562
|
-
await processEvent(socket.current)
|
|
609
|
+
await processEvent(socket.current);
|
|
563
610
|
}
|
|
564
|
-
})()
|
|
611
|
+
})();
|
|
565
612
|
}
|
|
566
|
-
})
|
|
567
|
-
|
|
613
|
+
});
|
|
568
614
|
|
|
569
615
|
// localStorage event handling
|
|
570
616
|
useEffect(() => {
|
|
@@ -583,9 +629,12 @@ export const useEventLoop = (
|
|
|
583
629
|
// e is StorageEvent
|
|
584
630
|
const handleStorage = (e) => {
|
|
585
631
|
if (storage_to_state_map[e.key]) {
|
|
586
|
-
const vars = {}
|
|
587
|
-
vars[storage_to_state_map[e.key]] = e.newValue
|
|
588
|
-
const event = Event(
|
|
632
|
+
const vars = {};
|
|
633
|
+
vars[storage_to_state_map[e.key]] = e.newValue;
|
|
634
|
+
const event = Event(
|
|
635
|
+
`${state_name}.update_vars_internal_state.update_vars_internal`,
|
|
636
|
+
{ vars: vars }
|
|
637
|
+
);
|
|
589
638
|
addEvents([event], e);
|
|
590
639
|
}
|
|
591
640
|
};
|
|
@@ -594,18 +643,17 @@ export const useEventLoop = (
|
|
|
594
643
|
return () => window.removeEventListener("storage", handleStorage);
|
|
595
644
|
});
|
|
596
645
|
|
|
597
|
-
|
|
598
646
|
// Route after the initial page hydration.
|
|
599
647
|
useEffect(() => {
|
|
600
|
-
const change_complete = () => addEvents(onLoadInternalEvent())
|
|
601
|
-
router.events.on(
|
|
648
|
+
const change_complete = () => addEvents(onLoadInternalEvent());
|
|
649
|
+
router.events.on("routeChangeComplete", change_complete);
|
|
602
650
|
return () => {
|
|
603
|
-
router.events.off(
|
|
604
|
-
}
|
|
605
|
-
}, [router])
|
|
651
|
+
router.events.off("routeChangeComplete", change_complete);
|
|
652
|
+
};
|
|
653
|
+
}, [router]);
|
|
606
654
|
|
|
607
|
-
return [addEvents,
|
|
608
|
-
}
|
|
655
|
+
return [addEvents, connectErrors];
|
|
656
|
+
};
|
|
609
657
|
|
|
610
658
|
/***
|
|
611
659
|
* Check if a value is truthy in python.
|
|
@@ -626,17 +674,25 @@ export const getRefValue = (ref) => {
|
|
|
626
674
|
return;
|
|
627
675
|
}
|
|
628
676
|
if (ref.current.type == "checkbox") {
|
|
629
|
-
return ref.current.checked;
|
|
630
|
-
} else if (
|
|
631
|
-
|
|
632
|
-
|
|
677
|
+
return ref.current.checked; // chakra
|
|
678
|
+
} else if (
|
|
679
|
+
ref.current.className?.includes("rt-CheckboxButton") ||
|
|
680
|
+
ref.current.className?.includes("rt-SwitchButton")
|
|
681
|
+
) {
|
|
682
|
+
return ref.current.ariaChecked == "true"; // radix
|
|
683
|
+
} else if (ref.current.className?.includes("rt-SliderRoot")) {
|
|
633
684
|
// find the actual slider
|
|
634
|
-
return ref.current.querySelector(".rt-SliderThumb")
|
|
685
|
+
return ref.current.querySelector(".rt-SliderThumb")?.ariaValueNow;
|
|
635
686
|
} else {
|
|
636
687
|
//querySelector(":checked") is needed to get value from radio_group
|
|
637
|
-
return
|
|
688
|
+
return (
|
|
689
|
+
ref.current.value ||
|
|
690
|
+
(ref.current.querySelector &&
|
|
691
|
+
ref.current.querySelector(":checked") &&
|
|
692
|
+
ref.current.querySelector(":checked")?.value)
|
|
693
|
+
);
|
|
638
694
|
}
|
|
639
|
-
}
|
|
695
|
+
};
|
|
640
696
|
|
|
641
697
|
/**
|
|
642
698
|
* Get the values from a ref array.
|
|
@@ -648,21 +704,25 @@ export const getRefValues = (refs) => {
|
|
|
648
704
|
return;
|
|
649
705
|
}
|
|
650
706
|
// getAttribute is used by RangeSlider because it doesn't assign value
|
|
651
|
-
return refs.map((ref) =>
|
|
652
|
-
|
|
707
|
+
return refs.map((ref) =>
|
|
708
|
+
ref.current
|
|
709
|
+
? ref.current.value || ref.current.getAttribute("aria-valuenow")
|
|
710
|
+
: null
|
|
711
|
+
);
|
|
712
|
+
};
|
|
653
713
|
|
|
654
714
|
/**
|
|
655
|
-
* Spread two arrays or two objects.
|
|
656
|
-
* @param first The first array or object.
|
|
657
|
-
* @param second The second array or object.
|
|
658
|
-
* @returns The final merged array or object.
|
|
659
|
-
*/
|
|
715
|
+
* Spread two arrays or two objects.
|
|
716
|
+
* @param first The first array or object.
|
|
717
|
+
* @param second The second array or object.
|
|
718
|
+
* @returns The final merged array or object.
|
|
719
|
+
*/
|
|
660
720
|
export const spreadArraysOrObjects = (first, second) => {
|
|
661
721
|
if (Array.isArray(first) && Array.isArray(second)) {
|
|
662
722
|
return [...first, ...second];
|
|
663
|
-
} else if (typeof first ===
|
|
723
|
+
} else if (typeof first === "object" && typeof second === "object") {
|
|
664
724
|
return { ...first, ...second };
|
|
665
725
|
} else {
|
|
666
|
-
throw new Error(
|
|
726
|
+
throw new Error("Both parameters must be either arrays or objects.");
|
|
667
727
|
}
|
|
668
|
-
}
|
|
728
|
+
};
|