feat: enhance StorageCleanerPage with accordion for storage options and immediate preference saving (#4)
This commit is contained in:
@@ -8,9 +8,14 @@ import {
|
|||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
Alert,
|
Alert,
|
||||||
Snackbar,
|
Snackbar,
|
||||||
|
Accordion,
|
||||||
|
AccordionSummary,
|
||||||
|
AccordionDetails,
|
||||||
|
Divider,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||||
import WarningIcon from '@mui/icons-material/Warning';
|
import WarningIcon from '@mui/icons-material/Warning';
|
||||||
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||||
import { storageUtil } from '@/utils/chromeStorage';
|
import { storageUtil } from '@/utils/chromeStorage';
|
||||||
import type { StorageCleanerOptions, CleaningResult, StorageCleanerPreferences } from 'types/storage';
|
import type { StorageCleanerOptions, CleaningResult, StorageCleanerPreferences } from 'types/storage';
|
||||||
import {
|
import {
|
||||||
@@ -76,8 +81,57 @@ export default function StorageCleanerPage() {
|
|||||||
loadInfo();
|
loadInfo();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleOptionChange = useCallback((key: keyof StorageCleanerOptions) => {
|
const handleAutoRefreshChange = useCallback(async (checked: boolean) => {
|
||||||
setOptions((prev) => ({ ...prev, [key]: !prev[key] }));
|
setAutoRefresh(checked);
|
||||||
|
// Save preference immediately
|
||||||
|
const prefs = await storageUtil.get(
|
||||||
|
'storageCleaner/preferences',
|
||||||
|
DEFAULT_PREFERENCES,
|
||||||
|
);
|
||||||
|
await storageUtil.set('storageCleaner/preferences', {
|
||||||
|
...(prefs || DEFAULT_PREFERENCES),
|
||||||
|
autoRefresh: checked,
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleOptionChange = useCallback(async (key: keyof StorageCleanerOptions) => {
|
||||||
|
setOptions((prev) => {
|
||||||
|
const newOptions = { ...prev, [key]: !prev[key] };
|
||||||
|
// Save options immediately
|
||||||
|
storageUtil.get('storageCleaner/preferences', DEFAULT_PREFERENCES)
|
||||||
|
.then(prefs => {
|
||||||
|
storageUtil.set('storageCleaner/preferences', {
|
||||||
|
...(prefs || DEFAULT_PREFERENCES),
|
||||||
|
selectedTypes: newOptions,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return newOptions;
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const allSelected = Object.values(options).every(Boolean);
|
||||||
|
const someSelected = Object.values(options).some(Boolean) && !allSelected;
|
||||||
|
|
||||||
|
const handleSelectAll = useCallback(async (checked: boolean) => {
|
||||||
|
const newOptions = {
|
||||||
|
localStorage: checked,
|
||||||
|
sessionStorage: checked,
|
||||||
|
indexedDB: checked,
|
||||||
|
cookies: checked,
|
||||||
|
cacheStorage: checked,
|
||||||
|
serviceWorkers: checked,
|
||||||
|
};
|
||||||
|
setOptions(newOptions);
|
||||||
|
|
||||||
|
// Save options immediately
|
||||||
|
const prefs = await storageUtil.get(
|
||||||
|
'storageCleaner/preferences',
|
||||||
|
DEFAULT_PREFERENCES,
|
||||||
|
);
|
||||||
|
await storageUtil.set('storageCleaner/preferences', {
|
||||||
|
...(prefs || DEFAULT_PREFERENCES),
|
||||||
|
selectedTypes: newOptions,
|
||||||
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleClean = useCallback(async () => {
|
const handleClean = useCallback(async () => {
|
||||||
@@ -148,67 +202,111 @@ export default function StorageCleanerPage() {
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Storage Type Options */}
|
{/* Storage Type Options */}
|
||||||
<Box sx={{ mb: 2 }}>
|
<Accordion
|
||||||
<Typography variant="subtitle1" sx={{ mb: 1 }}>
|
disableGutters
|
||||||
选择要清理的存储类型:
|
elevation={0}
|
||||||
|
sx={{
|
||||||
|
bgcolor: 'grey.50',
|
||||||
|
borderRadius: 2,
|
||||||
|
mb: 2,
|
||||||
|
'&:before': { display: 'none' },
|
||||||
|
'&.Mui-expanded': { m: 0, mb: 2 },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AccordionSummary
|
||||||
|
expandIcon={<ExpandMoreIcon sx={{ fontSize: '1.1rem' }} />}
|
||||||
|
sx={{
|
||||||
|
px: 2,
|
||||||
|
minHeight: 48,
|
||||||
|
'&.Mui-expanded': { minHeight: 48 },
|
||||||
|
'& .MuiAccordionSummary-content': { my: 1, '&.Mui-expanded': { my: 1 } },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="subtitle2" sx={{ fontWeight: 600, color: 'text.primary' }}>
|
||||||
|
清理选项 {allSelected ? '(全部)' : someSelected ? '(部分)' : '(未选)'}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails sx={{ px: 2, pt: 0, pb: 1.5 }}>
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={
|
control={
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
size="small"
|
||||||
checked={options.localStorage}
|
checked={options.localStorage}
|
||||||
onChange={() => handleOptionChange('localStorage')}
|
onChange={() => handleOptionChange('localStorage')}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label="localStorage"
|
label={<Typography variant="body2">localStorage</Typography>}
|
||||||
/>
|
/>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={
|
control={
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
size="small"
|
||||||
checked={options.sessionStorage}
|
checked={options.sessionStorage}
|
||||||
onChange={() => handleOptionChange('sessionStorage')}
|
onChange={() => handleOptionChange('sessionStorage')}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label="sessionStorage"
|
label={<Typography variant="body2">sessionStorage</Typography>}
|
||||||
/>
|
/>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={
|
control={
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
size="small"
|
||||||
checked={options.indexedDB}
|
checked={options.indexedDB}
|
||||||
onChange={() => handleOptionChange('indexedDB')}
|
onChange={() => handleOptionChange('indexedDB')}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label="IndexedDB"
|
label={<Typography variant="body2">IndexedDB</Typography>}
|
||||||
/>
|
/>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={
|
control={
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
size="small"
|
||||||
checked={options.cookies}
|
checked={options.cookies}
|
||||||
onChange={() => handleOptionChange('cookies')}
|
onChange={() => handleOptionChange('cookies')}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label="Cookies"
|
label={<Typography variant="body2">Cookies</Typography>}
|
||||||
/>
|
/>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={
|
control={
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
size="small"
|
||||||
checked={options.cacheStorage}
|
checked={options.cacheStorage}
|
||||||
onChange={() => handleOptionChange('cacheStorage')}
|
onChange={() => handleOptionChange('cacheStorage')}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label="Cache Storage"
|
label={<Typography variant="body2">Cache Storage</Typography>}
|
||||||
/>
|
/>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={
|
control={
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
size="small"
|
||||||
checked={options.serviceWorkers}
|
checked={options.serviceWorkers}
|
||||||
onChange={() => handleOptionChange('serviceWorkers')}
|
onChange={() => handleOptionChange('serviceWorkers')}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label="Service Workers"
|
label={<Typography variant="body2">Service Workers</Typography>}
|
||||||
|
/>
|
||||||
|
<Divider sx={{ my: 1, opacity: 0.6 }} />
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
size="small"
|
||||||
|
checked={allSelected}
|
||||||
|
indeterminate={someSelected}
|
||||||
|
onChange={(e) => handleSelectAll(e.target.checked)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
||||||
|
全选
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
{/* Auto Refresh Option */}
|
{/* Auto Refresh Option */}
|
||||||
<Box sx={{ mb: 2 }}>
|
<Box sx={{ mb: 2 }}>
|
||||||
@@ -216,7 +314,7 @@ export default function StorageCleanerPage() {
|
|||||||
control={
|
control={
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={autoRefresh}
|
checked={autoRefresh}
|
||||||
onChange={(e) => setAutoRefresh(e.target.checked)}
|
onChange={(e) => handleAutoRefreshChange(e.target.checked)}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label="清理完成后自动刷新页面"
|
label="清理完成后自动刷新页面"
|
||||||
@@ -228,6 +326,12 @@ export default function StorageCleanerPage() {
|
|||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={() => setShowConfirm(true)}
|
onClick={() => setShowConfirm(true)}
|
||||||
|
sx={{
|
||||||
|
py: 1.6, borderRadius: 3, fontSize: '1rem', fontWeight: 600, textTransform: 'none',
|
||||||
|
bgcolor: 'primary.main', transition: 'all 0.2s',
|
||||||
|
'&:hover': { bgcolor: 'primary.dark', transform: 'translateY(-1px)' },
|
||||||
|
'&:active': { transform: 'translateY(0)' }
|
||||||
|
}}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user