mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-12-08 14:25:41 +03:00
* Translation module init
* Add more i18n keys
* fix: fix string wrong
* refactor: use namespace translation file
* chore: add more translation key
* fix: fix some page name error
* refactor: change Trans tag for t function
* chore: fix some key not work
* chore: fix SearchFilterDialog i18n key error
Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
* chore: fix en i18n file filter missing some keys
* chore: add some i18n keys
* chore: add more i18n keys again
* feat: add search page i18n
* feat: add explore model i18n keys
* Update web/src/components/menu/GeneralSettings.tsx
Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
* Update web/src/components/menu/GeneralSettings.tsx
Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
* Update web/src/components/menu/GeneralSettings.tsx
Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
* feat: add more live i18n keys
* feat: add more search setting i18n keys
* fix: remove some comment
* fix: fix some setting page url error
* Update web/src/views/settings/SearchSettingsView.tsx
Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
* fix: add system missing keys
* fix: update password update i18n keys
* chore: remove outdate translation.json file
* fix: fix exploreSettings error
* chore: add object setting i18n keys
* Update web/src/views/recording/RecordingView.tsx
Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
* Update web/public/locales/en/components/filter.json
Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
* Update web/src/components/overlay/ExportDialog.tsx
Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
* feat: add more i18n keys
* fix: fix motionDetectionTuner html node
* feat: add more page i18n keys
* fix: cameraStream i18n keys error
* feat: add Player i18n keys
* feat: add more toast i18n keys
* feat: change explore setting name
* feat: add more document title i18n keys
* feat: add more search i18n keys
* fix: fix accessDenied i18n keys error
* chore: add objectType i18n
* chore: add inputWithTags i18n
* chore: add SearchFilterDialog i18n
* Update web/src/views/settings/ObjectSettingsView.tsx
Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
* Update web/src/views/settings/ObjectSettingsView.tsx
Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
* Update web/src/views/settings/ObjectSettingsView.tsx
Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
* Update web/src/views/settings/ObjectSettingsView.tsx
Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
* Update web/src/views/settings/ObjectSettingsView.tsx
Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
* chore: add some missing i18n keys
* chore: remove most import { t } from "i18next";
---------
Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
268 lines
9.2 KiB
TypeScript
268 lines
9.2 KiB
TypeScript
import Heading from "@/components/ui/heading";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Switch } from "@/components/ui/switch";
|
|
import { useCallback, useEffect } from "react";
|
|
import { Toaster } from "sonner";
|
|
import { toast } from "sonner";
|
|
import { Separator } from "../../components/ui/separator";
|
|
import { Button } from "../../components/ui/button";
|
|
import useSWR from "swr";
|
|
import { FrigateConfig } from "@/types/frigateConfig";
|
|
import { del as delData } from "idb-keyval";
|
|
import { usePersistence } from "@/hooks/use-persistence";
|
|
import { isSafari } from "react-device-detect";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectGroup,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
} from "../../components/ui/select";
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
const PLAYBACK_RATE_DEFAULT = isSafari ? [0.5, 1, 2] : [0.5, 1, 2, 4, 8, 16];
|
|
const WEEK_STARTS_ON = ["Sunday", "Monday"];
|
|
|
|
export default function UiSettingsView() {
|
|
const { data: config } = useSWR<FrigateConfig>("config");
|
|
const { t } = useTranslation("views/settings");
|
|
const clearStoredLayouts = useCallback(() => {
|
|
if (!config) {
|
|
return [];
|
|
}
|
|
|
|
Object.entries(config.camera_groups).forEach(async (value) => {
|
|
await delData(`${value[0]}-draggable-layout`)
|
|
.then(() => {
|
|
toast.success(
|
|
t("general.toast.success.clearStoredLayout", {
|
|
cameraName: value[0],
|
|
}),
|
|
{
|
|
position: "top-center",
|
|
},
|
|
);
|
|
})
|
|
.catch((error) => {
|
|
const errorMessage =
|
|
error.response?.data?.message ||
|
|
error.response?.data?.detail ||
|
|
"Unknown error";
|
|
toast.error(
|
|
t("general.toast.error.clearStoredLayoutFailed", { errorMessage }),
|
|
{
|
|
position: "top-center",
|
|
},
|
|
);
|
|
});
|
|
});
|
|
}, [config, t]);
|
|
|
|
const clearStreamingSettings = useCallback(async () => {
|
|
if (!config) {
|
|
return [];
|
|
}
|
|
|
|
await delData(`streaming-settings`)
|
|
.then(() => {
|
|
toast.success(t("general.toast.success.clearStreamingSettings"), {
|
|
position: "top-center",
|
|
});
|
|
})
|
|
.catch((error) => {
|
|
const errorMessage =
|
|
error.response?.data?.message ||
|
|
error.response?.data?.detail ||
|
|
"Unknown error";
|
|
toast.error(
|
|
t("general.toast.error.clearStreamingSettingsFailed", {
|
|
errorMessage,
|
|
}),
|
|
{
|
|
position: "top-center",
|
|
},
|
|
);
|
|
});
|
|
}, [config, t]);
|
|
|
|
useEffect(() => {
|
|
document.title = t("documentTitle.general");
|
|
}, [t]);
|
|
|
|
// settings
|
|
|
|
const [autoLive, setAutoLive] = usePersistence("autoLiveView", true);
|
|
const [playbackRate, setPlaybackRate] = usePersistence("playbackRate", 1);
|
|
const [weekStartsOn, setWeekStartsOn] = usePersistence("weekStartsOn", 0);
|
|
const [alertVideos, setAlertVideos] = usePersistence("alertVideos", true);
|
|
|
|
return (
|
|
<>
|
|
<div className="flex size-full flex-col md:flex-row">
|
|
<Toaster position="top-center" closeButton={true} />
|
|
<div className="scrollbar-container order-last mb-10 mt-2 flex h-full w-full flex-col overflow-y-auto rounded-lg border-[1px] border-secondary-foreground bg-background_alt p-2 md:order-none md:mb-0 md:mr-2 md:mt-0">
|
|
<Heading as="h3" className="my-2">
|
|
{t("general.title")}
|
|
</Heading>
|
|
|
|
<Separator className="my-2 flex bg-secondary" />
|
|
|
|
<Heading as="h4" className="my-2">
|
|
{t("general.liveDashboard.title")}
|
|
</Heading>
|
|
|
|
<div className="mt-2 space-y-6">
|
|
<div className="space-y-3">
|
|
<div className="flex flex-row items-center justify-start gap-2">
|
|
<Switch
|
|
id="auto-live"
|
|
checked={autoLive}
|
|
onCheckedChange={setAutoLive}
|
|
/>
|
|
<Label className="cursor-pointer" htmlFor="auto-live">
|
|
{t("general.liveDashboard.automaticLiveView.label")}
|
|
</Label>
|
|
</div>
|
|
<div className="my-2 max-w-5xl text-sm text-muted-foreground">
|
|
<p>{t("general.liveDashboard.automaticLiveView.desc")}</p>
|
|
</div>
|
|
</div>
|
|
<div className="space-y-3">
|
|
<div className="flex flex-row items-center justify-start gap-2">
|
|
<Switch
|
|
id="images-only"
|
|
checked={alertVideos}
|
|
onCheckedChange={setAlertVideos}
|
|
/>
|
|
<Label className="cursor-pointer" htmlFor="images-only">
|
|
{t("general.liveDashboard.playAlertVideos.label")}
|
|
</Label>
|
|
</div>
|
|
<div className="my-2 max-w-5xl text-sm text-muted-foreground">
|
|
<p>{t("general.liveDashboard.playAlertVideos.desc")}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="my-3 flex w-full flex-col space-y-6">
|
|
<div className="mt-2 space-y-3">
|
|
<div className="space-y-0.5">
|
|
<div className="text-md">
|
|
{t("general.storedLayouts.title")}
|
|
</div>
|
|
<div className="my-2 text-sm text-muted-foreground">
|
|
<p>{t("general.storedLayouts.desc")}</p>
|
|
</div>
|
|
</div>
|
|
<Button
|
|
aria-label={t("general.storedLayouts.clearAll")}
|
|
onClick={clearStoredLayouts}
|
|
>
|
|
{t("general.storedLayouts.clearAll")}
|
|
</Button>
|
|
</div>
|
|
|
|
<div className="mt-2 space-y-3">
|
|
<div className="space-y-0.5">
|
|
<div className="text-md">
|
|
{t("general.cameraGroupStreaming.title")}
|
|
</div>
|
|
<div className="my-2 max-w-5xl text-sm text-muted-foreground">
|
|
<p>{t("general.cameraGroupStreaming.desc")}</p>
|
|
</div>
|
|
</div>
|
|
<Button
|
|
aria-label={t("general.cameraGroupStreaming.clearAll")}
|
|
onClick={clearStreamingSettings}
|
|
>
|
|
{t("general.cameraGroupStreaming.clearAll")}
|
|
</Button>
|
|
</div>
|
|
|
|
<Separator className="my-2 flex bg-secondary" />
|
|
|
|
<Heading as="h4" className="my-2">
|
|
{t("general.recordingsViewer.title")}
|
|
</Heading>
|
|
|
|
<div className="mt-2 space-y-6">
|
|
<div className="space-y-0.5">
|
|
<div className="text-md">
|
|
{t("general.recordingsViewer.defaultPlaybackRate.label")}
|
|
</div>
|
|
<div className="my-2 text-sm text-muted-foreground">
|
|
<p>
|
|
{t("general.recordingsViewer.defaultPlaybackRate.desc")}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<Select
|
|
value={playbackRate?.toString()}
|
|
onValueChange={(value) => setPlaybackRate(parseFloat(value))}
|
|
>
|
|
<SelectTrigger className="w-20">
|
|
{`${playbackRate}x`}
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectGroup>
|
|
{PLAYBACK_RATE_DEFAULT.map((rate) => (
|
|
<SelectItem
|
|
key={rate}
|
|
className="cursor-pointer"
|
|
value={rate.toString()}
|
|
>
|
|
{rate}x
|
|
</SelectItem>
|
|
))}
|
|
</SelectGroup>
|
|
</SelectContent>
|
|
</Select>
|
|
<Separator className="my-2 flex bg-secondary" />
|
|
|
|
<Heading as="h4" className="my-2">
|
|
{t("general.calendar.title")}
|
|
</Heading>
|
|
|
|
<div className="mt-2 space-y-6">
|
|
<div className="space-y-0.5">
|
|
<div className="text-md">
|
|
{t("general.calendar.firstWeekday.label")}
|
|
</div>
|
|
<div className="my-2 text-sm text-muted-foreground">
|
|
<p>{t("general.calendar.firstWeekday.desc")}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<Select
|
|
value={weekStartsOn?.toString()}
|
|
onValueChange={(value) => setWeekStartsOn(parseInt(value))}
|
|
>
|
|
<SelectTrigger className="w-32">
|
|
{t(
|
|
"general.calendar.firstWeekday." +
|
|
WEEK_STARTS_ON[weekStartsOn ?? 0].toLowerCase(),
|
|
)}
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectGroup>
|
|
{WEEK_STARTS_ON.map((day, index) => (
|
|
<SelectItem
|
|
key={index}
|
|
className="cursor-pointer"
|
|
value={index.toString()}
|
|
>
|
|
{t("general.calendar.firstWeekday." + day.toLowerCase())}
|
|
</SelectItem>
|
|
))}
|
|
</SelectGroup>
|
|
</SelectContent>
|
|
</Select>
|
|
<Separator className="my-2 flex bg-secondary" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|