Develop fastapi (#6)

* feat: add side panel with navigation and storage cleaner functionality

* feat: add OpenUrlPage and integrate into app navigation

* feat: 添加 GlobalSnackbar 组件并在多个页面中集成,替换原有 Snackbar 实现

* feat: fix OpenUrl sidebar issue with architecture refactor

- 修复原问题:不再直接替换侧边栏 URL,保持插件导航可见
- 采用配置页 + 查看页分离架构:OpenUrlPage (配置) + OpenUrlViewerPage (查看)
- 支持多个 URL 快捷方式管理(添加/删除)
- 每个 URL 提供两种打开方式:在侧边栏打开 / 在新标签页打开
- 侧边栏查看页使用 iframe 占满全部剩余空间
- 更新 TypeScript 类型定义
- 保留原有混合内容警告检查
- 数据持久化到 Chrome Storage

* refactor: centralized route management - consolidate routing config into single source

* feat: 更换logo

* refactor: code review fixes - security and race condition improvements

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Changes:

- wxt.config.ts: Remove unused `debugger` permission (no code uses it)
- OpenUrlPage.tsx: Fix unreachable showMessage after window.close()
- OpenUrlPage.tsx: Replace unnecessary div wrapper with Fragment to reduce DOM nesting
- OpenUrlViewerPage.tsx: Add URL validation to prevent XSS via javascript:/data: URLs
- OpenUrlViewerPage.tsx: Add sandbox attribute to iframe for security isolation
- OpenUrlViewerPage.tsx: Add error handling for invalid URLs
- StorageCleanerPage.tsx: Fix race condition in handleOptionChange preference saving
- StorageCleanerPage.tsx: Remove unnecessary storage reads when saving preferences (use state directly)
- StorageCleanerPage.tsx: Add timeout cleanup for setTimeout to follow React best practices

* feat: implement drill-down navigation with master-detail dashboard

* feat: enhance dashboard dynamism and refine options UI

* feat: overhaul storage cleaner UI with real-time size estimation and modern aesthetics

* feat: overhaul TimestampPage UI/UX and fix GlobalSnackbar positioning

* fix: decouple popup routing from storage sync and enhance OpenUrlPage UI

* fix: avoid closing sidepanel when opening URL preview from within sidepanel
This commit is contained in:
LingandRX
2026-04-16 08:54:23 +08:00
committed by GitHub
parent 2cd21973f3
commit 84ffd2a132
34 changed files with 11680 additions and 739 deletions
+178
View File
@@ -0,0 +1,178 @@
import { useState } from 'react';
import { Snackbar, Alert, type SxProps, type Theme, alpha } from '@mui/material';
export type SnackbarSeverity = 'success' | 'info' | 'warning' | 'error';
export interface GlobalSnackbarProps {
/** 消息内容 */
message: string;
/** 是否显示 */
open: boolean;
/** 关闭回调 */
onClose: () => void;
/** 消息级别:影响颜色 */
severity?: SnackbarSeverity;
/** 自动隐藏时间,毫秒,0 不自动关闭 */
autoHideDuration?: number;
/** 弹出位置 */
anchorOrigin?: {
vertical: 'top' | 'bottom';
horizontal: 'left' | 'center' | 'right';
};
/** 是否使用 Alert 包裹(false 则使用原生 Snackbar message */
showAlert?: boolean;
/** Alert 是否隐藏图标 */
hideIcon?: boolean;
/** 自定义样式,透传给 Snackbar */
sx?: SxProps<Theme>;
/** 自定义样式,透传给 Alert(仅当 showAlert=true 时生效) */
alertSx?: SxProps<Theme>;
}
export interface SnackbarOptions {
severity?: SnackbarSeverity;
autoHideDuration?: number;
hideIcon?: boolean;
showAlert?: boolean;
}
export interface UseSnackbarResult {
snackbarProps: GlobalSnackbarProps;
showMessage: (message: string, options?: SnackbarOptions) => void;
closeMessage: () => void;
}
const defaultProps: Required<
Pick<
GlobalSnackbarProps,
'severity' | 'autoHideDuration' | 'anchorOrigin' | 'showAlert' | 'hideIcon'
>
> = {
severity: 'info',
autoHideDuration: 2000,
anchorOrigin: { vertical: 'bottom', horizontal: 'center' },
showAlert: true,
hideIcon: false,
};
export function GlobalSnackbar({
message,
open,
onClose,
severity = defaultProps.severity,
autoHideDuration = defaultProps.autoHideDuration,
anchorOrigin = defaultProps.anchorOrigin,
showAlert = defaultProps.showAlert,
hideIcon = defaultProps.hideIcon,
sx,
alertSx,
}: GlobalSnackbarProps) {
// 共享的固定定位样式
const fixedSx: SxProps<Theme> = {
position: 'fixed',
bottom: '24px !important', // 固定在视口底部
left: '50% !important',
transform: 'translateX(-50%) !important',
zIndex: (theme) => theme.zIndex.tooltip + 100,
maxWidth: '90%',
width: 'max-content',
};
if (showAlert) {
return (
<Snackbar
open={open}
autoHideDuration={autoHideDuration}
onClose={onClose}
anchorOrigin={anchorOrigin}
disableWindowBlurListener
sx={[fixedSx, ...(Array.isArray(sx) ? sx : [sx])]}
>
<Alert
severity={severity}
variant="filled"
icon={hideIcon ? false : undefined}
sx={[
{
borderRadius: '50px',
px: 2.5,
py: 0.2,
minWidth: '140px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontWeight: 800,
fontSize: '0.75rem',
letterSpacing: '0.02em',
backgroundImage: 'none',
boxShadow: (theme: Theme) => `0 12px 32px ${alpha(theme.palette[severity].main, 0.35)}`,
'& .MuiAlert-icon': {
mr: 0.5,
fontSize: '1.1rem',
color: '#fff'
},
'& .MuiAlert-message': {
color: '#fff',
padding: '6px 0',
textAlign: 'center'
}
},
...(Array.isArray(alertSx) ? alertSx : [alertSx]),
]}
>
{message}
</Alert>
</Snackbar>
);
}
return (
<Snackbar
open={open}
autoHideDuration={autoHideDuration}
onClose={onClose}
anchorOrigin={anchorOrigin}
message={message}
sx={[fixedSx, ...(Array.isArray(sx) ? sx : [sx])]}
/>
);
}
export function useSnackbar(initialOptions?: SnackbarOptions): UseSnackbarResult {
const [open, setOpen] = useState(false);
const [message, setMessage] = useState('');
const [options, setOptions] = useState<SnackbarOptions>(initialOptions || {});
const showMessage = (newMessage: string, newOptions: SnackbarOptions = {}) => {
setMessage(newMessage);
setOptions({ ...initialOptions, ...newOptions });
setOpen(true);
};
const closeMessage = () => {
setOpen(false);
};
const handleClose = (_event?: React.SyntheticEvent | Event, reason?: string) => {
if (reason === 'clickaway') return;
closeMessage();
};
const snackbarProps: GlobalSnackbarProps = {
message,
open,
onClose: handleClose,
severity: options.severity,
autoHideDuration: options.autoHideDuration,
hideIcon: options.hideIcon,
};
return {
snackbarProps,
showMessage,
closeMessage,
};
}
export default GlobalSnackbar;