banner.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. """Banner components."""
  2. from __future__ import annotations
  3. from typing import Optional
  4. from reflex.components.base.bare import Bare
  5. from reflex.components.component import Component
  6. from reflex.components.core.cond import cond
  7. from reflex.components.el.elements.typography import Div
  8. from reflex.components.lucide.icon import Icon
  9. from reflex.components.radix.themes.components.dialog import (
  10. DialogContent,
  11. DialogRoot,
  12. DialogTitle,
  13. )
  14. from reflex.components.radix.themes.layout import Flex
  15. from reflex.components.radix.themes.typography.text import Text
  16. from reflex.constants import Dirs, Hooks, Imports
  17. from reflex.utils import imports
  18. from reflex.vars import Var, VarData
  19. connect_error_var_data: VarData = VarData( # type: ignore
  20. imports=Imports.EVENTS,
  21. hooks={Hooks.EVENTS: None},
  22. )
  23. connection_error: Var = Var.create_safe(
  24. value="(connectErrors.length > 0) ? connectErrors[connectErrors.length - 1].message : ''",
  25. _var_is_local=False,
  26. _var_is_string=False,
  27. )._replace(merge_var_data=connect_error_var_data)
  28. connection_errors_count: Var = Var.create_safe(
  29. value="connectErrors.length",
  30. _var_is_string=False,
  31. _var_is_local=False,
  32. )._replace(merge_var_data=connect_error_var_data)
  33. has_connection_errors: Var = Var.create_safe(
  34. value="connectErrors.length > 0",
  35. _var_is_string=False,
  36. )._replace(_var_type=bool, merge_var_data=connect_error_var_data)
  37. has_too_many_connection_errors: Var = Var.create_safe(
  38. value="connectErrors.length >= 2",
  39. _var_is_string=False,
  40. )._replace(_var_type=bool, merge_var_data=connect_error_var_data)
  41. class WebsocketTargetURL(Bare):
  42. """A component that renders the websocket target URL."""
  43. def _get_imports(self) -> imports.ImportDict:
  44. return {
  45. f"/{Dirs.STATE_PATH}": [imports.ImportVar(tag="getBackendURL")],
  46. "/env.json": [imports.ImportVar(tag="env", is_default=True)],
  47. }
  48. @classmethod
  49. def create(cls) -> Component:
  50. """Create a websocket target URL component.
  51. Returns:
  52. The websocket target URL component.
  53. """
  54. return super().create(contents="{getBackendURL(env.EVENT).href}")
  55. def default_connection_error() -> list[str | Var | Component]:
  56. """Get the default connection error message.
  57. Returns:
  58. The default connection error message.
  59. """
  60. return [
  61. "Cannot connect to server: ",
  62. connection_error,
  63. ". Check if server is reachable at ",
  64. WebsocketTargetURL.create(),
  65. ]
  66. class ConnectionBanner(Component):
  67. """A connection banner component."""
  68. @classmethod
  69. def create(cls, comp: Optional[Component] = None) -> Component:
  70. """Create a connection banner component.
  71. Args:
  72. comp: The component to render when there's a server connection error.
  73. Returns:
  74. The connection banner component.
  75. """
  76. if not comp:
  77. comp = Flex.create(
  78. Text.create(
  79. *default_connection_error(),
  80. color="black",
  81. size="4",
  82. ),
  83. justify="center",
  84. background_color="crimson",
  85. width="100vw",
  86. padding="5px",
  87. position="fixed",
  88. )
  89. return cond(has_connection_errors, comp)
  90. class ConnectionModal(Component):
  91. """A connection status modal window."""
  92. @classmethod
  93. def create(cls, comp: Optional[Component] = None) -> Component:
  94. """Create a connection banner component.
  95. Args:
  96. comp: The component to render when there's a server connection error.
  97. Returns:
  98. The connection banner component.
  99. """
  100. if not comp:
  101. comp = Text.create(*default_connection_error())
  102. return cond(
  103. has_too_many_connection_errors,
  104. DialogRoot.create(
  105. DialogContent.create(
  106. DialogTitle.create("Connection Error"),
  107. comp,
  108. ),
  109. open=has_too_many_connection_errors,
  110. z_index=9999,
  111. ),
  112. )
  113. class WifiOffPulse(Icon):
  114. """A wifi_off icon with an animated opacity pulse."""
  115. @classmethod
  116. def create(cls, **props) -> Component:
  117. """Create a wifi_off icon with an animated opacity pulse.
  118. Args:
  119. **props: The properties of the component.
  120. Returns:
  121. The icon component with default props applied.
  122. """
  123. return super().create(
  124. "wifi_off",
  125. color=props.pop("color", "crimson"),
  126. size=props.pop("size", 32),
  127. z_index=props.pop("z_index", 9999),
  128. position=props.pop("position", "fixed"),
  129. bottom=props.pop("botton", "30px"),
  130. right=props.pop("right", "30px"),
  131. animation=Var.create(f"${{pulse}} 1s infinite", _var_is_string=True),
  132. **props,
  133. )
  134. def _get_imports(self) -> imports.ImportDict:
  135. return imports.merge_imports(
  136. super()._get_imports(),
  137. {"@emotion/react": [imports.ImportVar(tag="keyframes")]},
  138. )
  139. def _get_custom_code(self) -> str | None:
  140. return """
  141. const pulse = keyframes`
  142. 0% {
  143. opacity: 0;
  144. }
  145. 100% {
  146. opacity: 1;
  147. }
  148. `
  149. """
  150. class ConnectionPulser(Div):
  151. """A connection pulser component."""
  152. @classmethod
  153. def create(cls, **props) -> Component:
  154. """Create a connection pulser component.
  155. Args:
  156. **props: The properties of the component.
  157. Returns:
  158. The connection pulser component.
  159. """
  160. return super().create(
  161. cond(
  162. has_connection_errors,
  163. WifiOffPulse.create(**props),
  164. ),
  165. position="fixed",
  166. width="100vw",
  167. height="0",
  168. )