From 05d29ebc93f78d5727a084522f5031a4c6e513e5 Mon Sep 17 00:00:00 2001 From: 0x464e <36742501+0x464e@users.noreply.github.com> Date: Thu, 19 Mar 2026 16:33:30 +0200 Subject: [PATCH] revise url format --- web/src/pages/Events.tsx | 4 ++- web/src/utils/recordingReviewUrl.ts | 42 ++++++++++++++++++----- web/src/views/recording/RecordingView.tsx | 14 +++++--- 3 files changed, 45 insertions(+), 15 deletions(-) diff --git a/web/src/pages/Events.tsx b/web/src/pages/Events.tsx index c065ffe14..ee6638ba2 100644 --- a/web/src/pages/Events.tsx +++ b/web/src/pages/Events.tsx @@ -24,6 +24,7 @@ import { import { parseRecordingReviewLink, RECORDING_REVIEW_START_PARAM, + RECORDING_REVIEW_TIMEZONE_PARAM, } from "@/utils/recordingReviewUrl"; import EventView from "@/views/events/EventView"; import MotionSearchView from "@/views/motion-search/MotionSearchView"; @@ -248,6 +249,7 @@ export default function Events() { useEffect(() => { const timestamp = searchParams.get(RECORDING_REVIEW_START_PARAM); + const timezone = searchParams.get(RECORDING_REVIEW_TIMEZONE_PARAM); if (!timestamp) { return; @@ -257,7 +259,7 @@ export default function Events() { ? decodeURIComponent(location.hash.substring(1)) : null; - const reviewLink = parseRecordingReviewLink(camera, timestamp); + const reviewLink = parseRecordingReviewLink(camera, timestamp, timezone); if (!reviewLink) { navigate(location.pathname + location.hash, { diff --git a/web/src/utils/recordingReviewUrl.ts b/web/src/utils/recordingReviewUrl.ts index 8fddd0530..e409c7db9 100644 --- a/web/src/utils/recordingReviewUrl.ts +++ b/web/src/utils/recordingReviewUrl.ts @@ -1,19 +1,39 @@ +import { formatInTimeZone, fromZonedTime } from "date-fns-tz"; + export const RECORDING_REVIEW_START_PARAM = "start_time"; +export const RECORDING_REVIEW_TIMEZONE_PARAM = "timezone"; export type RecordingReviewLinkState = { camera: string; timestamp: number; }; +function formatRecordingReviewTimestamp( + timestamp: number, + timezone: string | undefined, +): string { + const date = new Date(Math.floor(timestamp) * 1000); + + if (timezone) { + return formatInTimeZone(date, timezone, "yyyy-MM-dd'T'HH:mm:ss"); + } + + return formatInTimeZone(date, "UTC", "yyyy-MM-dd'T'HH:mm:ss'Z'"); +} + export function parseRecordingReviewLink( camera: string | null, start: string | null, + timezone: string | null, ): RecordingReviewLinkState | undefined { if (!camera || !start) { return undefined; } - const parsedTimestamp = Number(start); + const parsedDate = timezone + ? fromZonedTime(start, timezone) + : new Date(start); + const parsedTimestamp = parsedDate.getTime() / 1000; if (!Number.isFinite(parsedTimestamp)) { return undefined; @@ -28,15 +48,19 @@ export function parseRecordingReviewLink( export function createRecordingReviewUrl( pathname: string, state: RecordingReviewLinkState, + timezone?: string, ): string { - const url = new URL(window.location.href); - url.pathname = pathname; - url.hash = state.camera; - url.search = ""; - url.searchParams.set( - RECORDING_REVIEW_START_PARAM, - `${Math.floor(state.timestamp)}`, + const url = new URL(globalThis.location.href); + const formattedTimestamp = formatRecordingReviewTimestamp( + state.timestamp, + timezone, ); + const normalizedPathname = pathname.startsWith("/") + ? pathname + : `/${pathname}`; + const timezoneParam = timezone + ? `&${RECORDING_REVIEW_TIMEZONE_PARAM}=${encodeURIComponent(timezone)}` + : ""; - return url.toString(); + return `${url.origin}${normalizedPathname}?${RECORDING_REVIEW_START_PARAM}=${formattedTimestamp}${timezoneParam}#${encodeURIComponent(state.camera)}`; } diff --git a/web/src/views/recording/RecordingView.tsx b/web/src/views/recording/RecordingView.tsx index 34efb8686..620f89cd5 100644 --- a/web/src/views/recording/RecordingView.tsx +++ b/web/src/views/recording/RecordingView.tsx @@ -334,16 +334,20 @@ export function RecordingView({ }, [startTime, manuallySetCurrentTime]); const onCopyReviewLink = useCallback(() => { - const reviewUrl = createRecordingReviewUrl(location.pathname, { - camera: mainCamera, - timestamp: Math.floor(currentTime), - }); + const reviewUrl = createRecordingReviewUrl( + location.pathname, + { + camera: mainCamera, + timestamp: Math.floor(currentTime), + }, + config?.ui.timezone, + ); copy(reviewUrl); toast.success(t("toast.copyUrlToClipboard", { ns: "common" }), { position: "top-center", }); - }, [location.pathname, mainCamera, currentTime, t]); + }, [location.pathname, mainCamera, currentTime, config?.ui.timezone, t]); useEffect(() => { if (!scrubbing) {