
* delete most references to varr * [REF-3562][REF-3563] Replace chakra usage (#3872) * only one mention of var * delete vars.py why not * remove reflex.vars * rename immutable var to var * rename ivars to vars * add vars back smh * ruff * no more create_safe * reorder deprecated * remove raises * remove all Var.create * move to new api * fix unit tests * fix pyi hopefully * sort literals * fix event handler issues * update poetry * fix silly issues i'm very silly * add var_operation_return * rename immutable to not immutable * add str type * it's ruff out there --------- Co-authored-by: Elijah Ahianyo <elijahahianyo@gmail.com>
300 lines
8.5 KiB
Python
300 lines
8.5 KiB
Python
"""Banner components."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Optional
|
|
|
|
from reflex.components.component import Component
|
|
from reflex.components.core.cond import cond
|
|
from reflex.components.el.elements.typography import Div
|
|
from reflex.components.lucide.icon import Icon
|
|
from reflex.components.radix.themes.components.dialog import (
|
|
DialogContent,
|
|
DialogRoot,
|
|
DialogTitle,
|
|
)
|
|
from reflex.components.radix.themes.layout.flex import Flex
|
|
from reflex.components.radix.themes.typography.text import Text
|
|
from reflex.components.sonner.toast import Toaster, ToastProps
|
|
from reflex.constants import Dirs, Hooks, Imports
|
|
from reflex.constants.compiler import CompileVars
|
|
from reflex.utils.imports import ImportVar
|
|
from reflex.vars import VarData
|
|
from reflex.vars.base import LiteralVar, Var
|
|
from reflex.vars.function import FunctionStringVar
|
|
from reflex.vars.number import BooleanVar
|
|
from reflex.vars.sequence import LiteralArrayVar
|
|
|
|
connect_error_var_data: VarData = VarData( # type: ignore
|
|
imports=Imports.EVENTS,
|
|
hooks={Hooks.EVENTS: None},
|
|
)
|
|
|
|
connect_errors = Var(
|
|
_js_expr=CompileVars.CONNECT_ERROR, _var_data=connect_error_var_data
|
|
)
|
|
|
|
connection_error = Var(
|
|
_js_expr="((connectErrors.length > 0) ? connectErrors[connectErrors.length - 1].message : '')",
|
|
_var_data=connect_error_var_data,
|
|
)
|
|
|
|
connection_errors_count = Var(
|
|
_js_expr="connectErrors.length", _var_data=connect_error_var_data
|
|
)
|
|
|
|
has_connection_errors = Var(
|
|
_js_expr="(connectErrors.length > 0)", _var_data=connect_error_var_data
|
|
).to(BooleanVar)
|
|
|
|
has_too_many_connection_errors = Var(
|
|
_js_expr="(connectErrors.length >= 2)", _var_data=connect_error_var_data
|
|
).to(BooleanVar)
|
|
|
|
|
|
class WebsocketTargetURL(Var):
|
|
"""A component that renders the websocket target URL."""
|
|
|
|
@classmethod
|
|
def create(cls) -> Var:
|
|
"""Create a websocket target URL component.
|
|
|
|
Returns:
|
|
The websocket target URL component.
|
|
"""
|
|
return Var(
|
|
_js_expr="getBackendURL(env.EVENT).href",
|
|
_var_data=VarData(
|
|
imports={
|
|
"/env.json": [ImportVar(tag="env", is_default=True)],
|
|
f"/{Dirs.STATE_PATH}": [ImportVar(tag="getBackendURL")],
|
|
},
|
|
),
|
|
_var_type=WebsocketTargetURL,
|
|
)
|
|
|
|
|
|
def default_connection_error() -> list[str | Var | Component]:
|
|
"""Get the default connection error message.
|
|
|
|
Returns:
|
|
The default connection error message.
|
|
"""
|
|
return [
|
|
"Cannot connect to server: ",
|
|
connection_error,
|
|
". Check if server is reachable at ",
|
|
WebsocketTargetURL.create(),
|
|
]
|
|
|
|
|
|
class ConnectionToaster(Toaster):
|
|
"""A connection toaster component."""
|
|
|
|
def add_hooks(self) -> list[str | Var]:
|
|
"""Add the hooks for the connection toaster.
|
|
|
|
Returns:
|
|
The hooks for the connection toaster.
|
|
"""
|
|
toast_id = "websocket-error"
|
|
target_url = WebsocketTargetURL.create()
|
|
props = ToastProps( # type: ignore
|
|
description=LiteralVar.create(
|
|
f"Check if server is reachable at {target_url}",
|
|
),
|
|
close_button=True,
|
|
duration=120000,
|
|
id=toast_id,
|
|
)
|
|
|
|
individual_hooks = [
|
|
f"const toast_props = {str(LiteralVar.create(props))};",
|
|
f"const [userDismissed, setUserDismissed] = useState(false);",
|
|
FunctionStringVar(
|
|
"useEffect",
|
|
_var_data=VarData(
|
|
imports={
|
|
"react": ["useEffect", "useState"],
|
|
**dict(target_url._get_all_var_data().imports), # type: ignore
|
|
}
|
|
),
|
|
).call(
|
|
# TODO: This breaks the assumption that Vars are JS expressions
|
|
Var(
|
|
_js_expr=f"""
|
|
() => {{
|
|
if ({str(has_too_many_connection_errors)}) {{
|
|
if (!userDismissed) {{
|
|
toast.error(
|
|
`Cannot connect to server: ${{{connection_error}}}.`,
|
|
{{...toast_props, onDismiss: () => setUserDismissed(true)}},
|
|
)
|
|
}}
|
|
}} else {{
|
|
toast.dismiss("{toast_id}");
|
|
setUserDismissed(false); // after reconnection reset dismissed state
|
|
}}
|
|
}}
|
|
"""
|
|
),
|
|
LiteralArrayVar.create([connect_errors]),
|
|
),
|
|
]
|
|
|
|
return [
|
|
Hooks.EVENTS,
|
|
*individual_hooks,
|
|
]
|
|
|
|
@classmethod
|
|
def create(cls, *children, **props) -> Component:
|
|
"""Create a connection toaster component.
|
|
|
|
Args:
|
|
*children: The children of the component.
|
|
**props: The properties of the component.
|
|
|
|
Returns:
|
|
The connection toaster component.
|
|
"""
|
|
Toaster.is_used = True
|
|
return super().create(*children, **props)
|
|
|
|
|
|
class ConnectionBanner(Component):
|
|
"""A connection banner component."""
|
|
|
|
@classmethod
|
|
def create(cls, comp: Optional[Component] = None) -> Component:
|
|
"""Create a connection banner component.
|
|
|
|
Args:
|
|
comp: The component to render when there's a server connection error.
|
|
|
|
Returns:
|
|
The connection banner component.
|
|
"""
|
|
if not comp:
|
|
comp = Flex.create(
|
|
Text.create(
|
|
*default_connection_error(),
|
|
color="black",
|
|
size="4",
|
|
),
|
|
justify="center",
|
|
background_color="crimson",
|
|
width="100vw",
|
|
padding="5px",
|
|
position="fixed",
|
|
)
|
|
|
|
return cond(has_connection_errors, comp)
|
|
|
|
|
|
class ConnectionModal(Component):
|
|
"""A connection status modal window."""
|
|
|
|
@classmethod
|
|
def create(cls, comp: Optional[Component] = None) -> Component:
|
|
"""Create a connection banner component.
|
|
|
|
Args:
|
|
comp: The component to render when there's a server connection error.
|
|
|
|
Returns:
|
|
The connection banner component.
|
|
"""
|
|
if not comp:
|
|
comp = Text.create(*default_connection_error())
|
|
return cond(
|
|
has_too_many_connection_errors,
|
|
DialogRoot.create(
|
|
DialogContent.create(
|
|
DialogTitle.create("Connection Error"),
|
|
comp,
|
|
),
|
|
open=has_too_many_connection_errors,
|
|
z_index=9999,
|
|
),
|
|
)
|
|
|
|
|
|
class WifiOffPulse(Icon):
|
|
"""A wifi_off icon with an animated opacity pulse."""
|
|
|
|
@classmethod
|
|
def create(cls, *children, **props) -> Icon:
|
|
"""Create a wifi_off icon with an animated opacity pulse.
|
|
|
|
Args:
|
|
*children: The children of the component.
|
|
**props: The properties of the component.
|
|
|
|
Returns:
|
|
The icon component with default props applied.
|
|
"""
|
|
pulse_var = Var(_js_expr="pulse")
|
|
return super().create(
|
|
"wifi_off",
|
|
color=props.pop("color", "crimson"),
|
|
size=props.pop("size", 32),
|
|
z_index=props.pop("z_index", 9999),
|
|
position=props.pop("position", "fixed"),
|
|
bottom=props.pop("botton", "33px"),
|
|
right=props.pop("right", "33px"),
|
|
animation=LiteralVar.create(f"{pulse_var} 1s infinite"),
|
|
**props,
|
|
)
|
|
|
|
def add_imports(self) -> dict[str, str | ImportVar | list[str | ImportVar]]:
|
|
"""Add imports for the WifiOffPulse component.
|
|
|
|
Returns:
|
|
The import dict.
|
|
"""
|
|
return {"@emotion/react": [ImportVar(tag="keyframes")]}
|
|
|
|
def _get_custom_code(self) -> str | None:
|
|
return """
|
|
const pulse = keyframes`
|
|
0% {
|
|
opacity: 0;
|
|
}
|
|
100% {
|
|
opacity: 1;
|
|
}
|
|
`
|
|
"""
|
|
|
|
|
|
class ConnectionPulser(Div):
|
|
"""A connection pulser component."""
|
|
|
|
@classmethod
|
|
def create(cls, **props) -> Component:
|
|
"""Create a connection pulser component.
|
|
|
|
Args:
|
|
**props: The properties of the component.
|
|
|
|
Returns:
|
|
The connection pulser component.
|
|
"""
|
|
return super().create(
|
|
cond(
|
|
has_connection_errors,
|
|
WifiOffPulse.create(**props),
|
|
),
|
|
title=f"Connection Error: {connection_error}",
|
|
position="fixed",
|
|
width="100vw",
|
|
height="0",
|
|
)
|
|
|
|
|
|
connection_banner = ConnectionBanner.create
|
|
connection_modal = ConnectionModal.create
|
|
connection_toaster = ConnectionToaster.create
|
|
connection_pulser = ConnectionPulser.create
|