diff --git a/web/src/App.jsx b/web/src/App.jsx index 7222d4f7b..f12c8eeb4 100644 --- a/web/src/App.jsx +++ b/web/src/App.jsx @@ -1,4 +1,5 @@ import { h } from 'preact'; +import ActivityIndicator from './components/ActivityIndicator'; import Camera from './Camera'; import CameraMap from './CameraMap'; import Cameras from './Cameras'; @@ -7,12 +8,14 @@ import Event from './Event'; import Events from './Events'; import { Router } from 'preact-router'; import Sidebar from './Sidebar'; -import Api, { useConfig } from './api'; +import Api, { FetchStatus, useConfig } from './api'; export default function App() { const { data, status } = useConfig(); - return !data ? ( -
+ return status !== FetchStatus.LOADED ? ( +
+ +
) : (
diff --git a/web/src/Cameras.jsx b/web/src/Cameras.jsx index ab01eb9ff..04052ba29 100644 --- a/web/src/Cameras.jsx +++ b/web/src/Cameras.jsx @@ -1,4 +1,5 @@ import { h } from 'preact'; +import ActivityIndicator from './components/ActivityIndicator'; import Box from './components/Box'; import CameraImage from './components/CameraImage'; import Events from './Events'; diff --git a/web/src/Debug.jsx b/web/src/Debug.jsx index 40c77f37b..dab25d6df 100644 --- a/web/src/Debug.jsx +++ b/web/src/Debug.jsx @@ -1,9 +1,10 @@ import { h } from 'preact'; +import ActivityIndicator from './components/ActivityIndicator'; import Box from './components/Box'; import Button from './components/Button'; import Heading from './components/Heading'; import Link from './components/Link'; -import { useConfig, useStats } from './api'; +import { FetchStatus, useConfig, useStats } from './api'; import { Table, Tbody, Thead, Tr, Th, Td } from './components/Table'; import { useCallback, useEffect, useState } from 'preact/hooks'; @@ -27,8 +28,8 @@ export default function Debug() { }, [timeoutId]); const { data: stats, status } = useStats(null, timeoutId); - if (!stats) { - return 'loading…'; + if (stats === null && (status === FetchStatus.LOADING || status === FetchStatus.NONE)) { + return ; } const { detectors, detection_fps, service, ...cameras } = stats; diff --git a/web/src/Event.jsx b/web/src/Event.jsx index 9243f679c..7a4d3aff3 100644 --- a/web/src/Event.jsx +++ b/web/src/Event.jsx @@ -1,21 +1,17 @@ import { h, Fragment } from 'preact'; +import ActivityIndicator from './components/ActivityIndicator'; import Box from './components/Box'; import Heading from './components/Heading'; import Link from './components/Link'; +import { FetchStatus, useApiHost, useEvent } from './api'; import { Table, Thead, Tbody, Tfoot, Th, Tr, Td } from './components/Table'; -import { useApiHost, useEvent } from './api'; export default function Event({ eventId }) { const apiHost = useApiHost(); - const { data } = useEvent(eventId); + const { data, status } = useEvent(eventId); - if (!data) { - return ( -
- {eventId} -

loading…

-
- ); + if (status !== FetchStatus.LOADED) { + return ; } const startime = new Date(data.start_time * 1000); diff --git a/web/src/Events.jsx b/web/src/Events.jsx index 4b7be88cf..c43aee132 100644 --- a/web/src/Events.jsx +++ b/web/src/Events.jsx @@ -1,11 +1,12 @@ import { h } from 'preact'; +import ActivityIndicator from './components/ActivityIndicator'; import Box from './components/Box'; import Heading from './components/Heading'; import Link from './components/Link'; import produce from 'immer'; import { route } from 'preact-router'; +import { FetchStatus, useApiHost, useConfig, useEvents } from './api'; import { Table, Thead, Tbody, Tfoot, Th, Tr, Td } from './components/Table'; -import { useApiHost, useConfig, useEvents } from './api'; import { useCallback, useContext, useEffect, useMemo, useRef, useReducer, useState } from 'preact/hooks'; const API_LIMIT = 25; @@ -194,8 +195,8 @@ export default function Events({ path: pathname } = {}) { - - {status === 'loading' ? 'Loading…' : reachedEnd ? 'No more events' : null} + + {status === FetchStatus.LOADING ? : reachedEnd ? 'No more events' : null} diff --git a/web/src/components/ActivityIndicator.jsx b/web/src/components/ActivityIndicator.jsx new file mode 100644 index 000000000..4d08ce506 --- /dev/null +++ b/web/src/components/ActivityIndicator.jsx @@ -0,0 +1,15 @@ +import { h } from 'preact'; + +const sizes = { + sm: 'h-4 w-4 border-2 border-t-2', + md: 'h-8 w-8 border-4 border-t-4', + lg: 'h-16 w-16 border-8 border-t-8', +}; + +export default function ActivityIndicator({ size = 'md' }) { + return ( +
+
+
+ ); +} diff --git a/web/src/components/CameraImage.jsx b/web/src/components/CameraImage.jsx index 84694476a..62aa5e405 100644 --- a/web/src/components/CameraImage.jsx +++ b/web/src/components/CameraImage.jsx @@ -1,4 +1,5 @@ import { h } from 'preact'; +import ActivityIndicator from './ActivityIndicator'; import { useApiHost, useConfig } from '../api'; import { useCallback, useEffect, useContext, useMemo, useRef, useState } from 'preact/hooks'; @@ -54,7 +55,11 @@ export default function CameraImage({ camera, onload, searchParams = '' }) { return (
- {loadedSrc ? {name} : null} + {loadedSrc ? ( + {name} + ) : ( + + )}
); } diff --git a/web/src/index.css b/web/src/index.css index b5c61c956..d55a9aabc 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -1,3 +1,27 @@ @tailwind base; @tailwind components; @tailwind utilities; + +.activityindicator { + border-top-color: currentColor; + -webkit-animation: spinner 0.75s linear infinite; + animation: spinner 0.75s linear infinite; +} + +@-webkit-keyframes spinner { + 0% { + -webkit-transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + } +} + +@keyframes spinner { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +}