reflex 0.7.14a5__py3-none-any.whl → 0.8.0__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/jinja/app/rxconfig.py.jinja2 +4 -1
- reflex/.templates/jinja/web/package.json.jinja2 +1 -1
- reflex/.templates/jinja/web/pages/_app.js.jinja2 +21 -11
- reflex/.templates/jinja/web/pages/_document.js.jinja2 +1 -1
- reflex/.templates/jinja/web/pages/base_page.js.jinja2 +0 -1
- reflex/.templates/jinja/web/pages/stateful_component.js.jinja2 +4 -0
- reflex/.templates/jinja/web/styles/styles.css.jinja2 +1 -0
- reflex/.templates/jinja/web/utils/context.js.jinja2 +25 -8
- reflex/.templates/web/app/entry.client.js +8 -0
- reflex/.templates/web/app/routes.js +10 -0
- reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +12 -37
- reflex/.templates/web/postcss.config.js +1 -1
- reflex/.templates/web/react-router.config.js +6 -0
- reflex/.templates/web/styles/__reflex_style_reset.css +399 -0
- reflex/.templates/web/utils/client_side_routing.js +21 -19
- reflex/.templates/web/utils/react-theme.js +92 -0
- reflex/.templates/web/utils/state.js +251 -100
- reflex/.templates/web/vite-plugin-safari-cachebust.js +160 -0
- reflex/.templates/web/vite.config.js +39 -0
- reflex/__init__.py +1 -6
- reflex/__init__.pyi +327 -192
- reflex/app.py +103 -152
- reflex/base.py +1 -87
- reflex/compiler/compiler.py +70 -19
- reflex/compiler/templates.py +3 -3
- reflex/compiler/utils.py +91 -33
- reflex/components/__init__.py +0 -2
- reflex/components/__init__.pyi +34 -18
- reflex/components/base/__init__.py +1 -5
- reflex/components/base/__init__.pyi +30 -21
- reflex/components/base/app_wrap.pyi +7 -7
- reflex/components/base/body.pyi +7 -7
- reflex/components/base/document.py +18 -14
- reflex/components/base/document.pyi +88 -38
- reflex/components/base/error_boundary.pyi +7 -7
- reflex/components/base/fragment.pyi +7 -7
- reflex/components/base/link.pyi +12 -12
- reflex/components/base/meta.py +4 -15
- reflex/components/base/meta.pyi +31 -31
- reflex/components/base/script.py +60 -58
- reflex/components/base/script.pyi +248 -34
- reflex/components/base/strict_mode.pyi +7 -7
- reflex/components/component.py +146 -217
- reflex/components/core/__init__.py +1 -0
- reflex/components/core/__init__.pyi +77 -37
- reflex/components/core/auto_scroll.pyi +7 -7
- reflex/components/core/banner.pyi +33 -33
- reflex/components/core/client_side_routing.py +7 -6
- reflex/components/core/client_side_routing.pyi +8 -59
- reflex/components/core/clipboard.pyi +7 -7
- reflex/components/core/debounce.py +1 -0
- reflex/components/core/debounce.pyi +7 -7
- reflex/components/core/foreach.py +5 -4
- reflex/components/core/helmet.py +14 -0
- reflex/components/{next/base.pyi → core/helmet.pyi} +12 -10
- reflex/components/core/html.pyi +7 -7
- reflex/components/core/match.py +3 -3
- reflex/components/core/sticky.pyi +21 -20
- reflex/components/core/upload.py +4 -2
- reflex/components/core/upload.pyi +26 -25
- reflex/components/datadisplay/__init__.pyi +13 -7
- reflex/components/datadisplay/code.py +14 -79
- reflex/components/datadisplay/code.pyi +11 -13
- reflex/components/datadisplay/dataeditor.pyi +38 -15
- reflex/components/datadisplay/shiki_code_block.py +5 -3
- reflex/components/datadisplay/shiki_code_block.pyi +16 -15
- reflex/components/dynamic.py +5 -5
- reflex/components/el/__init__.pyi +506 -246
- reflex/components/el/element.pyi +7 -7
- reflex/components/el/elements/__init__.pyi +504 -245
- reflex/components/el/elements/base.pyi +7 -7
- reflex/components/el/elements/forms.pyi +146 -101
- reflex/components/el/elements/inline.pyi +142 -142
- reflex/components/el/elements/media.pyi +131 -130
- reflex/components/el/elements/metadata.pyi +32 -32
- reflex/components/el/elements/other.pyi +37 -37
- reflex/components/el/elements/scripts.pyi +17 -17
- reflex/components/el/elements/sectioning.pyi +77 -77
- reflex/components/el/elements/tables.pyi +52 -52
- reflex/components/el/elements/typography.pyi +77 -77
- reflex/components/field.py +175 -0
- reflex/components/gridjs/datatable.py +2 -2
- reflex/components/gridjs/datatable.pyi +14 -14
- reflex/components/lucide/icon.py +6 -2
- reflex/components/lucide/icon.pyi +19 -17
- reflex/components/markdown/markdown.py +5 -3
- reflex/components/markdown/markdown.pyi +7 -7
- reflex/components/moment/moment.py +1 -1
- reflex/components/moment/moment.pyi +7 -7
- reflex/components/plotly/plotly.py +12 -6
- reflex/components/plotly/plotly.pyi +50 -49
- reflex/components/props.py +376 -27
- reflex/components/radix/__init__.pyi +123 -65
- reflex/components/radix/primitives/__init__.pyi +6 -4
- reflex/components/radix/primitives/accordion.py +8 -1
- reflex/components/radix/primitives/accordion.pyi +37 -37
- reflex/components/radix/primitives/base.pyi +12 -12
- reflex/components/radix/primitives/drawer.pyi +56 -55
- reflex/components/radix/primitives/form.pyi +63 -53
- reflex/components/radix/primitives/progress.pyi +26 -25
- reflex/components/radix/primitives/slider.pyi +27 -27
- reflex/components/radix/themes/__init__.pyi +5 -6
- reflex/components/radix/themes/base.py +3 -3
- reflex/components/radix/themes/base.pyi +42 -42
- reflex/components/radix/themes/color_mode.py +5 -6
- reflex/components/radix/themes/color_mode.pyi +17 -17
- reflex/components/radix/themes/components/__init__.pyi +75 -38
- reflex/components/radix/themes/components/alert_dialog.pyi +37 -37
- reflex/components/radix/themes/components/aspect_ratio.pyi +7 -7
- reflex/components/radix/themes/components/avatar.pyi +7 -7
- reflex/components/radix/themes/components/badge.pyi +7 -7
- reflex/components/radix/themes/components/button.pyi +7 -7
- reflex/components/radix/themes/components/callout.pyi +26 -25
- reflex/components/radix/themes/components/card.pyi +7 -7
- reflex/components/radix/themes/components/checkbox.pyi +16 -15
- reflex/components/radix/themes/components/checkbox_cards.pyi +12 -12
- reflex/components/radix/themes/components/checkbox_group.pyi +12 -12
- reflex/components/radix/themes/components/context_menu.pyi +67 -67
- reflex/components/radix/themes/components/data_list.pyi +22 -22
- reflex/components/radix/themes/components/dialog.pyi +36 -35
- reflex/components/radix/themes/components/dropdown_menu.pyi +42 -42
- reflex/components/radix/themes/components/hover_card.pyi +21 -20
- reflex/components/radix/themes/components/icon_button.pyi +7 -7
- reflex/components/radix/themes/components/inset.pyi +7 -7
- reflex/components/radix/themes/components/popover.pyi +22 -22
- reflex/components/radix/themes/components/progress.pyi +7 -7
- reflex/components/radix/themes/components/radio.pyi +7 -7
- reflex/components/radix/themes/components/radio_cards.pyi +12 -12
- reflex/components/radix/themes/components/radio_group.pyi +21 -20
- reflex/components/radix/themes/components/scroll_area.pyi +7 -7
- reflex/components/radix/themes/components/segmented_control.pyi +12 -12
- reflex/components/radix/themes/components/select.pyi +46 -45
- reflex/components/radix/themes/components/separator.pyi +7 -7
- reflex/components/radix/themes/components/skeleton.pyi +7 -7
- reflex/components/radix/themes/components/slider.pyi +17 -9
- reflex/components/radix/themes/components/spinner.pyi +7 -7
- reflex/components/radix/themes/components/switch.pyi +7 -7
- reflex/components/radix/themes/components/table.pyi +37 -37
- reflex/components/radix/themes/components/tabs.pyi +26 -25
- reflex/components/radix/themes/components/text_area.pyi +15 -9
- reflex/components/radix/themes/components/text_field.pyi +32 -19
- reflex/components/radix/themes/components/tooltip.pyi +7 -7
- reflex/components/radix/themes/layout/__init__.pyi +27 -14
- reflex/components/radix/themes/layout/base.pyi +7 -7
- reflex/components/radix/themes/layout/box.pyi +7 -7
- reflex/components/radix/themes/layout/center.pyi +7 -7
- reflex/components/radix/themes/layout/container.pyi +7 -7
- reflex/components/radix/themes/layout/flex.pyi +7 -7
- reflex/components/radix/themes/layout/grid.pyi +7 -7
- reflex/components/radix/themes/layout/list.pyi +26 -25
- reflex/components/radix/themes/layout/section.pyi +7 -7
- reflex/components/radix/themes/layout/spacer.pyi +7 -7
- reflex/components/radix/themes/layout/stack.pyi +17 -17
- reflex/components/radix/themes/typography/__init__.pyi +7 -5
- reflex/components/radix/themes/typography/blockquote.pyi +7 -7
- reflex/components/radix/themes/typography/code.pyi +7 -7
- reflex/components/radix/themes/typography/heading.pyi +7 -7
- reflex/components/radix/themes/typography/link.py +46 -11
- reflex/components/radix/themes/typography/link.pyi +312 -9
- reflex/components/radix/themes/typography/text.pyi +36 -35
- reflex/components/react_player/audio.pyi +10 -8
- reflex/components/react_player/react_player.pyi +7 -7
- reflex/components/react_player/video.pyi +10 -8
- reflex/components/recharts/__init__.pyi +208 -100
- reflex/components/recharts/cartesian.py +10 -8
- reflex/components/recharts/cartesian.pyi +90 -94
- reflex/components/recharts/charts.py +4 -2
- reflex/components/recharts/charts.pyi +49 -49
- reflex/components/recharts/general.pyi +31 -31
- reflex/components/recharts/polar.py +8 -4
- reflex/components/recharts/polar.pyi +23 -23
- reflex/components/recharts/recharts.py +2 -2
- reflex/components/recharts/recharts.pyi +12 -12
- reflex/components/sonner/toast.py +3 -3
- reflex/components/sonner/toast.pyi +9 -9
- reflex/config.py +10 -113
- reflex/constants/__init__.py +2 -2
- reflex/constants/base.py +28 -11
- reflex/constants/compiler.py +12 -3
- reflex/constants/event.py +1 -0
- reflex/constants/installer.py +26 -20
- reflex/constants/route.py +27 -8
- reflex/constants/state.py +2 -0
- reflex/custom_components/custom_components.py +0 -14
- reflex/environment.py +77 -5
- reflex/event.py +178 -81
- reflex/experimental/__init__.py +0 -30
- reflex/istate/__init__.py +69 -0
- reflex/istate/manager.py +1 -0
- reflex/istate/proxy.py +5 -3
- reflex/page.py +0 -27
- reflex/plugins/__init__.py +3 -2
- reflex/plugins/base.py +5 -1
- reflex/plugins/shared_tailwind.py +215 -0
- reflex/plugins/sitemap.py +206 -0
- reflex/plugins/tailwind_v3.py +15 -108
- reflex/plugins/tailwind_v4.py +18 -110
- reflex/reflex.py +1 -0
- reflex/route.py +157 -75
- reflex/state.py +171 -155
- reflex/testing.py +86 -16
- reflex/utils/build.py +38 -82
- reflex/utils/exec.py +83 -175
- reflex/utils/export.py +2 -2
- reflex/utils/format.py +1 -5
- reflex/utils/imports.py +5 -16
- reflex/utils/misc.py +67 -0
- reflex/utils/prerequisites.py +66 -68
- reflex/utils/processes.py +24 -47
- reflex/utils/pyi_generator.py +44 -49
- reflex/utils/serializers.py +14 -1
- reflex/utils/telemetry.py +0 -15
- reflex/utils/types.py +197 -62
- reflex/vars/__init__.py +2 -0
- reflex/vars/base.py +367 -134
- {reflex-0.7.14a5.dist-info → reflex-0.8.0.dist-info}/METADATA +15 -8
- reflex-0.8.0.dist-info/RECORD +403 -0
- reflex/.templates/web/next.config.js +0 -7
- reflex/components/base/head.py +0 -20
- reflex/components/base/head.pyi +0 -116
- reflex/components/next/__init__.py +0 -10
- reflex/components/next/base.py +0 -7
- reflex/components/next/image.py +0 -117
- reflex/components/next/image.pyi +0 -94
- reflex/components/next/link.py +0 -20
- reflex/components/next/link.pyi +0 -67
- reflex/components/next/video.py +0 -38
- reflex/components/next/video.pyi +0 -68
- reflex/components/suneditor/__init__.py +0 -5
- reflex/components/suneditor/editor.py +0 -269
- reflex/components/suneditor/editor.pyi +0 -199
- reflex/experimental/layout.py +0 -254
- reflex-0.7.14a5.dist-info/RECORD +0 -407
- {reflex-0.7.14a5.dist-info → reflex-0.8.0.dist-info}/WHEEL +0 -0
- {reflex-0.7.14a5.dist-info → reflex-0.8.0.dist-info}/entry_points.txt +0 -0
- {reflex-0.7.14a5.dist-info → reflex-0.8.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
// State management for Reflex web apps.
|
|
2
|
-
import axios from "axios";
|
|
3
2
|
import io from "socket.io-client";
|
|
4
3
|
import JSON5 from "json5";
|
|
5
4
|
import env from "$/env.json";
|
|
6
5
|
import reflexEnvironment from "$/reflex.json";
|
|
7
6
|
import Cookies from "universal-cookie";
|
|
8
|
-
import { useEffect, useRef, useState } from "react";
|
|
9
|
-
import
|
|
7
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
8
|
+
import {
|
|
9
|
+
useLocation,
|
|
10
|
+
useNavigate,
|
|
11
|
+
useSearchParams,
|
|
12
|
+
useParams,
|
|
13
|
+
} from "react-router";
|
|
10
14
|
import {
|
|
11
15
|
initialEvents,
|
|
12
16
|
initialState,
|
|
13
17
|
onLoadInternalEvent,
|
|
14
18
|
state_name,
|
|
15
19
|
exception_state_name,
|
|
16
|
-
} from "$/utils/context
|
|
20
|
+
} from "$/utils/context";
|
|
17
21
|
import debounce from "$/utils/helpers/debounce";
|
|
18
22
|
import throttle from "$/utils/helpers/throttle";
|
|
19
23
|
|
|
@@ -158,24 +162,48 @@ export const evalReactComponent = async (component) => {
|
|
|
158
162
|
* Only Queue and process events when websocket connection exists.
|
|
159
163
|
* @param event The event to queue.
|
|
160
164
|
* @param socket The socket object to send the event on.
|
|
165
|
+
* @param navigate The navigate function from React Router
|
|
166
|
+
* @param params The params object from React Router
|
|
161
167
|
*
|
|
162
168
|
* @returns Adds event to queue and processes it if websocket exits, does nothing otherwise.
|
|
163
169
|
*/
|
|
164
|
-
export const queueEventIfSocketExists = async (
|
|
170
|
+
export const queueEventIfSocketExists = async (
|
|
171
|
+
events,
|
|
172
|
+
socket,
|
|
173
|
+
navigate,
|
|
174
|
+
params,
|
|
175
|
+
) => {
|
|
165
176
|
if (!socket) {
|
|
166
177
|
return;
|
|
167
178
|
}
|
|
168
|
-
await queueEvents(events, socket);
|
|
179
|
+
await queueEvents(events, socket, navigate, params);
|
|
169
180
|
};
|
|
170
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Check if a string is a valid HTTP URL.
|
|
184
|
+
* @param string The string to check.
|
|
185
|
+
*
|
|
186
|
+
* @returns The URL object if valid, undefined otherwise.
|
|
187
|
+
* */
|
|
188
|
+
function urlFrom(string) {
|
|
189
|
+
try {
|
|
190
|
+
return new URL(string);
|
|
191
|
+
} catch {
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
|
|
171
197
|
/**
|
|
172
198
|
* Handle frontend event or send the event to the backend via Websocket.
|
|
173
199
|
* @param event The event to send.
|
|
174
200
|
* @param socket The socket object to send the event on.
|
|
201
|
+
* @param navigate The navigate function from useNavigate
|
|
202
|
+
* @param params The params object from useParams
|
|
175
203
|
*
|
|
176
204
|
* @returns True if the event was sent, false if it was handled locally.
|
|
177
205
|
*/
|
|
178
|
-
export const applyEvent = async (event, socket) => {
|
|
206
|
+
export const applyEvent = async (event, socket, navigate, params) => {
|
|
179
207
|
// Handle special events
|
|
180
208
|
if (event.name == "_redirect") {
|
|
181
209
|
if ((event.payload.path ?? undefined) === undefined) {
|
|
@@ -183,41 +211,54 @@ export const applyEvent = async (event, socket) => {
|
|
|
183
211
|
}
|
|
184
212
|
if (event.payload.external) {
|
|
185
213
|
window.open(event.payload.path, "_blank", "noopener");
|
|
186
|
-
|
|
187
|
-
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
const url = urlFrom(event.payload.path);
|
|
217
|
+
let pathname = event.payload.path;
|
|
218
|
+
if (url) {
|
|
219
|
+
if (url.host !== window.location.host) {
|
|
220
|
+
// External URL
|
|
221
|
+
window.location.assign(event.payload.path);
|
|
222
|
+
return false;
|
|
223
|
+
} else {
|
|
224
|
+
pathname = url.pathname + url.search + url.hash;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (event.payload.replace) {
|
|
228
|
+
navigate(pathname, { replace: true });
|
|
188
229
|
} else {
|
|
189
|
-
|
|
230
|
+
navigate(pathname);
|
|
190
231
|
}
|
|
191
232
|
return false;
|
|
192
233
|
}
|
|
193
234
|
|
|
194
235
|
if (event.name == "_remove_cookie") {
|
|
195
236
|
cookies.remove(event.payload.key, { ...event.payload.options });
|
|
196
|
-
queueEventIfSocketExists(initialEvents(), socket);
|
|
237
|
+
queueEventIfSocketExists(initialEvents(), socket, navigate, params);
|
|
197
238
|
return false;
|
|
198
239
|
}
|
|
199
240
|
|
|
200
241
|
if (event.name == "_clear_local_storage") {
|
|
201
242
|
localStorage.clear();
|
|
202
|
-
queueEventIfSocketExists(initialEvents(), socket);
|
|
243
|
+
queueEventIfSocketExists(initialEvents(), socket, navigate, params);
|
|
203
244
|
return false;
|
|
204
245
|
}
|
|
205
246
|
|
|
206
247
|
if (event.name == "_remove_local_storage") {
|
|
207
248
|
localStorage.removeItem(event.payload.key);
|
|
208
|
-
queueEventIfSocketExists(initialEvents(), socket);
|
|
249
|
+
queueEventIfSocketExists(initialEvents(), socket, navigate, params);
|
|
209
250
|
return false;
|
|
210
251
|
}
|
|
211
252
|
|
|
212
253
|
if (event.name == "_clear_session_storage") {
|
|
213
254
|
sessionStorage.clear();
|
|
214
|
-
queueEvents(initialEvents(), socket);
|
|
255
|
+
queueEvents(initialEvents(), socket, navigate, params);
|
|
215
256
|
return false;
|
|
216
257
|
}
|
|
217
258
|
|
|
218
259
|
if (event.name == "_remove_session_storage") {
|
|
219
260
|
sessionStorage.removeItem(event.payload.key);
|
|
220
|
-
queueEvents(initialEvents(), socket);
|
|
261
|
+
queueEvents(initialEvents(), socket, navigate, params);
|
|
221
262
|
return false;
|
|
222
263
|
}
|
|
223
264
|
|
|
@@ -322,11 +363,15 @@ export const applyEvent = async (event, socket) => {
|
|
|
322
363
|
event.router_data === undefined ||
|
|
323
364
|
Object.keys(event.router_data).length === 0
|
|
324
365
|
) {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
366
|
+
// Since we don't have router directly, we need to get info from our hooks
|
|
367
|
+
event.router_data = {
|
|
368
|
+
pathname: window.location.pathname,
|
|
369
|
+
query: {
|
|
370
|
+
...Object.fromEntries(new URLSearchParams(window.location.search)),
|
|
371
|
+
...params(),
|
|
372
|
+
},
|
|
373
|
+
asPath: window.location.pathname + window.location.search,
|
|
374
|
+
};
|
|
330
375
|
}
|
|
331
376
|
|
|
332
377
|
// Send the event to the server.
|
|
@@ -342,15 +387,22 @@ export const applyEvent = async (event, socket) => {
|
|
|
342
387
|
* Send an event to the server via REST.
|
|
343
388
|
* @param event The current event.
|
|
344
389
|
* @param socket The socket object to send the response event(s) on.
|
|
390
|
+
* @param navigate The navigate function from React Router
|
|
391
|
+
* @param params The params object from React Router
|
|
345
392
|
*
|
|
346
393
|
* @returns Whether the event was sent.
|
|
347
394
|
*/
|
|
348
|
-
export const applyRestEvent = async (event, socket) => {
|
|
395
|
+
export const applyRestEvent = async (event, socket, navigate, params) => {
|
|
349
396
|
let eventSent = false;
|
|
350
397
|
if (event.handler === "uploadFiles") {
|
|
351
398
|
if (event.payload.files === undefined || event.payload.files.length === 0) {
|
|
352
399
|
// Submit the event over the websocket to trigger the event handler.
|
|
353
|
-
return await applyEvent(
|
|
400
|
+
return await applyEvent(
|
|
401
|
+
Event(event.name, { files: [] }),
|
|
402
|
+
socket,
|
|
403
|
+
navigate,
|
|
404
|
+
params,
|
|
405
|
+
);
|
|
354
406
|
}
|
|
355
407
|
|
|
356
408
|
// Start upload, but do not wait for it, which would block other events.
|
|
@@ -371,8 +423,16 @@ export const applyRestEvent = async (event, socket) => {
|
|
|
371
423
|
* @param events Array of events to queue.
|
|
372
424
|
* @param socket The socket object to send the event on.
|
|
373
425
|
* @param prepend Whether to place the events at the beginning of the queue.
|
|
426
|
+
* @param navigate The navigate function from React Router
|
|
427
|
+
* @param params The params object from React Router
|
|
374
428
|
*/
|
|
375
|
-
export const queueEvents = async (
|
|
429
|
+
export const queueEvents = async (
|
|
430
|
+
events,
|
|
431
|
+
socket,
|
|
432
|
+
prepend,
|
|
433
|
+
navigate,
|
|
434
|
+
params,
|
|
435
|
+
) => {
|
|
376
436
|
if (prepend) {
|
|
377
437
|
// Drain the existing queue and place it after the given events.
|
|
378
438
|
events = [
|
|
@@ -383,14 +443,16 @@ export const queueEvents = async (events, socket, prepend) => {
|
|
|
383
443
|
];
|
|
384
444
|
}
|
|
385
445
|
event_queue.push(...events.filter((e) => e !== undefined && e !== null));
|
|
386
|
-
await processEvent(socket.current);
|
|
446
|
+
await processEvent(socket.current, navigate, params);
|
|
387
447
|
};
|
|
388
448
|
|
|
389
449
|
/**
|
|
390
450
|
* Process an event off the event queue.
|
|
391
451
|
* @param socket The socket object to send the event on.
|
|
452
|
+
* @param navigate The navigate function from React Router
|
|
453
|
+
* @param params The params object from React Router
|
|
392
454
|
*/
|
|
393
|
-
export const processEvent = async (socket) => {
|
|
455
|
+
export const processEvent = async (socket, navigate, params) => {
|
|
394
456
|
// Only proceed if the socket is up and no event in the queue uses state, otherwise we throw the event into the void
|
|
395
457
|
if (!socket && isStateful()) {
|
|
396
458
|
return;
|
|
@@ -410,16 +472,16 @@ export const processEvent = async (socket) => {
|
|
|
410
472
|
let eventSent = false;
|
|
411
473
|
// Process events with handlers via REST and all others via websockets.
|
|
412
474
|
if (event.handler) {
|
|
413
|
-
eventSent = await applyRestEvent(event, socket);
|
|
475
|
+
eventSent = await applyRestEvent(event, socket, navigate, params);
|
|
414
476
|
} else {
|
|
415
|
-
eventSent = await applyEvent(event, socket);
|
|
477
|
+
eventSent = await applyEvent(event, socket, navigate, params);
|
|
416
478
|
}
|
|
417
479
|
// If no event was sent, set processing to false.
|
|
418
480
|
if (!eventSent) {
|
|
419
481
|
event_processing = false;
|
|
420
482
|
// recursively call processEvent to drain the queue, since there is
|
|
421
483
|
// no state update to trigger the useEffect event loop.
|
|
422
|
-
await processEvent(socket);
|
|
484
|
+
await processEvent(socket, navigate, params);
|
|
423
485
|
}
|
|
424
486
|
};
|
|
425
487
|
|
|
@@ -430,6 +492,8 @@ export const processEvent = async (socket) => {
|
|
|
430
492
|
* @param transports The transports to use.
|
|
431
493
|
* @param setConnectErrors The function to update connection error value.
|
|
432
494
|
* @param client_storage The client storage object from context.js
|
|
495
|
+
* @param navigate The navigate function from React Router
|
|
496
|
+
* @param params The params object from React Router
|
|
433
497
|
*/
|
|
434
498
|
export const connect = async (
|
|
435
499
|
socket,
|
|
@@ -437,6 +501,8 @@ export const connect = async (
|
|
|
437
501
|
transports,
|
|
438
502
|
setConnectErrors,
|
|
439
503
|
client_storage = {},
|
|
504
|
+
navigate,
|
|
505
|
+
params,
|
|
440
506
|
) => {
|
|
441
507
|
// Get backend URL object from the endpoint.
|
|
442
508
|
const endpoint = getBackendURL(EVENTURL);
|
|
@@ -511,12 +577,12 @@ export const connect = async (
|
|
|
511
577
|
applyClientStorageDelta(client_storage, update.delta);
|
|
512
578
|
event_processing = !update.final;
|
|
513
579
|
if (update.events) {
|
|
514
|
-
queueEvents(update.events, socket);
|
|
580
|
+
queueEvents(update.events, socket, false, navigate, params);
|
|
515
581
|
}
|
|
516
582
|
});
|
|
517
583
|
socket.current.on("reload", async (event) => {
|
|
518
584
|
event_processing = false;
|
|
519
|
-
queueEvents([...initialEvents(), event], socket, true);
|
|
585
|
+
queueEvents([...initialEvents(), event], socket, true, navigate, params);
|
|
520
586
|
});
|
|
521
587
|
|
|
522
588
|
document.addEventListener("visibilitychange", checkVisibility);
|
|
@@ -588,17 +654,6 @@ export const uploadFiles = async (
|
|
|
588
654
|
};
|
|
589
655
|
|
|
590
656
|
const controller = new AbortController();
|
|
591
|
-
const config = {
|
|
592
|
-
headers: {
|
|
593
|
-
"Reflex-Client-Token": getToken(),
|
|
594
|
-
"Reflex-Event-Handler": handler,
|
|
595
|
-
},
|
|
596
|
-
signal: controller.signal,
|
|
597
|
-
onDownloadProgress: eventHandler,
|
|
598
|
-
};
|
|
599
|
-
if (on_upload_progress) {
|
|
600
|
-
config["onUploadProgress"] = on_upload_progress;
|
|
601
|
-
}
|
|
602
657
|
const formdata = new FormData();
|
|
603
658
|
|
|
604
659
|
// Add the token and handler to the file name.
|
|
@@ -609,26 +664,85 @@ export const uploadFiles = async (
|
|
|
609
664
|
// Send the file to the server.
|
|
610
665
|
refs[upload_ref_name] = controller;
|
|
611
666
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
667
|
+
return new Promise((resolve, reject) => {
|
|
668
|
+
const xhr = new XMLHttpRequest();
|
|
669
|
+
|
|
670
|
+
// Set up event handlers
|
|
671
|
+
xhr.onload = function () {
|
|
672
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
673
|
+
resolve({
|
|
674
|
+
data: xhr.responseText,
|
|
675
|
+
status: xhr.status,
|
|
676
|
+
statusText: xhr.statusText,
|
|
677
|
+
headers: {
|
|
678
|
+
get: (name) => xhr.getResponseHeader(name),
|
|
679
|
+
},
|
|
680
|
+
});
|
|
681
|
+
} else {
|
|
682
|
+
reject(new Error(`HTTP error! status: ${xhr.status}`));
|
|
683
|
+
}
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
xhr.onerror = function () {
|
|
687
|
+
reject(new Error("Network error"));
|
|
688
|
+
};
|
|
689
|
+
|
|
690
|
+
xhr.onabort = function () {
|
|
691
|
+
reject(new Error("Upload aborted"));
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
// Handle upload progress
|
|
695
|
+
if (on_upload_progress) {
|
|
696
|
+
xhr.upload.onprogress = function (event) {
|
|
697
|
+
if (event.lengthComputable) {
|
|
698
|
+
const progressEvent = {
|
|
699
|
+
loaded: event.loaded,
|
|
700
|
+
total: event.total,
|
|
701
|
+
progress: event.loaded / event.total,
|
|
702
|
+
};
|
|
703
|
+
on_upload_progress(progressEvent);
|
|
704
|
+
}
|
|
705
|
+
};
|
|
627
706
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
707
|
+
|
|
708
|
+
// Handle download progress with streaming response parsing
|
|
709
|
+
xhr.onprogress = function (event) {
|
|
710
|
+
if (eventHandler) {
|
|
711
|
+
const progressEvent = {
|
|
712
|
+
event: {
|
|
713
|
+
target: {
|
|
714
|
+
responseText: xhr.responseText,
|
|
715
|
+
},
|
|
716
|
+
},
|
|
717
|
+
progress: event.lengthComputable ? event.loaded / event.total : 0,
|
|
718
|
+
};
|
|
719
|
+
eventHandler(progressEvent);
|
|
720
|
+
}
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
// Handle abort controller
|
|
724
|
+
controller.signal.addEventListener("abort", () => {
|
|
725
|
+
xhr.abort();
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
// Configure and send request
|
|
729
|
+
xhr.open("POST", getBackendURL(UPLOADURL));
|
|
730
|
+
xhr.setRequestHeader("Reflex-Client-Token", getToken());
|
|
731
|
+
xhr.setRequestHeader("Reflex-Event-Handler", handler);
|
|
732
|
+
|
|
733
|
+
try {
|
|
734
|
+
xhr.send(formdata);
|
|
735
|
+
} catch (error) {
|
|
736
|
+
reject(error);
|
|
737
|
+
}
|
|
738
|
+
})
|
|
739
|
+
.catch((error) => {
|
|
740
|
+
console.log("Upload error:", error.message);
|
|
741
|
+
return false;
|
|
742
|
+
})
|
|
743
|
+
.finally(() => {
|
|
744
|
+
delete refs[upload_ref_name];
|
|
745
|
+
});
|
|
632
746
|
};
|
|
633
747
|
|
|
634
748
|
/**
|
|
@@ -710,7 +824,10 @@ const applyClientStorageDelta = (client_storage, delta) => {
|
|
|
710
824
|
);
|
|
711
825
|
if (unqualified_states.length === 1) {
|
|
712
826
|
const main_state = delta[unqualified_states[0]];
|
|
713
|
-
if (
|
|
827
|
+
if (
|
|
828
|
+
main_state.is_hydrated_rx_state_ !== undefined &&
|
|
829
|
+
!main_state.is_hydrated_rx_state_
|
|
830
|
+
) {
|
|
714
831
|
// skip if the state is not hydrated yet, since all client storage
|
|
715
832
|
// values are sent in the hydrate event
|
|
716
833
|
return;
|
|
@@ -748,7 +865,7 @@ const applyClientStorageDelta = (client_storage, delta) => {
|
|
|
748
865
|
};
|
|
749
866
|
|
|
750
867
|
/**
|
|
751
|
-
* Establish websocket event loop for a
|
|
868
|
+
* Establish websocket event loop for a React Router page.
|
|
752
869
|
* @param dispatch The reducer dispatch function to update state.
|
|
753
870
|
* @param initial_events The initial app events.
|
|
754
871
|
* @param client_storage The client storage object from context.js
|
|
@@ -763,11 +880,25 @@ export const useEventLoop = (
|
|
|
763
880
|
client_storage = {},
|
|
764
881
|
) => {
|
|
765
882
|
const socket = useRef(null);
|
|
766
|
-
const
|
|
883
|
+
const location = useLocation();
|
|
884
|
+
const navigate = useNavigate();
|
|
885
|
+
const paramsR = useParams();
|
|
886
|
+
const prevLocationRef = useRef(location);
|
|
887
|
+
const [searchParams] = useSearchParams();
|
|
767
888
|
const [connectErrors, setConnectErrors] = useState([]);
|
|
889
|
+
const params = useRef(paramsR);
|
|
890
|
+
|
|
891
|
+
useEffect(() => {
|
|
892
|
+
const { "*": splat, ...remainingParams } = paramsR;
|
|
893
|
+
if (splat) {
|
|
894
|
+
params.current = { ...remainingParams, splat: splat.split("/") };
|
|
895
|
+
} else {
|
|
896
|
+
params.current = remainingParams;
|
|
897
|
+
}
|
|
898
|
+
}, [paramsR]);
|
|
768
899
|
|
|
769
900
|
// Function to add new events to the event queue.
|
|
770
|
-
const addEvents = (events, args, event_actions) => {
|
|
901
|
+
const addEvents = useCallback((events, args, event_actions) => {
|
|
771
902
|
const _events = events.filter((e) => e !== undefined && e !== null);
|
|
772
903
|
|
|
773
904
|
if (!(args instanceof Array)) {
|
|
@@ -803,32 +934,38 @@ export const useEventLoop = (
|
|
|
803
934
|
// If debounce is used, queue the events after some delay
|
|
804
935
|
debounce(
|
|
805
936
|
combined_name,
|
|
806
|
-
() =>
|
|
937
|
+
() =>
|
|
938
|
+
queueEvents(_events, socket, false, navigate, () => params.current),
|
|
807
939
|
event_actions.debounce,
|
|
808
940
|
);
|
|
809
941
|
} else {
|
|
810
|
-
queueEvents(_events, socket);
|
|
942
|
+
queueEvents(_events, socket, false, navigate, () => params.current);
|
|
811
943
|
}
|
|
812
|
-
};
|
|
944
|
+
}, []);
|
|
813
945
|
|
|
814
946
|
const sentHydrate = useRef(false); // Avoid double-hydrate due to React strict-mode
|
|
815
947
|
useEffect(() => {
|
|
816
|
-
if (
|
|
948
|
+
if (!sentHydrate.current) {
|
|
817
949
|
queueEvents(
|
|
818
950
|
initial_events().map((e) => ({
|
|
819
951
|
...e,
|
|
820
|
-
router_data:
|
|
821
|
-
pathname,
|
|
822
|
-
query
|
|
823
|
-
|
|
824
|
-
|
|
952
|
+
router_data: {
|
|
953
|
+
pathname: location.pathname,
|
|
954
|
+
query: {
|
|
955
|
+
...Object.fromEntries(searchParams.entries()),
|
|
956
|
+
...params.current,
|
|
957
|
+
},
|
|
958
|
+
asPath: location.pathname + location.search,
|
|
959
|
+
},
|
|
825
960
|
})),
|
|
826
961
|
socket,
|
|
827
962
|
true,
|
|
963
|
+
navigate,
|
|
964
|
+
() => params.current,
|
|
828
965
|
);
|
|
829
966
|
sentHydrate.current = true;
|
|
830
967
|
}
|
|
831
|
-
}, [
|
|
968
|
+
}, []);
|
|
832
969
|
|
|
833
970
|
// Handle frontend errors and send them to the backend via websocket.
|
|
834
971
|
useEffect(() => {
|
|
@@ -871,6 +1008,8 @@ export const useEventLoop = (
|
|
|
871
1008
|
["websocket"],
|
|
872
1009
|
setConnectErrors,
|
|
873
1010
|
client_storage,
|
|
1011
|
+
navigate,
|
|
1012
|
+
() => params.current,
|
|
874
1013
|
);
|
|
875
1014
|
}
|
|
876
1015
|
}
|
|
@@ -885,14 +1024,14 @@ export const useEventLoop = (
|
|
|
885
1024
|
|
|
886
1025
|
// Main event loop.
|
|
887
1026
|
useEffect(() => {
|
|
888
|
-
// Skip if the
|
|
889
|
-
if (
|
|
1027
|
+
// Skip if the backend is disabled
|
|
1028
|
+
if (isBackendDisabled()) {
|
|
890
1029
|
return;
|
|
891
1030
|
}
|
|
892
1031
|
(async () => {
|
|
893
1032
|
// Process all outstanding events.
|
|
894
1033
|
while (event_queue.length > 0 && !event_processing) {
|
|
895
|
-
await processEvent(socket.current);
|
|
1034
|
+
await processEvent(socket.current, navigate, () => params.current);
|
|
896
1035
|
}
|
|
897
1036
|
})();
|
|
898
1037
|
});
|
|
@@ -928,31 +1067,43 @@ export const useEventLoop = (
|
|
|
928
1067
|
return () => window.removeEventListener("storage", handleStorage);
|
|
929
1068
|
});
|
|
930
1069
|
|
|
931
|
-
|
|
1070
|
+
const handleNavigationEvents = useRef(false);
|
|
1071
|
+
// Route after the initial page hydration
|
|
932
1072
|
useEffect(() => {
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
//
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
1073
|
+
// The first time this effect runs is initial load, so don't handle
|
|
1074
|
+
// any navigation events.
|
|
1075
|
+
if (!handleNavigationEvents.current) {
|
|
1076
|
+
handleNavigationEvents.current = true;
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
if (location.state?.fromNotFound) {
|
|
1080
|
+
// If the redirect is from a 404 page, we skip onLoadInternalEvent,
|
|
1081
|
+
// since it was already run when the 404 page was first rendered.
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
// This will run when the location changes
|
|
1085
|
+
if (
|
|
1086
|
+
location.pathname + location.search ===
|
|
1087
|
+
prevLocationRef.current.pathname + prevLocationRef.current.search
|
|
1088
|
+
) {
|
|
1089
|
+
if (location.hash) {
|
|
1090
|
+
// If the hash is the same, we don't need to do anything.
|
|
1091
|
+
return;
|
|
945
1092
|
}
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
// Equivalent to routeChangeStart - runs when navigation begins
|
|
1096
|
+
const main_state_dispatch = dispatch["reflex___state____state"];
|
|
1097
|
+
if (main_state_dispatch !== undefined) {
|
|
1098
|
+
main_state_dispatch({ is_hydrated_rx_state_: false });
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
// Equivalent to routeChangeComplete - runs after navigation completes
|
|
1102
|
+
addEvents(onLoadInternalEvent());
|
|
1103
|
+
|
|
1104
|
+
// Update the ref
|
|
1105
|
+
prevLocationRef.current = location;
|
|
1106
|
+
}, [location, dispatch, onLoadInternalEvent, addEvents]);
|
|
956
1107
|
|
|
957
1108
|
return [addEvents, connectErrors];
|
|
958
1109
|
};
|