@@ -351,38 +351,161 @@ def theme_template(theme: str):
351351 return f"""export default { theme } """
352352
353353
354- def _context_template (** kwargs ):
354+ def context_template (
355+ * ,
356+ is_dev_mode : bool ,
357+ default_color_mode : str ,
358+ initial_state : dict [str , Any ] | None = None ,
359+ state_name : str | None = None ,
360+ client_storage : dict [str , dict [str , dict [str , Any ]]] | None = None ,
361+ ):
355362 """Template for the context file.
356363
357364 Args:
358- **kwargs: Template context variables including initial_state, state_name, etc.
365+ initial_state: The initial state for the context.
366+ state_name: The name of the state.
367+ client_storage: The client storage for the context.
368+ is_dev_mode: Whether the app is in development mode.
369+ default_color_mode: The default color mode for the context.
359370
360371 Returns:
361372 Rendered context file content as string.
362373 """
363- initial_state = kwargs .get ("initial_state" , "null" )
364- state_name = kwargs .get ("state_name" , "" )
365- client_storage = kwargs .get ("client_storage" , "" )
366- is_dev_mode = kwargs .get ("is_dev_mode" , False )
367- default_color_mode = kwargs .get ("default_color_mode" , "" )
374+ initial_state = initial_state or {}
375+ state_contexts_str = "" .join (
376+ [f"{ state_name } : createContext(null)," for state_name in initial_state ]
377+ )
378+
379+ state_str = (
380+ rf"""
381+ export const state_name = "{ state_name } "
382+
383+ export const exception_state_name = "{ constants .CompileVars .FRONTEND_EXCEPTION_STATE_FULL } "
384+
385+ // These events are triggered on initial load and each page navigation.
386+ export const onLoadInternalEvent = () => {{
387+ const internal_events = [];
388+
389+ // Get tracked cookie and local storage vars to send to the backend.
390+ const client_storage_vars = hydrateClientStorage(clientStorage);
391+ // But only send the vars if any are actually set in the browser.
392+ if (client_storage_vars && Object.keys(client_storage_vars).length !== 0) {{
393+ internal_events.push(
394+ Event(
395+ '{ state_name } .{ constants .CompileVars .UPDATE_VARS_INTERNAL } ',
396+ {{vars: client_storage_vars}},
397+ ),
398+ );
399+ }}
400+
401+ // `on_load_internal` triggers the correct on_load event(s) for the current page.
402+ // If the page does not define any on_load event, this will just set `is_hydrated = true`.
403+ internal_events.push(Event('{ state_name } .{ constants .CompileVars .ON_LOAD_INTERNAL } '));
404+
405+ return internal_events;
406+ }}
407+
408+ // The following events are sent when the websocket connects or reconnects.
409+ export const initialEvents = () => [
410+ Event('{ state_name } .{ constants .CompileVars .HYDRATE } '),
411+ ...onLoadInternalEvent()
412+ ]
413+ """
414+ if state_name
415+ else """
416+ export const state_name = undefined
368417
369- return f"""import {{ createContext, useContext, useMemo, useReducer, useState }} from "react"
418+ export const exception_state_name = undefined
370419
371- export const StateContexts = {{
372- { state_name or "" } : createContext(null),
420+ export const onLoadInternalEvent = () => []
421+
422+ export const initialEvents = () => []
423+ """
424+ )
425+
426+ state_reducer_str = "\n " .join (
427+ rf'const [{ state_name } , dispatch_{ state_name } ] = useReducer(applyDelta, initialState["{ state_name } "])'
428+ for state_name in initial_state
429+ )
430+
431+ create_state_contexts_str = "\n " .join (
432+ rf"createElement(StateContexts.{ state_name } ,{{value: { state_name } }},"
433+ for state_name in initial_state
434+ )
435+
436+ dispatchers_str = "\n " .join (
437+ f'"{ state_name } ": dispatch_{ state_name } ,' for state_name in initial_state
438+ )
439+
440+ return rf"""import {{ createContext, useContext, useMemo, useReducer, useState, createElement, useEffect }} from "react"
441+ import {{ applyDelta, Event, hydrateClientStorage, useEventLoop, refs }} from "$/utils/state"
442+ import {{ jsx }} from "@emotion/react";
443+
444+ export const initialState = { "{}" if initial_state else json_dumps (initial_state )}
445+
446+ export const defaultColorMode = { default_color_mode }
447+ export const ColorModeContext = createContext(null);
448+ export const UploadFilesContext = createContext(null);
449+ export const DispatchContext = createContext(null);
450+ export const StateContexts = {{{ state_contexts_str } }};
451+ export const EventLoopContext = createContext(null);
452+ export const clientStorage = { "{}" if client_storage is None else json_dumps (client_storage )}
453+
454+ { state_str }
455+
456+ export const isDevMode = { json .dumps (is_dev_mode )} ;
457+
458+ export function UploadFilesProvider({{ children }}) {{
459+ const [filesById, setFilesById] = useState({{}})
460+ refs["__clear_selected_files"] = (id) => setFilesById(filesById => {{
461+ const newFilesById = {{...filesById}}
462+ delete newFilesById[id]
463+ return newFilesById
464+ }})
465+ return createElement(
466+ UploadFilesContext.Provider,
467+ {{ value: [filesById, setFilesById] }},
468+ children
469+ );
373470}}
374471
375- export const EventContexts = {{
376- { state_name or "" } : createContext(null),
472+ export function ClientSide(component) {{
473+ return ({{ children, ...props }}) => {{
474+ const [Component, setComponent] = useState(null);
475+ useEffect(() => {{
476+ setComponent(component);
477+ }}, []);
478+ return Component ? jsx(Component, props, children) : null;
479+ }};
377480}}
378481
379- export const initialState = { initial_state }
380- export const clientStorage = { client_storage }
381- export const isDevMode = { str (is_dev_mode ).lower ()}
382- export const colorModeManager = {{
383- get: () => { default_color_mode } ,
384- set: () => {{}},
385- type: "localStorage"
482+ export function EventLoopProvider({{ children }}) {{
483+ const dispatch = useContext(DispatchContext)
484+ const [addEvents, connectErrors] = useEventLoop(
485+ dispatch,
486+ initialEvents,
487+ clientStorage,
488+ )
489+ return createElement(
490+ EventLoopContext.Provider,
491+ {{ value: [addEvents, connectErrors] }},
492+ children
493+ );
494+ }}
495+
496+ export function StateProvider({{ children }}) {{
497+ { state_reducer_str }
498+ const dispatchers = useMemo(() => {{
499+ return {{
500+ { dispatchers_str }
501+ }}
502+ }}, [])
503+
504+ return (
505+ { create_state_contexts_str }
506+ createElement(DispatchContext, {{value: dispatchers}}, children)
507+ { ")" * len (initial_state )}
508+ )
386509}}"""
387510
388511
@@ -698,9 +821,6 @@ def render(self, **kwargs) -> str:
698821 return self .func (** kwargs )
699822
700823
701- # Template for the context file.
702- CONTEXT = TemplateFunction (_context_template )
703-
704824# Template to render a component tag.
705825COMPONENT = TemplateFunction (_component_template )
706826
0 commit comments