Files
frigate/web/src/components/CameraImage.jsx
T

79 lines
2.9 KiB
React
Raw Normal View History

2021-01-25 09:49:00 -08:00
import { h } from 'preact';
2021-01-30 08:52:37 -08:00
import ActivityIndicator from './ActivityIndicator';
2022-02-26 13:11:00 -06:00
import { useApiHost } from '../api';
import useSWR from 'swr';
import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks';
2021-02-12 08:23:58 -08:00
import { useResizeObserver } from '../hooks';
2021-01-25 09:49:00 -08:00
2021-02-13 07:24:53 -08:00
export default function CameraImage({ camera, onload, searchParams = '', stretch = false }) {
2022-02-26 13:11:00 -06:00
const { data: config } = useSWR('config');
2021-01-26 07:04:03 -08:00
const apiHost = useApiHost();
const [hasLoaded, setHasLoaded] = useState(false);
const containerRef = useRef(null);
const canvasRef = useRef(null);
2023-02-05 08:13:15 -07:00
const [{ width: containerWidth }] = useResizeObserver(containerRef);
// Add scrollbar width (when visible) to the available observer width to eliminate screen juddering.
// https://github.com/blakeblackshear/frigate/issues/1657
let scrollBarWidth = 0;
if (window.innerWidth && document.body.offsetWidth) {
scrollBarWidth = window.innerWidth - document.body.offsetWidth;
}
const availableWidth = scrollBarWidth ? containerWidth + scrollBarWidth : containerWidth;
2022-03-05 22:16:31 -06:00
const { name } = config ? config.cameras[camera] : '';
const enabled = config ? config.cameras[camera].enabled : 'True';
2022-03-05 22:16:31 -06:00
const { width, height } = config ? config.cameras[camera].detect : { width: 1, height: 1 };
2021-01-25 09:49:00 -08:00
const aspectRatio = width / height;
2021-02-13 07:24:53 -08:00
const scaledHeight = useMemo(() => {
const scaledHeight = Math.floor(availableWidth / aspectRatio);
2023-06-30 06:34:10 -06:00
const finalHeight = stretch ? scaledHeight : Math.min(scaledHeight, height);
if (finalHeight > 0) {
return finalHeight;
}
return 100;
2021-02-13 07:24:53 -08:00
}, [availableWidth, aspectRatio, height, stretch]);
2023-06-30 06:34:10 -06:00
const scaledWidth = useMemo(
() => Math.ceil(scaledHeight * aspectRatio - scrollBarWidth),
[scaledHeight, aspectRatio, scrollBarWidth]
);
const img = useMemo(() => new Image(), []);
img.onload = useCallback(
(event) => {
setHasLoaded(true);
if (canvasRef.current) {
const ctx = canvasRef.current.getContext('2d');
ctx.drawImage(img, 0, 0, scaledWidth, scaledHeight);
}
onload && onload(event);
2021-01-25 09:49:00 -08:00
},
[img, scaledHeight, scaledWidth, setHasLoaded, onload, canvasRef]
2021-01-25 09:49:00 -08:00
);
useEffect(() => {
2022-03-05 22:16:31 -06:00
if (!config || scaledHeight === 0 || !canvasRef.current) {
return;
}
2023-10-06 22:20:30 -05:00
img.src = `${apiHost}api/${name}/latest.jpg?h=${scaledHeight}${searchParams ? `&${searchParams}` : ''}`;
2022-03-05 22:16:31 -06:00
}, [apiHost, canvasRef, name, img, searchParams, scaledHeight, config]);
2021-01-25 09:49:00 -08:00
return (
<div className="relative w-full" ref={containerRef}>
2023-06-30 06:34:10 -06:00
{enabled ? (
<canvas data-testid="cameraimage-canvas" height={scaledHeight} ref={canvasRef} width={scaledWidth} />
) : (
<div class="text-center pt-6">Camera is disabled in config, no stream or snapshot available!</div>
)}
{!hasLoaded && enabled ? (
<div className="absolute inset-0 flex justify-center" style={`height: ${scaledHeight}px`}>
<ActivityIndicator />
</div>
) : null}
</div>
2021-01-25 09:49:00 -08:00
);
}