mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 13:00:39 +00:00
feat: add backup start time configuration and theme switch functionality
- Introduced BACKUP_DEFAULT_START_TIME constant for backup scheduling. - Updated BackupScheduleConfig interface to include startTime. - Implemented normalizeStartTime function for validating and normalizing start time input. - Enhanced backup settings parsing to accommodate start time. - Added start time input field in BackupDestinationDetail component. - Created ThemeSwitch component for toggling between light and dark themes. - Integrated theme preference management in App component. - Updated styles for dark mode support across the application. - Added translations for theme toggle and backup start time labels.
This commit is contained in:
@@ -57,11 +57,26 @@ const IMPORT_ROUTE_PATHS = [IMPORT_ROUTE, '/tools/import', '/tools/import-export
|
||||
const IMPORT_ROUTE_ALIASES: ReadonlySet<string> = new Set(IMPORT_ROUTE_PATHS.filter((path) => path !== IMPORT_ROUTE));
|
||||
const SETTINGS_HOME_ROUTE = '/settings';
|
||||
const SETTINGS_ACCOUNT_ROUTE = '/settings/account';
|
||||
const THEME_STORAGE_KEY = 'nodewarden.theme.preference.v1';
|
||||
const SIGNALR_RECORD_SEPARATOR = String.fromCharCode(0x1e);
|
||||
const SIGNALR_UPDATE_TYPE_SYNC_VAULT = 5;
|
||||
const SIGNALR_UPDATE_TYPE_LOG_OUT = 11;
|
||||
const SIGNALR_UPDATE_TYPE_DEVICE_STATUS = 12;
|
||||
|
||||
type ThemePreference = 'system' | 'light' | 'dark';
|
||||
|
||||
function readThemePreference(): ThemePreference {
|
||||
if (typeof window === 'undefined') return 'system';
|
||||
const stored = String(window.localStorage.getItem(THEME_STORAGE_KEY) || '').trim();
|
||||
if (stored === 'light' || stored === 'dark' || stored === 'system') return stored;
|
||||
return 'system';
|
||||
}
|
||||
|
||||
function resolveSystemTheme(): 'light' | 'dark' {
|
||||
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return 'light';
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
const initialBootstrap = useMemo(() => readInitialAppBootstrapState(), []);
|
||||
const initialInviteCode = useMemo(() => readInviteCodeFromUrl(), []);
|
||||
@@ -100,6 +115,8 @@ export default function App() {
|
||||
const [disableTotpOpen, setDisableTotpOpen] = useState(false);
|
||||
const [disableTotpPassword, setDisableTotpPassword] = useState('');
|
||||
const [recoverValues, setRecoverValues] = useState({ email: '', password: '', recoveryCode: '' });
|
||||
const [themePreference, setThemePreference] = useState<ThemePreference>(() => readThemePreference());
|
||||
const [systemTheme, setSystemTheme] = useState<'light' | 'dark'>(() => resolveSystemTheme());
|
||||
|
||||
const [confirm, setConfirm] = useState<AppConfirmState | null>(null);
|
||||
const [mobileLayout, setMobileLayout] = useState(false);
|
||||
@@ -175,6 +192,39 @@ export default function App() {
|
||||
return () => media.removeListener(sync);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return;
|
||||
const media = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
const sync = () => setSystemTheme(media.matches ? 'dark' : 'light');
|
||||
sync();
|
||||
if (typeof media.addEventListener === 'function') {
|
||||
media.addEventListener('change', sync);
|
||||
return () => media.removeEventListener('change', sync);
|
||||
}
|
||||
media.addListener(sync);
|
||||
return () => media.removeListener(sync);
|
||||
}, []);
|
||||
|
||||
const resolvedTheme = themePreference === 'system' ? systemTheme : themePreference;
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof document === 'undefined') return;
|
||||
document.documentElement.dataset.theme = resolvedTheme;
|
||||
document.documentElement.style.colorScheme = resolvedTheme;
|
||||
}, [resolvedTheme]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined') return;
|
||||
window.localStorage.setItem(THEME_STORAGE_KEY, themePreference);
|
||||
}, [themePreference]);
|
||||
|
||||
function handleToggleTheme() {
|
||||
setThemePreference((prev) => {
|
||||
const current = prev === 'system' ? systemTheme : prev;
|
||||
return current === 'dark' ? 'light' : 'dark';
|
||||
});
|
||||
}
|
||||
|
||||
function setSession(next: SessionState | null) {
|
||||
sessionRef.current = next;
|
||||
setSessionState(next);
|
||||
@@ -1135,8 +1185,11 @@ export default function App() {
|
||||
settingsAccountRoute={SETTINGS_ACCOUNT_ROUTE}
|
||||
importRoute={IMPORT_ROUTE}
|
||||
isImportRoute={isImportRoute}
|
||||
darkMode={resolvedTheme === 'dark'}
|
||||
themeToggleTitle={resolvedTheme === 'dark' ? t('txt_switch_to_light_mode') : t('txt_switch_to_dark_mode')}
|
||||
onLock={handleLock}
|
||||
onLogout={handleLogout}
|
||||
onToggleTheme={handleToggleTheme}
|
||||
mainRoutesProps={mainRoutesProps}
|
||||
/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user