add motion search to history actions menu and mobile drawer

This commit is contained in:
Josh Hawkins 2026-05-30 19:24:35 -05:00
parent 196195ccb0
commit f7e1ff690b
4 changed files with 47 additions and 7 deletions

View File

@ -12,14 +12,21 @@ type ActionsDropdownProps = {
onDebugReplayClick?: () => void; onDebugReplayClick?: () => void;
onExportClick: () => void; onExportClick: () => void;
onShareTimestampClick: () => void; onShareTimestampClick: () => void;
onMotionSearchClick?: () => void;
}; };
export default function ActionsDropdown({ export default function ActionsDropdown({
onDebugReplayClick, onDebugReplayClick,
onExportClick, onExportClick,
onShareTimestampClick, onShareTimestampClick,
onMotionSearchClick,
}: Readonly<ActionsDropdownProps>) { }: Readonly<ActionsDropdownProps>) {
const { t } = useTranslation(["components/dialog", "views/replay", "common"]); const { t } = useTranslation([
"components/dialog",
"views/replay",
"views/events",
"common",
]);
return ( return (
<DropdownMenu> <DropdownMenu>
@ -42,6 +49,11 @@ export default function ActionsDropdown({
<DropdownMenuItem onClick={onShareTimestampClick}> <DropdownMenuItem onClick={onShareTimestampClick}>
{t("recording.shareTimestamp.label", { ns: "components/dialog" })} {t("recording.shareTimestamp.label", { ns: "components/dialog" })}
</DropdownMenuItem> </DropdownMenuItem>
{onMotionSearchClick && (
<DropdownMenuItem onClick={onMotionSearchClick}>
{t("motionSearch.menuItem", { ns: "views/events" })}
</DropdownMenuItem>
)}
{onDebugReplayClick && ( {onDebugReplayClick && (
<DropdownMenuItem onClick={onDebugReplayClick}> <DropdownMenuItem onClick={onDebugReplayClick}>
{t("title", { ns: "views/replay" })} {t("title", { ns: "views/replay" })}

View File

@ -3,7 +3,7 @@ import { baseUrl } from "@/api/baseUrl";
import { Drawer, DrawerContent, DrawerTrigger } from "../ui/drawer"; import { Drawer, DrawerContent, DrawerTrigger } from "../ui/drawer";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import { FaArrowDown, FaCalendarAlt, FaCog, FaFilter } from "react-icons/fa"; import { FaArrowDown, FaCalendarAlt, FaCog, FaFilter } from "react-icons/fa";
import { LuBug, LuShare2 } from "react-icons/lu"; import { LuBug, LuSearch, LuShare2 } from "react-icons/lu";
import { TimeRange } from "@/types/timeline"; import { TimeRange } from "@/types/timeline";
import { ExportContent, ExportPreviewDialog, ExportTab } from "./ExportDialog"; import { ExportContent, ExportPreviewDialog, ExportTab } from "./ExportDialog";
import { import {
@ -46,6 +46,7 @@ const DRAWER_FEATURES = [
"filter", "filter",
"debug-replay", "debug-replay",
"share-timestamp", "share-timestamp",
"motion-search",
] as const; ] as const;
export type DrawerFeatures = (typeof DRAWER_FEATURES)[number]; export type DrawerFeatures = (typeof DRAWER_FEATURES)[number];
const DEFAULT_DRAWER_FEATURES: DrawerFeatures[] = [ const DEFAULT_DRAWER_FEATURES: DrawerFeatures[] = [
@ -54,6 +55,7 @@ const DEFAULT_DRAWER_FEATURES: DrawerFeatures[] = [
"filter", "filter",
"debug-replay", "debug-replay",
"share-timestamp", "share-timestamp",
"motion-search",
]; ];
type MobileReviewSettingsDrawerProps = { type MobileReviewSettingsDrawerProps = {
@ -75,6 +77,7 @@ type MobileReviewSettingsDrawerProps = {
setDebugReplayMode?: (mode: ExportMode) => void; setDebugReplayMode?: (mode: ExportMode) => void;
setDebugReplayRange?: (range: TimeRange | undefined) => void; setDebugReplayRange?: (range: TimeRange | undefined) => void;
onShareTimestamp?: (timestamp: number) => void; onShareTimestamp?: (timestamp: number) => void;
onMotionSearch?: () => void;
onUpdateFilter: (filter: ReviewFilter) => void; onUpdateFilter: (filter: ReviewFilter) => void;
setRange: (range: TimeRange | undefined) => void; setRange: (range: TimeRange | undefined) => void;
setMode: (mode: ExportMode) => void; setMode: (mode: ExportMode) => void;
@ -99,6 +102,7 @@ export default function MobileReviewSettingsDrawer({
setDebugReplayMode = () => {}, setDebugReplayMode = () => {},
setDebugReplayRange = () => {}, setDebugReplayRange = () => {},
onShareTimestamp = () => {}, onShareTimestamp = () => {},
onMotionSearch,
onUpdateFilter, onUpdateFilter,
setRange, setRange,
setMode, setMode,
@ -108,6 +112,7 @@ export default function MobileReviewSettingsDrawer({
"views/recording", "views/recording",
"components/dialog", "components/dialog",
"views/replay", "views/replay",
"views/events",
"common", "common",
]); ]);
const isAdmin = useIsAdmin(); const isAdmin = useIsAdmin();
@ -364,6 +369,19 @@ export default function MobileReviewSettingsDrawer({
})} })}
</Button> </Button>
)} )}
{features.includes("motion-search") && onMotionSearch && (
<Button
className="flex w-full items-center justify-center gap-2"
aria-label={t("motionSearch.menuItem", { ns: "views/events" })}
onClick={() => {
onMotionSearch();
setDrawerMode("none");
}}
>
<LuSearch className="size-5 rounded-md bg-secondary-foreground stroke-secondary p-1" />
{t("motionSearch.menuItem", { ns: "views/events" })}
</Button>
)}
{features.includes("calendar") && ( {features.includes("calendar") && (
<Button <Button
className="flex w-full items-center justify-center gap-2" className="flex w-full items-center justify-center gap-2"

View File

@ -56,11 +56,9 @@ export default function Events() {
false, false,
); );
const [recording, setRecording] = useOverlayState<RecordingStartingPoint>( const [recording, setRecording] = useOverlayState<
"recording", RecordingStartingPoint | undefined
undefined, >("recording", undefined, false);
false,
);
const [motionPreviewsCamera, setMotionPreviewsCamera] = useOverlayState< const [motionPreviewsCamera, setMotionPreviewsCamera] = useOverlayState<
string | undefined string | undefined
>("motionPreviewsCamera", undefined); >("motionPreviewsCamera", undefined);
@ -668,6 +666,10 @@ export default function Events() {
filter={reviewFilter} filter={reviewFilter}
updateFilter={onUpdateFilter} updateFilter={onUpdateFilter}
refreshData={reloadData} refreshData={reloadData}
onMotionSearch={(camera) => {
setMotionSearchCamera(camera);
setRecording(undefined);
}}
/> />
); );
} }

View File

@ -95,6 +95,7 @@ type RecordingViewProps = {
filter?: ReviewFilter; filter?: ReviewFilter;
updateFilter: (newFilter: ReviewFilter) => void; updateFilter: (newFilter: ReviewFilter) => void;
refreshData?: () => void; refreshData?: () => void;
onMotionSearch?: (camera: string) => void;
}; };
export function RecordingView({ export function RecordingView({
startCamera, startCamera,
@ -107,6 +108,7 @@ export function RecordingView({
filter, filter,
updateFilter, updateFilter,
refreshData, refreshData,
onMotionSearch,
}: RecordingViewProps) { }: RecordingViewProps) {
const { t } = useTranslation(["views/events", "components/dialog"]); const { t } = useTranslation(["views/events", "components/dialog"]);
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
@ -725,6 +727,9 @@ export function RecordingView({
setCustomShareTimestamp(initialTimestamp); setCustomShareTimestamp(initialTimestamp);
setShareTimestampOpen(true); setShareTimestampOpen(true);
}} }}
onMotionSearchClick={
onMotionSearch ? () => onMotionSearch(mainCamera) : undefined
}
onDebugReplayClick={ onDebugReplayClick={
isAdmin isAdmin
? () => { ? () => {
@ -807,6 +812,9 @@ export function RecordingView({
} }
}} }}
onShareTimestamp={onShareReviewLink} onShareTimestamp={onShareReviewLink}
onMotionSearch={
onMotionSearch ? () => onMotionSearch(mainCamera) : undefined
}
onUpdateFilter={updateFilter} onUpdateFilter={updateFilter}
setRange={setExportRange} setRange={setExportRange}
setMode={setExportMode} setMode={setExportMode}