Files
clash-verge-rev/src/components/setting/mods/auto-backup-settings.tsx
T
renovate[bot]Tungliesrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
71dc5f0bdb chore(deps): update npm dependencies to v9 (major) (#6819)
* fix: replace DeleteOutline with DeleteOutlined icon in BackupHistoryViewer

* chore(deps): update npm dependencies to v9

* refactor: bump @mui to v9

---------

Co-authored-by: Tunglies <77394545+Tunglies@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-15 14:12:06 +00:00

218 lines
5.8 KiB
TypeScript

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/notice-service'
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<AutoBackupState>(() => {
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<AutoBackupState | null>(
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<string | null>(
null,
)
const applyPatch = useLockFn(
async (
partial: Partial<AutoBackupState>,
payload: Partial<IVergeConfig>,
) => {
const nextValues = { ...values, ...partial }
setPendingValues(nextValues)
try {
await patchVerge(payload)
} catch (error) {
showNotice.error(error)
setPendingValues(null)
}
},
)
const disabled = !verge
const handleScheduleToggle = (
_: ChangeEvent<HTMLInputElement>,
checked: boolean,
) => {
applyPatch(
{ scheduleEnabled: checked },
{
enable_auto_backup_schedule: checked,
auto_backup_interval_hours: values.intervalHours,
},
)
}
const handleChangeToggle = (
_: ChangeEvent<HTMLInputElement>,
checked: boolean,
) => {
applyPatch({ changeEnabled: checked }, { auto_backup_on_change: checked })
}
const handleIntervalInputChange = (event: ChangeEvent<HTMLInputElement>) => {
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 (
<Fragment>
<ListItem divider disableGutters>
<Stack
direction="row"
spacing={1}
sx={{ alignItems: 'center', width: '100%' }}
>
<ListItemText
primary={t('settings.modals.backup.auto.scheduleLabel')}
secondary={t('settings.modals.backup.auto.scheduleHelper')}
/>
<Switch
edge="end"
checked={values.scheduleEnabled}
onChange={handleScheduleToggle}
disabled={disabled}
/>
</Stack>
</ListItem>
<ListItem divider disableGutters>
<Stack
direction="row"
spacing={2}
sx={{ alignItems: 'center', width: '100%' }}
>
<ListItemText
primary={t('settings.modals.backup.auto.intervalLabel')}
/>
<TextField
label={t('settings.modals.backup.auto.intervalLabel')}
size="small"
type="number"
value={intervalInputDraft ?? values.intervalHours.toString()}
disabled={scheduleDisabled}
onChange={handleIntervalInputChange}
onBlur={commitIntervalInput}
onKeyDown={(event) => {
if (event.key === 'Enter') {
event.preventDefault()
commitIntervalInput()
}
}}
sx={{ minWidth: 160 }}
slotProps={{
input: {
endAdornment: (
<InputAdornment position="end">
{t('shared.units.hours')}
</InputAdornment>
),
},
htmlInput: {
min: MIN_INTERVAL_HOURS,
max: MAX_INTERVAL_HOURS,
inputMode: 'numeric',
},
}}
/>
</Stack>
</ListItem>
<ListItem divider disableGutters>
<Stack
direction="row"
spacing={1}
sx={{ alignItems: 'center', width: '100%' }}
>
<ListItemText
primary={t('settings.modals.backup.auto.changeLabel')}
secondary={t('settings.modals.backup.auto.changeHelper')}
/>
<Switch
edge="end"
checked={values.changeEnabled}
onChange={handleChangeToggle}
disabled={disabled}
/>
</Stack>
</ListItem>
</Fragment>
)
}