Display activity indicators when debug and mask/zone images load (#12411)

This commit is contained in:
Josh Hawkins 2024-07-12 09:02:43 -05:00 committed by GitHub
parent aaafd63b94
commit 2ebd2dfcc7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 52 additions and 22 deletions

View File

@ -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>

View File

@ -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}