mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-14 23:25:25 +03:00
Use query arg for search and remove unused recording opening
This commit is contained in:
parent
de0019fe90
commit
e6059a4982
@ -584,24 +584,26 @@ export default function InputWithTags({
|
|||||||
)}
|
)}
|
||||||
{Object.entries(filters).map(([filterType, filterValues]) =>
|
{Object.entries(filters).map(([filterType, filterValues]) =>
|
||||||
Array.isArray(filterValues) ? (
|
Array.isArray(filterValues) ? (
|
||||||
filterValues.map((value, index) => (
|
filterValues
|
||||||
<span
|
.filter(() => filterType !== "query")
|
||||||
key={`${filterType}-${index}`}
|
.map((value, index) => (
|
||||||
className="inline-flex items-center whitespace-nowrap rounded-full bg-green-100 px-2 py-0.5 text-sm capitalize text-green-800"
|
<span
|
||||||
>
|
key={`${filterType}-${index}`}
|
||||||
{filterType.replaceAll("_", " ")}:{" "}
|
className="inline-flex items-center whitespace-nowrap rounded-full bg-green-100 px-2 py-0.5 text-sm capitalize text-green-800"
|
||||||
{value.replaceAll("_", " ")}
|
|
||||||
<button
|
|
||||||
onClick={() =>
|
|
||||||
removeFilter(filterType as FilterType, value)
|
|
||||||
}
|
|
||||||
className="ml-1 focus:outline-none"
|
|
||||||
aria-label={`Remove ${filterType}:${value.replaceAll("_", " ")} filter`}
|
|
||||||
>
|
>
|
||||||
<LuX className="h-3 w-3" />
|
{filterType.replaceAll("_", " ")}:{" "}
|
||||||
</button>
|
{value.replaceAll("_", " ")}
|
||||||
</span>
|
<button
|
||||||
))
|
onClick={() =>
|
||||||
|
removeFilter(filterType as FilterType, value)
|
||||||
|
}
|
||||||
|
className="ml-1 focus:outline-none"
|
||||||
|
aria-label={`Remove ${filterType}:${value.replaceAll("_", " ")} filter`}
|
||||||
|
>
|
||||||
|
<LuX className="h-3 w-3" />
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
))
|
||||||
) : (
|
) : (
|
||||||
<span
|
<span
|
||||||
key={filterType}
|
key={filterType}
|
||||||
|
|||||||
@ -1,30 +1,24 @@
|
|||||||
import { useApiFilterArgs } from "@/hooks/use-api-filter";
|
import { useApiFilterArgs } from "@/hooks/use-api-filter";
|
||||||
import { useCameraPreviews } from "@/hooks/use-camera-previews";
|
import { useSearchEffect } from "@/hooks/use-overlay-state";
|
||||||
import { useOverlayState, useSearchEffect } from "@/hooks/use-overlay-state";
|
|
||||||
import { FrigateConfig } from "@/types/frigateConfig";
|
|
||||||
import { RecordingStartingPoint } from "@/types/record";
|
|
||||||
import { SearchFilter, SearchQuery, SearchResult } from "@/types/search";
|
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 SearchView from "@/views/search/SearchView";
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import useSWR from "swr";
|
|
||||||
import useSWRInfinite from "swr/infinite";
|
import useSWRInfinite from "swr/infinite";
|
||||||
|
|
||||||
const API_LIMIT = 25;
|
const API_LIMIT = 25;
|
||||||
|
|
||||||
export default function Explore() {
|
export default function Explore() {
|
||||||
const { data: config } = useSWR<FrigateConfig>("config", {
|
|
||||||
revalidateOnFocus: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
// search field handler
|
// search field handler
|
||||||
|
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
|
||||||
|
|
||||||
const [recording, setRecording] =
|
const [searchFilter, setSearchFilter, searchSearchParams] =
|
||||||
useOverlayState<RecordingStartingPoint>("recording");
|
useApiFilterArgs<SearchFilter>();
|
||||||
|
|
||||||
|
const searchTerm = useMemo(
|
||||||
|
() => searchSearchParams?.["query"] || "",
|
||||||
|
[searchSearchParams],
|
||||||
|
);
|
||||||
|
|
||||||
// search filter
|
// search filter
|
||||||
|
|
||||||
@ -36,9 +30,6 @@ export default function Explore() {
|
|||||||
return searchTerm.split(":")[1];
|
return searchTerm.split(":")[1];
|
||||||
}, [searchTerm]);
|
}, [searchTerm]);
|
||||||
|
|
||||||
const [searchFilter, setSearchFilter, searchSearchParams] =
|
|
||||||
useApiFilterArgs<SearchFilter>();
|
|
||||||
|
|
||||||
// search api
|
// search api
|
||||||
|
|
||||||
useSearchEffect("similarity_search_id", (similarityId) => {
|
useSearchEffect("similarity_search_id", (similarityId) => {
|
||||||
@ -49,7 +40,16 @@ export default function Explore() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
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]);
|
}, [search]);
|
||||||
|
|
||||||
const searchQuery: SearchQuery = useMemo(() => {
|
const searchQuery: SearchQuery = useMemo(() => {
|
||||||
@ -168,109 +168,19 @@ export default function Explore() {
|
|||||||
}
|
}
|
||||||
}, [isReachingEnd, isLoadingMore, setSize, size, searchResults, searchQuery]);
|
}, [isReachingEnd, isLoadingMore, setSize, size, searchResults, searchQuery]);
|
||||||
|
|
||||||
// previews
|
return (
|
||||||
|
<SearchView
|
||||||
const previewTimeRange = useMemo<TimeRange>(() => {
|
search={search}
|
||||||
if (!searchResults) {
|
searchTerm={searchTerm}
|
||||||
return { after: 0, before: 0 };
|
searchFilter={searchFilter}
|
||||||
}
|
searchResults={searchResults}
|
||||||
|
isLoading={(isLoadingInitialData || isLoadingMore) ?? true}
|
||||||
return {
|
setSearch={setSearch}
|
||||||
after: Math.min(...searchResults.map((res) => res.start_time)),
|
setSimilaritySearch={(search) => setSearch(`similarity:${search.id}`)}
|
||||||
before: Math.max(
|
setSearchFilter={setSearchFilter}
|
||||||
...searchResults.map((res) => res.end_time ?? Date.now() / 1000),
|
onUpdateFilter={setSearchFilter}
|
||||||
),
|
loadMore={loadMore}
|
||||||
};
|
hasMore={!isReachingEnd}
|
||||||
}, [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],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
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}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,6 +29,7 @@ export type SearchResult = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type SearchFilter = {
|
export type SearchFilter = {
|
||||||
|
query?: string;
|
||||||
cameras?: string[];
|
cameras?: string[];
|
||||||
labels?: string[];
|
labels?: string[];
|
||||||
subLabels?: string[];
|
subLabels?: string[];
|
||||||
|
|||||||
@ -33,7 +33,6 @@ type SearchViewProps = {
|
|||||||
setSimilaritySearch: (search: SearchResult) => void;
|
setSimilaritySearch: (search: SearchResult) => void;
|
||||||
setSearchFilter: (filter: SearchFilter) => void;
|
setSearchFilter: (filter: SearchFilter) => void;
|
||||||
onUpdateFilter: (filter: SearchFilter) => void;
|
onUpdateFilter: (filter: SearchFilter) => void;
|
||||||
onOpenSearch: (item: SearchResult) => void;
|
|
||||||
loadMore: () => void;
|
loadMore: () => void;
|
||||||
hasMore: boolean;
|
hasMore: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user