mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-08-11 13:21:10 +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:
@@ -42,7 +42,7 @@ import {
|
||||
isTablet,
|
||||
} from "react-device-detect";
|
||||
import { IoMdArrowRoundBack } from "react-icons/io";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
import useSWR from "swr";
|
||||
import { TimeRange, TimelineType } from "@/types/timeline";
|
||||
@@ -77,6 +77,9 @@ import {
|
||||
GenAISummaryDialog,
|
||||
GenAISummaryChip,
|
||||
} from "@/components/overlay/chip/GenAISummaryChip";
|
||||
import ShareTimestampDialog from "@/components/overlay/ShareTimestampDialog";
|
||||
import { shareOrCopy } from "@/utils/browserUtil";
|
||||
import { createRecordingReviewUrl } from "@/utils/recordingReviewUrl";
|
||||
|
||||
const DATA_REFRESH_TIME = 600000; // 10 minutes
|
||||
|
||||
@@ -104,9 +107,10 @@ export function RecordingView({
|
||||
updateFilter,
|
||||
refreshData,
|
||||
}: RecordingViewProps) {
|
||||
const { t } = useTranslation(["views/events"]);
|
||||
const { t } = useTranslation(["views/events", "components/dialog"]);
|
||||
const { data: config } = useSWR<FrigateConfig>("config");
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const contentRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
// recordings summary
|
||||
@@ -205,6 +209,16 @@ export function RecordingView({
|
||||
|
||||
const [debugReplayMode, setDebugReplayMode] = useState<ExportMode>("none");
|
||||
const [debugReplayRange, setDebugReplayRange] = useState<TimeRange>();
|
||||
const [shareTimestampOpen, setShareTimestampOpen] = useState(false);
|
||||
const [shareTimestampAtOpen, setShareTimestampAtOpen] = useState(
|
||||
Math.floor(startTime),
|
||||
);
|
||||
const [shareTimestampOption, setShareTimestampOption] = useState<
|
||||
"current" | "custom"
|
||||
>("current");
|
||||
const [customShareTimestamp, setCustomShareTimestamp] = useState(
|
||||
Math.floor(startTime),
|
||||
);
|
||||
|
||||
// move to next clip
|
||||
|
||||
@@ -317,6 +331,34 @@ export function RecordingView({
|
||||
[currentTimeRange, updateSelectedSegment],
|
||||
);
|
||||
|
||||
const onShareReviewLink = useCallback(
|
||||
(timestamp: number) => {
|
||||
const reviewUrl = createRecordingReviewUrl(location.pathname, {
|
||||
camera: mainCamera,
|
||||
timestamp: Math.floor(timestamp),
|
||||
});
|
||||
|
||||
shareOrCopy(
|
||||
reviewUrl,
|
||||
t("recording.shareTimestamp.shareTitle", {
|
||||
ns: "components/dialog",
|
||||
camera: mainCamera,
|
||||
}),
|
||||
);
|
||||
},
|
||||
[location.pathname, mainCamera, t],
|
||||
);
|
||||
|
||||
const handleBack = useCallback(() => {
|
||||
// if we came from a direct share link, there is no history to go back to, so navigate to the homepage instead
|
||||
if (recording?.navigationSource === "shared-link") {
|
||||
navigate("/");
|
||||
return;
|
||||
}
|
||||
|
||||
navigate(-1);
|
||||
}, [navigate, recording?.navigationSource]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!scrubbing) {
|
||||
if (Math.abs(currentTime - playerTime) > 10) {
|
||||
@@ -567,7 +609,7 @@ export function RecordingView({
|
||||
className="flex items-center gap-2.5 rounded-lg"
|
||||
aria-label={t("label.back", { ns: "common" })}
|
||||
size="sm"
|
||||
onClick={() => navigate(-1)}
|
||||
onClick={handleBack}
|
||||
>
|
||||
<IoMdArrowRoundBack className="size-5 text-secondary-foreground" />
|
||||
{isDesktop && (
|
||||
@@ -663,8 +705,28 @@ export function RecordingView({
|
||||
setMotionOnly={() => {}}
|
||||
/>
|
||||
)}
|
||||
{isDesktop && (
|
||||
<ShareTimestampDialog
|
||||
currentTime={shareTimestampAtOpen}
|
||||
open={shareTimestampOpen}
|
||||
onOpenChange={setShareTimestampOpen}
|
||||
selectedOption={shareTimestampOption}
|
||||
setSelectedOption={setShareTimestampOption}
|
||||
customTimestamp={customShareTimestamp}
|
||||
setCustomTimestamp={setCustomShareTimestamp}
|
||||
onShareTimestamp={onShareReviewLink}
|
||||
/>
|
||||
)}
|
||||
{isDesktop && (
|
||||
<ActionsDropdown
|
||||
onShareTimestampClick={() => {
|
||||
const initialTimestamp = Math.floor(currentTime);
|
||||
|
||||
setShareTimestampAtOpen(initialTimestamp);
|
||||
setShareTimestampOption("current");
|
||||
setCustomShareTimestamp(initialTimestamp);
|
||||
setShareTimestampOpen(true);
|
||||
}}
|
||||
onDebugReplayClick={() => {
|
||||
const now = new Date(timeRange.before * 1000);
|
||||
now.setHours(now.getHours() - 1);
|
||||
@@ -744,6 +806,7 @@ export function RecordingView({
|
||||
mainControllerRef.current?.pause();
|
||||
}
|
||||
}}
|
||||
onShareTimestamp={onShareReviewLink}
|
||||
onUpdateFilter={updateFilter}
|
||||
setRange={setExportRange}
|
||||
setMode={setExportMode}
|
||||
|
||||
Reference in New Issue
Block a user