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 ? (
-
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 (
-
- );
+ 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 ?
: null}
+ {loadedSrc ? (
+
+ ) : (
+
+ )}
);
}
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);
+ }
+}