From a1158cdb1c5fdb95b8afaca02b28eb289e35340e Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Tue, 12 Nov 2024 12:36:42 -0800 Subject: [PATCH] redesign error boundary screen (#4329) * redesign error boundary screen * pyi time * add color * i hate python 3.9 --- reflex/components/base/error_boundary.py | 135 ++++++++++++++++------ reflex/components/base/error_boundary.pyi | 7 +- reflex/components/core/cond.py | 8 ++ reflex/components/datadisplay/logo.py | 39 ++++--- reflex/event.py | 2 +- 5 files changed, 137 insertions(+), 54 deletions(-) diff --git a/reflex/components/base/error_boundary.py b/reflex/components/base/error_boundary.py index 83becc034..f328773c2 100644 --- a/reflex/components/base/error_boundary.py +++ b/reflex/components/base/error_boundary.py @@ -2,14 +2,15 @@ from __future__ import annotations -from typing import Dict, List, Tuple +from typing import Dict, Tuple -from reflex.compiler.compiler import _compile_component from reflex.components.component import Component -from reflex.components.el import div, p -from reflex.event import EventHandler +from reflex.components.datadisplay.logo import svg_logo +from reflex.components.el import a, button, details, div, h2, hr, p, pre, summary +from reflex.event import EventHandler, set_clipboard from reflex.state import FrontendEventExceptionState from reflex.vars.base import Var +from reflex.vars.function import ArgsFunctionOperation def on_error_spec( @@ -40,38 +41,7 @@ class ErrorBoundary(Component): on_error: EventHandler[on_error_spec] # Rendered instead of the children when an error is caught. - Fallback_component: Var[Component] = Var(_js_expr="Fallback")._replace( - _var_type=Component - ) - - def add_custom_code(self) -> List[str]: - """Add custom Javascript code into the page that contains this component. - - Custom code is inserted at module level, after any imports. - - Returns: - The custom code to add. - """ - fallback_container = div( - p("Ooops...Unknown Reflex error has occured:"), - p( - Var(_js_expr="error.message"), - color="red", - ), - p("Please contact the support."), - ) - - compiled_fallback = _compile_component(fallback_container) - - return [ - f""" - function Fallback({{ error, resetErrorBoundary }}) {{ - return ( - {compiled_fallback} - ); - }} - """ - ] + fallback_render: Var[Component] @classmethod def create(cls, *children, **props): @@ -86,6 +56,99 @@ class ErrorBoundary(Component): """ if "on_error" not in props: props["on_error"] = FrontendEventExceptionState.handle_frontend_exception + if "fallback_render" not in props: + props["fallback_render"] = ArgsFunctionOperation.create( + ("event_args",), + Var.create( + div( + div( + div( + h2( + "An error occurred while rendering this page.", + font_size="1.25rem", + font_weight="bold", + ), + p( + "This is an error with the application itself.", + opacity="0.75", + ), + details( + summary("Error message", padding="0.5rem"), + div( + div( + pre( + Var( + _js_expr="event_args.error.stack", + ), + ), + padding="0.5rem", + width="fit-content", + ), + width="100%", + max_height="50vh", + overflow="auto", + background="#000", + color="#fff", + border_radius="0.25rem", + ), + button( + "Copy", + on_click=set_clipboard( + Var(_js_expr="event_args.error.stack"), + ), + padding="0.35rem 0.75rem", + margin="0.5rem", + background="#fff", + color="#000", + border="1px solid #000", + border_radius="0.25rem", + font_weight="bold", + ), + ), + display="flex", + flex_direction="column", + gap="1rem", + max_width="50ch", + border="1px solid #888888", + border_radius="0.25rem", + padding="1rem", + ), + hr( + border_color="currentColor", + opacity="0.25", + ), + a( + div( + "Built with ", + svg_logo("currentColor"), + display="flex", + align_items="baseline", + justify_content="center", + font_family="monospace", + gap="0.5rem", + ), + href="https://reflex.dev", + ), + display="flex", + flex_direction="column", + gap="1rem", + ), + height="100%", + width="100%", + position="absolute", + display="flex", + align_items="center", + justify_content="center", + ) + ), + _var_type=Component, + ) + else: + props["fallback_render"] = ArgsFunctionOperation.create( + ("event_args",), + props["fallback_render"], + _var_type=Component, + ) return super().create(*children, **props) diff --git a/reflex/components/base/error_boundary.pyi b/reflex/components/base/error_boundary.pyi index dfe34fc9e..2e01c7da0 100644 --- a/reflex/components/base/error_boundary.pyi +++ b/reflex/components/base/error_boundary.pyi @@ -3,7 +3,7 @@ # ------------------- DO NOT EDIT ---------------------- # This file was generated by `reflex/utils/pyi_generator.py`! # ------------------------------------------------------ -from typing import Any, Dict, List, Optional, Tuple, Union, overload +from typing import Any, Dict, Optional, Tuple, Union, overload from reflex.components.component import Component from reflex.event import BASE_STATE, EventType @@ -15,13 +15,12 @@ def on_error_spec( ) -> Tuple[Var[str], Var[str]]: ... class ErrorBoundary(Component): - def add_custom_code(self) -> List[str]: ... @overload @classmethod def create( # type: ignore cls, *children, - Fallback_component: Optional[Union[Component, Var[Component]]] = None, + fallback_render: Optional[Union[Component, Var[Component]]] = None, style: Optional[Style] = None, key: Optional[Any] = None, id: Optional[Any] = None, @@ -57,7 +56,7 @@ class ErrorBoundary(Component): Args: *children: The children of the component. on_error: Fired when the boundary catches an error. - Fallback_component: Rendered instead of the children when an error is caught. + fallback_render: Rendered instead of the children when an error is caught. style: The style of the component. key: A unique key for the component. id: The id for the component. diff --git a/reflex/components/core/cond.py b/reflex/components/core/cond.py index e0c47f0fe..5b6ee2a7f 100644 --- a/reflex/components/core/cond.py +++ b/reflex/components/core/cond.py @@ -171,6 +171,14 @@ def cond(condition: Any, c1: Any, c2: Any = None) -> Component | Var: ) +@overload +def color_mode_cond(light: Component, dark: Component | None = None) -> Component: ... # type: ignore + + +@overload +def color_mode_cond(light: Any, dark: Any = None) -> Var: ... + + def color_mode_cond(light: Any, dark: Any = None) -> Var | Component: """Create a component or Prop based on color_mode. diff --git a/reflex/components/datadisplay/logo.py b/reflex/components/datadisplay/logo.py index beb9b9d10..d960b8cee 100644 --- a/reflex/components/datadisplay/logo.py +++ b/reflex/components/datadisplay/logo.py @@ -1,22 +1,23 @@ """A Reflex logo component.""" +from typing import Union + import reflex as rx -def logo(**props): - """A Reflex logo. +def svg_logo(color: Union[str, rx.Var[str]] = rx.color_mode_cond("#110F1F", "white")): + """A Reflex logo SVG. Args: - **props: The props to pass to the component. + color: The color of the logo. Returns: - The logo component. + The Reflex logo SVG. """ def logo_path(d): return rx.el.svg.path( d=d, - fill=rx.color_mode_cond("#110F1F", "white"), ) paths = [ @@ -28,18 +29,30 @@ def logo(**props): "M47.04 4.8799V0.399902H49.28V4.8799H47.04ZM53.76 4.8799V0.399902H56V4.8799H53.76ZM49.28 7.1199V4.8799H53.76V7.1199H49.28ZM47.04 11.5999V7.1199H49.28V11.5999H47.04ZM53.76 11.5999V7.1199H56V11.5999H53.76Z", ] + return rx.el.svg( + *[logo_path(d) for d in paths], + width="56", + height="12", + viewBox="0 0 56 12", + fill=color, + xmlns="http://www.w3.org/2000/svg", + ) + + +def logo(**props): + """A Reflex logo. + + Args: + **props: The props to pass to the component. + + Returns: + The logo component. + """ return rx.center( rx.link( rx.hstack( "Built with ", - rx.el.svg( - *[logo_path(d) for d in paths], - width="56", - height="12", - viewBox="0 0 56 12", - fill="none", - xmlns="http://www.w3.org/2000/svg", - ), + svg_logo(), text_align="center", align="center", padding="1em", diff --git a/reflex/event.py b/reflex/event.py index a64d4d6c1..85a2541a5 100644 --- a/reflex/event.py +++ b/reflex/event.py @@ -899,7 +899,7 @@ def remove_session_storage(key: str) -> EventSpec: ) -def set_clipboard(content: str) -> EventSpec: +def set_clipboard(content: Union[str, Var[str]]) -> EventSpec: """Set the text in content in the clipboard. Args: