mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-07-31 16:12:19 +03:00
Feature: Share Timestamped URL for Camera Footage History (#22537)
* Initial copy timestamp url implementation
* revise url format
* Implement share timestamp dialog
* Use translations
* Add comments
* Add validations to shared link
* Switch to searchEffect implementation
* Add missing accessibility related dialog description
* Change URL format to unix timestamps
* Remove unnecessary useEffect
* Remove duplicated dialog title
* Fixes/improvements based off PR review comments
* Add missing cancel button & separators to dialog
* Make share description clearer
* Bugfix: guard against showing toasts twice
Because this effect ends up running multiple times
* Clamp future timestamps to now
* Revert "Bugfix: guard against showing toasts twice"
This reverts commit 99fa5e1dee.
* Use normal separator
Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
* Fixes based off PR review comments
* Bugfix: Share dialog was not receiving the player timestamp after removing key that triggered remounts
* Defer `setRecording` and return true from hook for cleanup
* Remove timeout defer hack in favor of refactored hook
* Attempt to replay video muted on NotAllowedError
* Use separate persistent mute and temporary forced mute states
* Align cancel button with other dialogs
* Prevent wrapping on dialog title
* Remove extra "back" button on mobile drawer
* Fix back navigation when coming from direct shared timestamp links
* Use new timeformat hook
* Simplify dialog radio buttons
* Apply suggestions from code review
Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
---------
Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
This commit is contained in:
@@ -21,6 +21,10 @@ import {
|
||||
getBeginningOfDayTimestamp,
|
||||
getEndOfDayTimestamp,
|
||||
} from "@/utils/dateUtil";
|
||||
import {
|
||||
parseRecordingReviewLink,
|
||||
RECORDING_REVIEW_LINK_PARAM,
|
||||
} from "@/utils/recordingReviewUrl";
|
||||
import EventView from "@/views/events/EventView";
|
||||
import MotionSearchView from "@/views/motion-search/MotionSearchView";
|
||||
import { RecordingView } from "@/views/recording/RecordingView";
|
||||
@@ -28,6 +32,7 @@ import { useFrigateReviews } from "@/api/ws";
|
||||
import axios from "axios";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
import useSWR from "swr";
|
||||
|
||||
export default function Events() {
|
||||
@@ -128,6 +133,14 @@ export default function Events() {
|
||||
const [notificationTab, setNotificationTab] =
|
||||
useState<TimelineType>("timeline");
|
||||
|
||||
const getReviewDayBounds = useCallback((date: Date) => {
|
||||
const now = Date.now() / 1000;
|
||||
return {
|
||||
after: getBeginningOfDayTimestamp(date),
|
||||
before: Math.min(getEndOfDayTimestamp(date), now),
|
||||
};
|
||||
}, []);
|
||||
|
||||
useSearchEffect("tab", (tab: string) => {
|
||||
if (tab === "timeline" || tab === "events" || tab === "detail") {
|
||||
setNotificationTab(tab as TimelineType);
|
||||
@@ -143,10 +156,7 @@ export default function Events() {
|
||||
const startTime = resp.data.start_time - REVIEW_PADDING;
|
||||
const date = new Date(startTime * 1000);
|
||||
|
||||
setReviewFilter({
|
||||
after: getBeginningOfDayTimestamp(date),
|
||||
before: getEndOfDayTimestamp(date),
|
||||
});
|
||||
setReviewFilter(getReviewDayBounds(date));
|
||||
setRecording(
|
||||
{
|
||||
camera: resp.data.camera,
|
||||
@@ -234,6 +244,51 @@ export default function Events() {
|
||||
[recording, setRecording, setReviewFilter],
|
||||
);
|
||||
|
||||
useSearchEffect(RECORDING_REVIEW_LINK_PARAM, (reviewLinkValue: string) => {
|
||||
if (!config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const reviewLink = parseRecordingReviewLink(reviewLinkValue);
|
||||
|
||||
if (!reviewLink) {
|
||||
toast.error(t("recordings.invalidSharedLink"), {
|
||||
position: "top-center",
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
const validCamera =
|
||||
config.cameras[reviewLink.camera] &&
|
||||
allowedCameras.includes(reviewLink.camera);
|
||||
|
||||
if (!validCamera) {
|
||||
toast.error(t("recordings.invalidSharedCamera"), {
|
||||
position: "top-center",
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
setReviewFilter({
|
||||
...reviewFilter,
|
||||
...getReviewDayBounds(new Date(reviewLink.timestamp * 1000)),
|
||||
});
|
||||
setRecording(
|
||||
{
|
||||
camera: reviewLink.camera,
|
||||
startTime: reviewLink.timestamp,
|
||||
// severity not actually applicable here, but the type requires it
|
||||
// this pattern is also used LiveCameraView to enter recording view
|
||||
severity: "alert",
|
||||
timelineType: notificationTab,
|
||||
navigationSource: "shared-link",
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// review paging
|
||||
|
||||
const [beforeTs, setBeforeTs] = useState(Math.ceil(Date.now() / 1000));
|
||||
|
||||
Reference in New Issue
Block a user