diff --git a/web/src/components/card/ExportCard.tsx b/web/src/components/card/ExportCard.tsx index 045740a97..efbc61de2 100644 --- a/web/src/components/card/ExportCard.tsx +++ b/web/src/components/card/ExportCard.tsx @@ -2,7 +2,7 @@ import ActivityIndicator from "../indicators/activity-indicator"; import { LuTrash } from "react-icons/lu"; import { Button } from "../ui/button"; import { useCallback, useState } from "react"; -import { isDesktop } from "react-device-detect"; +import { isDesktop, isMobile } from "react-device-detect"; import { FaDownload, FaPlay, FaShareAlt } from "react-icons/fa"; import Chip from "../indicators/Chip"; import { Skeleton } from "../ui/skeleton"; @@ -82,7 +82,13 @@ export default function ExportCard({ } }} > - + { + if (isMobile) { + e.preventDefault(); + } + }} + > Rename Export Enter a new name for this export. @@ -90,7 +96,7 @@ export default function ExportCard({ {editName && ( <> Name User @@ -89,7 +89,7 @@ export default function CreateUserDialog({ Password diff --git a/web/src/components/overlay/ExportDialog.tsx b/web/src/components/overlay/ExportDialog.tsx index b1360aa36..8e865c923 100644 --- a/web/src/components/overlay/ExportDialog.tsx +++ b/web/src/components/overlay/ExportDialog.tsx @@ -273,7 +273,7 @@ export function ExportContent({ /> )} - + e.preventDefault()}> Set Password setPassword(event.target.value)} diff --git a/web/src/components/overlay/detail/AnnotationSettingsPane.tsx b/web/src/components/overlay/detail/AnnotationSettingsPane.tsx index 6a14b8390..64f41d4d4 100644 --- a/web/src/components/overlay/detail/AnnotationSettingsPane.tsx +++ b/web/src/components/overlay/detail/AnnotationSettingsPane.tsx @@ -178,7 +178,7 @@ export function AnnotationSettingsPane({
diff --git a/web/src/components/settings/ZoneEditPane.tsx b/web/src/components/settings/ZoneEditPane.tsx index 3b9f0dd7e..7600c3b29 100644 --- a/web/src/components/settings/ZoneEditPane.tsx +++ b/web/src/components/settings/ZoneEditPane.tsx @@ -373,7 +373,7 @@ export default function ZoneEditPane({ Name @@ -395,7 +395,7 @@ export default function ZoneEditPane({ Inertia @@ -417,7 +417,7 @@ export default function ZoneEditPane({ Loitering Time diff --git a/web/src/pages/Exports.tsx b/web/src/pages/Exports.tsx index a4659551b..79555909c 100644 --- a/web/src/pages/Exports.tsx +++ b/web/src/pages/Exports.tsx @@ -175,7 +175,7 @@ function Exports() { {exports && (
setSearch(e.target.value)} diff --git a/web/src/views/explore/ExploreView.tsx b/web/src/views/explore/ExploreView.tsx index b8ab51d80..48fee439c 100644 --- a/web/src/views/explore/ExploreView.tsx +++ b/web/src/views/explore/ExploreView.tsx @@ -17,7 +17,7 @@ import useImageLoaded from "@/hooks/use-image-loaded"; import ActivityIndicator from "@/components/indicators/activity-indicator"; type ExploreViewProps = { - onSelectSearch: (searchResult: SearchResult) => void; + onSelectSearch: (searchResult: SearchResult, index: number) => void; }; export default function ExploreView({ onSelectSearch }: ExploreViewProps) { @@ -76,7 +76,7 @@ export default function ExploreView({ onSelectSearch }: ExploreViewProps) { type ThumbnailRowType = { objectType: string; searchResults?: SearchResult[]; - onSelectSearch: (searchResult: SearchResult) => void; + onSelectSearch: (searchResult: SearchResult, index: number) => void; }; function ThumbnailRow({ @@ -145,7 +145,7 @@ function ThumbnailRow({ type ExploreThumbnailImageProps = { event: SearchResult; - onSelectSearch: (searchResult: SearchResult) => void; + onSelectSearch: (searchResult: SearchResult, index: number) => void; }; function ExploreThumbnailImage({ event, @@ -176,7 +176,7 @@ function ExploreThumbnailImage({ loading={isSafari ? "eager" : "lazy"} draggable={false} src={`${apiHost}api/events/${event.id}/thumbnail.jpg`} - onClick={() => onSelectSearch(event)} + onClick={() => onSelectSearch(event, 0)} onLoad={() => { onImgLoad(); }} diff --git a/web/src/views/search/SearchView.tsx b/web/src/views/search/SearchView.tsx index f1f706dab..3b6fb49e3 100644 --- a/web/src/views/search/SearchView.tsx +++ b/web/src/views/search/SearchView.tsx @@ -13,11 +13,15 @@ import { import { cn } from "@/lib/utils"; import { FrigateConfig } from "@/types/frigateConfig"; import { SearchFilter, SearchResult } from "@/types/search"; -import { useCallback, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { isMobileOnly } from "react-device-detect"; import { LuImage, LuSearchX, LuText, LuXCircle } from "react-icons/lu"; import useSWR from "swr"; import ExploreView from "../explore/ExploreView"; +import useKeyboardListener, { + KeyModifiers, +} from "@/hooks/use-keyboard-listener"; +import scrollIntoView from "scroll-into-view-if-needed"; type SearchViewProps = { search: string; @@ -59,8 +63,12 @@ export default function SearchView({ // search interaction - const onSelectSearch = useCallback((item: SearchResult) => { + const [selectedIndex, setSelectedIndex] = useState(null); + const itemRefs = useRef<(HTMLDivElement | null)[]>([]); + + const onSelectSearch = useCallback((item: SearchResult, index: number) => { setSearchDetail(item); + setSelectedIndex(index); }, []); // confidence score - probably needs tweaking @@ -87,6 +95,56 @@ export default function SearchView({ [searchResults, searchFilter], ); + // keyboard listener + + const onKeyboardShortcut = useCallback( + (key: string | null, modifiers: KeyModifiers) => { + if (!modifiers.down || !uniqueResults) { + return; + } + + switch (key) { + case "ArrowLeft": + setSelectedIndex((prevIndex) => { + const newIndex = + prevIndex === null + ? uniqueResults.length - 1 + : (prevIndex - 1 + uniqueResults.length) % uniqueResults.length; + setSearchDetail(uniqueResults[newIndex]); + return newIndex; + }); + break; + case "ArrowRight": + setSelectedIndex((prevIndex) => { + const newIndex = + prevIndex === null ? 0 : (prevIndex + 1) % uniqueResults.length; + setSearchDetail(uniqueResults[newIndex]); + return newIndex; + }); + break; + } + }, + [uniqueResults], + ); + + useKeyboardListener(["ArrowLeft", "ArrowRight"], onKeyboardShortcut); + + // scroll into view + + useEffect(() => { + if ( + selectedIndex !== null && + uniqueResults && + itemRefs.current?.[selectedIndex] + ) { + scrollIntoView(itemRefs.current[selectedIndex], { + block: "center", + behavior: "smooth", + scrollMode: "if-needed", + }); + } + }, [selectedIndex, uniqueResults]); + return (
@@ -156,12 +214,13 @@ export default function SearchView({ {uniqueResults && (
{uniqueResults && - uniqueResults.map((value) => { - const selected = false; + uniqueResults.map((value, index) => { + const selected = selectedIndex === index; return (
(itemRefs.current[index] = item)} data-start={value.start_time} className="review-item relative rounded-lg" > @@ -173,7 +232,7 @@ export default function SearchView({ setSimilaritySearch(value)} - onClick={() => onSelectSearch(value)} + onClick={() => onSelectSearch(value, index)} /> {searchTerm && (
@@ -207,7 +266,7 @@ export default function SearchView({ )}
); diff --git a/web/src/views/settings/NotificationsSettingsView.tsx b/web/src/views/settings/NotificationsSettingsView.tsx index 5e592b7a2..51d467fc3 100644 --- a/web/src/views/settings/NotificationsSettingsView.tsx +++ b/web/src/views/settings/NotificationsSettingsView.tsx @@ -254,7 +254,7 @@ export default function NotificationView({ Email