mimic behavior of review

This commit is contained in:
Josh Hawkins 2024-12-02 10:46:56 -06:00
parent cf2735085f
commit 04af3fdd1e
2 changed files with 90 additions and 51 deletions

View File

@ -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(

View File

@ -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([uniqueResults[0].id]);
} else {
setSelectedObjects([]); setSelectedObjects([]);
} // unselect items when search term or filter changes
// 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,13 +286,10 @@ 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
const currentIndex =
prevSelected.length > 0
? uniqueResults.findIndex( ? uniqueResults.findIndex(
(result) => result.id === prevSelected[0], (result) => result.id === searchDetail.id,
) )
: -1; : -1;
@ -308,19 +299,15 @@ 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) {
const currentIndex = searchDetail
? uniqueResults.findIndex( ? uniqueResults.findIndex(
(result) => result.id === prevSelected[0], (result) => result.id === searchDetail.id,
) )
: -1; : -1;
@ -329,10 +316,8 @@ export default function SearchView({
? 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={(
value: SearchResult,
ctrl: boolean,
detail: boolean,
) => {
if (detail) {
setSearchDetail(value);
} else {
onSelectSearch(value, ctrl); onSelectSearch(value, ctrl);
}
}} }}
/> />
{(searchTerm || {(searchTerm ||