🐛 fix(ui): 修复旧版编辑器布局并迁移默认界面至 V2

- 修复旧版 SQL 编辑器在弹性布局中的遮挡问题

- 将既有用户的界面偏好一次性迁移至 V2 并保留后续手动选择

- 覆盖持久化迁移与旧版编辑器布局回归测试
This commit is contained in:
Syngnat
2026-07-10 22:00:05 +08:00
parent 1ac9103735
commit 9870959ca6
5 changed files with 78 additions and 7 deletions

View File

@@ -239,6 +239,20 @@ body[data-theme='light'] ::-webkit-scrollbar-thumb:hover {
background-clip: content-box;
}
.gn-query-monaco-stage {
position: relative;
display: flex;
flex-direction: column;
min-height: 0;
overflow: hidden;
}
.gn-query-monaco-shell {
flex: 1 1 auto;
min-height: 0;
min-width: 0;
}
.gn-query-monaco-stage .monaco-editor .suggest-details-container {
min-height: 260px;
}

View File

@@ -0,0 +1,13 @@
import { readFileSync } from 'node:fs';
import { describe, expect, it } from 'vitest';
const queryEditorSource = readFileSync(new URL('./QueryEditor.tsx', import.meta.url), 'utf8');
const appCss = readFileSync(new URL('../App.css', import.meta.url), 'utf8');
describe('QueryEditor legacy layout', () => {
it('keeps the legacy Monaco editor inside a flexing stage and shell', () => {
expect(queryEditorSource).toContain("className={isV2Ui ? 'gn-v2-query-monaco-shell gn-query-monaco-shell' : 'gn-query-monaco-shell'}");
expect(appCss).toMatch(/\.gn-query-monaco-stage\s*\{[\s\S]*?position:\s*relative;[\s\S]*?display:\s*flex;[\s\S]*?flex-direction:\s*column;[\s\S]*?min-height:\s*0;[\s\S]*?overflow:\s*hidden;/);
expect(appCss).toMatch(/\.gn-query-monaco-shell\s*\{[\s\S]*?flex:\s*1 1 auto;[\s\S]*?min-height:\s*0;[\s\S]*?min-width:\s*0;/);
});
});

View File

@@ -8022,8 +8022,8 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
>
<div
ref={editorShellRef}
className={isV2Ui ? 'gn-v2-query-monaco-shell' : undefined}
style={isV2Ui ? { flex: '1 1 auto', minHeight: 0 } : undefined}
className={isV2Ui ? 'gn-v2-query-monaco-shell gn-query-monaco-shell' : 'gn-query-monaco-shell'}
style={{ flex: '1 1 auto', minHeight: 0, minWidth: 0 }}
>
<Editor
height="100%"

View File

@@ -65,7 +65,7 @@ describe('store appearance persistence', () => {
const { useStore } = await importStore();
const appearance = useStore.getState().appearance;
expect(appearance.uiVersion).toBe('legacy');
expect(appearance.uiVersion).toBe('v2');
expect(appearance.enabled).toBe(false);
expect(appearance.opacity).toBe(0.75);
expect(appearance.blur).toBe(6);
@@ -92,6 +92,38 @@ describe('store appearance persistence', () => {
});
});
it('migrates an existing legacy UI selection to V2', async () => {
storage.setItem('lite-db-storage', JSON.stringify({
state: {
appearance: {
uiVersion: 'legacy',
},
},
version: 13,
}));
const { useStore } = await importStore();
expect(useStore.getState().appearance.uiVersion).toBe('v2');
const persisted = JSON.parse(storage.getItem('lite-db-storage') || '{}');
expect(persisted.version).toBe(14);
expect(persisted.state.appearance.uiVersion).toBe('v2');
});
it('keeps a legacy UI selection made after the V2 migration', async () => {
storage.setItem('lite-db-storage', JSON.stringify({
state: {
appearance: {
uiVersion: 'legacy',
},
},
version: 14,
}));
const { useStore } = await importStore();
expect(useStore.getState().appearance.uiVersion).toBe('legacy');
});
it('persists DataGrid appearance settings and restores them after reload', async () => {
const { useStore } = await importStore();

View File

@@ -137,7 +137,7 @@ 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",
uiVersion: "v2",
enabled: true,
opacity: 1.0,
blur: 0,
@@ -217,7 +217,8 @@ const MIN_KEEPALIVE_INTERVAL_MINUTES = 1;
const MAX_KEEPALIVE_INTERVAL_MINUTES = 1440;
const DEFAULT_DIAGNOSTIC_TIMEOUT_SECONDS = 15;
const MAX_DIAGNOSTIC_TIMEOUT_SECONDS = 300;
const PERSIST_VERSION = 13;
const PERSIST_VERSION = 14;
const UI_VERSION_V2_MIGRATION_VERSION = 14;
const PERSIST_STORAGE_KEY = "lite-db-storage";
const PERSIST_WRITE_DEBOUNCE_MS = 160;
const MAX_PERSISTED_QUERY_TABS = 20;
@@ -4505,7 +4506,12 @@ export const useStore = create<AppState>()(
) as Partial<AppState>;
captureLegacySavedQueriesSnapshot(state.savedQueries, state.connections);
const nextState: Partial<AppState> = { ...state };
nextState.connections = sanitizeConnections(state.connections);
// 缺失连接表示由启动阶段从后端加载,迁移时不能把它写成空数组,
// 否则会让持久化的侧栏根节点顺序提前丢失。
nextState.connections =
state.connections === undefined
? undefined
: sanitizeConnections(state.connections);
const safeTabs = sanitizeQueryTabs(state.tabs);
nextState.tabs = safeTabs;
nextState.activeTabId = sanitizeActiveTabId(state.activeTabId, safeTabs);
@@ -4535,7 +4541,13 @@ export const useStore = create<AppState>()(
nextState.languagePreference = sanitizeLanguagePreference(
state.languagePreference,
);
nextState.appearance = sanitizeAppearance(state.appearance, version);
const appearance = sanitizeAppearance(state.appearance, version);
// 旧版界面在窄布局下可能遮挡 SQL 编辑器。仅在本次版本升级时
// 将已保存的选择迁移至 V2之后用户仍可自行切换并保留偏好。
nextState.appearance =
version < UI_VERSION_V2_MIGRATION_VERSION
? { ...appearance, uiVersion: "v2" }
: appearance;
nextState.uiScale = sanitizeUiScale(state.uiScale);
nextState.fontSize = sanitizeFontSize(state.fontSize);
nextState.startupFullscreen = sanitizeStartupFullscreen(