From 04af3fdd1e59a583a778a9a33a0cac2e8fe86eaa Mon Sep 17 00:00:00 2001
From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
Date: Mon, 2 Dec 2024 10:46:56 -0600
Subject: [PATCH] mimic behavior of review
---
web/src/components/card/SearchThumbnail.tsx | 10 +-
web/src/views/search/SearchView.tsx | 131 +++++++++++++-------
2 files changed, 90 insertions(+), 51 deletions(-)
diff --git a/web/src/components/card/SearchThumbnail.tsx b/web/src/components/card/SearchThumbnail.tsx
index a7ea574b3..0deb3c928 100644
--- a/web/src/components/card/SearchThumbnail.tsx
+++ b/web/src/components/card/SearchThumbnail.tsx
@@ -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({
onClick(searchResult, false)}
+ onClick={() => onClick(searchResult, false, true)}
>
{getIconForLabel(objectLabel, "size-3 text-white")}
{Math.round(
diff --git a/web/src/views/search/SearchView.tsx b/web/src/views/search/SearchView.tsx
index 0b009ec1b..863d26d23 100644
--- a/web/src/views/search/SearchView.tsx
+++ b/web/src/views/search/SearchView.tsx
@@ -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
+ setSelectedObjects([]);
+ // unselect items when search term or filter changes
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchTerm, searchFilter]);
@@ -292,15 +286,12 @@ export default function SearchView({
}
break;
case "ArrowLeft":
- setSelectedObjects((prevSelected) => {
- if (uniqueResults.length === 0) return prevSelected;
-
- const currentIndex =
- prevSelected.length > 0
- ? uniqueResults.findIndex(
- (result) => result.id === prevSelected[0],
- )
- : -1;
+ if (uniqueResults.length > 0) {
+ const currentIndex = searchDetail
+ ? uniqueResults.findIndex(
+ (result) => result.id === searchDetail.id,
+ )
+ : -1;
const newIndex =
currentIndex === -1
@@ -308,31 +299,25 @@ 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
- ? uniqueResults.findIndex(
- (result) => result.id === prevSelected[0],
- )
- : -1;
+ case "ArrowRight":
+ if (uniqueResults.length > 0) {
+ const currentIndex = searchDetail
+ ? uniqueResults.findIndex(
+ (result) => result.id === searchDetail.id,
+ )
+ : -1;
const newIndex =
currentIndex === -1
? 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();
+
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 && (
- {selectedObjects.length <= 1 ? (
+ {selectedObjects.length == 0 ? (
<>
(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"
>
{
- onSelectSearch(value, ctrl);
+ onClick={(
+ value: SearchResult,
+ ctrl: boolean,
+ detail: boolean,
+ ) => {
+ if (detail) {
+ setSearchDetail(value);
+ } else {
+ onSelectSearch(value, ctrl);
+ }
}}
/>
{(searchTerm ||