mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-04-10 09:07:37 +03:00
Allow clicking on a recording
This commit is contained in:
parent
04908301ec
commit
8c09103841
@ -114,6 +114,33 @@ export default function Events() {
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Wrapper to update URL with review ID for deep linking when opening a recording.
|
||||||
|
// This does a single atomic navigation to avoid creating multiple history entries
|
||||||
|
// which would cause issues with the back button.
|
||||||
|
const onOpenRecording = useCallback(
|
||||||
|
(recordingInfo: RecordingStartingPoint) => {
|
||||||
|
const updated = new URLSearchParams(searchParams);
|
||||||
|
if (recordingInfo.reviewId) {
|
||||||
|
updated.set("id", recordingInfo.reviewId);
|
||||||
|
}
|
||||||
|
const search = updated.toString();
|
||||||
|
|
||||||
|
// Single navigation that updates both URL params and location state
|
||||||
|
navigate(
|
||||||
|
{
|
||||||
|
pathname: location.pathname,
|
||||||
|
search: search ? `?${search}` : "",
|
||||||
|
hash: location.hash,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
replace: true,
|
||||||
|
state: { ...location.state, recording: recordingInfo },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[searchParams, navigate, location.pathname, location.hash, location.state],
|
||||||
|
);
|
||||||
|
|
||||||
const [notificationTab, setNotificationTab] =
|
const [notificationTab, setNotificationTab] =
|
||||||
useState<TimelineType>("timeline");
|
useState<TimelineType>("timeline");
|
||||||
|
|
||||||
@ -125,6 +152,13 @@ export default function Events() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useSearchEffect("id", (reviewId: string) => {
|
useSearchEffect("id", (reviewId: string) => {
|
||||||
|
// If recording is already set (e.g., from clicking a review), don't re-fetch.
|
||||||
|
// Return false to keep the id param in the URL for deep linking.
|
||||||
|
if (recording) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fresh deep link - fetch review data and open recording
|
||||||
axios
|
axios
|
||||||
.get(`review/${reviewId}`)
|
.get(`review/${reviewId}`)
|
||||||
.then((resp) => {
|
.then((resp) => {
|
||||||
@ -142,6 +176,7 @@ export default function Events() {
|
|||||||
startTime,
|
startTime,
|
||||||
severity: resp.data.severity,
|
severity: resp.data.severity,
|
||||||
timelineType: notificationTab,
|
timelineType: notificationTab,
|
||||||
|
reviewId,
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
@ -149,7 +184,8 @@ export default function Events() {
|
|||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
|
|
||||||
return true;
|
// Return false to keep the id param in the URL for deep linking
|
||||||
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
const [startTime, setStartTime] = useState<number>();
|
const [startTime, setStartTime] = useState<number>();
|
||||||
@ -560,7 +596,7 @@ export default function Events() {
|
|||||||
setSeverity={setSeverity}
|
setSeverity={setSeverity}
|
||||||
markItemAsReviewed={markItemAsReviewed}
|
markItemAsReviewed={markItemAsReviewed}
|
||||||
markAllItemsAsReviewed={markAllItemsAsReviewed}
|
markAllItemsAsReviewed={markAllItemsAsReviewed}
|
||||||
onOpenRecording={setRecording}
|
onOpenRecording={onOpenRecording}
|
||||||
pullLatestData={reloadData}
|
pullLatestData={reloadData}
|
||||||
updateFilter={onUpdateFilter}
|
updateFilter={onUpdateFilter}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -39,6 +39,8 @@ export type RecordingStartingPoint = {
|
|||||||
startTime: number;
|
startTime: number;
|
||||||
severity: ReviewSeverity;
|
severity: ReviewSeverity;
|
||||||
timelineType?: TimelineType;
|
timelineType?: TimelineType;
|
||||||
|
// Optional review ID for deep linking to specific review segments
|
||||||
|
reviewId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RecordingPlayerError = "stalled" | "startup";
|
export type RecordingPlayerError = "stalled" | "startup";
|
||||||
|
|||||||
@ -168,6 +168,7 @@ export default function EventView({
|
|||||||
startTime: effectiveStartTime - REVIEW_PADDING,
|
startTime: effectiveStartTime - REVIEW_PADDING,
|
||||||
severity: review.severity,
|
severity: review.severity,
|
||||||
timelineType: detail ? "detail" : undefined,
|
timelineType: detail ? "detail" : undefined,
|
||||||
|
reviewId: review.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
review.has_been_reviewed = true;
|
review.has_been_reviewed = true;
|
||||||
|
|||||||
@ -35,7 +35,7 @@ import {
|
|||||||
} from "react";
|
} from "react";
|
||||||
import { isDesktop, isMobile } from "react-device-detect";
|
import { isDesktop, isMobile } from "react-device-detect";
|
||||||
import { IoMdArrowRoundBack } from "react-icons/io";
|
import { IoMdArrowRoundBack } from "react-icons/io";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate, useSearchParams } from "react-router-dom";
|
||||||
import { Toaster } from "@/components/ui/sonner";
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { TimeRange, TimelineType } from "@/types/timeline";
|
import { TimeRange, TimelineType } from "@/types/timeline";
|
||||||
@ -97,8 +97,26 @@ export function RecordingView({
|
|||||||
const { t } = useTranslation(["views/events"]);
|
const { t } = useTranslation(["views/events"]);
|
||||||
const { data: config } = useSWR<FrigateConfig>("config");
|
const { data: config } = useSWR<FrigateConfig>("config");
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
const contentRef = useRef<HTMLDivElement | null>(null);
|
const contentRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
// Navigate back while clearing the review id param to prevent
|
||||||
|
// useSearchEffect from re-opening the recording
|
||||||
|
const handleBack = useCallback(() => {
|
||||||
|
const updated = new URLSearchParams(searchParams);
|
||||||
|
updated.delete("id");
|
||||||
|
const search = updated.toString();
|
||||||
|
navigate(
|
||||||
|
{
|
||||||
|
pathname: location.pathname,
|
||||||
|
search: search ? `?${search}` : "",
|
||||||
|
hash: location.hash,
|
||||||
|
},
|
||||||
|
{ replace: true },
|
||||||
|
);
|
||||||
|
}, [searchParams, navigate, location.pathname, location.hash]);
|
||||||
|
|
||||||
// recordings summary
|
// recordings summary
|
||||||
|
|
||||||
const timezone = useTimezone(config);
|
const timezone = useTimezone(config);
|
||||||
@ -541,7 +559,7 @@ export function RecordingView({
|
|||||||
className="flex items-center gap-2.5 rounded-lg"
|
className="flex items-center gap-2.5 rounded-lg"
|
||||||
aria-label={t("label.back", { ns: "common" })}
|
aria-label={t("label.back", { ns: "common" })}
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => navigate(-1)}
|
onClick={handleBack}
|
||||||
>
|
>
|
||||||
<IoMdArrowRoundBack className="size-5 text-secondary-foreground" />
|
<IoMdArrowRoundBack className="size-5 text-secondary-foreground" />
|
||||||
{isDesktop && (
|
{isDesktop && (
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user