mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-12-06 13:34:13 +03:00
Some checks are pending
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
* detail stream settings * fix mobile landscape * mobile landscape * tweak * tweaks
137 lines
4.2 KiB
TypeScript
137 lines
4.2 KiB
TypeScript
import { useCallback, useState } from "react";
|
|
import { Slider } from "@/components/ui/slider";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Popover, PopoverContent, PopoverTrigger } from "../../ui/popover";
|
|
import { useDetailStream } from "@/context/detail-stream-context";
|
|
import axios from "axios";
|
|
import { useSWRConfig } from "swr";
|
|
import { toast } from "sonner";
|
|
import { Trans, useTranslation } from "react-i18next";
|
|
import { LuInfo } from "react-icons/lu";
|
|
import { cn } from "@/lib/utils";
|
|
import { isMobile } from "react-device-detect";
|
|
|
|
type Props = {
|
|
className?: string;
|
|
};
|
|
|
|
export default function AnnotationOffsetSlider({ className }: Props) {
|
|
const { annotationOffset, setAnnotationOffset, camera } = useDetailStream();
|
|
const { mutate } = useSWRConfig();
|
|
const { t } = useTranslation(["views/explore"]);
|
|
const [isSaving, setIsSaving] = useState(false);
|
|
|
|
const handleChange = useCallback(
|
|
(values: number[]) => {
|
|
if (!values || values.length === 0) return;
|
|
const valueMs = values[0];
|
|
setAnnotationOffset(valueMs);
|
|
},
|
|
[setAnnotationOffset],
|
|
);
|
|
|
|
const reset = useCallback(() => {
|
|
setAnnotationOffset(0);
|
|
}, [setAnnotationOffset]);
|
|
|
|
const save = useCallback(async () => {
|
|
setIsSaving(true);
|
|
try {
|
|
// save value in milliseconds to config
|
|
await axios.put(
|
|
`config/set?cameras.${camera}.detect.annotation_offset=${annotationOffset}`,
|
|
{ requires_restart: 0 },
|
|
);
|
|
|
|
toast.success(
|
|
t("trackingDetails.annotationSettings.offset.toast.success", {
|
|
camera,
|
|
}),
|
|
{ position: "top-center" },
|
|
);
|
|
|
|
// refresh config
|
|
await mutate("config");
|
|
} catch (e: unknown) {
|
|
const err = e as {
|
|
response?: { data?: { message?: string } };
|
|
message?: string;
|
|
};
|
|
const errorMessage =
|
|
err?.response?.data?.message || err?.message || "Unknown error";
|
|
toast.error(t("toast.save.error.title", { errorMessage, ns: "common" }), {
|
|
position: "top-center",
|
|
});
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}, [annotationOffset, camera, mutate, t]);
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"flex flex-col gap-0.5",
|
|
isMobile && "landscape:gap-3",
|
|
className,
|
|
)}
|
|
>
|
|
<div
|
|
className={cn(
|
|
"flex items-center gap-3",
|
|
isMobile &&
|
|
"landscape:flex-col landscape:items-start landscape:gap-4",
|
|
)}
|
|
>
|
|
<div className="flex max-w-28 flex-row items-center gap-2 text-sm md:max-w-48">
|
|
<span className="max-w-24 md:max-w-44">
|
|
{t("trackingDetails.annotationSettings.offset.label")}:
|
|
</span>
|
|
<span className="text-primary-variant">{annotationOffset}</span>
|
|
</div>
|
|
<div className="w-full flex-1 landscape:flex">
|
|
<Slider
|
|
value={[annotationOffset]}
|
|
min={-1500}
|
|
max={1500}
|
|
step={50}
|
|
onValueChange={handleChange}
|
|
/>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<Button size="sm" variant="ghost" onClick={reset}>
|
|
{t("button.reset", { ns: "common" })}
|
|
</Button>
|
|
<Button size="sm" onClick={save} disabled={isSaving}>
|
|
{isSaving
|
|
? t("button.saving", { ns: "common" })
|
|
: t("button.save", { ns: "common" })}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
<div
|
|
className={cn(
|
|
"flex items-center gap-2 text-xs text-muted-foreground",
|
|
isMobile && "landscape:flex-col landscape:items-start",
|
|
)}
|
|
>
|
|
<Trans ns="views/explore">
|
|
trackingDetails.annotationSettings.offset.millisecondsToOffset
|
|
</Trans>
|
|
<Popover>
|
|
<PopoverTrigger asChild>
|
|
<button
|
|
className="focus:outline-none"
|
|
aria-label={t("trackingDetails.annotationSettings.offset.desc")}
|
|
>
|
|
<LuInfo className="size-4" />
|
|
</button>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="w-80 text-sm">
|
|
{t("trackingDetails.annotationSettings.offset.desc")}
|
|
</PopoverContent>
|
|
</Popover>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|