From 02c5b9a4f5d5fe4d270ec8013cfe6127aab5d4f7 Mon Sep 17 00:00:00 2001
From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
Date: Wed, 5 Nov 2025 20:22:22 -0600
Subject: [PATCH] refactor to remove duplication between mobile and desktop
---
.../overlay/detail/SearchDetailDialog.tsx | 473 ++++++++++--------
.../overlay/detail/TrackingDetails.tsx | 43 +-
2 files changed, 277 insertions(+), 239 deletions(-)
diff --git a/web/src/components/overlay/detail/SearchDetailDialog.tsx b/web/src/components/overlay/detail/SearchDetailDialog.tsx
index 2ea44d8c9..c6581a796 100644
--- a/web/src/components/overlay/detail/SearchDetailDialog.tsx
+++ b/web/src/components/overlay/detail/SearchDetailDialog.tsx
@@ -32,6 +32,7 @@ import {
FaChevronRight,
} from "react-icons/fa";
import { TrackingDetails } from "./TrackingDetails";
+import { LuSettings } from "react-icons/lu";
import { DetailStreamProvider } from "@/context/detail-stream-context";
import {
MobilePage,
@@ -77,6 +78,234 @@ import { DialogPortal } from "@radix-ui/react-dialog";
const SEARCH_TABS = ["snapshot", "tracking_details"] as const;
export type SearchTab = (typeof SEARCH_TABS)[number];
+type TabsWithActionsProps = {
+ search: SearchResult;
+ searchTabs: SearchTab[];
+ pageToggle: SearchTab;
+ setPageToggle: (v: SearchTab) => void;
+ config?: FrigateConfig;
+ setSearch: (s: SearchResult | undefined) => void;
+ setSimilarity?: () => void;
+ showControls?: boolean;
+ setShowControls?: (v: boolean) => void;
+};
+
+function TabsWithActions({
+ search,
+ searchTabs,
+ pageToggle,
+ setPageToggle,
+ config,
+ setSearch,
+ setSimilarity,
+ showControls,
+ setShowControls,
+}: TabsWithActionsProps) {
+ const { t } = useTranslation(["views/explore", "views/faceLibrary"]);
+
+ if (!search) return null;
+
+ return (
+
+
+
+
{
+ if (value) {
+ setPageToggle(value);
+ }
+ }}
+ >
+ {Object.values(searchTabs).map((item) => (
+
+
+ {item === "snapshot"
+ ? search?.has_snapshot
+ ? t("type.snapshot")
+ : t("type.thumbnail")
+ : t(`type.${item}`)}
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
+ {t("trackingDetails.adjustAnnotationSettings")}
+
+
+
+
+
+ );
+}
+
+type DialogContentComponentProps = {
+ page: SearchTab;
+ search: SearchResult;
+ isDesktop: boolean;
+ apiHost: string;
+ config?: FrigateConfig;
+ searchTabs: SearchTab[];
+ pageToggle: SearchTab;
+ setPageToggle: (v: SearchTab) => void;
+ setSearch: (s: SearchResult | undefined) => void;
+ setInputFocused: React.Dispatch>;
+ setSimilarity?: () => void;
+ showControls?: boolean;
+ setShowControls?: (v: boolean) => void;
+};
+
+function DialogContentComponent({
+ page,
+ search,
+ isDesktop,
+ apiHost,
+ config,
+ searchTabs,
+ pageToggle,
+ setPageToggle,
+ setSearch,
+ setInputFocused,
+ setSimilarity,
+ showControls,
+ setShowControls,
+}: DialogContentComponentProps) {
+ if (page === "tracking_details") {
+ return (
+
+ ) : undefined
+ }
+ showControls={showControls}
+ setShowControls={setShowControls}
+ />
+ );
+ }
+
+ // Snapshot page content
+ const snapshotElement = search.has_snapshot ? (
+
+ ) : (
+
+

+
+ );
+
+ if (isDesktop) {
+ return (
+
+
+ {snapshotElement}
+
+
+
+ );
+ }
+
+ // mobile
+ return (
+ <>
+ {snapshotElement}
+
+ >
+ );
+}
+
type SearchDetailDialogProps = {
search?: SearchResult;
page: SearchTab;
@@ -87,6 +316,7 @@ type SearchDetailDialogProps = {
onPrevious?: () => void;
onNext?: () => void;
};
+
export default function SearchDetailDialog({
search,
page,
@@ -135,6 +365,10 @@ export default function SearchDetailDialog({
}
}, [search]);
+ // show/hide annotation settings
+
+ const [showControls, setShowControls] = useState(false);
+
const searchTabs = useMemo(() => {
if (!config || !search) {
return [];
@@ -160,44 +394,6 @@ export default function SearchDetailDialog({
}
}, [pageToggle, searchTabs, setSearchPage]);
- // Tabs component for reuse
- const tabsComponent = (
-
-
-
{
- if (value) {
- setPageToggle(value);
- }
- }}
- >
- {Object.values(searchTabs).map((item) => (
-
-
- {item === "snapshot"
- ? search?.has_snapshot
- ? t("type.snapshot")
- : t("type.thumbnail")
- : t(`type.${item}`)}
-
-
- ))}
-
-
-
-
- );
-
if (!search) {
return;
}
@@ -293,152 +489,38 @@ export default function SearchDetailDialog({
- {isDesktop ? (
- page === "tracking_details" ? (
-
- }
+
+ {!isDesktop && (
+
+
- ) : (
-
-
- {page === "snapshot" && search.has_snapshot && (
-
- )}
- {page === "snapshot" && !search.has_snapshot && (
-
-

-
- )}
-
-
-
- {page == "snapshot" && (
-
- )}
-
-
-
- )
- ) : (
- <>
-
-
-
{
- if (value) {
- setPageToggle(value);
- }
- }}
- >
- {Object.values(searchTabs).map((item) => (
-
-
- {t(`type.${item}`)}
-
-
- ))}
-
-
-
-
- {page == "snapshot" && (
- <>
- {search.has_snapshot && (
-
- )}
- {page == "snapshot" && !search.has_snapshot && (
-

- )}
-
- >
- )}
- {page == "tracking_details" && (
-
- )}
- >
+
)}
+
+
@@ -449,17 +531,13 @@ type ObjectDetailsTabProps = {
search: SearchResult;
config?: FrigateConfig;
setSearch: (search: SearchResult | undefined) => void;
- setSimilarity?: () => void;
setInputFocused: React.Dispatch>;
- tabs?: React.ReactNode;
};
function ObjectDetailsTab({
search,
config,
setSearch,
- setSimilarity,
setInputFocused,
- tabs,
}: ObjectDetailsTabProps) {
const { t, i18n } = useTranslation([
"views/explore",
@@ -895,20 +973,6 @@ function ObjectDetailsTab({
const popoverContainerRef = useRef(null);
return (
- {tabs && (
-
- )}
-
@@ -1301,12 +1365,17 @@ function ObjectDetailsTab({
type ObjectSnapshotTabProps = {
search: Event;
+ className?: string;
+ onEventUploaded?: () => void;
};
-export function ObjectSnapshotTab({ search }: ObjectSnapshotTabProps) {
+export function ObjectSnapshotTab({
+ search,
+ className,
+}: ObjectSnapshotTabProps) {
const [imgRef, imgLoaded, onImgLoad] = useImageLoaded();
return (
-
+
void;
};
export function TrackingDetails({
className,
event,
tabs,
- actions,
+ showControls = false,
}: TrackingDetailsProps) {
const videoRef = useRef(null);
const { t } = useTranslation(["views/explore"]);
@@ -95,7 +90,6 @@ export function TrackingDetails({
const containerRef = useRef(null);
const [_selectedZone, setSelectedZone] = useState("");
const [_lifecycleZones, setLifecycleZones] = useState([]);
- const [showControls, setShowControls] = useState(false);
const [showZones, setShowZones] = useState(true);
const [seekToTimestamp, setSeekToTimestamp] = useState(null);
@@ -454,10 +448,9 @@ export function TrackingDetails({
- {isDesktop && (tabs || actions) && (
+ {isDesktop && tabs && (
)}
-
-
-
-
-
-
-
-
- {t("trackingDetails.adjustAnnotationSettings")}
-
-
-
-
-
-
{config?.cameras[event.camera]?.onvif.autotracking
.enabled_in_config && (