2026-03-19 17:33:30 +03:00
|
|
|
import { formatInTimeZone, fromZonedTime } from "date-fns-tz";
|
|
|
|
|
|
2026-03-19 23:54:54 +03:00
|
|
|
export const RECORDING_REVIEW_LINK_PARAM = "timestamp";
|
2026-03-19 01:02:48 +03:00
|
|
|
|
|
|
|
|
export type RecordingReviewLinkState = {
|
|
|
|
|
camera: string;
|
|
|
|
|
timestamp: number;
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-19 17:33:30 +03:00
|
|
|
function formatRecordingReviewTimestamp(
|
|
|
|
|
timestamp: number,
|
|
|
|
|
timezone: string | undefined,
|
|
|
|
|
): string {
|
|
|
|
|
const date = new Date(Math.floor(timestamp) * 1000);
|
|
|
|
|
|
|
|
|
|
if (timezone) {
|
2026-03-19 21:37:15 +03:00
|
|
|
// when the UI timezone is configured, keep the URL readable by storing
|
|
|
|
|
// local time plus a separate timezone query param
|
2026-03-19 17:33:30 +03:00
|
|
|
return formatInTimeZone(date, timezone, "yyyy-MM-dd'T'HH:mm:ss");
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-19 21:37:15 +03:00
|
|
|
// without a configured UI timezone, fall back to UTC timestamp
|
2026-03-19 17:33:30 +03:00
|
|
|
return formatInTimeZone(date, "UTC", "yyyy-MM-dd'T'HH:mm:ss'Z'");
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-19 01:02:48 +03:00
|
|
|
export function parseRecordingReviewLink(
|
2026-03-19 23:54:54 +03:00
|
|
|
value: string | null,
|
2026-03-19 01:02:48 +03:00
|
|
|
): RecordingReviewLinkState | undefined {
|
2026-03-19 23:54:54 +03:00
|
|
|
if (!value) {
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const [camera, start, timezone] = value.split("|");
|
|
|
|
|
|
2026-03-19 01:02:48 +03:00
|
|
|
if (!camera || !start) {
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-19 17:33:30 +03:00
|
|
|
const parsedDate = timezone
|
|
|
|
|
? fromZonedTime(start, timezone)
|
|
|
|
|
: new Date(start);
|
|
|
|
|
const parsedTimestamp = parsedDate.getTime() / 1000;
|
2026-03-19 01:02:48 +03:00
|
|
|
|
|
|
|
|
if (!Number.isFinite(parsedTimestamp)) {
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
camera,
|
|
|
|
|
timestamp: Math.floor(parsedTimestamp),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function createRecordingReviewUrl(
|
|
|
|
|
pathname: string,
|
|
|
|
|
state: RecordingReviewLinkState,
|
2026-03-19 17:33:30 +03:00
|
|
|
timezone?: string,
|
2026-03-19 01:02:48 +03:00
|
|
|
): string {
|
2026-03-19 17:33:30 +03:00
|
|
|
const url = new URL(globalThis.location.href);
|
|
|
|
|
const formattedTimestamp = formatRecordingReviewTimestamp(
|
|
|
|
|
state.timestamp,
|
|
|
|
|
timezone,
|
2026-03-19 01:02:48 +03:00
|
|
|
);
|
2026-03-19 17:33:30 +03:00
|
|
|
const normalizedPathname = pathname.startsWith("/")
|
|
|
|
|
? pathname
|
|
|
|
|
: `/${pathname}`;
|
2026-03-19 23:54:54 +03:00
|
|
|
const reviewLink = timezone
|
|
|
|
|
? `${state.camera}|${formattedTimestamp}|${timezone}`
|
|
|
|
|
: `${state.camera}|${formattedTimestamp}`;
|
2026-03-19 01:02:48 +03:00
|
|
|
|
2026-03-19 23:54:54 +03:00
|
|
|
return `${url.origin}${normalizedPathname}?${RECORDING_REVIEW_LINK_PARAM}=${reviewLink}`;
|
2026-03-19 01:02:48 +03:00
|
|
|
}
|