2021-01-25 20:49:00 +03:00
|
|
|
import { h } from 'preact';
|
2021-01-30 19:52:37 +03:00
|
|
|
import ActivityIndicator from './ActivityIndicator';
|
2022-02-26 22:11:00 +03:00
|
|
|
import { useApiHost } from '../api';
|
|
|
|
|
import useSWR from 'swr';
|
2021-02-09 22:35:33 +03:00
|
|
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks';
|
2021-02-12 19:23:58 +03:00
|
|
|
import { useResizeObserver } from '../hooks';
|
2021-01-25 20:49:00 +03:00
|
|
|
|
2021-02-13 18:24:53 +03:00
|
|
|
export default function CameraImage({ camera, onload, searchParams = '', stretch = false }) {
|
2022-02-26 22:11:00 +03:00
|
|
|
const { data: config } = useSWR('config');
|
2021-01-26 18:04:03 +03:00
|
|
|
const apiHost = useApiHost();
|
2021-02-01 03:43:12 +03:00
|
|
|
const [hasLoaded, setHasLoaded] = useState(false);
|
2021-01-27 03:18:45 +03:00
|
|
|
const containerRef = useRef(null);
|
2021-02-01 03:43:12 +03:00
|
|
|
const canvasRef = useRef(null);
|
2023-02-05 18:13:15 +03: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;
|
2021-01-25 20:49:00 +03:00
|
|
|
|
2022-03-06 07:16:31 +03:00
|
|
|
const { name } = config ? config.cameras[camera] : '';
|
2022-11-02 14:41:44 +03:00
|
|
|
const enabled = config ? config.cameras[camera].enabled : 'True';
|
2022-03-06 07:16:31 +03:00
|
|
|
const { width, height } = config ? config.cameras[camera].detect : { width: 1, height: 1 };
|
2021-01-25 20:49:00 +03:00
|
|
|
const aspectRatio = width / height;
|
2021-01-27 03:18:45 +03:00
|
|
|
|
2021-02-13 18:24:53 +03:00
|
|
|
const scaledHeight = useMemo(() => {
|
|
|
|
|
const scaledHeight = Math.floor(availableWidth / aspectRatio);
|
2023-06-30 15:34:10 +03:00
|
|
|
const finalHeight = stretch ? scaledHeight : Math.min(scaledHeight, height);
|
|
|
|
|
|
|
|
|
|
if (finalHeight > 0) {
|
|
|
|
|
return finalHeight;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 100;
|
2021-02-13 18:24:53 +03:00
|
|
|
}, [availableWidth, aspectRatio, height, stretch]);
|
2023-06-30 15:34:10 +03:00
|
|
|
const scaledWidth = useMemo(
|
|
|
|
|
() => Math.ceil(scaledHeight * aspectRatio - scrollBarWidth),
|
|
|
|
|
[scaledHeight, aspectRatio, scrollBarWidth]
|
|
|
|
|
);
|
2021-01-27 03:18:45 +03:00
|
|
|
|
2021-02-09 22:35:33 +03:00
|
|
|
const img = useMemo(() => new Image(), []);
|
2021-01-27 03:18:45 +03:00
|
|
|
img.onload = useCallback(
|
|
|
|
|
(event) => {
|
2021-02-01 03:43:12 +03:00
|
|
|
setHasLoaded(true);
|
2021-02-09 22:35:33 +03:00
|
|
|
if (canvasRef.current) {
|
|
|
|
|
const ctx = canvasRef.current.getContext('2d');
|
|
|
|
|
ctx.drawImage(img, 0, 0, scaledWidth, scaledHeight);
|
|
|
|
|
}
|
2021-01-27 03:18:45 +03:00
|
|
|
onload && onload(event);
|
2021-01-25 20:49:00 +03:00
|
|
|
},
|
2021-02-09 22:35:33 +03:00
|
|
|
[img, scaledHeight, scaledWidth, setHasLoaded, onload, canvasRef]
|
2021-01-25 20:49:00 +03:00
|
|
|
);
|
|
|
|
|
|
2021-01-27 03:18:45 +03:00
|
|
|
useEffect(() => {
|
2022-03-06 07:16:31 +03:00
|
|
|
if (!config || scaledHeight === 0 || !canvasRef.current) {
|
2021-01-27 03:18:45 +03:00
|
|
|
return;
|
|
|
|
|
}
|
2023-10-07 06:20:30 +03:00
|
|
|
img.src = `${apiHost}api/${name}/latest.jpg?h=${scaledHeight}${searchParams ? `&${searchParams}` : ''}`;
|
2022-03-06 07:16:31 +03:00
|
|
|
}, [apiHost, canvasRef, name, img, searchParams, scaledHeight, config]);
|
2021-01-27 03:18:45 +03:00
|
|
|
|
2021-01-25 20:49:00 +03:00
|
|
|
return (
|
2021-02-09 22:35:33 +03:00
|
|
|
<div className="relative w-full" ref={containerRef}>
|
2023-06-30 15:34:10 +03: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 20:49:00 +03:00
|
|
|
);
|
|
|
|
|
}
|