Implement paging

This commit is contained in:
Nicolas Mowen 2024-02-17 16:40:04 -07:00
parent 7b1c7f9be9
commit 153a8be7c1

View File

@ -6,35 +6,97 @@ import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import { FrigateConfig } from "@/types/frigateConfig"; import { FrigateConfig } from "@/types/frigateConfig";
import { ReviewSegment, ReviewSeverity } from "@/types/review"; import { ReviewSegment, ReviewSeverity } from "@/types/review";
import { formatUnixTimestampToDateTime } from "@/utils/dateUtil"; import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
import { useMemo, useState } from "react"; import axios from "axios";
import { useCallback, useMemo, useRef, useState } from "react";
import { LuCalendar, LuFilter, LuVideo } from "react-icons/lu"; import { LuCalendar, LuFilter, LuVideo } from "react-icons/lu";
import { MdCircle } from "react-icons/md"; import { MdCircle } from "react-icons/md";
import useSWR from "swr"; import useSWR from "swr";
import useSWRInfinite from "swr/infinite";
const API_LIMIT = 12;
export default function Events() { export default function Events() {
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
const [severity, setSeverity] = useState<ReviewSeverity>("alert"); const [severity, setSeverity] = useState<ReviewSeverity>("alert");
const { data: reviewSegments } = useSWR<ReviewSegment[]>([ // review paging
"review", const reviewSearchParams = {};
{ limit: 500 }, const reviewSegmentFetcher = useCallback((key: any) => {
]); const [path, params] = Array.isArray(key) ? key : [key, undefined];
return axios.get(path, { params }).then((res) => res.data);
}, []);
const getKey = useCallback(
(index: number, prevData: ReviewSegment[]) => {
if (index > 0) {
const lastDate = prevData[prevData.length - 1].start_time;
const pagedParams = reviewSearchParams
? { before: lastDate, limit: API_LIMIT, severity: severity }
: {
...reviewSearchParams,
before: lastDate,
limit: API_LIMIT,
};
return ["review", pagedParams];
}
const params = reviewSearchParams
? { limit: API_LIMIT, severity: severity }
: { ...reviewSearchParams, limit: API_LIMIT };
return ["review", params];
},
[reviewSearchParams]
);
const {
data: reviewPages,
mutate: updateSegments,
size,
setSize,
isValidating,
} = useSWRInfinite<ReviewSegment[]>(getKey, reviewSegmentFetcher);
const isDone = useMemo(
() => (reviewPages?.at(-1)?.length ?? 0) < API_LIMIT,
[reviewPages]
);
const observer = useRef<IntersectionObserver | null>();
const lastReviewRef = useCallback(
(node: HTMLElement | null) => {
if (isValidating) return;
if (observer.current) observer.current.disconnect();
try {
observer.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && !isDone) {
setSize(size + 1);
}
});
if (node) observer.current.observe(node);
} catch (e) {
// no op
}
},
[isValidating, isDone]
);
// preview videos
const previewTimes = useMemo(() => { const previewTimes = useMemo(() => {
if (!reviewSegments) { if (!reviewPages) {
return undefined; return undefined;
} }
const startDate = new Date(); const startDate = new Date();
startDate.setMinutes(0, 0, 0); startDate.setMinutes(0, 0, 0);
const endDate = new Date(reviewSegments.at(-1)!!.end_time); const endDate = new Date(reviewPages.at(-1)!!.at(-1)!!.end_time);
endDate.setHours(0, 0, 0, 0); endDate.setHours(0, 0, 0, 0);
return { return {
start: startDate.getTime() / 1000, start: startDate.getTime() / 1000,
end: endDate.getTime() / 1000, end: endDate.getTime() / 1000,
}; };
}, [reviewSegments]); }, [reviewPages]);
const { data: allPreviews } = useSWR<Preview[]>( const { data: allPreviews } = useSWR<Preview[]>(
previewTimes previewTimes
? `preview/all/start/${previewTimes.start}/end/${previewTimes.end}` ? `preview/all/start/${previewTimes.start}/end/${previewTimes.end}`
@ -103,8 +165,10 @@ export default function Events() {
</div> </div>
<div className="flex flex-wrap gap-2 mt-2"> <div className="flex flex-wrap gap-2 mt-2">
{reviewSegments?.map((value) => { {reviewPages?.map((reviewSegments, pageIdx) => {
if (value.severity == severity) { return reviewSegments.map((value, segIdx) => {
const lastRow =
pageIdx == size - 1 && segIdx == reviewSegments.length - 1;
const detectConfig = config.cameras[value.camera].detect; const detectConfig = config.cameras[value.camera].detect;
const relevantPreview = Object.values(allPreviews || []).find( const relevantPreview = Object.values(allPreviews || []).find(
(preview) => (preview) =>
@ -114,37 +178,42 @@ export default function Events() {
); );
return ( return (
<div <>
className="relative h-[234px] rounded-2xl overflow-hidden" <div
style={{ ref={lastRow ? lastReviewRef : null}
aspectRatio: detectConfig.width / detectConfig.height, key={value.id}
}} className="relative h-[234px] rounded-2xl overflow-hidden"
> style={{
{relevantPreview ? ( aspectRatio: detectConfig.width / detectConfig.height,
<PreviewThumbnailPlayer }}
relevantPreview={relevantPreview} >
camera={value.camera} {relevantPreview ? (
startTs={value.start_time} <PreviewThumbnailPlayer
isMobile={false} relevantPreview={relevantPreview}
eventId="" camera={value.camera}
/> startTs={value.start_time}
) : ( isMobile={false}
<div> eventId=""
{value.camera} {value.data.objects} />
) : (
<div>
{value.camera} {value.data.objects}
</div>
)}
<div className="absolute left-1 right-1 bottom-1 flex justify-between">
<TimeAgo time={value.start_time * 1000} />
{formatUnixTimestampToDateTime(value.start_time, {
strftime_fmt:
config.ui.time_format == "24hour"
? "%b %-d, %H:%M"
: "%b %-d, %I:%M %p",
})}
</div> </div>
)}
<div className="absolute left-1 right-1 bottom-1 flex justify-between">
<TimeAgo time={value.start_time * 1000} />
{formatUnixTimestampToDateTime(value.start_time, {
strftime_fmt:
config.ui.time_format == "24hour"
? "%b %-d, %H:%M"
: "%b %-d, %I:%M %p",
})}
</div> </div>
</div> {lastRow && !isDone && <ActivityIndicator />}
</>
); );
} });
})} })}
</div> </div>
</> </>