import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Drawer, DrawerContent } from "@/components/ui/drawer"; import { Label } from "@/components/ui/label"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { SelectSeparator } from "@/components/ui/select"; import { useFormattedTimestamp } from "@/hooks/use-date-utils"; import { FrigateConfig } from "@/types/frigateConfig"; import { getUTCOffset } from "@/utils/dateUtil"; import { useCallback, useMemo, useState } from "react"; import useSWR from "swr"; import { TimezoneAwareCalendar } from "./ReviewActivityCalendar"; import { FaCalendarAlt } from "react-icons/fa"; import { isDesktop, isIOS, isMobile } from "react-device-detect"; import { useTranslation } from "react-i18next"; type ShareTimestampDialogProps = { currentTime: number; open: boolean; onOpenChange: (open: boolean) => void; onShareTimestamp: (timestamp: number) => void; }; export default function ShareTimestampDialog({ currentTime, open, onOpenChange, onShareTimestamp, }: Readonly) { const { t } = useTranslation(["components/dialog"]); const [selectedOption, setSelectedOption] = useState<"current" | "custom">( "current", ); const [openedCurrentTime, setOpenedCurrentTime] = useState( Math.floor(currentTime), ); const [customTimestamp, setCustomTimestamp] = useState(openedCurrentTime); const handleOpenChange = useCallback( (nextOpen: boolean) => { if (nextOpen) { const initialTimestamp = Math.floor(currentTime); setOpenedCurrentTime(initialTimestamp); setSelectedOption("current"); setCustomTimestamp(initialTimestamp); } onOpenChange(nextOpen); }, [currentTime, onOpenChange], ); const content = ( { onShareTimestamp(timestamp); onOpenChange(false); }} onCancel={() => onOpenChange(false)} /> ); if (isMobile) { return ( {content} ); } return ( {t("recording.shareTimestamp.title", { ns: "components/dialog" })} {t("recording.shareTimestamp.description", { ns: "components/dialog", })} {content} ); } type ShareTimestampContentProps = { currentTime: number; selectedOption: "current" | "custom"; setSelectedOption: (option: "current" | "custom") => void; customTimestamp: number; setCustomTimestamp: (timestamp: number) => void; onShareTimestamp: (timestamp: number) => void; onCancel?: () => void; }; export function ShareTimestampContent({ currentTime, selectedOption, setSelectedOption, customTimestamp, setCustomTimestamp, onShareTimestamp, onCancel, }: Readonly) { const { t } = useTranslation(["common", "components/dialog"]); const { data: config } = useSWR("config"); const currentTimestampLabel = useFormattedTimestamp( currentTime, config?.ui.time_format == "24hour" ? t("time.formattedTimestamp.24hour") : t("time.formattedTimestamp.12hour"), config?.ui.timezone, ); const selectedTimestamp = selectedOption === "current" ? currentTime : customTimestamp; return (
{t("recording.shareTimestamp.description", { ns: "components/dialog", })}
{isDesktop && } setSelectedOption(value as "current" | "custom") } >
{selectedOption === "custom" && ( )}
{isDesktop && } {onCancel && ( )}
); } type CustomTimestampSelectorProps = { timestamp: number; setTimestamp: (timestamp: number) => void; label: string; }; function CustomTimestampSelector({ timestamp, setTimestamp, label, }: Readonly) { const { t } = useTranslation(["common"]); const { data: config } = useSWR("config"); const timezoneOffset = useMemo( () => config?.ui.timezone ? Math.round(getUTCOffset(new Date(), config.ui.timezone)) : undefined, [config?.ui.timezone], ); const localTimeOffset = useMemo( () => Math.round( getUTCOffset( new Date(), Intl.DateTimeFormat().resolvedOptions().timeZone, ), ), [], ); const offsetDeltaSeconds = useMemo(() => { if (timezoneOffset === undefined) { return 0; } // the picker edits a timestamp in the configured UI timezone, // but the stored value remains a unix timestamp return (timezoneOffset - localTimeOffset) * 60; }, [timezoneOffset, localTimeOffset]); const displayTimestamp = useMemo( () => timestamp + offsetDeltaSeconds, [timestamp, offsetDeltaSeconds], ); const formattedTimestamp = useFormattedTimestamp( displayTimestamp, config?.ui.time_format == "24hour" ? t("time.formattedTimestamp.24hour") : t("time.formattedTimestamp.12hour"), ); const clock = useMemo(() => { const date = new Date(displayTimestamp * 1000); return `${date.getHours().toString().padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")}:${date.getSeconds().toString().padStart(2, "0")}`; }, [displayTimestamp]); const [selectorOpen, setSelectorOpen] = useState(false); const setFromDisplayDate = useCallback( (date: Date) => { // convert the edited display time back into the underlying Unix timestamp setTimestamp(date.getTime() / 1000 - offsetDeltaSeconds); }, [offsetDeltaSeconds, setTimestamp], ); return (
{ if (!open) { setSelectorOpen(false); } }} > { if (!day) { return; } const nextTimestamp = new Date(displayTimestamp * 1000); nextTimestamp.setFullYear( day.getFullYear(), day.getMonth(), day.getDate(), ); setFromDisplayDate(nextTimestamp); }} />
{ const nextClock = e.target.value; const [hour, minute, second] = isIOS ? [...nextClock.split(":"), "00"] : nextClock.split(":"); const nextTimestamp = new Date(displayTimestamp * 1000); nextTimestamp.setHours( Number.parseInt(hour), Number.parseInt(minute), Number.parseInt(second ?? "0"), 0, ); setFromDisplayDate(nextTimestamp); }} />
); }