diff --git a/web/package-lock.json b/web/package-lock.json
index ab6144ec1..1ba3fac94 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -1721,6 +1721,11 @@
"resolved": "https://registry.npmjs.org/preact/-/preact-10.5.9.tgz",
"integrity": "sha512-X4m+4VMVINl/JFQKALOCwa3p8vhMAhBvle0hJ/W44w/WWfNb2TA7RNicDV3K2dNVs57f61GviEnVLiwN+fxiIg=="
},
+ "preact-async-route": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/preact-async-route/-/preact-async-route-2.2.1.tgz",
+ "integrity": "sha512-8bg1007akXs3YDmzYT4McaTe6ji2FIzcc0/NTlu+vjJaKPNQ8lNG/HQ6LP+FoIxQ4m/KH5vvJCHJN5ADp2iNGA=="
+ },
"preact-router": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/preact-router/-/preact-router-3.2.1.tgz",
diff --git a/web/package.json b/web/package.json
index cf5569bcd..9bc642727 100644
--- a/web/package.json
+++ b/web/package.json
@@ -16,6 +16,7 @@
"postcss": "^8.2.2",
"postcss-cli": "^8.3.1",
"preact": "^10.5.9",
+ "preact-async-route": "^2.2.1",
"preact-router": "^3.2.1",
"rimraf": "^3.0.2",
"snowpack": "^3.0.11",
diff --git a/web/src/App.jsx b/web/src/App.jsx
index 198bc7411..3f943181b 100644
--- a/web/src/App.jsx
+++ b/web/src/App.jsx
@@ -1,15 +1,11 @@
+import * as Routes from './routes';
import { h } from 'preact';
import ActivityIndicator from './components/ActivityIndicator';
+import AsyncRoute from 'preact-async-route';
import AppBar from './components/AppBar';
-import Camera from './Camera';
-import CameraMap from './CameraMap';
-import Cameras from './Cameras';
-import Debug from './Debug';
-import Event from './Event';
-import Events from './Events';
+import Cameras from './routes/Cameras';
import { Router } from 'preact-router';
import Sidebar from './Sidebar';
-import StyleGuide from './StyleGuide';
import Api, { FetchStatus, useConfig } from './api';
import { DarkModeProvider, DrawerProvider } from './context';
@@ -29,12 +25,12 @@ export default function App() {
-
-
-
-
-
- {import.meta.env.NODE_ENV !== 'development' ? : null}
+
+
+
+
+
+
diff --git a/web/src/Settings.jsx b/web/src/Settings.jsx
deleted file mode 100644
index ef47a55aa..000000000
--- a/web/src/Settings.jsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import { h } from 'preact';
-import { useDarkMode } from './context';
-import { useCallback } from 'preact/hooks';
-
-export default function Settings() {
- const { currentMode, persistedMode, setDarkMode } = useDarkMode();
-
- const handleSelect = useCallback(
- (event) => {
- const mode = event.target.value;
- setDarkMode(mode);
- },
- [setDarkMode]
- );
-
- return (
-
-
-
- );
-}
diff --git a/web/src/Sidebar.jsx b/web/src/Sidebar.jsx
index a1d58385d..decc709ff 100644
--- a/web/src/Sidebar.jsx
+++ b/web/src/Sidebar.jsx
@@ -30,6 +30,12 @@ export default function Sidebar() {
+ {import.meta.env.MODE !== 'production' ? (
+
+
+
+
+ ) : null}
diff --git a/web/src/components/CameraImage.jsx b/web/src/components/CameraImage.jsx
index b1634c95d..d5cf6a65d 100644
--- a/web/src/components/CameraImage.jsx
+++ b/web/src/components/CameraImage.jsx
@@ -50,7 +50,7 @@ export default function CameraImage({ camera, onload, searchParams = '' }) {
);
useEffect(() => {
- if (!scaledHeight) {
+ if (!scaledHeight || !canvasRef.current) {
return;
}
img.src = `${apiHost}/api/${name}/latest.jpg?h=${scaledHeight}${searchParams ? `&${searchParams}` : ''}`;
diff --git a/web/src/Camera.jsx b/web/src/routes/Camera.jsx
similarity index 89%
rename from web/src/Camera.jsx
rename to web/src/routes/Camera.jsx
index a586e8a21..42429d670 100644
--- a/web/src/Camera.jsx
+++ b/web/src/routes/Camera.jsx
@@ -1,15 +1,15 @@
import { h } from 'preact';
-import AutoUpdatingCameraImage from './components/AutoUpdatingCameraImage';
-import Button from './components/Button';
-import Card from './components/Card';
-import Heading from './components/Heading';
-import Link from './components/Link';
-import SettingsIcon from './icons/Settings';
-import Switch from './components/Switch';
+import AutoUpdatingCameraImage from '../components/AutoUpdatingCameraImage';
+import Button from '../components/Button';
+import Card from '../components/Card';
+import Heading from '../components/Heading';
+import Link from '../components/Link';
+import SettingsIcon from '../icons/Settings';
+import Switch from '../components/Switch';
import { route } from 'preact-router';
-import { usePersistence } from './context';
+import { usePersistence } from '../context';
import { useCallback, useContext, useMemo, useState } from 'preact/hooks';
-import { useApiHost, useConfig } from './api';
+import { useApiHost, useConfig } from '../api';
export default function Camera({ camera }) {
const { data: config } = useConfig();
diff --git a/web/src/CameraMap.jsx b/web/src/routes/CameraMap.jsx
similarity index 99%
rename from web/src/CameraMap.jsx
rename to web/src/routes/CameraMap.jsx
index c267a6c57..344606a59 100644
--- a/web/src/CameraMap.jsx
+++ b/web/src/routes/CameraMap.jsx
@@ -1,8 +1,8 @@
import { h } from 'preact';
-import Card from './components/Card';
-import Button from './components/Button';
-import Heading from './components/Heading';
-import Switch from './components/Switch';
+import Card from '../components/Card';
+import Button from '../components/Button';
+import Heading from '../components/Heading';
+import Switch from '../components/Switch';
import { route } from 'preact-router';
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'preact/hooks';
import { useApiHost, useConfig } from './api';
diff --git a/web/src/Cameras.jsx b/web/src/routes/Cameras.jsx
similarity index 72%
rename from web/src/Cameras.jsx
rename to web/src/routes/Cameras.jsx
index 91f7e4d70..70ca74a8f 100644
--- a/web/src/Cameras.jsx
+++ b/web/src/routes/Cameras.jsx
@@ -1,11 +1,10 @@
import { h } from 'preact';
-import ActivityIndicator from './components/ActivityIndicator';
-import Card from './components/Card';
-import CameraImage from './components/CameraImage';
-import Events from './Events';
-import Heading from './components/Heading';
+import ActivityIndicator from '../components/ActivityIndicator';
+import Card from '../components/Card';
+import CameraImage from '../components/CameraImage';
+import Heading from '../components/Heading';
import { route } from 'preact-router';
-import { useConfig } from './api';
+import { useConfig } from '../api';
import { useMemo } from 'preact/hooks';
export default function Cameras() {
diff --git a/web/src/Debug.jsx b/web/src/routes/Debug.jsx
similarity index 90%
rename from web/src/Debug.jsx
rename to web/src/routes/Debug.jsx
index 6d164f3fb..5eaa0b544 100644
--- a/web/src/Debug.jsx
+++ b/web/src/routes/Debug.jsx
@@ -1,10 +1,10 @@
import { h } from 'preact';
-import ActivityIndicator from './components/ActivityIndicator';
-import Button from './components/Button';
-import Heading from './components/Heading';
-import Link from './components/Link';
-import { FetchStatus, useConfig, useStats } from './api';
-import { Table, Tbody, Thead, Tr, Th, Td } from './components/Table';
+import ActivityIndicator from '../components/ActivityIndicator';
+import Button from '../components/Button';
+import Heading from '../components/Heading';
+import Link from '../components/Link';
+import { FetchStatus, useConfig, useStats } from '../api';
+import { Table, Tbody, Thead, Tr, Th, Td } from '../components/Table';
import { useCallback, useEffect, useState } from 'preact/hooks';
export default function Debug() {
diff --git a/web/src/Event.jsx b/web/src/routes/Event.jsx
similarity index 86%
rename from web/src/Event.jsx
rename to web/src/routes/Event.jsx
index 142284d6f..704ba20a6 100644
--- a/web/src/Event.jsx
+++ b/web/src/routes/Event.jsx
@@ -1,9 +1,9 @@
import { h, Fragment } from 'preact';
-import ActivityIndicator from './components/ActivityIndicator';
-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 ActivityIndicator from '../components/ActivityIndicator';
+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';
export default function Event({ eventId }) {
const apiHost = useApiHost();
diff --git a/web/src/Events.jsx b/web/src/routes/Events.jsx
similarity index 96%
rename from web/src/Events.jsx
rename to web/src/routes/Events.jsx
index e145889c5..29f258ad0 100644
--- a/web/src/Events.jsx
+++ b/web/src/routes/Events.jsx
@@ -1,13 +1,13 @@
import { h } from 'preact';
-import ActivityIndicator from './components/ActivityIndicator';
-import Card from './components/Card';
-import Heading from './components/Heading';
-import Link from './components/Link';
-import Select from './components/Select';
+import ActivityIndicator from '../components/ActivityIndicator';
+import Card from '../components/Card';
+import Heading from '../components/Heading';
+import Link from '../components/Link';
+import Select from '../components/Select';
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 { FetchStatus, useApiHost, useConfig, useEvents } from '../api';
+import { Table, Thead, Tbody, Tfoot, Th, Tr, Td } from '../components/Table';
import { useCallback, useContext, useEffect, useMemo, useRef, useReducer, useState } from 'preact/hooks';
const API_LIMIT = 25;
diff --git a/web/src/StyleGuide.jsx b/web/src/routes/StyleGuide.jsx
similarity index 87%
rename from web/src/StyleGuide.jsx
rename to web/src/routes/StyleGuide.jsx
index 8f2235ef4..32f51a2fa 100644
--- a/web/src/StyleGuide.jsx
+++ b/web/src/routes/StyleGuide.jsx
@@ -1,12 +1,12 @@
import { h } from 'preact';
-import ArrowDropdown from './icons/ArrowDropdown';
-import ArrowDropup from './icons/ArrowDropup';
-import Card from './components/Card';
-import Button from './components/Button';
-import Heading from './components/Heading';
-import Select from './components/Select';
-import Switch from './components/Switch';
-import TextField from './components/TextField';
+import ArrowDropdown from '../icons/ArrowDropdown';
+import ArrowDropup from '../icons/ArrowDropup';
+import Card from '../components/Card';
+import Button from '../components/Button';
+import Heading from '../components/Heading';
+import Select from '../components/Select';
+import Switch from '../components/Switch';
+import TextField from '../components/TextField';
import { useCallback, useState } from 'preact/hooks';
export default function StyleGuide() {
diff --git a/web/src/routes/index.js b/web/src/routes/index.js
new file mode 100644
index 000000000..1fdabe1ac
--- /dev/null
+++ b/web/src/routes/index.js
@@ -0,0 +1,29 @@
+export async function getCameraMap(url, cb, props) {
+ const module = await import('./CameraMap.jsx');
+ return module.default;
+}
+
+export async function getCamera(url, cb, props) {
+ const module = await import('./Camera.jsx');
+ return module.default;
+}
+
+export async function getEvent(url, cb, props) {
+ const module = await import('./Event.jsx');
+ return module.default;
+}
+
+export async function getEvents(url, cb, props) {
+ const module = await import('./Events.jsx');
+ return module.default;
+}
+
+export async function getDebug(url, cb, props) {
+ const module = await import('./Debug.jsx');
+ return module.default;
+}
+
+export async function getStyleGuide(url, cb, props) {
+ const module = await import('./StyleGuide.jsx');
+ return module.default;
+}