mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-12-19 19:06:16 +01:00
Display activity indicators when debug and mask/zone images load (#12411)
This commit is contained in:
parent
aaafd63b94
commit
2ebd2dfcc7
@ -4,6 +4,7 @@ import useSWR from "swr";
|
||||
import ActivityIndicator from "../indicators/activity-indicator";
|
||||
import { useResizeObserver } from "@/hooks/resize-observer";
|
||||
import { isDesktop } from "react-device-detect";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type CameraImageProps = {
|
||||
className?: string;
|
||||
@ -20,7 +21,7 @@ export default function CameraImage({
|
||||
}: CameraImageProps) {
|
||||
const { data: config } = useSWR("config");
|
||||
const apiHost = useApiHost();
|
||||
const [hasLoaded, setHasLoaded] = useState(false);
|
||||
const [imageLoaded, setImageLoaded] = useState(false);
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
const imgRef = useRef<HTMLImageElement | null>(null);
|
||||
|
||||
@ -31,7 +32,7 @@ export default function CameraImage({
|
||||
useResizeObserver(containerRef);
|
||||
|
||||
const requestHeight = useMemo(() => {
|
||||
if (!config || containerHeight == 0 || !hasLoaded) {
|
||||
if (!config || containerHeight == 0) {
|
||||
return 360;
|
||||
}
|
||||
|
||||
@ -39,47 +40,66 @@ export default function CameraImage({
|
||||
config.cameras[camera].detect.height,
|
||||
Math.round(containerHeight * (isDesktop ? 1.1 : 1.25)),
|
||||
);
|
||||
}, [config, camera, containerHeight, hasLoaded]);
|
||||
}, [config, camera, containerHeight]);
|
||||
|
||||
const isPortraitImage = useMemo(() => {
|
||||
if (imgRef.current && containerWidth && containerHeight && hasLoaded) {
|
||||
const { naturalHeight, naturalWidth } = imgRef.current;
|
||||
return naturalWidth / naturalHeight < containerWidth / containerHeight;
|
||||
}
|
||||
}, [containerWidth, containerHeight, hasLoaded]);
|
||||
const [isPortraitImage, setIsPortraitImage] = useState(false);
|
||||
|
||||
useEffect(() => setHasLoaded(false), [camera]);
|
||||
useEffect(() => {
|
||||
setImageLoaded(false);
|
||||
setIsPortraitImage(false);
|
||||
}, [camera]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!config || !imgRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
imgRef.current.src = `${apiHost}api/${name}/latest.webp?h=${requestHeight}${
|
||||
const newSrc = `${apiHost}api/${name}/latest.webp?h=${requestHeight}${
|
||||
searchParams ? `&${searchParams}` : ""
|
||||
}`;
|
||||
}, [apiHost, name, imgRef, searchParams, requestHeight, config]);
|
||||
|
||||
if (imgRef.current.src !== newSrc) {
|
||||
imgRef.current.src = newSrc;
|
||||
}
|
||||
}, [apiHost, name, searchParams, requestHeight, config, camera]);
|
||||
|
||||
const handleImageLoad = () => {
|
||||
if (imgRef.current && containerWidth && containerHeight) {
|
||||
const { naturalWidth, naturalHeight } = imgRef.current;
|
||||
setIsPortraitImage(
|
||||
naturalWidth / naturalHeight < containerWidth / containerHeight,
|
||||
);
|
||||
}
|
||||
|
||||
setImageLoaded(true);
|
||||
|
||||
if (onload) {
|
||||
onload();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={className} ref={containerRef}>
|
||||
{enabled ? (
|
||||
<img
|
||||
ref={imgRef}
|
||||
className={`object-contain ${isPortraitImage ? "h-full w-auto" : "h-auto w-full"} rounded-lg md:rounded-2xl`}
|
||||
onLoad={() => {
|
||||
setHasLoaded(true);
|
||||
|
||||
if (onload) {
|
||||
onload();
|
||||
}
|
||||
}}
|
||||
className={cn(
|
||||
"object-contain",
|
||||
imageLoaded
|
||||
? isPortraitImage
|
||||
? "h-full w-auto"
|
||||
: "h-auto w-full"
|
||||
: "invisible",
|
||||
"rounded-lg md:rounded-2xl",
|
||||
)}
|
||||
onLoad={handleImageLoad}
|
||||
/>
|
||||
) : (
|
||||
<div className="pt-6 text-center">
|
||||
Camera is disabled in config, no stream or snapshot available!
|
||||
</div>
|
||||
)}
|
||||
{!hasLoaded && enabled ? (
|
||||
{!imageLoaded && enabled ? (
|
||||
<div className="absolute bottom-0 left-0 right-0 top-0 flex items-center justify-center">
|
||||
<ActivityIndicator />
|
||||
</div>
|
||||
|
@ -5,6 +5,7 @@ import Konva from "konva";
|
||||
import type { KonvaEventObject } from "konva/lib/Node";
|
||||
import { Polygon, PolygonType } from "@/types/canvas";
|
||||
import { useApiHost } from "@/api";
|
||||
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
||||
|
||||
type PolygonCanvasProps = {
|
||||
containerRef: RefObject<HTMLDivElement>;
|
||||
@ -29,6 +30,7 @@ export function PolygonCanvas({
|
||||
hoveredPolygonIndex,
|
||||
selectedZoneMask,
|
||||
}: PolygonCanvasProps) {
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
const [image, setImage] = useState<HTMLImageElement | undefined>();
|
||||
const imageRef = useRef<Konva.Image | null>(null);
|
||||
const stageRef = useRef<Konva.Stage>(null);
|
||||
@ -36,13 +38,16 @@ export function PolygonCanvas({
|
||||
|
||||
const videoElement = useMemo(() => {
|
||||
if (camera && width && height) {
|
||||
setIsLoaded(false);
|
||||
const element = new window.Image();
|
||||
element.width = width;
|
||||
element.height = height;
|
||||
element.src = `${apiHost}api/${camera}/latest.webp?cache=${Date.now()}`;
|
||||
return element;
|
||||
}
|
||||
}, [camera, width, height, apiHost]);
|
||||
// we know that these deps are correct
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [camera, apiHost]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!videoElement) {
|
||||
@ -50,6 +55,7 @@ export function PolygonCanvas({
|
||||
}
|
||||
const onload = function () {
|
||||
setImage(videoElement);
|
||||
setIsLoaded(true);
|
||||
};
|
||||
videoElement.addEventListener("load", onload);
|
||||
return () => {
|
||||
@ -218,6 +224,10 @@ export function PolygonCanvas({
|
||||
}
|
||||
}, [activePolygonIndex, polygons, setPolygons]);
|
||||
|
||||
if (!isLoaded) {
|
||||
return <ActivityIndicator />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Stage
|
||||
ref={stageRef}
|
||||
|
Loading…
Reference in New Issue
Block a user