From 3cddb005dae363e529d58d6fcbd29aea13db3aea Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Tue, 28 Nov 2023 19:38:51 +0000 Subject: [PATCH] custom spinner and 404 page --- packages/fastui/src/DefaultLoading.tsx | 1 - packages/fastui/src/Defaults.tsx | 3 ++ packages/fastui/src/components/ServerLoad.tsx | 30 ++++++++----- packages/fastui/src/hooks/config.ts | 10 +---- packages/fastui/src/index.tsx | 3 +- packages/vanilla/src/App.tsx | 23 +++++++++- packages/vanilla/src/main.scss | 44 +++++++++++++++++++ python/demo/main.py | 4 +- 8 files changed, 95 insertions(+), 23 deletions(-) delete mode 100644 packages/fastui/src/DefaultLoading.tsx create mode 100644 packages/fastui/src/Defaults.tsx diff --git a/packages/fastui/src/DefaultLoading.tsx b/packages/fastui/src/DefaultLoading.tsx deleted file mode 100644 index b73fce86..00000000 --- a/packages/fastui/src/DefaultLoading.tsx +++ /dev/null @@ -1 +0,0 @@ -export const DefaultLoading = () =>
loading...
diff --git a/packages/fastui/src/Defaults.tsx b/packages/fastui/src/Defaults.tsx new file mode 100644 index 00000000..95c1e0d6 --- /dev/null +++ b/packages/fastui/src/Defaults.tsx @@ -0,0 +1,3 @@ +export const DefaultSpinner = () =>
loading...
+ +export const DefaultNotFound = ({ url }: { url: string }) =>
Page not found: {url}
diff --git a/packages/fastui/src/components/ServerLoad.tsx b/packages/fastui/src/components/ServerLoad.tsx index b07cba08..1060ca99 100644 --- a/packages/fastui/src/components/ServerLoad.tsx +++ b/packages/fastui/src/components/ServerLoad.tsx @@ -2,7 +2,7 @@ import { FC, useCallback, useContext, useEffect, useState } from 'react' import { ErrorContext } from '../hooks/error' import { useRequest, useSSE } from '../tools' -import { DefaultLoading } from '../DefaultLoading' +import { DefaultSpinner, DefaultNotFound } from '../Defaults' import { ConfigContext } from '../hooks/config' import { PageEvent, usePageEventListen } from '../events' import { EventContextProvider, useEventContext } from '../hooks/eventContext' @@ -16,6 +16,7 @@ export interface ServerLoadProps { loadTrigger?: PageEvent sse?: boolean } + export const ServerLoadComp: FC = ({ path, components, loadTrigger, sse }) => { if (components) { return @@ -47,20 +48,27 @@ const ServerLoadDefer: FC<{ path: string; components: FastProps[]; loadTrigger?: export const ServerLoadFetch: FC<{ path: string; devReload?: number }> = ({ path, devReload }) => { const [componentProps, setComponentProps] = useState(null) + const [notFoundUrl, setNotFoundUrl] = useState(undefined) const url = useServerUrl(path) const request = useRequest() useEffect(() => { - const promise = request({ url }) - promise.then(([, data]) => setComponentProps(data as FastProps[])) + const promise = request({ url, expectedStatus: [200, 404] }) + promise.then(([status, data]) => { + if (status === 200) { + setComponentProps(data as FastProps[]) + } else { + setNotFoundUrl(url) + } + }) return () => { promise.then(() => null) } }, [url, request, devReload]) - return + return } export const ServerLoadSSE: FC<{ path: string }> = ({ path }) => { @@ -73,17 +81,19 @@ export const ServerLoadSSE: FC<{ path: string }> = ({ path }) => { return } -const Render: FC<{ propsList: FastProps[] | null }> = ({ propsList }) => { +const Render: FC<{ propsList: FastProps[] | null; notFoundUrl?: string }> = ({ propsList, notFoundUrl }) => { const { error } = useContext(ErrorContext) - const { Loading } = useContext(ConfigContext) + const { Spinner, NotFound } = useContext(ConfigContext) + const SpinnerComp = Spinner ?? DefaultSpinner + const NotFoundComp = NotFound ?? DefaultNotFound - if (propsList === null) { + if (notFoundUrl) { + return + } else if (propsList === null) { if (error) { return <> - } else if (Loading) { - return } else { - return + return } } else { return diff --git a/packages/fastui/src/hooks/config.ts b/packages/fastui/src/hooks/config.ts index 0b22f9dd..07e1eeae 100644 --- a/packages/fastui/src/hooks/config.ts +++ b/packages/fastui/src/hooks/config.ts @@ -1,16 +1,10 @@ import { createContext, FC, useContext } from 'react' -import type { CustomRender } from '../index' +import type { FastUIProps } from '../index' import { FastProps } from '../components' -interface Config { - rootUrl: string - // defaults to 'append' - pathSendMode?: 'append' | 'query' - customRender?: CustomRender - Loading?: FC -} +type Config = Omit export const ConfigContext = createContext({ rootUrl: '' }) diff --git a/packages/fastui/src/index.tsx b/packages/fastui/src/index.tsx index 67da415f..f44f0df5 100644 --- a/packages/fastui/src/index.tsx +++ b/packages/fastui/src/index.tsx @@ -23,7 +23,8 @@ export interface FastUIProps { rootUrl: string // defaults to 'append' pathSendMode?: 'append' | 'query' - Loading?: FC + Spinner?: FC + NotFound?: FC<{ url: string }> DisplayError?: ErrorDisplayType classNameGenerator?: ClassNameGenerator customRender?: CustomRender diff --git a/packages/vanilla/src/App.tsx b/packages/vanilla/src/App.tsx index 0544fdb7..7afb3184 100644 --- a/packages/vanilla/src/App.tsx +++ b/packages/vanilla/src/App.tsx @@ -4,7 +4,28 @@ import * as bootstrap from 'fastui-bootstrap' export default function App() { return (
- +
) } + +const NotFound = ({ url }: { url: string }) => ( +
+

Page not found

+

+ No page found at {url}. +

+
+) + +const Spinner = () => ( +
+
+
+) diff --git a/packages/vanilla/src/main.scss b/packages/vanilla/src/main.scss index 54adde9c..8bae6805 100644 --- a/packages/vanilla/src/main.scss +++ b/packages/vanilla/src/main.scss @@ -1 +1,45 @@ @import 'bootstrap/scss/bootstrap'; + +// custom spinner from https://cssloaders.github.io/ + +.spinner, +.spinner:before, +.spinner:after { + border-radius: 50%; + width: 2.5em; + height: 2.5em; + animation-fill-mode: both; + animation: dots 1.8s infinite ease-in-out; +} +.spinner { + color: var(--bs-dark); + font-size: 7px; + position: relative; + text-indent: -9999em; + transform: translateZ(0); + animation-delay: -0.16s; + &:before, + &:after { + content: ''; + position: absolute; + top: 0; + } + &:before { + left: -3.5em; + animation-delay: -0.32s; + } + &:after { + left: 3.5em; + } +} + +@keyframes dots { + 0%, + 80%, + 100% { + box-shadow: 0 2.5em 0 -1.3em; + } + 40% { + box-shadow: 0 2.5em 0 0; + } +} diff --git a/python/demo/main.py b/python/demo/main.py index 55962e00..3f9d2b80 100644 --- a/python/demo/main.py +++ b/python/demo/main.py @@ -29,7 +29,7 @@ def panel(*components: AnyComponent) -> AnyComponent: @app.get('/api/', response_model=FastUI, response_model_exclude_none=True) -def read_root() -> list[AnyComponent]: +def api_index() -> list[AnyComponent]: return [ c.PageTitle(text='FastUI Demo'), navbar(), @@ -245,7 +245,7 @@ async def sse_generator(): d = datetime.now() m = FastUI(root=[c.Div(components=[c.Text(text=f'Time {d:%H:%M:%S.%f}'[:-4])], class_name='font-monospace')]) yield dict(data=m.model_dump_json(by_alias=True)) - await asyncio.sleep(0.15) + await asyncio.sleep(0.09) @app.get('/api/sse', response_class=EventSourceResponse)