Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into masenf/static-hosting…
Browse files Browse the repository at this point in the history
…-docker
  • Loading branch information
masenf committed May 17, 2024
2 parents 2a7b963 + 9ba1794 commit fcf9200
Show file tree
Hide file tree
Showing 28 changed files with 715 additions and 95 deletions.
9 changes: 7 additions & 2 deletions reflex/.templates/web/utils/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,13 @@ export const applyDelta = (state, delta) => {
export const applyEvent = async (event, socket) => {
// Handle special events
if (event.name == "_redirect") {
if (event.payload.external) window.open(event.payload.path, "_blank");
else Router.push(event.payload.path);
if (event.payload.external) {
window.open(event.payload.path, "_blank");
} else if (event.payload.replace) {
Router.replace(event.payload.path);
} else {
Router.push(event.payload.path);
}
return false;
}

Expand Down
4 changes: 2 additions & 2 deletions reflex/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@
from reflex.compiler import compiler
from reflex.compiler import utils as compiler_utils
from reflex.compiler.compiler import ExecutorSafeFunctions
from reflex.components import connection_modal, connection_pulser
from reflex.components.base.app_wrap import AppWrap
from reflex.components.base.fragment import Fragment
from reflex.components.component import (
Component,
ComponentStyle,
evaluate_style_namespaces,
)
from reflex.components.core import connection_pulser, connection_toaster
from reflex.components.core.client_side_routing import (
Default404Page,
wait_for_client_redirect,
Expand Down Expand Up @@ -91,7 +91,7 @@ def default_overlay_component() -> Component:
Returns:
The default overlay_component, which is a connection_modal.
"""
return Fragment.create(connection_pulser(), connection_modal())
return Fragment.create(connection_pulser(), connection_toaster())


class OverlayFragment(Fragment):
Expand Down
57 changes: 46 additions & 11 deletions reflex/components/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ def add_imports(self) -> dict[str, str | ImportVar | list[str | ImportVar]]:
"""
return {}

def add_hooks(self) -> list[str]:
def add_hooks(self) -> list[str | Var]:
"""Add hooks inside the component function.
Hooks are pieces of literal Javascript code that is inserted inside the
Expand Down Expand Up @@ -1265,11 +1265,20 @@ def _get_hooks_imports(self) -> imports.ImportDict:
},
)

other_imports = []
user_hooks = self._get_hooks()
if user_hooks is not None and isinstance(user_hooks, Var):
_imports = imports.merge_imports(_imports, user_hooks._var_data.imports) # type: ignore
if (
user_hooks is not None
and isinstance(user_hooks, Var)
and user_hooks._var_data is not None
and user_hooks._var_data.imports
):
other_imports.append(user_hooks._var_data.imports)
other_imports.extend(
hook_imports for hook_imports in self._get_added_hooks().values()
)

return _imports
return imports.merge_imports(_imports, *other_imports)

def _get_imports(self) -> imports.ImportDict:
"""Get all the libraries and fields that are used by the component.
Expand Down Expand Up @@ -1416,6 +1425,36 @@ def _get_hooks_internal(self) -> dict[str, None]:
**self._get_special_hooks(),
}

def _get_added_hooks(self) -> dict[str, imports.ImportDict]:
"""Get the hooks added via `add_hooks` method.
Returns:
The deduplicated hooks and imports added by the component and parent components.
"""
code = {}

def extract_var_hooks(hook: Var):
_imports = {}
if hook._var_data is not None:
for sub_hook in hook._var_data.hooks:
code[sub_hook] = {}
if hook._var_data.imports:
_imports = hook._var_data.imports
if str(hook) in code:
code[str(hook)] = imports.merge_imports(code[str(hook)], _imports)
else:
code[str(hook)] = _imports

# Add the hook code from add_hooks for each parent class (this is reversed to preserve
# the order of the hooks in the final output)
for clz in reversed(tuple(self._iter_parent_classes_with_method("add_hooks"))):
for hook in clz.add_hooks(self):
if isinstance(hook, Var):
extract_var_hooks(hook)
else:
code[hook] = {}
return code

def _get_hooks(self) -> str | None:
"""Get the React hooks for this component.
Expand Down Expand Up @@ -1454,11 +1493,7 @@ def _get_all_hooks(self) -> dict[str, None]:
if hooks is not None:
code[hooks] = None

# Add the hook code from add_hooks for each parent class (this is reversed to preserve
# the order of the hooks in the final output)
for clz in reversed(tuple(self._iter_parent_classes_with_method("add_hooks"))):
for hook in clz.add_hooks(self):
code[hook] = None
code.update(self._get_added_hooks())

# Add the hook code for the children.
for child in self.children:
Expand Down Expand Up @@ -2092,8 +2127,8 @@ def _get_memoized_event_triggers(
var_deps.extend(cls._get_hook_deps(hook))
memo_var_data = VarData.merge(
*[var._var_data for var in event_args],
VarData( # type: ignore
imports={"react": {ImportVar(tag="useCallback")}},
VarData(
imports={"react": [ImportVar(tag="useCallback")]},
),
)

Expand Down
8 changes: 7 additions & 1 deletion reflex/components/core/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
"""Core Reflex components."""

from . import layout as layout
from .banner import ConnectionBanner, ConnectionModal, ConnectionPulser
from .banner import (
ConnectionBanner,
ConnectionModal,
ConnectionPulser,
ConnectionToaster,
)
from .colors import color
from .cond import Cond, color_mode_cond, cond
from .debounce import DebounceInput
Expand All @@ -26,6 +31,7 @@

connection_banner = ConnectionBanner.create
connection_modal = ConnectionModal.create
connection_toaster = ConnectionToaster.create
connection_pulser = ConnectionPulser.create
debounce_input = DebounceInput.create
foreach = Foreach.create
Expand Down
85 changes: 79 additions & 6 deletions reflex/components/core/banner.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,36 +16,50 @@
)
from reflex.components.radix.themes.layout 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 import imports
from reflex.utils.serializers import serialize
from reflex.vars import Var, VarData

connect_error_var_data: VarData = VarData( # type: ignore
imports=Imports.EVENTS,
hooks={Hooks.EVENTS: None},
)

connect_errors: Var = Var.create_safe(
value=CompileVars.CONNECT_ERROR,
_var_is_local=True,
_var_is_string=False,
_var_data=connect_error_var_data,
)

connection_error: Var = Var.create_safe(
value="(connectErrors.length > 0) ? connectErrors[connectErrors.length - 1].message : ''",
_var_is_local=False,
_var_is_string=False,
)._replace(merge_var_data=connect_error_var_data)
_var_data=connect_error_var_data,
)

connection_errors_count: Var = Var.create_safe(
value="connectErrors.length",
_var_is_string=False,
_var_is_local=False,
)._replace(merge_var_data=connect_error_var_data)
_var_data=connect_error_var_data,
)

has_connection_errors: Var = Var.create_safe(
value="connectErrors.length > 0",
_var_is_string=False,
)._replace(_var_type=bool, merge_var_data=connect_error_var_data)
_var_data=connect_error_var_data,
).to(bool)

has_too_many_connection_errors: Var = Var.create_safe(
value="connectErrors.length >= 2",
_var_is_string=False,
)._replace(_var_type=bool, merge_var_data=connect_error_var_data)
_var_data=connect_error_var_data,
).to(bool)


class WebsocketTargetURL(Bare):
Expand Down Expand Up @@ -81,6 +95,64 @@ def default_connection_error() -> list[str | Var | Component]:
]


class ConnectionToaster(Toaster):
"""A connection toaster component."""

def add_hooks(self) -> list[str]:
"""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=Var.create(
f"`Check if server is reachable at ${target_url}`",
_var_is_string=False,
_var_is_local=False,
),
close_button=True,
duration=120000,
id=toast_id,
)
hook = Var.create(
f"""
const toast_props = {serialize(props)};
const [userDismissed, setUserDismissed] = useState(false);
useEffect(() => {{
if ({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
}}
}}, [{connect_errors}]);"""
)

hook._var_data = VarData.merge( # type: ignore
connect_errors._var_data,
VarData(
imports={
"react": [
imports.ImportVar(tag="useEffect"),
imports.ImportVar(tag="useState"),
],
**target_url._get_imports(),
}
),
)
return [
Hooks.EVENTS,
hook, # type: ignore
]


class ConnectionBanner(Component):
"""A connection banner component."""

Expand Down Expand Up @@ -158,8 +230,8 @@ def create(cls, **props) -> Component:
size=props.pop("size", 32),
z_index=props.pop("z_index", 9999),
position=props.pop("position", "fixed"),
bottom=props.pop("botton", "30px"),
right=props.pop("right", "30px"),
bottom=props.pop("botton", "33px"),
right=props.pop("right", "33px"),
animation=Var.create(f"${{pulse}} 1s infinite", _var_is_string=True),
**props,
)
Expand Down Expand Up @@ -201,6 +273,7 @@ def create(cls, **props) -> Component:
has_connection_errors,
WifiOffPulse.create(**props),
),
title=f"Connection Error: {connection_error}",
position="fixed",
width="100vw",
height="0",
Expand Down
Loading

0 comments on commit fcf9200

Please sign in to comment.