frigate/web/src/components/camera/CameraImage.tsx
Rui Alves cffc431bf0
Frigate HTTP API using FastAPI (#13871)
* POC: Added FastAPI with one endpoint (get /logs/service)

* POC: Revert error_log

* POC: Converted preview related endpoints to FastAPI

* POC: Converted two more endpoints to FastAPI

* POC: lint

* Convert all media endpoints to FastAPI. Added /media prefix (/media/camera && media/events && /media/preview)

* Convert all notifications API endpoints to FastAPI

* Convert first review API endpoints to FastAPI

* Convert remaining review API endpoints to FastAPI

* Convert export endpoints to FastAPI

* Fix path parameters

* Convert events endpoints to FastAPI

* Use body for multiple events endpoints

* Use body for multiple events endpoints (create and end event)

* Convert app endpoints to FastAPI

* Convert app endpoints to FastAPI

* Convert auth endpoints to FastAPI

* Removed flask app in favour of FastAPI app. Implemented FastAPI middleware to check CSRF, connect and disconnect from DB. Added middleware x-forwared-for headers

* Added starlette plugin to expose custom headers

* Use slowapi as the limiter

* Use query parameters for the frame latest endpoint

* Use query parameters for the media snapshot.jpg endpoint

* Use query parameters for the media MJPEG feed endpoint

* Revert initial nginx.conf change

* Added missing even_id for /events/search endpoint

* Removed left over comment

* Use FastAPI TestClient

* severity query parameter should be a string

* Use the same pattern for all tests

* Fix endpoint

* Revert media routers to old names. Order routes to make sure the dynamic ones from media.py are only used whenever there's no match on auth/etc

* Reverted paths for media on tsx files

* Deleted file

* Fix test_http to use TestClient

* Formatting

* Bind timeline to DB

* Fix http tests

* Replace filename with pathvalidate

* Fix latest.ext handling and disable uvicorn access logs

* Add cosntraints to api provided values

* Formatting

* Remove unused

* Remove unused

* Get rate limiter working

---------

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
2024-09-24 07:05:30 -06:00

111 lines
2.9 KiB
TypeScript

import { useApiHost } from "@/api";
import { useEffect, useMemo, useRef, useState } from "react";
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;
camera: string;
onload?: () => void;
searchParams?: string;
};
export default function CameraImage({
className,
camera,
onload,
searchParams = "",
}: CameraImageProps) {
const { data: config } = useSWR("config");
const apiHost = useApiHost();
const [imageLoaded, setImageLoaded] = useState(false);
const containerRef = useRef<HTMLDivElement | null>(null);
const imgRef = useRef<HTMLImageElement | null>(null);
const { name } = config ? config.cameras[camera] : "";
const enabled = config ? config.cameras[camera].enabled : "True";
const [{ width: containerWidth, height: containerHeight }] =
useResizeObserver(containerRef);
const requestHeight = useMemo(() => {
if (!config || containerHeight == 0) {
return 360;
}
return Math.min(
config.cameras[camera].detect.height,
Math.round(containerHeight * (isDesktop ? 1.1 : 1.25)),
);
}, [config, camera, containerHeight]);
const [isPortraitImage, setIsPortraitImage] = useState(false);
useEffect(() => {
setImageLoaded(false);
setIsPortraitImage(false);
}, [camera]);
useEffect(() => {
if (!config || !imgRef.current) {
return;
}
const newSrc = `${apiHost}api/${name}/latest.webp?height=${requestHeight}${
searchParams ? `&${searchParams}` : ""
}`;
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={cn(
"object-contain",
imageLoaded
? isPortraitImage
? "h-full w-auto"
: "h-auto w-full"
: "invisible",
"rounded-lg md:rounded-2xl",
)}
onLoad={handleImageLoad}
loading="lazy"
/>
) : (
<div className="pt-6 text-center">
Camera is disabled in config, no stream or snapshot available!
</div>
)}
{!imageLoaded && enabled ? (
<div className="absolute bottom-0 left-0 right-0 top-0 flex items-center justify-center">
<ActivityIndicator />
</div>
) : null}
</div>
);
}