UI connection warning (#1111)

This commit is contained in:
Elijah Ahianyo 2023-06-05 21:00:44 +00:00 committed by GitHub
parent 895719cf68
commit 4c84a349e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 76 additions and 7 deletions

View File

@ -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 %}

View File

@ -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.

View File

@ -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()

View File

@ -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

View File

@ -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

View File

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

View File

@ -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