multi select in explore

This commit is contained in:
Josh Hawkins 2024-12-02 08:34:33 -06:00
parent 96a8caa9b2
commit fd173e998b
3 changed files with 94 additions and 39 deletions

View File

@ -1,4 +1,4 @@
import { useCallback, useMemo } from "react";
import { useMemo } from "react";
import { useApiHost } from "@/api";
import { getIconForLabel } from "@/utils/iconUtil";
import useSWR from "swr";
@ -12,10 +12,12 @@ import { capitalizeFirstLetter } from "@/utils/stringUtil";
import { SearchResult } from "@/types/search";
import { cn } from "@/lib/utils";
import { TooltipPortal } from "@radix-ui/react-tooltip";
import usePress from "@/hooks/use-press";
import useContextMenu from "@/hooks/use-contextmenu";
type SearchThumbnailProps = {
searchResult: SearchResult;
onClick: (searchResult: SearchResult) => void;
onClick: (searchResult: SearchResult, ctrl: boolean) => void;
};
export default function SearchThumbnail({
@ -28,9 +30,14 @@ export default function SearchThumbnail({
// interactions
const handleOnClick = useCallback(() => {
onClick(searchResult);
}, [searchResult, onClick]);
useContextMenu(imgRef, () => {
onClick(searchResult, true);
});
const bindClickAndLongPress = usePress({
onLongPress: () => onClick(searchResult, true),
onPress: () => onClick(searchResult, false),
})();
const objectLabel = useMemo(() => {
if (
@ -45,7 +52,10 @@ export default function SearchThumbnail({
}, [config, searchResult]);
return (
<div className="relative size-full cursor-pointer" onClick={handleOnClick}>
<div
className="relative size-full cursor-pointer"
{...bindClickAndLongPress}
>
<ImageLoadingIndicator
className="absolute inset-0"
imgLoaded={imgLoaded}
@ -79,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)}
onClick={() => onClick(searchResult, false)}
>
{getIconForLabel(objectLabel, "size-3 text-white")}
{Math.round(

View File

@ -26,7 +26,7 @@ type ExploreViewProps = {
searchDetail: SearchResult | undefined;
setSearchDetail: (search: SearchResult | undefined) => void;
setSimilaritySearch: (search: SearchResult) => void;
onSelectSearch: (item: SearchResult, page?: SearchTab) => void;
onSelectSearch: (item: SearchResult, ctrl: boolean, 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, page?: SearchTab) => void;
onSelectSearch: (item: SearchResult, ctrl: boolean, 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, page?: SearchTab) => void;
onSelectSearch: (item: SearchResult, ctrl: boolean, page?: SearchTab) => void;
};
function ExploreThumbnailImage({
event,
@ -225,11 +225,11 @@ function ExploreThumbnailImage({
};
const handleShowObjectLifecycle = () => {
onSelectSearch(event, "object lifecycle");
onSelectSearch(event, false, "object lifecycle");
};
const handleShowSnapshot = () => {
onSelectSearch(event, "snapshot");
onSelectSearch(event, false, "snapshot");
};
return (

View File

@ -30,6 +30,7 @@ import {
} from "@/components/ui/tooltip";
import Chip from "@/components/indicators/Chip";
import { TooltipPortal } from "@radix-ui/react-tooltip";
import SearchActionGroup from "@/components/filter/SearchActionGroup";
type SearchViewProps = {
search: string;
@ -185,8 +186,8 @@ export default function SearchView({
const itemRefs = useRef<(HTMLDivElement | null)[]>([]);
const onSelectSearch = useCallback(
(item: SearchResult, page: SearchTab = "details") => {
if (selectedObjects.length > 1) {
(item: SearchResult, ctrl: boolean, page: SearchTab = "details") => {
if (selectedObjects.length > 1 || ctrl) {
const index = selectedObjects.indexOf(item.id);
if (index != -1) {
@ -207,19 +208,37 @@ export default function SearchView({
} else {
setSelectedObjects([item.id]);
}
if (!ctrl) {
setPage(page);
setSearchDetail(item);
} else {
setSearchDetail(undefined);
}
},
[selectedObjects],
);
const onSelectAllObjects = useCallback(() => {
if (!uniqueResults || uniqueResults.length == 0) {
return;
}
if (selectedObjects.length < uniqueResults.length) {
setSelectedObjects(uniqueResults.map((value) => value.id));
} else {
setSelectedObjects([]);
}
}, [uniqueResults, selectedObjects]);
useEffect(() => {
if (uniqueResults && uniqueResults.length > 0) {
setSelectedObjects([uniqueResults[0].id]);
} else {
setSelectedObjects([]);
}
}, [searchTerm, searchFilter, uniqueResults]);
// only select first item when search term or filter changes
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchTerm, searchFilter]);
// confidence score
@ -267,6 +286,11 @@ export default function SearchView({
}
switch (key) {
case "a":
if (modifiers.ctrl) {
onSelectAllObjects();
}
break;
case "ArrowLeft":
setSelectedObjects((prevSelected) => {
if (uniqueResults.length === 0) return prevSelected;
@ -324,11 +348,11 @@ export default function SearchView({
break;
}
},
[uniqueResults, inputFocused],
[uniqueResults, inputFocused, onSelectAllObjects],
);
useKeyboardListener(
["ArrowLeft", "ArrowRight", "PageDown", "PageUp"],
["a", "ArrowLeft", "ArrowRight", "PageDown", "PageUp"],
onKeyboardShortcut,
!inputFocused,
);
@ -336,7 +360,7 @@ export default function SearchView({
// scroll into view
useEffect(() => {
if (selectedObjects.length > 0 && uniqueResults && itemRefs.current) {
if (selectedObjects.length == 1 && uniqueResults && itemRefs.current) {
const selectedIndex = uniqueResults.findIndex(
(result) => result.id === selectedObjects[0],
);
@ -420,6 +444,8 @@ export default function SearchView({
{hasExistingSearch && (
<ScrollArea className="w-full whitespace-nowrap lg:ml-[35%]">
<div className="flex flex-row gap-2">
{selectedObjects.length <= 1 ? (
<>
<SearchFilterGroup
className={cn(
"w-full justify-between md:justify-start lg:justify-end",
@ -436,6 +462,21 @@ export default function SearchView({
onUpdateFilter={onUpdateFilter}
/>
<ScrollBar orientation="horizontal" className="h-0" />
</>
) : (
<div
className={cn(
"scrollbar-container flex justify-center gap-2 overflow-x-auto",
"h-10 w-full justify-between md:justify-start lg:justify-end",
)}
>
<SearchActionGroup
selectedObjects={selectedObjects}
setSelectedObjects={setSelectedObjects}
pullLatestData={refresh}
/>
</div>
)}
</div>
</ScrollArea>
)}
@ -479,7 +520,9 @@ export default function SearchView({
>
<SearchThumbnail
searchResult={value}
onClick={() => onSelectSearch(value)}
onClick={(value: SearchResult, ctrl: boolean) => {
onSelectSearch(value, ctrl);
}}
/>
{(searchTerm ||
searchFilter?.search_type?.includes("similarity")) && (
@ -520,9 +563,11 @@ export default function SearchView({
}}
refreshResults={refresh}
showObjectLifecycle={() =>
onSelectSearch(value, "object lifecycle")
onSelectSearch(value, false, "object lifecycle")
}
showSnapshot={() =>
onSelectSearch(value, false, "snapshot")
}
showSnapshot={() => onSelectSearch(value, "snapshot")}
/>
</div>
</div>