Quellcode durchsuchen

fix(web): make camera latest.jpg responsive

Paul Armstrong vor 4 Jahren
Ursprung
Commit
f0f3764992

+ 18 - 5
web/src/CameraMap.jsx

@@ -1,6 +1,7 @@
 import { h } from 'preact';
 import Box from './components/Box';
 import Button from './components/Button';
+import CameraImage from './components/CameraImage';
 import Heading from './components/Heading';
 import Switch from './components/Switch';
 import { route } from 'preact-router';
@@ -27,14 +28,26 @@ export default function CameraMasks({ camera, url }) {
     zones,
   } = cameraConfig;
 
+  const resizeObserver = useMemo(
+    () =>
+      new ResizeObserver((entries) => {
+        window.requestAnimationFrame(() => {
+          if (Array.isArray(entries) && entries.length) {
+            const scaledWidth = entries[0].contentRect.width;
+            const scale = scaledWidth / width;
+            setImageScale(scale);
+          }
+        });
+      }),
+    [camera, width, setImageScale]
+  );
+
   useEffect(() => {
     if (!imageRef.current) {
       return;
     }
-    const scaledWidth = imageRef.current.width;
-    const scale = scaledWidth / width;
-    setImageScale(scale);
-  }, [imageRef.current, setImageScale]);
+    resizeObserver.observe(imageRef.current);
+  }, [resizeObserver, imageRef.current]);
 
   const [motionMaskPoints, setMotionMaskPoints] = useState(
     Array.isArray(motionMask)
@@ -226,7 +239,7 @@ ${Object.keys(objectMaskPoints)
 
       <Box className="space-y-4">
         <div className="relative">
-          <img ref={imageRef} className="w-full" src={`${apiHost}/api/${camera}/latest.jpg`} />
+          <CameraImage imageRef={imageRef} camera={camera} />
           <EditableMask
             onChange={handleUpdateEditable}
             points={editing.subkey ? editing.set[editing.key][editing.subkey] : editing.set[editing.key]}

+ 2 - 2
web/src/Cameras.jsx

@@ -1,5 +1,6 @@
 import { h } from 'preact';
 import Box from './components/Box';
+import CameraImage from './components/CameraImage';
 import Events from './Events';
 import Heading from './components/Heading';
 import { route } from 'preact-router';
@@ -23,7 +24,6 @@ export default function Cameras() {
 }
 
 function Camera({ name }) {
-  const apiHost = useContext(ApiHost);
   const href = `/cameras/${name}`;
 
   return (
@@ -32,7 +32,7 @@ function Camera({ name }) {
       href={href}
     >
       <Heading size="base">{name}</Heading>
-      <img className="w-full" src={`${apiHost}/api/${name}/latest.jpg`} />
+      <CameraImage camera={name} />
     </Box>
   );
 }

+ 2 - 9
web/src/components/AutoUpdatingCameraImage.jsx

@@ -1,11 +1,10 @@
 import { h } from 'preact';
+import CameraImage from './CameraImage';
 import { ApiHost, Config } from '../context';
 import { useCallback, useEffect, useContext, useState } from 'preact/hooks';
 
 export default function AutoUpdatingCameraImage({ camera, searchParams }) {
-  const config = useContext(Config);
   const apiHost = useContext(ApiHost);
-  const cameraConfig = config.cameras[camera];
 
   const [key, setKey] = useState(Date.now());
   useEffect(() => {
@@ -17,11 +16,5 @@ export default function AutoUpdatingCameraImage({ camera, searchParams }) {
     };
   }, [key, searchParams]);
 
-  return (
-    <img
-      className="w-full"
-      src={`${apiHost}/api/${camera}/latest.jpg?cache=${key}&${searchParams}`}
-      alt={`Auto-updating ${camera} image`}
-    />
-  );
+  return <CameraImage camera={camera} searchParams={`cache=${key}&${searchParams}`} />;
 }

+ 38 - 0
web/src/components/CameraImage.jsx

@@ -0,0 +1,38 @@
+import { h } from 'preact';
+import { ApiHost, Config } from '../context';
+import { useCallback, useEffect, useContext, useState } from 'preact/hooks';
+
+export default function CameraImage({ camera, searchParams = '', imageRef }) {
+  const config = useContext(Config);
+  const apiHost = useContext(ApiHost);
+  const { name, width, height } = config.cameras[camera];
+
+  const aspectRatio = width / height;
+  const innerWidth = parseInt(window.innerWidth, 10);
+
+  const responsiveWidths = [640, 768, 1024, 1280];
+  if (innerWidth > responsiveWidths[responsiveWidths.length - 1]) {
+    responsiveWidths.push(innerWidth);
+  }
+
+  const src = `${apiHost}/api/${camera}/latest.jpg`;
+  const { srcset, sizes } = responsiveWidths.reduce(
+    (memo, w, i) => {
+      memo.srcset.push(`${src}?h=${Math.ceil(w / aspectRatio)}&${searchParams} ${w}w`);
+      memo.sizes.push(`(max-width: ${w}) ${Math.ceil((w / innerWidth) * 100)}vw`);
+      return memo;
+    },
+    { srcset: [], sizes: [] }
+  );
+
+  return (
+    <img
+      className="w-full"
+      srcset={srcset.join(', ')}
+      sizes={sizes.join(', ')}
+      src={`${srcset[srcset.length - 1]}`}
+      alt={name}
+      ref={imageRef}
+    />
+  );
+}

+ 3 - 7
web/src/components/Switch.jsx

@@ -2,13 +2,9 @@ import { h } from 'preact';
 import { useCallback, useState } from 'preact/hooks';
 
 export default function Switch({ checked, label, id, onChange }) {
-  const handleChange = useCallback(
-    (event) => {
-      console.log(event.target.checked, !checked);
-      onChange(id, !checked);
-    },
-    [id, onChange, checked]
-  );
+  const handleChange = useCallback(() => {
+    onChange(id, !checked);
+  }, [id, onChange, checked]);
 
   return (
     <label for={id} className="flex items-center cursor-pointer">