mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-21 12:51:47 +08:00
- 整体布局:按新版 UI 重构左侧导航、对象树、连接分组和右键菜单体系 - 数据视图:优化 DDL 侧栏、横向滚动、筛选输入、编辑入口和虚拟表格体验 - AI 面板:重构新版入口、输入区、模型选择、快捷键和悬浮布局 - 标签与快捷键:补齐 Tab 悬浮信息、复制交互和 Mac/Windows 快捷键配置 - 工程质量:新增 v2 主题样式、菜单组件、外观工具和回归测试覆盖
215 lines
9.4 KiB
TypeScript
215 lines
9.4 KiB
TypeScript
import { resolveTextInputSafeBackdropFilter } from '../utils/appearance';
|
|
|
|
/** v2 = body[data-ui-version="v2"],由 App.tsx 切换。 */
|
|
const isV2 = (): boolean => {
|
|
if (typeof document === 'undefined' || !document.body) return false;
|
|
return document.body.getAttribute('data-ui-version') === 'v2';
|
|
};
|
|
|
|
type RedisWorkbenchThemeInput = {
|
|
darkMode: boolean;
|
|
opacity: number;
|
|
blur: number;
|
|
disableBackdropFilter?: boolean;
|
|
};
|
|
|
|
type RedisWorkbenchTheme = {
|
|
isDark: boolean;
|
|
appBg: string;
|
|
panelBg: string;
|
|
panelBgStrong: string;
|
|
panelBgSubtle: string;
|
|
panelBorder: string;
|
|
panelInset: string;
|
|
toolbarPrimaryBg: string;
|
|
contentEmptyBg: string;
|
|
textPrimary: string;
|
|
textSecondary: string;
|
|
textMuted: string;
|
|
accent: string;
|
|
accentSoft: string;
|
|
accentBorder: string;
|
|
actionSecondaryBg: string;
|
|
actionSecondaryBorder: string;
|
|
actionDangerBg: string;
|
|
actionDangerBorder: string;
|
|
actionDangerText: string;
|
|
statusTagBg: string;
|
|
statusTagBorder: string;
|
|
statusTagMutedBg: string;
|
|
statusTagMutedBorder: string;
|
|
treeHoverBg: string;
|
|
treeSelectedBg: string;
|
|
treeSelectedBorder: string;
|
|
divider: string;
|
|
shadow: string;
|
|
backdropFilter: string;
|
|
};
|
|
|
|
const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));
|
|
|
|
export const buildRedisWorkbenchTheme = ({
|
|
darkMode,
|
|
opacity,
|
|
blur,
|
|
disableBackdropFilter,
|
|
}: RedisWorkbenchThemeInput): RedisWorkbenchTheme => {
|
|
const normalizedOpacity = clamp(opacity, 0.1, 1);
|
|
const normalizedBlur = Math.max(0, Math.round(blur));
|
|
const isTranslucent = normalizedOpacity < 0.999 || normalizedBlur > 0;
|
|
const backdropFilter = resolveTextInputSafeBackdropFilter(
|
|
normalizedBlur > 0 ? `blur(${normalizedBlur}px)` : 'none',
|
|
disableBackdropFilter ?? false,
|
|
);
|
|
|
|
// ─── v2 palette: Redis-red accent on GoNavi neutral surfaces ──
|
|
if (isV2()) {
|
|
if (darkMode) {
|
|
return {
|
|
isDark: true,
|
|
appBg: '#0c0e12',
|
|
panelBg: '#161a21',
|
|
panelBgStrong: '#1b1f27',
|
|
panelBgSubtle: 'rgba(255,255,255,0.04)',
|
|
panelBorder: '0.5px solid rgba(255,255,255,0.10)',
|
|
panelInset: 'inset 0 0.5px 0 rgba(255,255,255,0.05)',
|
|
toolbarPrimaryBg: 'linear-gradient(135deg, rgba(248,113,113,0.18) 0%, rgba(248,113,113,0.08) 100%)',
|
|
contentEmptyBg: 'linear-gradient(180deg, rgba(255,255,255,0.02) 0%, rgba(255,255,255,0.01) 100%)',
|
|
textPrimary: '#f1f3f5',
|
|
textSecondary: '#d6dade',
|
|
textMuted: '#80868f',
|
|
accent: '#f87171',
|
|
accentSoft: 'rgba(248, 113, 113, 0.16)',
|
|
accentBorder: 'rgba(248, 113, 113, 0.32)',
|
|
actionSecondaryBg: 'rgba(255,255,255,0.05)',
|
|
actionSecondaryBorder: '0.5px solid rgba(255,255,255,0.10)',
|
|
actionDangerBg: 'rgba(220, 38, 38, 0.16)',
|
|
actionDangerBorder: '0.5px solid rgba(220, 38, 38, 0.32)',
|
|
actionDangerText: '#f87171',
|
|
statusTagBg: 'rgba(56, 189, 248, 0.16)',
|
|
statusTagBorder: '0.5px solid rgba(56, 189, 248, 0.30)',
|
|
statusTagMutedBg: 'rgba(255,255,255,0.06)',
|
|
statusTagMutedBorder: '0.5px solid rgba(255,255,255,0.10)',
|
|
treeHoverBg: 'rgba(255,255,255,0.05)',
|
|
treeSelectedBg: 'linear-gradient(90deg, rgba(248,113,113,0.18) 0%, rgba(248,113,113,0.06) 100%)',
|
|
treeSelectedBorder: 'rgba(248, 113, 113, 0.28)',
|
|
divider: 'rgba(255,255,255,0.06)',
|
|
shadow: '0 12px 40px rgba(0,0,0,0.55)',
|
|
backdropFilter,
|
|
};
|
|
}
|
|
return {
|
|
isDark: false,
|
|
appBg: '#f6f6f4',
|
|
panelBg: '#ffffff',
|
|
panelBgStrong: '#ffffff',
|
|
panelBgSubtle: '#fafaf8',
|
|
panelBorder: '0.5px solid rgba(15,23,42,0.12)',
|
|
panelInset: 'inset 0 0.5px 0 rgba(255,255,255,0.6)',
|
|
toolbarPrimaryBg: 'linear-gradient(135deg, rgba(220,38,38,0.10) 0%, rgba(220,38,38,0.04) 100%)',
|
|
contentEmptyBg: 'linear-gradient(180deg, rgba(15,23,42,0.02) 0%, rgba(15,23,42,0.01) 100%)',
|
|
textPrimary: '#0c1322',
|
|
textSecondary: '#1f2937',
|
|
textMuted: '#6b7280',
|
|
accent: '#dc2626',
|
|
accentSoft: 'rgba(220, 38, 38, 0.10)',
|
|
accentBorder: 'rgba(220, 38, 38, 0.24)',
|
|
actionSecondaryBg: '#ffffff',
|
|
actionSecondaryBorder: '0.5px solid rgba(15,23,42,0.12)',
|
|
actionDangerBg: 'rgba(220, 38, 38, 0.10)',
|
|
actionDangerBorder: '0.5px solid rgba(220, 38, 38, 0.24)',
|
|
actionDangerText: '#dc2626',
|
|
statusTagBg: 'rgba(2, 132, 199, 0.10)',
|
|
statusTagBorder: '0.5px solid rgba(2, 132, 199, 0.24)',
|
|
statusTagMutedBg: 'rgba(15,23,42,0.04)',
|
|
statusTagMutedBorder: '0.5px solid rgba(15,23,42,0.08)',
|
|
treeHoverBg: 'rgba(15,23,42,0.045)',
|
|
treeSelectedBg: 'linear-gradient(90deg, rgba(220,38,38,0.10) 0%, rgba(220,38,38,0.02) 100%)',
|
|
treeSelectedBorder: 'rgba(220, 38, 38, 0.22)',
|
|
divider: 'rgba(15,23,42,0.08)',
|
|
shadow: '0 12px 40px rgba(15,23,42,0.14)',
|
|
backdropFilter,
|
|
};
|
|
}
|
|
|
|
// ─── legacy palette (existing behavior) ──────────────────────
|
|
if (darkMode) {
|
|
const appTopAlpha = isTranslucent ? Math.max(0.08, Math.min(0.22, normalizedOpacity * 0.16)) : 0.92;
|
|
const appBottomAlpha = isTranslucent ? Math.max(0.12, Math.min(0.28, normalizedOpacity * 0.22)) : 0.96;
|
|
const panelAlpha = isTranslucent ? Math.max(0.06, Math.min(0.16, normalizedOpacity * 0.1)) : 0.34;
|
|
const strongAlpha = isTranslucent ? Math.max(0.1, Math.min(0.22, normalizedOpacity * 0.16)) : 0.42;
|
|
const subtleAlpha = isTranslucent ? Math.max(0.03, Math.min(0.08, normalizedOpacity * 0.05)) : 0.08;
|
|
return {
|
|
isDark: true,
|
|
appBg: `linear-gradient(180deg, rgba(15, 15, 17, ${appTopAlpha}) 0%, rgba(11, 11, 13, ${appBottomAlpha}) 100%)`,
|
|
panelBg: `rgba(24, 24, 28, ${panelAlpha})`,
|
|
panelBgStrong: `rgba(31, 31, 36, ${strongAlpha})`,
|
|
panelBgSubtle: `rgba(255, 255, 255, ${subtleAlpha})`,
|
|
panelBorder: `1px solid rgba(255, 255, 255, ${isTranslucent ? Math.max(0.12, Math.min(0.24, normalizedOpacity * 0.2)) : 0.08})`,
|
|
panelInset: `inset 0 1px 0 rgba(255,255,255,${isTranslucent ? Math.max(0.05, Math.min(0.12, normalizedOpacity * 0.1)) : 0.04})`,
|
|
toolbarPrimaryBg: `linear-gradient(135deg, rgba(246,196,83,0.22) 0%, rgba(246,196,83,0.12) 100%)`,
|
|
contentEmptyBg: `linear-gradient(180deg, rgba(255,255,255,0.03) 0%, rgba(255,255,255,0.015) 100%)`,
|
|
textPrimary: 'rgba(245, 247, 251, 0.96)',
|
|
textSecondary: 'rgba(218, 224, 235, 0.82)',
|
|
textMuted: 'rgba(168, 177, 194, 0.72)',
|
|
accent: '#f6c453',
|
|
accentSoft: 'rgba(246, 196, 83, 0.18)',
|
|
accentBorder: 'rgba(246, 196, 83, 0.3)',
|
|
actionSecondaryBg: 'rgba(255, 255, 255, 0.04)',
|
|
actionSecondaryBorder: 'rgba(255, 255, 255, 0.09)',
|
|
actionDangerBg: 'rgba(255, 95, 95, 0.12)',
|
|
actionDangerBorder: 'rgba(255, 95, 95, 0.28)',
|
|
actionDangerText: '#ff8f8f',
|
|
statusTagBg: 'rgba(25, 106, 255, 0.16)',
|
|
statusTagBorder: 'rgba(25, 106, 255, 0.28)',
|
|
statusTagMutedBg: 'rgba(255, 255, 255, 0.04)',
|
|
statusTagMutedBorder: 'rgba(255, 255, 255, 0.08)',
|
|
treeHoverBg: 'rgba(255, 255, 255, 0.045)',
|
|
treeSelectedBg: 'linear-gradient(90deg, rgba(246,196,83,0.2) 0%, rgba(246,196,83,0.08) 100%)',
|
|
treeSelectedBorder: 'rgba(246, 196, 83, 0.24)',
|
|
divider: 'rgba(255, 255, 255, 0.07)',
|
|
shadow: '0 20px 48px rgba(0, 0, 0, 0.26)',
|
|
backdropFilter,
|
|
};
|
|
}
|
|
|
|
const appTopAlpha = isTranslucent ? Math.max(0.16, Math.min(0.36, normalizedOpacity * 0.24)) : 0.98;
|
|
const appBottomAlpha = isTranslucent ? Math.max(0.22, Math.min(0.44, normalizedOpacity * 0.32)) : 0.96;
|
|
const panelAlpha = isTranslucent ? Math.max(0.18, Math.min(0.4, normalizedOpacity * 0.26)) : 0.94;
|
|
const strongAlpha = isTranslucent ? Math.max(0.26, Math.min(0.52, normalizedOpacity * 0.34)) : 0.98;
|
|
return {
|
|
isDark: false,
|
|
appBg: `linear-gradient(180deg, rgba(248, 250, 252, ${appTopAlpha}) 0%, rgba(242, 245, 248, ${appBottomAlpha}) 100%)`,
|
|
panelBg: `rgba(255, 255, 255, ${panelAlpha})`,
|
|
panelBgStrong: `rgba(255, 255, 255, ${strongAlpha})`,
|
|
panelBgSubtle: 'rgba(15, 23, 42, 0.03)',
|
|
panelBorder: `1px solid rgba(15, 23, 42, ${isTranslucent ? Math.max(0.1, Math.min(0.18, normalizedOpacity * 0.12)) : 0.08})`,
|
|
panelInset: `inset 0 1px 0 rgba(255,255,255,${isTranslucent ? 0.38 : 0.72})`,
|
|
toolbarPrimaryBg: 'linear-gradient(135deg, rgba(22,119,255,0.12) 0%, rgba(22,119,255,0.06) 100%)',
|
|
contentEmptyBg: 'linear-gradient(180deg, rgba(15,23,42,0.02) 0%, rgba(15,23,42,0.01) 100%)',
|
|
textPrimary: 'rgba(15, 23, 42, 0.92)',
|
|
textSecondary: 'rgba(51, 65, 85, 0.82)',
|
|
textMuted: 'rgba(100, 116, 139, 0.76)',
|
|
accent: '#1677ff',
|
|
accentSoft: 'rgba(22, 119, 255, 0.12)',
|
|
accentBorder: 'rgba(22, 119, 255, 0.22)',
|
|
actionSecondaryBg: 'rgba(255, 255, 255, 0.72)',
|
|
actionSecondaryBorder: 'rgba(15, 23, 42, 0.08)',
|
|
actionDangerBg: 'rgba(255, 77, 79, 0.08)',
|
|
actionDangerBorder: 'rgba(255, 77, 79, 0.24)',
|
|
actionDangerText: '#cf1322',
|
|
statusTagBg: 'rgba(22, 119, 255, 0.1)',
|
|
statusTagBorder: 'rgba(22, 119, 255, 0.16)',
|
|
statusTagMutedBg: 'rgba(15, 23, 42, 0.04)',
|
|
statusTagMutedBorder: 'rgba(15, 23, 42, 0.08)',
|
|
treeHoverBg: 'rgba(15, 23, 42, 0.035)',
|
|
treeSelectedBg: 'linear-gradient(90deg, rgba(22,119,255,0.12) 0%, rgba(22,119,255,0.05) 100%)',
|
|
treeSelectedBorder: 'rgba(22, 119, 255, 0.18)',
|
|
divider: 'rgba(15, 23, 42, 0.08)',
|
|
shadow: '0 22px 52px rgba(15, 23, 42, 0.08)',
|
|
backdropFilter,
|
|
};
|
|
};
|
|
|
|
export type { RedisWorkbenchTheme, RedisWorkbenchThemeInput };
|