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 = {
|
type SearchThumbnailProps = {
|
||||||
searchResult: SearchResult;
|
searchResult: SearchResult;
|
||||||
onClick: (searchResult: SearchResult, ctrl: boolean) => void;
|
onClick: (searchResult: SearchResult, ctrl: boolean, detail: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function SearchThumbnail({
|
export default function SearchThumbnail({
|
||||||
@ -31,12 +31,12 @@ export default function SearchThumbnail({
|
|||||||
// interactions
|
// interactions
|
||||||
|
|
||||||
useContextMenu(imgRef, () => {
|
useContextMenu(imgRef, () => {
|
||||||
onClick(searchResult, true);
|
onClick(searchResult, true, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
const bindClickAndLongPress = usePress({
|
const bindClickAndLongPress = usePress({
|
||||||
onLongPress: () => onClick(searchResult, true),
|
onLongPress: () => onClick(searchResult, true, false),
|
||||||
onPress: () => onClick(searchResult, false),
|
onPress: () => onClick(searchResult, false, true),
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const objectLabel = useMemo(() => {
|
const objectLabel = useMemo(() => {
|
||||||
@ -89,7 +89,7 @@ export default function SearchThumbnail({
|
|||||||
<div className="mx-3 pb-1 text-sm text-white">
|
<div className="mx-3 pb-1 text-sm text-white">
|
||||||
<Chip
|
<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`}
|
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")}
|
{getIconForLabel(objectLabel, "size-3 text-white")}
|
||||||
{Math.round(
|
{Math.round(
|
||||||
|
|||||||
@ -187,7 +187,7 @@ export default function SearchView({
|
|||||||
|
|
||||||
const onSelectSearch = useCallback(
|
const onSelectSearch = useCallback(
|
||||||
(item: SearchResult, ctrl: boolean, page: SearchTab = "details") => {
|
(item: SearchResult, ctrl: boolean, page: SearchTab = "details") => {
|
||||||
if (selectedObjects.length > 1 || ctrl) {
|
if (selectedObjects.length > 0 || ctrl) {
|
||||||
const index = selectedObjects.indexOf(item.id);
|
const index = selectedObjects.indexOf(item.id);
|
||||||
|
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
@ -205,8 +205,6 @@ export default function SearchView({
|
|||||||
copy.push(item.id);
|
copy.push(item.id);
|
||||||
setSelectedObjects(copy);
|
setSelectedObjects(copy);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
setSelectedObjects([item.id]);
|
|
||||||
}
|
}
|
||||||
if (!ctrl) {
|
if (!ctrl) {
|
||||||
setPage(page);
|
setPage(page);
|
||||||
@ -231,12 +229,8 @@ export default function SearchView({
|
|||||||
}, [uniqueResults, selectedObjects]);
|
}, [uniqueResults, selectedObjects]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (uniqueResults && uniqueResults.length > 0) {
|
setSelectedObjects([]);
|
||||||
setSelectedObjects([uniqueResults[0].id]);
|
// unselect items when search term or filter changes
|
||||||
} else {
|
|
||||||
setSelectedObjects([]);
|
|
||||||
}
|
|
||||||
// only select first item when search term or filter changes
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [searchTerm, searchFilter]);
|
}, [searchTerm, searchFilter]);
|
||||||
|
|
||||||
@ -292,15 +286,12 @@ export default function SearchView({
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "ArrowLeft":
|
case "ArrowLeft":
|
||||||
setSelectedObjects((prevSelected) => {
|
if (uniqueResults.length > 0) {
|
||||||
if (uniqueResults.length === 0) return prevSelected;
|
const currentIndex = searchDetail
|
||||||
|
? uniqueResults.findIndex(
|
||||||
const currentIndex =
|
(result) => result.id === searchDetail.id,
|
||||||
prevSelected.length > 0
|
)
|
||||||
? uniqueResults.findIndex(
|
: -1;
|
||||||
(result) => result.id === prevSelected[0],
|
|
||||||
)
|
|
||||||
: -1;
|
|
||||||
|
|
||||||
const newIndex =
|
const newIndex =
|
||||||
currentIndex === -1
|
currentIndex === -1
|
||||||
@ -308,31 +299,25 @@ export default function SearchView({
|
|||||||
: (currentIndex - 1 + uniqueResults.length) %
|
: (currentIndex - 1 + uniqueResults.length) %
|
||||||
uniqueResults.length;
|
uniqueResults.length;
|
||||||
|
|
||||||
const newSelectedResult = uniqueResults[newIndex];
|
setSearchDetail(uniqueResults[newIndex]);
|
||||||
setSearchDetail(newSelectedResult);
|
}
|
||||||
return [newSelectedResult.id];
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
case "ArrowRight":
|
|
||||||
setSelectedObjects((prevSelected) => {
|
|
||||||
if (uniqueResults.length === 0) return prevSelected;
|
|
||||||
|
|
||||||
const currentIndex =
|
case "ArrowRight":
|
||||||
prevSelected.length > 0
|
if (uniqueResults.length > 0) {
|
||||||
? uniqueResults.findIndex(
|
const currentIndex = searchDetail
|
||||||
(result) => result.id === prevSelected[0],
|
? uniqueResults.findIndex(
|
||||||
)
|
(result) => result.id === searchDetail.id,
|
||||||
: -1;
|
)
|
||||||
|
: -1;
|
||||||
|
|
||||||
const newIndex =
|
const newIndex =
|
||||||
currentIndex === -1
|
currentIndex === -1
|
||||||
? 0
|
? 0
|
||||||
: (currentIndex + 1) % uniqueResults.length;
|
: (currentIndex + 1) % uniqueResults.length;
|
||||||
|
|
||||||
const newSelectedResult = uniqueResults[newIndex];
|
setSearchDetail(uniqueResults[newIndex]);
|
||||||
setSearchDetail(newSelectedResult);
|
}
|
||||||
return [newSelectedResult.id];
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
case "PageDown":
|
case "PageDown":
|
||||||
contentRef.current?.scrollBy({
|
contentRef.current?.scrollBy({
|
||||||
@ -348,7 +333,7 @@ export default function SearchView({
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[uniqueResults, inputFocused, onSelectAllObjects],
|
[uniqueResults, inputFocused, onSelectAllObjects, searchDetail],
|
||||||
);
|
);
|
||||||
|
|
||||||
useKeyboardListener(
|
useKeyboardListener(
|
||||||
@ -359,23 +344,69 @@ export default function SearchView({
|
|||||||
|
|
||||||
// scroll into view
|
// 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(() => {
|
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(
|
const selectedIndex = uniqueResults.findIndex(
|
||||||
(result) => result.id === selectedObjects[0],
|
(result) => result.id === prevSearchDetail.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (selectedIndex !== -1 && itemRefs.current[selectedIndex]) {
|
const parent = itemRefs.current[selectedIndex];
|
||||||
scrollIntoView(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",
|
block: "center",
|
||||||
behavior: "smooth",
|
behavior: "smooth",
|
||||||
scrollMode: "if-needed",
|
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
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [selectedObjects]);
|
}, [searchDetail]);
|
||||||
|
|
||||||
// observer for loading more
|
// observer for loading more
|
||||||
|
|
||||||
@ -444,7 +475,7 @@ export default function SearchView({
|
|||||||
{hasExistingSearch && (
|
{hasExistingSearch && (
|
||||||
<ScrollArea className="w-full whitespace-nowrap lg:ml-[35%]">
|
<ScrollArea className="w-full whitespace-nowrap lg:ml-[35%]">
|
||||||
<div className="flex flex-row gap-2">
|
<div className="flex flex-row gap-2">
|
||||||
{selectedObjects.length <= 1 ? (
|
{selectedObjects.length == 0 ? (
|
||||||
<>
|
<>
|
||||||
<SearchFilterGroup
|
<SearchFilterGroup
|
||||||
className={cn(
|
className={cn(
|
||||||
@ -511,7 +542,7 @@ export default function SearchView({
|
|||||||
key={value.id}
|
key={value.id}
|
||||||
ref={(item) => (itemRefs.current[index] = item)}
|
ref={(item) => (itemRefs.current[index] = item)}
|
||||||
data-start={value.start_time}
|
data-start={value.start_time}
|
||||||
className="review-item relative flex flex-col rounded-lg"
|
className="relative flex flex-col rounded-lg"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
@ -520,8 +551,16 @@ export default function SearchView({
|
|||||||
>
|
>
|
||||||
<SearchThumbnail
|
<SearchThumbnail
|
||||||
searchResult={value}
|
searchResult={value}
|
||||||
onClick={(value: SearchResult, ctrl: boolean) => {
|
onClick={(
|
||||||
onSelectSearch(value, ctrl);
|
value: SearchResult,
|
||||||
|
ctrl: boolean,
|
||||||
|
detail: boolean,
|
||||||
|
) => {
|
||||||
|
if (detail) {
|
||||||
|
setSearchDetail(value);
|
||||||
|
} else {
|
||||||
|
onSelectSearch(value, ctrl);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{(searchTerm ||
|
{(searchTerm ||
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user