forked from Github/frigate
Improve navigation (#13833)
* Fix infinite loop * Fix review page not opening to historical review items * Use query arg for search and remove unused recording opening * Retain query * Clean up typing
This commit is contained in:
@@ -14,6 +14,10 @@ import {
|
||||
ReviewSummary,
|
||||
SegmentedReviewData,
|
||||
} from "@/types/review";
|
||||
import {
|
||||
getBeginningOfDayTimestamp,
|
||||
getEndOfDayTimestamp,
|
||||
} from "@/utils/dateUtil";
|
||||
import EventView from "@/views/events/EventView";
|
||||
import { RecordingView } from "@/views/recording/RecordingView";
|
||||
import axios from "axios";
|
||||
@@ -43,10 +47,17 @@ export default function Events() {
|
||||
.get(`review/${reviewId}`)
|
||||
.then((resp) => {
|
||||
if (resp.status == 200 && resp.data) {
|
||||
const startTime = resp.data.start_time - REVIEW_PADDING;
|
||||
const date = new Date(startTime * 1000);
|
||||
|
||||
setReviewFilter({
|
||||
after: getBeginningOfDayTimestamp(date),
|
||||
before: getEndOfDayTimestamp(date),
|
||||
});
|
||||
setRecording(
|
||||
{
|
||||
camera: resp.data.camera,
|
||||
startTime: resp.data.start_time - REVIEW_PADDING,
|
||||
startTime,
|
||||
severity: resp.data.severity,
|
||||
},
|
||||
true,
|
||||
|
||||
@@ -1,30 +1,24 @@
|
||||
import { useApiFilterArgs } from "@/hooks/use-api-filter";
|
||||
import { useCameraPreviews } from "@/hooks/use-camera-previews";
|
||||
import { useOverlayState, useSearchEffect } from "@/hooks/use-overlay-state";
|
||||
import { FrigateConfig } from "@/types/frigateConfig";
|
||||
import { RecordingStartingPoint } from "@/types/record";
|
||||
import { useSearchEffect } from "@/hooks/use-overlay-state";
|
||||
import { SearchFilter, SearchQuery, SearchResult } from "@/types/search";
|
||||
import { TimeRange } from "@/types/timeline";
|
||||
import { RecordingView } from "@/views/recording/RecordingView";
|
||||
import SearchView from "@/views/search/SearchView";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import useSWR from "swr";
|
||||
import useSWRInfinite from "swr/infinite";
|
||||
|
||||
const API_LIMIT = 25;
|
||||
|
||||
export default function Explore() {
|
||||
const { data: config } = useSWR<FrigateConfig>("config", {
|
||||
revalidateOnFocus: false,
|
||||
});
|
||||
|
||||
// search field handler
|
||||
|
||||
const [search, setSearch] = useState("");
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
|
||||
const [recording, setRecording] =
|
||||
useOverlayState<RecordingStartingPoint>("recording");
|
||||
const [searchFilter, setSearchFilter, searchSearchParams] =
|
||||
useApiFilterArgs<SearchFilter>();
|
||||
|
||||
const searchTerm = useMemo(
|
||||
() => searchSearchParams?.["query"] || "",
|
||||
[searchSearchParams],
|
||||
);
|
||||
|
||||
// search filter
|
||||
|
||||
@@ -36,11 +30,13 @@ export default function Explore() {
|
||||
return searchTerm.split(":")[1];
|
||||
}, [searchTerm]);
|
||||
|
||||
const [searchFilter, setSearchFilter, searchSearchParams] =
|
||||
useApiFilterArgs<SearchFilter>();
|
||||
|
||||
// search api
|
||||
|
||||
useSearchEffect("query", (query) => {
|
||||
setSearch(query);
|
||||
return false;
|
||||
});
|
||||
|
||||
useSearchEffect("similarity_search_id", (similarityId) => {
|
||||
setSearch(`similarity:${similarityId}`);
|
||||
// @ts-expect-error we want to clear this
|
||||
@@ -49,7 +45,16 @@ export default function Explore() {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setSearchTerm(search);
|
||||
if (!searchTerm && !search) {
|
||||
return;
|
||||
}
|
||||
|
||||
setSearchFilter({
|
||||
...searchFilter,
|
||||
query: search.length > 0 ? search : undefined,
|
||||
});
|
||||
// only update when search is updated
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [search]);
|
||||
|
||||
const searchQuery: SearchQuery = useMemo(() => {
|
||||
@@ -168,109 +173,19 @@ export default function Explore() {
|
||||
}
|
||||
}, [isReachingEnd, isLoadingMore, setSize, size, searchResults, searchQuery]);
|
||||
|
||||
// previews
|
||||
|
||||
const previewTimeRange = useMemo<TimeRange>(() => {
|
||||
if (!searchResults) {
|
||||
return { after: 0, before: 0 };
|
||||
}
|
||||
|
||||
return {
|
||||
after: Math.min(...searchResults.map((res) => res.start_time)),
|
||||
before: Math.max(
|
||||
...searchResults.map((res) => res.end_time ?? Date.now() / 1000),
|
||||
),
|
||||
};
|
||||
}, [searchResults]);
|
||||
|
||||
const allPreviews = useCameraPreviews(previewTimeRange, {
|
||||
autoRefresh: false,
|
||||
fetchPreviews: searchResults != undefined,
|
||||
});
|
||||
|
||||
// selection
|
||||
|
||||
const onOpenSearch = useCallback(
|
||||
(item: SearchResult) => {
|
||||
setRecording({
|
||||
camera: item.camera,
|
||||
startTime: item.start_time,
|
||||
severity: "alert",
|
||||
});
|
||||
},
|
||||
[setRecording],
|
||||
return (
|
||||
<SearchView
|
||||
search={search}
|
||||
searchTerm={searchTerm}
|
||||
searchFilter={searchFilter}
|
||||
searchResults={searchResults}
|
||||
isLoading={(isLoadingInitialData || isLoadingMore) ?? true}
|
||||
setSearch={setSearch}
|
||||
setSimilaritySearch={(search) => setSearch(`similarity:${search.id}`)}
|
||||
setSearchFilter={setSearchFilter}
|
||||
onUpdateFilter={setSearchFilter}
|
||||
loadMore={loadMore}
|
||||
hasMore={!isReachingEnd}
|
||||
/>
|
||||
);
|
||||
|
||||
const selectedReviewData = useMemo(() => {
|
||||
if (!recording) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!config) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!searchResults) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const allCameras = searchFilter?.cameras ?? Object.keys(config.cameras);
|
||||
|
||||
return {
|
||||
camera: recording.camera,
|
||||
start_time: recording.startTime,
|
||||
allCameras: allCameras,
|
||||
};
|
||||
|
||||
// previews will not update after item is selected
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [recording, searchResults]);
|
||||
|
||||
const selectedTimeRange = useMemo(() => {
|
||||
if (!recording) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const time = new Date(recording.startTime * 1000);
|
||||
time.setUTCMinutes(0, 0, 0);
|
||||
const start = time.getTime() / 1000;
|
||||
time.setHours(time.getHours() + 2);
|
||||
const end = time.getTime() / 1000;
|
||||
return {
|
||||
after: start,
|
||||
before: end,
|
||||
};
|
||||
}, [recording]);
|
||||
|
||||
if (recording) {
|
||||
if (selectedReviewData && selectedTimeRange) {
|
||||
return (
|
||||
<RecordingView
|
||||
startCamera={selectedReviewData.camera}
|
||||
startTime={selectedReviewData.start_time}
|
||||
allCameras={selectedReviewData.allCameras}
|
||||
allPreviews={allPreviews}
|
||||
timeRange={selectedTimeRange}
|
||||
updateFilter={setSearchFilter}
|
||||
/>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
<SearchView
|
||||
search={search}
|
||||
searchTerm={searchTerm}
|
||||
searchFilter={searchFilter}
|
||||
searchResults={searchResults}
|
||||
isLoading={(isLoadingInitialData || isLoadingMore) ?? true}
|
||||
setSearch={setSearch}
|
||||
setSimilaritySearch={(search) => setSearch(`similarity:${search.id}`)}
|
||||
setSearchFilter={setSearchFilter}
|
||||
onUpdateFilter={setSearchFilter}
|
||||
onOpenSearch={onOpenSearch}
|
||||
loadMore={loadMore}
|
||||
hasMore={!isReachingEnd}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user