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