Elijah Ahianyo преди 1 година
родител
ревизия
4c84a349e1

+ 7 - 1
pynecone/.templates/jinja/web/pages/index.js.jinja2

@@ -11,6 +11,7 @@
 export default function Component() {
   const [{{state_name}}, {{state_name|react_setter}}] = useState({{initial_state|json_dumps}})
   const [{{const.result}}, {{const.result|react_setter}}] = useState({{const.initial_result|json_dumps}})
+  const [notConnected, setNotConnected] = useState(false)
   const {{const.router}} = useRouter()
   const {{const.socket}} = useRef(null)
   const { isReady } = {{const.router}}
@@ -35,7 +36,7 @@ export default function Component() {
       return;
     }
     if (!{{const.socket}}.current) {
-      connect({{const.socket}}, {{state_name}}, {{state_name|react_setter}}, {{const.result}}, {{const.result|react_setter}}, {{const.router}}, {{transports}})
+      connect({{const.socket}}, {{state_name}}, {{state_name|react_setter}}, {{const.result}}, {{const.result|react_setter}}, {{const.router}}, {{transports}}, setNotConnected)
     }
     const update = async () => {
       if ({{const.result}}.{{const.state}} != null){
@@ -70,7 +71,12 @@ export default function Component() {
   {% endfor %}
 
   return (
+  <Fragment>
+      {%- if err_comp -%}
+            {{ utils.render(err_comp, indent_width=1) }}
+       {%- endif -%}
     {{utils.render(render, indent_width=0)}}
+    </Fragment>
   )
 }
 {% endblock %}

+ 7 - 1
pynecone/.templates/web/utils/state.js

@@ -193,7 +193,8 @@ export const connect = async (
   result,
   setResult,
   router,
-  transports
+  transports,
+  setNotConnected
 ) => {
   // Get backend URL object from the endpoint
   const endpoint_url = new URL(EVENTURL);
@@ -207,6 +208,11 @@ export const connect = async (
   // Once the socket is open, hydrate the page.
   socket.current.on("connect", () => {
     updateState(state, setState, result, setResult, router, socket.current);
+    setNotConnected(false)
+  });
+
+  socket.current.on('connect_error', (error) => {
+    setNotConnected(true)
   });
 
   // On each received message, apply the delta and set the result.

+ 10 - 1
pynecone/app.py

@@ -24,6 +24,7 @@ from pynecone.base import Base
 from pynecone.compiler import compiler
 from pynecone.compiler import utils as compiler_utils
 from pynecone.components.component import Component, ComponentStyle
+from pynecone.components.overlay.banner import ConnectionBanner
 from pynecone.config import get_config
 from pynecone.event import Event, EventHandler
 from pynecone.middleware import HydrateMiddleware, Middleware
@@ -76,6 +77,9 @@ class App(Base):
     # List of event handlers to trigger when a page loads.
     load_events: Dict[str, List[EventHandler]] = {}
 
+    # The component to render if there is a connection error to the server.
+    connect_error_component: Optional[Component] = ConnectionBanner.create()
+
     def __init__(self, *args, **kwargs):
         """Initialize the app.
 
@@ -411,7 +415,12 @@ class App(Base):
         custom_components = set()
         for route, component in self.pages.items():
             component.add_style(self.style)
-            compiler.compile_page(route, component, self.state)
+            compiler.compile_page(
+                route,
+                component,
+                self.state,
+                self.connect_error_component,
+            )
 
             # Add the custom components from the page to the set.
             custom_components |= component.get_custom_components()

+ 17 - 4
pynecone/compiler/compiler.py

@@ -15,6 +15,7 @@ from pynecone.vars import ImportVar
 # Imports to be included in every Pynecone app.
 DEFAULT_IMPORTS: imports.ImportDict = {
     "react": {
+        ImportVar(tag="Fragment"),
         ImportVar(tag="useEffect"),
         ImportVar(tag="useRef"),
         ImportVar(tag="useState"),
@@ -31,7 +32,11 @@ DEFAULT_IMPORTS: imports.ImportDict = {
         ImportVar(tag="getRefValue"),
     },
     "": {ImportVar(tag="focus-visible/dist/focus-visible")},
-    "@chakra-ui/react": {ImportVar(tag=constants.USE_COLOR_MODE)},
+    "@chakra-ui/react": {
+        ImportVar(tag=constants.USE_COLOR_MODE),
+        ImportVar(tag="Box"),
+        ImportVar(tag="Text"),
+    },
 }
 
 
@@ -62,12 +67,15 @@ def _compile_theme(theme: dict) -> str:
     return templates.THEME.render(theme=theme)
 
 
-def _compile_page(component: Component, state: Type[State]) -> str:
+def _compile_page(
+    component: Component, state: Type[State], connect_error_component
+) -> str:
     """Compile the component given the app state.
 
     Args:
         component: The component to compile.
         state: The app state.
+        connect_error_component: The component to render on sever connection error.
 
     Returns:
         The compiled component.
@@ -85,6 +93,7 @@ def _compile_page(component: Component, state: Type[State]) -> str:
         hooks=component.get_hooks(),
         render=component.render(),
         transports=constants.Transports.POLLING_WEBSOCKET.get_transports(),
+        err_comp=connect_error_component.render() if connect_error_component else None,
     )
 
 
@@ -188,7 +197,10 @@ def compile_theme(style: Style) -> Tuple[str, str]:
 
 @write_output
 def compile_page(
-    path: str, component: Component, state: Type[State]
+    path: str,
+    component: Component,
+    state: Type[State],
+    connect_error_component: Component,
 ) -> Tuple[str, str]:
     """Compile a single page.
 
@@ -196,6 +208,7 @@ def compile_page(
         path: The path to compile the page to.
         component: The component to compile.
         state: The app state.
+        connect_error_component: The component to render on sever connection error.
 
     Returns:
         The path and code of the compiled page.
@@ -204,7 +217,7 @@ def compile_page(
     output_path = utils.get_page_path(path)
 
     # Add the style to the component.
-    code = _compile_page(component, state)
+    code = _compile_page(component, state, connect_error_component)
     return output_path, code
 
 

+ 1 - 0
pynecone/components/__init__.py

@@ -29,6 +29,7 @@ component = Component.create
 badge = Badge.create
 code = Code.create
 code_block = CodeBlock.create
+connection_banner = ConnectionBanner.create
 data_table = DataTable.create
 divider = Divider.create
 list = List.create

+ 1 - 0
pynecone/components/overlay/__init__.py

@@ -8,6 +8,7 @@ from .alertdialog import (
     AlertDialogHeader,
     AlertDialogOverlay,
 )
+from .banner import ConnectionBanner
 from .drawer import (
     Drawer,
     DrawerBody,

+ 33 - 0
pynecone/components/overlay/banner.py

@@ -0,0 +1,33 @@
+"""Banner components."""
+from typing import Optional
+
+from pynecone.components.component import Component
+from pynecone.components.layout import Box, Cond, Fragment
+from pynecone.components.typography import Text
+from pynecone.vars import Var
+
+
+class ConnectionBanner(Cond):
+    """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 = Box.create(
+                Text.create(
+                    "cannot connect to server. Check if server is reachable",
+                    bg="red",
+                    color="white",
+                ),
+                textAlign="center",
+            )
+
+        return super().create(Var.create("notConnected"), comp, Fragment.create())  # type: ignore