diff --git a/web/src/views/explore/ExploreView.tsx b/web/src/views/explore/ExploreView.tsx index ea9c3cbef..caff874d4 100644 --- a/web/src/views/explore/ExploreView.tsx +++ b/web/src/views/explore/ExploreView.tsx @@ -26,7 +26,7 @@ type ExploreViewProps = { searchDetail: SearchResult | undefined; setSearchDetail: (search: SearchResult | undefined) => void; setSimilaritySearch: (search: SearchResult) => void; - onSelectSearch: (item: SearchResult, index: number, page?: SearchTab) => void; + onSelectSearch: (item: SearchResult, page?: SearchTab) => void; }; export default function ExploreView({ @@ -125,7 +125,7 @@ type ThumbnailRowType = { setSearchDetail: (search: SearchResult | undefined) => void; mutate: () => void; setSimilaritySearch: (search: SearchResult) => void; - onSelectSearch: (item: SearchResult, index: number, page?: SearchTab) => void; + onSelectSearch: (item: SearchResult, page?: SearchTab) => void; }; function ThumbnailRow({ @@ -205,7 +205,7 @@ type ExploreThumbnailImageProps = { setSearchDetail: (search: SearchResult | undefined) => void; mutate: () => void; setSimilaritySearch: (search: SearchResult) => void; - onSelectSearch: (item: SearchResult, index: number, page?: SearchTab) => void; + onSelectSearch: (item: SearchResult, page?: SearchTab) => void; }; function ExploreThumbnailImage({ event, @@ -225,11 +225,11 @@ function ExploreThumbnailImage({ }; const handleShowObjectLifecycle = () => { - onSelectSearch(event, 0, "object lifecycle"); + onSelectSearch(event, "object lifecycle"); }; const handleShowSnapshot = () => { - onSelectSearch(event, 0, "snapshot"); + onSelectSearch(event, "snapshot"); }; return ( diff --git a/web/src/views/search/SearchView.tsx b/web/src/views/search/SearchView.tsx index 378b313e0..5d29758fc 100644 --- a/web/src/views/search/SearchView.tsx +++ b/web/src/views/search/SearchView.tsx @@ -181,21 +181,45 @@ export default function SearchView({ // search interaction - const [selectedIndex, setSelectedIndex] = useState(null); + const [selectedObjects, setSelectedObjects] = useState([]); const itemRefs = useRef<(HTMLDivElement | null)[]>([]); const onSelectSearch = useCallback( - (item: SearchResult, index: number, page: SearchTab = "details") => { + (item: SearchResult, page: SearchTab = "details") => { + if (selectedObjects.length > 1) { + const index = selectedObjects.indexOf(item.id); + + if (index != -1) { + if (selectedObjects.length == 1) { + setSelectedObjects([]); + } else { + const copy = [ + ...selectedObjects.slice(0, index), + ...selectedObjects.slice(index + 1), + ]; + setSelectedObjects(copy); + } + } else { + const copy = [...selectedObjects]; + copy.push(item.id); + setSelectedObjects(copy); + } + } else { + setSelectedObjects([item.id]); + } setPage(page); setSearchDetail(item); - setSelectedIndex(index); }, - [], + [selectedObjects], ); useEffect(() => { - setSelectedIndex(0); - }, [searchTerm, searchFilter]); + if (uniqueResults && uniqueResults.length > 0) { + setSelectedObjects([uniqueResults[0].id]); + } else { + setSelectedObjects([]); + } + }, [searchTerm, searchFilter, uniqueResults]); // confidence score @@ -244,21 +268,46 @@ export default function SearchView({ switch (key) { case "ArrowLeft": - setSelectedIndex((prevIndex) => { + setSelectedObjects((prevSelected) => { + if (uniqueResults.length === 0) return prevSelected; + + const currentIndex = + prevSelected.length > 0 + ? uniqueResults.findIndex( + (result) => result.id === prevSelected[0], + ) + : -1; + const newIndex = - prevIndex === null + currentIndex === -1 ? uniqueResults.length - 1 - : (prevIndex - 1 + uniqueResults.length) % uniqueResults.length; - setSearchDetail(uniqueResults[newIndex]); - return newIndex; + : (currentIndex - 1 + uniqueResults.length) % + uniqueResults.length; + + const newSelectedResult = uniqueResults[newIndex]; + setSearchDetail(newSelectedResult); + return [newSelectedResult.id]; }); break; case "ArrowRight": - setSelectedIndex((prevIndex) => { + setSelectedObjects((prevSelected) => { + if (uniqueResults.length === 0) return prevSelected; + + const currentIndex = + prevSelected.length > 0 + ? uniqueResults.findIndex( + (result) => result.id === prevSelected[0], + ) + : -1; + const newIndex = - prevIndex === null ? 0 : (prevIndex + 1) % uniqueResults.length; - setSearchDetail(uniqueResults[newIndex]); - return newIndex; + currentIndex === -1 + ? 0 + : (currentIndex + 1) % uniqueResults.length; + + const newSelectedResult = uniqueResults[newIndex]; + setSearchDetail(newSelectedResult); + return [newSelectedResult.id]; }); break; case "PageDown": @@ -287,20 +336,22 @@ export default function SearchView({ // scroll into view useEffect(() => { - if ( - selectedIndex !== null && - uniqueResults && - itemRefs.current?.[selectedIndex] - ) { - scrollIntoView(itemRefs.current[selectedIndex], { - block: "center", - behavior: "smooth", - scrollMode: "if-needed", - }); + if (selectedObjects.length > 0 && uniqueResults && itemRefs.current) { + const selectedIndex = uniqueResults.findIndex( + (result) => result.id === selectedObjects[0], + ); + + if (selectedIndex !== -1 && itemRefs.current[selectedIndex]) { + scrollIntoView(itemRefs.current[selectedIndex], { + block: "center", + behavior: "smooth", + scrollMode: "if-needed", + }); + } } - // we only want to scroll when the index changes + // we only want to scroll when the selected objects change // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedIndex]); + }, [selectedObjects]); // observer for loading more @@ -412,7 +463,7 @@ export default function SearchView({
{uniqueResults && uniqueResults.map((value, index) => { - const selected = selectedIndex === index; + const selected = selectedObjects.includes(value.id); return (
onSelectSearch(value, index)} + onClick={() => onSelectSearch(value)} /> {(searchTerm || searchFilter?.search_type?.includes("similarity")) && ( @@ -469,11 +520,9 @@ export default function SearchView({ }} refreshResults={refresh} showObjectLifecycle={() => - onSelectSearch(value, index, "object lifecycle") - } - showSnapshot={() => - onSelectSearch(value, index, "snapshot") + onSelectSearch(value, "object lifecycle") } + showSnapshot={() => onSelectSearch(value, "snapshot")} />