import { useState, useEffect, useMemo } from 'react'; import { Box, Typography, Paper, Switch, Button, CircularProgress, Stack, IconButton, Tabs, Tab, alpha, Divider, } from '@mui/material'; import SettingsIcon from '@mui/icons-material/Settings'; import RefreshIcon from '@mui/icons-material/Refresh'; import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; import DragIndicatorIcon from '@mui/icons-material/DragIndicator'; import type { PageType, StorageSchema } from '@/types/storage'; import { storageUtil } from '@/utils/chromeStorage'; import { getFeatureByKey, getDefaultPageOrder, getDefaultVisibleFeatureKeys, } from '@/config/features'; import GlobalSnackbar, { useSnackbarState } from '@/components/GlobalSnackbar'; import ErrorBoundary from '@/components/ErrorBoundary'; import PageHeader from '@/components/PageHeader'; import { THEME_COLORS } from '@/config/pageTheme'; type WindowType = 'popup' | 'sidepanel' | 'tab'; /** * Options 设置页面主组件 * 支持对不同窗口入口的功能显示和排序进行独立配置 */ export default function App() { // 从 URL 参数中初始化当前的 Tab 类型 const initialWindowType = useMemo(() => { if (typeof window === 'undefined') return 'popup'; const params = new URLSearchParams(window.location.search); const tab = params.get('tab'); if (tab === 'popup' || tab === 'sidepanel' || tab === 'tab') { return tab as WindowType; } return 'popup'; }, []); const [windowType, setWindowType] = useState(initialWindowType); const [visiblePages, setVisiblePages] = useState([]); const [pageOrder, setPageOrder] = useState([]); const [isLoaded, setIsLoaded] = useState(false); const { snackbarProps, showMessage } = useSnackbarState(); // 根据当前选择的窗口类型确定对应的 Storage Key const configKeys = useMemo(() => { switch (windowType) { case 'sidepanel': return { visible: 'app/sidepanelVisiblePages' as keyof StorageSchema, order: 'app/sidepanelPageOrder' as keyof StorageSchema, }; case 'tab': return { visible: 'app/tabVisiblePages' as keyof StorageSchema, order: 'app/tabPageOrder' as keyof StorageSchema, }; case 'popup': default: return { visible: 'app/popupVisiblePages' as keyof StorageSchema, order: 'app/popupPageOrder' as keyof StorageSchema, }; } }, [windowType]); // 当 windowType 改变时,同步更新 URL 参数 useEffect(() => { const url = new URL(window.location.href); url.searchParams.set('tab', windowType); window.history.replaceState({}, '', url.toString()); }, [windowType]); // 加载配置数据 useEffect(() => { const loadConfig = async () => { setIsLoaded(false); try { const [savedVisible, savedOrder] = await Promise.all([ storageUtil.get(configKeys.visible, getDefaultVisibleFeatureKeys()), storageUtil.get(configKeys.order, getDefaultPageOrder()), ]); setVisiblePages((savedVisible as PageType[]) ?? getDefaultVisibleFeatureKeys()); setPageOrder((savedOrder as PageType[]) ?? getDefaultPageOrder()); } catch (error) { console.error('Failed to load config:', error); setVisiblePages(getDefaultVisibleFeatureKeys()); setPageOrder(getDefaultPageOrder()); } finally { setIsLoaded(true); } }; loadConfig().catch(console.error); }, [configKeys]); /** * 切换页面可见性 */ const handlePageToggle = async (page: PageType) => { const isCurrentlyVisible = visiblePages.includes(page); let newPages: PageType[]; if (isCurrentlyVisible) { if (visiblePages.length <= 1) { showToast('至少需要保留一个可见页面', 'warning'); return; } newPages = visiblePages.filter((p) => p !== page); } else { newPages = [...visiblePages, page]; } try { await storageUtil.set(configKeys.visible, newPages); setVisiblePages(newPages); const feature = getFeatureByKey(page); showToast(`已${isCurrentlyVisible ? '隐藏' : '显示'} ${feature?.label || page}`, 'success'); } catch (error) { console.error('Failed to save config:', error); showToast('保存失败', 'warning'); } }; /** * 调整页面显示顺序 */ const handleMove = async (index: number, direction: 'up' | 'down') => { if (direction === 'up' && index === 0) return; if (direction === 'down' && index === pageOrder.length - 1) return; const newOrder = [...pageOrder]; const swapIndex = direction === 'up' ? index - 1 : index + 1; [newOrder[index], newOrder[swapIndex]] = [newOrder[swapIndex], newOrder[index]]; try { await storageUtil.set(configKeys.order, newOrder); setPageOrder(newOrder); } catch (error) { console.error('Failed to save order:', error); showToast('排序保存失败', 'warning'); } }; /** * 恢复默认设置 */ const handleRestoreDefaults = async () => { try { const defaults = getDefaultVisibleFeatureKeys(); const defaultOrder = getDefaultPageOrder(); await Promise.all([ storageUtil.set(configKeys.visible, defaults), storageUtil.set(configKeys.order, defaultOrder), ]); setVisiblePages(defaults); setPageOrder(defaultOrder); showToast('已恢复当前模式默认设置', 'success'); } catch (error) { console.error('Failed to restore defaults:', error); showToast('恢复失败', 'warning'); } }; const handleWindowTypeChange = (_event: React.SyntheticEvent, newType: WindowType) => { if (newType !== null) { setWindowType(newType); } }; const showToast = (message: string, severity: 'success' | 'info' | 'warning') => { showMessage(message, { severity }); }; return ( {/* 顶部标题与导航栏 */} } iconColor={THEME_COLORS.primary} title="应用设置" subtitle="针对不同窗口类型独立配置 Dashboard 中显示的功能及其排序" sx={{ mb: 4 }} /> {/* 主内容区域 */} {!isLoaded ? ( ) : ( {pageOrder.map((key, index, array) => { const feature = getFeatureByKey(key); if (!feature) return null; const isChecked = visiblePages.includes(key); const isDisabled = isChecked && visiblePages.length === 1; return ( {/* 拖拽/排序暗示图标 */} {/* 功能图标容器 */} {feature.icon} {/* 文本信息 */} {feature.label} {feature.description || '暂无描述'} {/* 移动操作按钮 */} handleMove(index, 'up')} disabled={index === 0} sx={{ color: 'grey.400', p: { xs: 0.5, sm: 1 }, '&:hover': { color: 'primary.main', bgcolor: alpha(THEME_COLORS.primary, 0.08), }, }} > handleMove(index, 'down')} disabled={index === array.length - 1} sx={{ color: 'grey.400', p: { xs: 0.5, sm: 1 }, '&:hover': { color: 'primary.main', bgcolor: alpha(THEME_COLORS.primary, 0.08), }, }} > {/* 显示切换开关 */} handlePageToggle(key)} disabled={isDisabled} sx={{ '& .MuiSwitch-switchBase.Mui-checked': { color: feature.themeColor || THEME_COLORS.primary, }, '& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': { backgroundColor: feature.themeColor || THEME_COLORS.primary, }, }} /> ); })} )} ); }