mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-17 16:44:29 +03:00
mimic behavior of review
This commit is contained in:
parent
cf2735085f
commit
04af3fdd1e
@ -17,7 +17,7 @@ import useContextMenu from "@/hooks/use-contextmenu";
|
||||
|
||||
type SearchThumbnailProps = {
|
||||
searchResult: SearchResult;
|
||||
onClick: (searchResult: SearchResult, ctrl: boolean) => void;
|
||||
onClick: (searchResult: SearchResult, ctrl: boolean, detail: boolean) => void;
|
||||
};
|
||||
|
||||
export default function SearchThumbnail({
|
||||
@ -31,12 +31,12 @@ export default function SearchThumbnail({
|
||||
// interactions
|
||||
|
||||
useContextMenu(imgRef, () => {
|
||||
onClick(searchResult, true);
|
||||
onClick(searchResult, true, false);
|
||||
});
|
||||
|
||||
const bindClickAndLongPress = usePress({
|
||||
onLongPress: () => onClick(searchResult, true),
|
||||
onPress: () => onClick(searchResult, false),
|
||||
onLongPress: () => onClick(searchResult, true, false),
|
||||
onPress: () => onClick(searchResult, false, true),
|
||||
})();
|
||||
|
||||
const objectLabel = useMemo(() => {
|
||||
@ -89,7 +89,7 @@ export default function SearchThumbnail({
|
||||
<div className="mx-3 pb-1 text-sm text-white">
|
||||
<Chip
|
||||
className={`z-0 flex items-center justify-between gap-1 space-x-1 bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500 text-xs`}
|
||||
onClick={() => onClick(searchResult, false)}
|
||||
onClick={() => onClick(searchResult, false, true)}
|
||||
>
|
||||
{getIconForLabel(objectLabel, "size-3 text-white")}
|
||||
{Math.round(
|
||||
|
||||
@ -187,7 +187,7 @@ export default function SearchView({
|
||||
|
||||
const onSelectSearch = useCallback(
|
||||
(item: SearchResult, ctrl: boolean, page: SearchTab = "details") => {
|
||||
if (selectedObjects.length > 1 || ctrl) {
|
||||
if (selectedObjects.length > 0 || ctrl) {
|
||||
const index = selectedObjects.indexOf(item.id);
|
||||
|
||||
if (index != -1) {
|
||||
@ -205,8 +205,6 @@ export default function SearchView({
|
||||
copy.push(item.id);
|
||||
setSelectedObjects(copy);
|
||||
}
|
||||
} else {
|
||||
setSelectedObjects([item.id]);
|
||||
}
|
||||
if (!ctrl) {
|
||||
setPage(page);
|
||||
@ -231,12 +229,8 @@ export default function SearchView({
|
||||
}, [uniqueResults, selectedObjects]);
|
||||
|
||||
useEffect(() => {
|
||||
if (uniqueResults && uniqueResults.length > 0) {
|
||||
setSelectedObjects([uniqueResults[0].id]);
|
||||
} else {
|
||||
setSelectedObjects([]);
|
||||
}
|
||||
// only select first item when search term or filter changes
|
||||
// unselect items when search term or filter changes
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [searchTerm, searchFilter]);
|
||||
|
||||
@ -292,13 +286,10 @@ export default function SearchView({
|
||||
}
|
||||
break;
|
||||
case "ArrowLeft":
|
||||
setSelectedObjects((prevSelected) => {
|
||||
if (uniqueResults.length === 0) return prevSelected;
|
||||
|
||||
const currentIndex =
|
||||
prevSelected.length > 0
|
||||
if (uniqueResults.length > 0) {
|
||||
const currentIndex = searchDetail
|
||||
? uniqueResults.findIndex(
|
||||
(result) => result.id === prevSelected[0],
|
||||
(result) => result.id === searchDetail.id,
|
||||
)
|
||||
: -1;
|
||||
|
||||
@ -308,19 +299,15 @@ export default function SearchView({
|
||||
: (currentIndex - 1 + uniqueResults.length) %
|
||||
uniqueResults.length;
|
||||
|
||||
const newSelectedResult = uniqueResults[newIndex];
|
||||
setSearchDetail(newSelectedResult);
|
||||
return [newSelectedResult.id];
|
||||
});
|
||||
setSearchDetail(uniqueResults[newIndex]);
|
||||
}
|
||||
break;
|
||||
case "ArrowRight":
|
||||
setSelectedObjects((prevSelected) => {
|
||||
if (uniqueResults.length === 0) return prevSelected;
|
||||
|
||||
const currentIndex =
|
||||
prevSelected.length > 0
|
||||
case "ArrowRight":
|
||||
if (uniqueResults.length > 0) {
|
||||
const currentIndex = searchDetail
|
||||
? uniqueResults.findIndex(
|
||||
(result) => result.id === prevSelected[0],
|
||||
(result) => result.id === searchDetail.id,
|
||||
)
|
||||
: -1;
|
||||
|
||||
@ -329,10 +316,8 @@ export default function SearchView({
|
||||
? 0
|
||||
: (currentIndex + 1) % uniqueResults.length;
|
||||
|
||||
const newSelectedResult = uniqueResults[newIndex];
|
||||
setSearchDetail(newSelectedResult);
|
||||
return [newSelectedResult.id];
|
||||
});
|
||||
setSearchDetail(uniqueResults[newIndex]);
|
||||
}
|
||||
break;
|
||||
case "PageDown":
|
||||
contentRef.current?.scrollBy({
|
||||
@ -348,7 +333,7 @@ export default function SearchView({
|
||||
break;
|
||||
}
|
||||
},
|
||||
[uniqueResults, inputFocused, onSelectAllObjects],
|
||||
[uniqueResults, inputFocused, onSelectAllObjects, searchDetail],
|
||||
);
|
||||
|
||||
useKeyboardListener(
|
||||
@ -359,23 +344,69 @@ export default function SearchView({
|
||||
|
||||
// scroll into view
|
||||
|
||||
const [prevSearchDetail, setPrevSearchDetail] = useState<
|
||||
SearchResult | undefined
|
||||
>();
|
||||
|
||||
// keep track of previous ref to outline thumbnail when dialog closes
|
||||
const prevSearchDetailRef = useRef<SearchResult | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedObjects.length == 1 && uniqueResults && itemRefs.current) {
|
||||
if (searchDetail === undefined && prevSearchDetailRef.current) {
|
||||
setPrevSearchDetail(prevSearchDetailRef.current);
|
||||
}
|
||||
prevSearchDetailRef.current = searchDetail;
|
||||
}, [searchDetail]);
|
||||
|
||||
useEffect(() => {
|
||||
if (uniqueResults && itemRefs.current && prevSearchDetail) {
|
||||
const selectedIndex = uniqueResults.findIndex(
|
||||
(result) => result.id === selectedObjects[0],
|
||||
(result) => result.id === prevSearchDetail.id,
|
||||
);
|
||||
|
||||
if (selectedIndex !== -1 && itemRefs.current[selectedIndex]) {
|
||||
scrollIntoView(itemRefs.current[selectedIndex], {
|
||||
const parent = itemRefs.current[selectedIndex];
|
||||
|
||||
if (selectedIndex !== -1 && parent) {
|
||||
const target = parent.querySelector(".review-item-ring");
|
||||
if (target) {
|
||||
scrollIntoView(target, {
|
||||
block: "center",
|
||||
behavior: "smooth",
|
||||
scrollMode: "if-needed",
|
||||
});
|
||||
target.classList.add(`outline-selected`);
|
||||
target.classList.remove("outline-transparent");
|
||||
|
||||
setTimeout(() => {
|
||||
target.classList.remove(`outline-selected`);
|
||||
target.classList.add("outline-transparent");
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
}
|
||||
// we only want to scroll when the dialog closes
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [prevSearchDetail]);
|
||||
|
||||
useEffect(() => {
|
||||
if (uniqueResults && itemRefs.current && searchDetail) {
|
||||
const selectedIndex = uniqueResults.findIndex(
|
||||
(result) => result.id === searchDetail.id,
|
||||
);
|
||||
|
||||
const parent = itemRefs.current[selectedIndex];
|
||||
|
||||
if (selectedIndex !== -1 && parent) {
|
||||
scrollIntoView(parent, {
|
||||
block: "center",
|
||||
behavior: "smooth",
|
||||
scrollMode: "if-needed",
|
||||
});
|
||||
}
|
||||
}
|
||||
// we only want to scroll when the selected objects change
|
||||
// we only want to scroll when changing the detail pane
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedObjects]);
|
||||
}, [searchDetail]);
|
||||
|
||||
// observer for loading more
|
||||
|
||||
@ -444,7 +475,7 @@ export default function SearchView({
|
||||
{hasExistingSearch && (
|
||||
<ScrollArea className="w-full whitespace-nowrap lg:ml-[35%]">
|
||||
<div className="flex flex-row gap-2">
|
||||
{selectedObjects.length <= 1 ? (
|
||||
{selectedObjects.length == 0 ? (
|
||||
<>
|
||||
<SearchFilterGroup
|
||||
className={cn(
|
||||
@ -511,7 +542,7 @@ export default function SearchView({
|
||||
key={value.id}
|
||||
ref={(item) => (itemRefs.current[index] = item)}
|
||||
data-start={value.start_time}
|
||||
className="review-item relative flex flex-col rounded-lg"
|
||||
className="relative flex flex-col rounded-lg"
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
@ -520,8 +551,16 @@ export default function SearchView({
|
||||
>
|
||||
<SearchThumbnail
|
||||
searchResult={value}
|
||||
onClick={(value: SearchResult, ctrl: boolean) => {
|
||||
onClick={(
|
||||
value: SearchResult,
|
||||
ctrl: boolean,
|
||||
detail: boolean,
|
||||
) => {
|
||||
if (detail) {
|
||||
setSearchDetail(value);
|
||||
} else {
|
||||
onSelectSearch(value, ctrl);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{(searchTerm ||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user