🐛 fix(sidebar): 支持调节V2左侧导航按钮大小

This commit is contained in:
Syngnat
2026-06-29 21:25:13 +08:00
parent d900bda756
commit b9ffcd2959
11 changed files with 152 additions and 60 deletions

View File

@@ -10,6 +10,10 @@ const appCss = readFileSync(
fileURLToPath(new globalThis.URL('./App.css', import.meta.url)),
'utf8',
);
const v2ThemeCss = readFileSync(
fileURLToPath(new globalThis.URL('./v2-theme.css', import.meta.url)),
'utf8',
);
const linuxCJKFontBannerSource = readFileSync(
fileURLToPath(new globalThis.URL('./components/LinuxCJKFontBanner.tsx', import.meta.url)),
'utf8',
@@ -321,7 +325,7 @@ describe('tool center menu entries', () => {
['newConnection', 'handleCreateConnection();'],
['toggleAIPanel', 'toggleAIPanel();'],
['toggleLogPanel', 'handleToggleLogPanel();'],
['toggleTheme', 'setTheme('],
['toggleTheme', 'setThemePreference('],
['openShortcutManager', 'setIsShortcutModalOpen(true);'],
['toggleMacFullscreen', 'handleTitleBarWindowToggle({ allowMacNativeFullscreen: true });'],
['resetWindowZoom', 'handleManualResetWindowZoom();'],
@@ -410,12 +414,20 @@ describe('global appearance tokens', () => {
expect(appSource).toContain("setProperty('--gn-font-size-mono'");
expect(appSource).toContain("setProperty('--gn-data-table-font-size'");
expect(appSource).toContain("setProperty('--gn-sidebar-tree-font-size'");
expect(appSource).toContain("setProperty('--gn-sidebar-rail-scale'");
expect(appSource).toContain("setProperty('--gn-control-height'");
expect(appSource).toContain("setProperty('--gn-control-height-sm'");
expect(appSource).toContain('fontFamily: resolvedUiFontFamily');
expect(appSource).toContain('fontFamilyCode: resolvedMonoFontFamily');
expect(appSource).toContain('const effectiveSidebarRailScale = sanitizeV2SidebarRailScale(appearance.v2SidebarRailScale);');
expect(appSource).toContain("t('app.theme.appearance.sidebar_rail_scale_title')");
expect(appSource).toContain("t('app.theme.appearance.sidebar_rail_scale_hint')");
expect(appSource).toContain('v2SidebarRailScale: sanitizeV2SidebarRailScale(value)');
expect(appSource).toContain("t('app.theme.data_table.font_size')");
expect(appSource).toContain("t('app.theme.data_table.sidebar_tree_font_size')");
expect(v2ThemeCss).toContain('--gn-sidebar-rail-scale');
expect(v2ThemeCss).toContain('font-size: calc(var(--gn-font-size-sm, 12px) * var(--gn-sidebar-rail-scale, 1));');
expect(v2ThemeCss).toContain('width: calc(38px * var(--gn-v2-rail-scale));');
expect(appSource).toContain("const tableDoubleClickAction = appearance.tableDoubleClickAction === 'open-design' ? 'open-design' : 'open-data';");
expect(appSource).toContain("t('app.theme.data_table.table_double_click_action')");
expect(appSource).toContain("t('app.theme.data_table.table_double_click_action.open_data')");

View File

@@ -21,7 +21,13 @@ import SecurityUpdateIntroModal from './components/SecurityUpdateIntroModal';
import SecurityUpdateProgressModal from './components/SecurityUpdateProgressModal';
import SecurityUpdateSettingsModal from './components/SecurityUpdateSettingsModal';
import LanguageSettingsPanel from './components/LanguageSettingsPanel';
import { DEFAULT_APPEARANCE, useStore } from './store';
import {
DEFAULT_APPEARANCE,
MAX_V2_SIDEBAR_RAIL_SCALE,
MIN_V2_SIDEBAR_RAIL_SCALE,
sanitizeV2SidebarRailScale,
useStore,
} from './store';
import { SavedConnection, SecurityUpdateIssue, SecurityUpdateStatus } from './types';
import { blurToFilter, normalizeBlurForPlatform, normalizeOpacityForPlatform, isWindowsPlatform, resolveAppearanceValues } from './utils/appearance';
import { buildFontFamilyOptions, DEFAULT_MONO_FONT_FAMILY, DEFAULT_UI_FONT_FAMILY, getLinuxCJKFontInstallHint, matchFontFamilyOption, resolveMonoFontFamily, resolveUIFontFamily, sanitizeFontFamilyInput, type FontFamilyOption, type InstalledFontFamily } from './utils/fontFamilies';
@@ -271,6 +277,7 @@ function App() {
const effectiveSidebarTreeFontSize = sidebarTreeFontSizeFollowsGlobal
? effectiveFontSize
: (sanitizeSidebarTreeFontSize(appearance.sidebarTreeFontSize) ?? effectiveFontSize);
const effectiveSidebarRailScale = sanitizeV2SidebarRailScale(appearance.v2SidebarRailScale);
const tableDoubleClickAction = appearance.tableDoubleClickAction === 'open-design' ? 'open-design' : 'open-data';
const tabDisplaySettings = useMemo(
() => sanitizeTabDisplaySettings(appearance.tabDisplay),
@@ -2727,6 +2734,7 @@ function App() {
document.documentElement.style.setProperty('--gn-font-size-mono', `${Math.max(10, Math.round(effectiveDataTableFontSize * 0.92))}px`);
document.documentElement.style.setProperty('--gn-data-table-font-size', `${effectiveDataTableFontSize}px`);
document.documentElement.style.setProperty('--gn-sidebar-tree-font-size', `${effectiveSidebarTreeFontSize}px`);
document.documentElement.style.setProperty('--gn-sidebar-rail-scale', `${effectiveSidebarRailScale}`);
document.documentElement.style.setProperty('--gn-control-height', `${tokenControlHeight}px`);
document.documentElement.style.setProperty('--gn-control-height-sm', `${tokenControlHeightSM}px`);
}, [
@@ -2737,6 +2745,7 @@ function App() {
resolvedMonoFontFamily,
resolvedUiFontFamily,
runtimePlatform,
effectiveSidebarRailScale,
effectiveSidebarTreeFontSize,
effectiveUiScale,
tokenControlHeight,
@@ -4693,6 +4702,27 @@ function App() {
<span style={{ width: 56 }}>{effectiveFontSize}px</span>
</div>
</div>
{appearance.uiVersion === 'v2' && (
<div style={utilityPanelStyle}>
<div style={{ marginBottom: 8, fontWeight: 500 }}>{t('app.theme.appearance.sidebar_rail_scale_title')}</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
<Slider
min={MIN_V2_SIDEBAR_RAIL_SCALE}
max={MAX_V2_SIDEBAR_RAIL_SCALE}
step={0.05}
value={effectiveSidebarRailScale}
onChange={(value) => setAppearance({
v2SidebarRailScale: sanitizeV2SidebarRailScale(value),
})}
style={{ flex: 1 }}
/>
<span style={{ width: 56 }}>{Math.round(effectiveSidebarRailScale * 100)}%</span>
</div>
<div style={{ ...utilityMutedTextStyle, marginTop: 4 }}>
{t('app.theme.appearance.sidebar_rail_scale_hint')}
</div>
</div>
)}
<div style={utilityPanelStyle}>
<div style={{ marginBottom: 10, fontWeight: 500 }}>{t('app.theme.font_family.title')}</div>
<div style={{ display: 'grid', gap: 14 }}>

View File

@@ -73,6 +73,7 @@ describe('store appearance persistence', () => {
expect(appearance.v2SidebarSearchMode).toBe('command');
expect(appearance.v2CommandSearchPersistentFilterEnabled).toBe(false);
expect(appearance.v2SidebarPersistedFilter).toBe('');
expect(appearance.v2SidebarRailScale).toBe(1);
expect(appearance.showDataTableVerticalBorders).toBe(false);
expect(appearance.dataTableDensity).toBe('comfortable');
expect(appearance.dataTableFontSize).toBeNull();
@@ -95,12 +96,14 @@ describe('store appearance persistence', () => {
showDataTableVerticalBorders: true,
dataTableDensity: 'compact',
tableDoubleClickAction: 'open-design',
v2SidebarRailScale: 1.55,
});
const persisted = JSON.parse(storage.getItem('lite-db-storage') || '{}');
expect(persisted.state.appearance.showDataTableVerticalBorders).toBe(true);
expect(persisted.state.appearance.dataTableDensity).toBe('compact');
expect(persisted.state.appearance.tableDoubleClickAction).toBe('open-design');
expect(persisted.state.appearance.v2SidebarRailScale).toBe(1.55);
vi.resetModules();
const reloaded = await importStore();
@@ -109,6 +112,7 @@ describe('store appearance persistence', () => {
expect(appearance.showDataTableVerticalBorders).toBe(true);
expect(appearance.dataTableDensity).toBe('compact');
expect(appearance.tableDoubleClickAction).toBe('open-design');
expect(appearance.v2SidebarRailScale).toBe(1.55);
});
it('restores query tabs from crash-recovery snapshots even when persisted tabs are missing', async () => {
@@ -158,6 +162,20 @@ describe('store appearance persistence', () => {
expect(useStore.getState().appearance.tableDoubleClickAction).toBe('open-data');
});
it('sanitizes persisted v2 sidebar rail scale settings into the supported range', async () => {
storage.setItem('lite-db-storage', JSON.stringify({
state: {
appearance: {
v2SidebarRailScale: 99,
},
},
version: 13,
}));
const { useStore } = await importStore();
expect(useStore.getState().appearance.v2SidebarRailScale).toBe(1);
});
it('persists language preference and sanitizes unsupported persisted values', async () => {
const { useStore } = await importStore();

View File

@@ -102,12 +102,17 @@ export interface AppearanceSettings extends DataGridDisplaySettings {
v2SidebarSearchMode: "command" | "filter";
v2CommandSearchPersistentFilterEnabled: boolean;
v2SidebarPersistedFilter: string;
v2SidebarRailScale: number;
customUIFontFamily: string | null;
customMonoFontFamily: string | null;
tabDisplay: TabDisplaySettings;
redisDbAliases: RedisDbAliasMap;
}
export const DEFAULT_V2_SIDEBAR_RAIL_SCALE = 1.0;
export const MIN_V2_SIDEBAR_RAIL_SCALE = 1.0;
export const MAX_V2_SIDEBAR_RAIL_SCALE = 1.8;
export const DEFAULT_APPEARANCE: AppearanceSettings = {
uiVersion: "legacy",
enabled: true,
@@ -118,6 +123,7 @@ export const DEFAULT_APPEARANCE: AppearanceSettings = {
v2SidebarSearchMode: "command",
v2CommandSearchPersistentFilterEnabled: false,
v2SidebarPersistedFilter: "",
v2SidebarRailScale: DEFAULT_V2_SIDEBAR_RAIL_SCALE,
customUIFontFamily: null,
customMonoFontFamily: null,
tabDisplay: DEFAULT_TAB_DISPLAY_SETTINGS,
@@ -153,6 +159,16 @@ const sanitizeV2SidebarPersistedFilter = (value: unknown): string => {
}
return value.trim().slice(0, MAX_SIDEBAR_PERSISTED_FILTER_LENGTH);
};
export const sanitizeV2SidebarRailScale = (value: unknown): number => {
return normalizeFloatInRange(
value,
DEFAULT_V2_SIDEBAR_RAIL_SCALE,
MIN_V2_SIDEBAR_RAIL_SCALE,
MAX_V2_SIDEBAR_RAIL_SCALE,
);
};
const MAX_URI_LENGTH = 4096;
const MAX_HOST_ENTRY_LENGTH = 512;
const MAX_HOST_ENTRIES = 64;
@@ -2193,6 +2209,9 @@ const sanitizeAppearance = (
v2SidebarPersistedFilter: sanitizeV2SidebarPersistedFilter(
appearance.v2SidebarPersistedFilter,
),
v2SidebarRailScale: sanitizeV2SidebarRailScale(
appearance.v2SidebarRailScale,
),
customUIFontFamily: sanitizeFontFamilyInput(appearance.customUIFontFamily),
customMonoFontFamily: sanitizeFontFamilyInput(appearance.customMonoFontFamily),
tabDisplay: sanitizeTabDisplaySettings(appearance.tabDisplay),

View File

@@ -1762,6 +1762,7 @@ body[data-ui-version="v2"] .gn-v2-app-sider {
}
body[data-ui-version="v2"] .gn-v2-sidebar-redesign {
--gn-v2-rail-scale: calc(var(--gn-ui-scale, 1) * var(--gn-sidebar-rail-scale, 1));
width: 100%;
background: var(--gn-bg-panel-2);
border-right: 0.5px solid var(--gn-br-1);
@@ -1769,14 +1770,14 @@ body[data-ui-version="v2"] .gn-v2-sidebar-redesign {
}
body[data-ui-version="v2"] .gn-v2-connection-rail {
width: calc(38px * var(--gn-ui-scale, 1));
flex: 0 0 calc(38px * var(--gn-ui-scale, 1));
width: calc(38px * var(--gn-v2-rail-scale));
flex: 0 0 calc(38px * var(--gn-v2-rail-scale));
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
gap: calc(4px * var(--gn-ui-scale, 1));
padding: calc(8px * var(--gn-ui-scale, 1)) 0;
gap: calc(4px * var(--gn-v2-rail-scale));
padding: calc(8px * var(--gn-v2-rail-scale)) 0;
border-right: 0.5px solid var(--gn-br-1);
background: transparent;
}
@@ -1787,10 +1788,10 @@ body[data-ui-version="v2"] .gn-v2-rail-items {
display: flex;
flex-direction: column;
align-items: center;
gap: calc(4px * var(--gn-ui-scale, 1));
gap: calc(4px * var(--gn-v2-rail-scale));
width: 100%;
box-sizing: border-box;
padding-top: calc(4px * var(--gn-ui-scale, 1));
padding-top: calc(4px * var(--gn-v2-rail-scale));
overflow-x: hidden;
overflow-y: auto;
scrollbar-width: thin;
@@ -1811,26 +1812,26 @@ body[data-ui-version="v2"] .gn-v2-rail-group {
display: flex;
flex-direction: column;
align-items: center;
gap: calc(3px * var(--gn-ui-scale, 1));
gap: calc(3px * var(--gn-v2-rail-scale));
}
body[data-ui-version="v2"] .gn-v2-rail-group + .gn-v2-rail-group {
padding-top: calc(5px * var(--gn-ui-scale, 1));
padding-top: calc(5px * var(--gn-v2-rail-scale));
border-top: 0.5px solid var(--gn-br-1);
}
body[data-ui-version="v2"] .gn-v2-rail-group-header {
position: relative;
width: 100%;
min-height: calc(24px * var(--gn-ui-scale, 1));
min-height: calc(24px * var(--gn-v2-rail-scale));
overflow: visible;
border: none;
border-radius: 0;
display: flex;
align-items: center;
justify-content: flex-start;
gap: 4px;
padding: 0 4px 0 6px;
gap: calc(4px * var(--gn-v2-rail-scale));
padding: 0 calc(4px * var(--gn-v2-rail-scale)) 0 calc(6px * var(--gn-v2-rail-scale));
background: transparent;
color: var(--gn-fg-5);
box-shadow: none;
@@ -1845,7 +1846,7 @@ body[data-ui-version="v2"] .gn-v2-rail-group-chevron {
position: static;
display: inline-flex;
color: var(--gn-fg-5);
font-size: 9px;
font-size: calc(9px * var(--gn-v2-rail-scale));
line-height: 1;
transform: rotate(0deg);
transition: transform 120ms ease;
@@ -1870,18 +1871,18 @@ body[data-ui-version="v2"] .gn-v2-rail-group-count {
position: absolute;
top: -1px;
right: -1px;
min-width: 16px;
height: 16px;
min-width: calc(16px * var(--gn-v2-rail-scale));
height: calc(16px * var(--gn-v2-rail-scale));
border-radius: 999px;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0 4px;
padding: 0 calc(4px * var(--gn-v2-rail-scale));
border: 2px solid var(--gn-bg-panel-2);
background: var(--gn-accent);
color: #fff;
font-family: var(--gn-font-mono);
font-size: 9px;
font-size: calc(9px * var(--gn-v2-rail-scale));
font-weight: 800;
line-height: 1;
}
@@ -1891,7 +1892,7 @@ body[data-ui-version="v2"] .gn-v2-rail-group-items {
display: flex;
flex-direction: column;
align-items: center;
gap: calc(4px * var(--gn-ui-scale, 1));
gap: calc(4px * var(--gn-v2-rail-scale));
}
body[data-ui-version="v2"] .gn-v2-rail-primary-actions,
@@ -1900,16 +1901,16 @@ body[data-ui-version="v2"] .gn-v2-rail-secondary-actions {
display: flex;
flex-direction: column;
align-items: center;
gap: calc(6px * var(--gn-ui-scale, 1));
gap: calc(6px * var(--gn-v2-rail-scale));
}
body[data-ui-version="v2"] .gn-v2-rail-primary-actions {
padding-top: calc(4px * var(--gn-ui-scale, 1));
padding-top: calc(4px * var(--gn-v2-rail-scale));
}
body[data-ui-version="v2"] .gn-v2-rail-secondary-actions {
margin-top: auto;
padding-bottom: calc(4px * var(--gn-ui-scale, 1));
padding-bottom: calc(4px * var(--gn-v2-rail-scale));
}
body[data-ui-version="v2"] .gn-v2-rail-action-group,
@@ -1917,11 +1918,11 @@ body[data-ui-version="v2"] .gn-v2-rail-system-actions {
display: flex;
flex-direction: column;
align-items: center;
gap: calc(4px * var(--gn-ui-scale, 1));
gap: calc(4px * var(--gn-v2-rail-scale));
}
body[data-ui-version="v2"] .gn-v2-rail-action-group {
padding-bottom: calc(6px * var(--gn-ui-scale, 1));
padding-bottom: calc(6px * var(--gn-v2-rail-scale));
border-bottom: 0.5px solid var(--gn-br-1);
}
@@ -1942,13 +1943,13 @@ body[data-ui-version="v2"] .gn-v2-rail-action:disabled {
body[data-ui-version="v2"] .gn-v2-rail-item,
body[data-ui-version="v2"] .gn-v2-rail-tool {
position: relative;
width: calc(36px * var(--gn-ui-scale, 1));
height: calc(38px * var(--gn-ui-scale, 1));
border-radius: calc(9px * var(--gn-ui-scale, 1));
width: calc(36px * var(--gn-v2-rail-scale));
height: calc(38px * var(--gn-v2-rail-scale));
border-radius: calc(9px * var(--gn-v2-rail-scale));
border: none;
background: transparent;
color: var(--gn-fg-4);
font-size: var(--gn-font-size-sm, 12px);
font-size: calc(var(--gn-font-size-sm, 12px) * var(--gn-sidebar-rail-scale, 1));
display: flex;
align-items: center;
justify-content: center;
@@ -1957,9 +1958,9 @@ body[data-ui-version="v2"] .gn-v2-rail-tool {
}
body[data-ui-version="v2"] .gn-v2-rail-tool {
width: calc(24px * var(--gn-ui-scale, 1));
height: calc(32px * var(--gn-ui-scale, 1));
border-radius: calc(8px * var(--gn-ui-scale, 1));
width: calc(24px * var(--gn-v2-rail-scale));
height: calc(32px * var(--gn-v2-rail-scale));
border-radius: calc(8px * var(--gn-v2-rail-scale));
justify-content: center;
padding: 0;
}
@@ -1977,11 +1978,11 @@ body[data-ui-version="v2"] .gn-v2-rail-item.is-active {
body[data-ui-version="v2"] .gn-v2-rail-active-bar {
display: none;
position: absolute;
left: -8px;
top: 7px;
bottom: 7px;
width: 3px;
border-radius: 3px;
left: calc(-8px * var(--gn-v2-rail-scale));
top: calc(7px * var(--gn-v2-rail-scale));
bottom: calc(7px * var(--gn-v2-rail-scale));
width: calc(3px * var(--gn-v2-rail-scale));
border-radius: calc(3px * var(--gn-v2-rail-scale));
background: var(--gn-accent);
}
@@ -1991,22 +1992,22 @@ body[data-ui-version="v2"] .gn-v2-rail-item.is-active .gn-v2-rail-active-bar {
body[data-ui-version="v2"] .gn-v2-rail-icon {
display: flex;
width: 24px;
height: 24px;
width: calc(24px * var(--gn-v2-rail-scale));
height: calc(24px * var(--gn-v2-rail-scale));
align-items: center;
justify-content: center;
}
body[data-ui-version="v2"] .gn-v2-rail-badge {
width: 22px;
height: 22px;
border-radius: 6px;
width: calc(22px * var(--gn-v2-rail-scale));
height: calc(22px * var(--gn-v2-rail-scale));
border-radius: calc(6px * var(--gn-v2-rail-scale));
display: inline-flex;
align-items: center;
justify-content: center;
color: #fff;
font-family: var(--gn-font-mono);
font-size: 9.5px;
font-size: calc(9.5px * var(--gn-sidebar-rail-scale, 1));
font-weight: 800;
line-height: 1;
box-shadow: inset 0 0.5px 0 rgba(255,255,255,0.3), 0 0.5px 1px rgba(0,0,0,0.2);
@@ -2015,9 +2016,9 @@ body[data-ui-version="v2"] .gn-v2-rail-badge {
body[data-ui-version="v2"] .gn-v2-rail-badge-wrap {
position: relative;
width: 22px;
height: 22px;
flex: 0 0 22px;
width: calc(22px * var(--gn-v2-rail-scale));
height: calc(22px * var(--gn-v2-rail-scale));
flex: 0 0 calc(22px * var(--gn-v2-rail-scale));
}
body[data-ui-version="v2"] .gn-v2-rail-fallback {
@@ -2026,12 +2027,12 @@ body[data-ui-version="v2"] .gn-v2-rail-fallback {
body[data-ui-version="v2"] .gn-v2-rail-status {
position: absolute;
right: -2px;
bottom: -2px;
width: 8px;
height: 8px;
right: calc(-2px * var(--gn-v2-rail-scale));
bottom: calc(-2px * var(--gn-v2-rail-scale));
width: calc(8px * var(--gn-v2-rail-scale));
height: calc(8px * var(--gn-v2-rail-scale));
border-radius: 50%;
border: 1.5px solid var(--gn-bg-panel);
border: calc(1.5px * var(--gn-sidebar-rail-scale, 1)) solid var(--gn-bg-panel);
background: var(--gn-fg-5);
}
@@ -2044,16 +2045,16 @@ body[data-ui-version="v2"] .gn-v2-rail-status.is-error {
}
body[data-ui-version="v2"] .gn-v2-rail-create-button {
margin-top: 4px;
width: calc(40px * var(--gn-ui-scale, 1));
height: calc(40px * var(--gn-ui-scale, 1));
margin-top: calc(4px * var(--gn-v2-rail-scale));
width: calc(40px * var(--gn-v2-rail-scale));
height: calc(40px * var(--gn-v2-rail-scale));
border: 1px dashed var(--gn-br-2);
border-radius: calc(14px * var(--gn-ui-scale, 1));
border-radius: calc(14px * var(--gn-v2-rail-scale));
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 6px;
gap: calc(6px * var(--gn-v2-rail-scale));
padding: 0;
color: var(--gn-fg-5);
background: transparent;
@@ -2064,9 +2065,9 @@ body[data-ui-version="v2"] .gn-v2-rail-create-button:hover {
}
body[data-ui-version="v2"] .gn-v2-rail-create-icon {
width: 24px;
height: 24px;
border-radius: 8px;
width: calc(24px * var(--gn-v2-rail-scale));
height: calc(24px * var(--gn-v2-rail-scale));
border-radius: calc(8px * var(--gn-v2-rail-scale));
display: inline-flex;
align-items: center;
justify-content: center;
@@ -2077,13 +2078,13 @@ body[data-ui-version="v2"] .gn-v2-rail-create-icon {
body[data-ui-version="v2"] .gn-v2-rail-create-label {
color: var(--gn-fg-5);
font-size: 10px;
font-size: calc(10px * var(--gn-sidebar-rail-scale, 1));
line-height: 1;
}
body[data-ui-version="v2"] .gn-v2-object-explorer {
min-width: 0;
flex: 1 1 calc(100% - 38px);
flex: 1 1 calc(100% - calc(38px * var(--gn-v2-rail-scale)));
background: var(--gn-bg-panel-2);
overflow: hidden;
}