import { InputAdornment, ListItem, ListItemText, Stack, TextField, } from "@mui/material"; import { useLockFn } from "ahooks"; import { Fragment, useMemo, useState, type ChangeEvent } from "react"; import { useTranslation } from "react-i18next"; import { Switch } from "@/components/base"; import { useVerge } from "@/hooks/use-verge"; import { showNotice } from "@/services/noticeService"; const MIN_INTERVAL_HOURS = 1; const MAX_INTERVAL_HOURS = 168; interface AutoBackupState { scheduleEnabled: boolean; intervalHours: number; changeEnabled: boolean; } export function AutoBackupSettings() { const { t } = useTranslation(); const { verge, patchVerge } = useVerge(); const derivedValues = useMemo(() => { return { scheduleEnabled: verge?.enable_auto_backup_schedule ?? false, intervalHours: verge?.auto_backup_interval_hours ?? 24, changeEnabled: verge?.auto_backup_on_change ?? true, }; }, [ verge?.enable_auto_backup_schedule, verge?.auto_backup_interval_hours, verge?.auto_backup_on_change, ]); const [pendingValues, setPendingValues] = useState( null, ); const values = useMemo(() => { if (!pendingValues) { return derivedValues; } if ( pendingValues.scheduleEnabled === derivedValues.scheduleEnabled && pendingValues.intervalHours === derivedValues.intervalHours && pendingValues.changeEnabled === derivedValues.changeEnabled ) { return derivedValues; } return pendingValues; }, [pendingValues, derivedValues]); const [intervalInputDraft, setIntervalInputDraft] = useState( null, ); const applyPatch = useLockFn( async ( partial: Partial, payload: Partial, ) => { const nextValues = { ...values, ...partial }; setPendingValues(nextValues); try { await patchVerge(payload); } catch (error) { showNotice.error(error); setPendingValues(null); } }, ); const disabled = !verge; const handleScheduleToggle = ( _: ChangeEvent, checked: boolean, ) => { applyPatch( { scheduleEnabled: checked }, { enable_auto_backup_schedule: checked, auto_backup_interval_hours: values.intervalHours, }, ); }; const handleChangeToggle = ( _: ChangeEvent, checked: boolean, ) => { applyPatch({ changeEnabled: checked }, { auto_backup_on_change: checked }); }; const handleIntervalInputChange = (event: ChangeEvent) => { setIntervalInputDraft(event.target.value); }; const commitIntervalInput = () => { const rawValue = intervalInputDraft ?? values.intervalHours.toString(); const trimmed = rawValue.trim(); if (trimmed === "") { setIntervalInputDraft(null); return; } const parsed = Number(trimmed); if (!Number.isFinite(parsed)) { setIntervalInputDraft(null); return; } const clamped = Math.min( MAX_INTERVAL_HOURS, Math.max(MIN_INTERVAL_HOURS, Math.round(parsed)), ); if (clamped === values.intervalHours) { setIntervalInputDraft(null); return; } applyPatch( { intervalHours: clamped }, { auto_backup_interval_hours: clamped }, ); setIntervalInputDraft(null); }; const scheduleDisabled = disabled || !values.scheduleEnabled; return ( { if (event.key === "Enter") { event.preventDefault(); commitIntervalInput(); } }} sx={{ minWidth: 160 }} slotProps={{ input: { endAdornment: ( {t("shared.units.hours")} ), }, htmlInput: { min: MIN_INTERVAL_HOURS, max: MAX_INTERVAL_HOURS, inputMode: "numeric", }, }} /> ); }