feat(query-editor): 记住查询编辑区结果区比例

- 持久化查询编辑器与结果面板的分割比例
- 新开查询 Tab 时按已保存比例恢复编辑区高度
- 补充分割比例计算与 QueryEditor 回归测试

Fixes #538
This commit is contained in:
Syngnat
2026-06-27 18:21:24 +08:00
parent 60eb696859
commit 6c53ef4eff
5 changed files with 303 additions and 5 deletions

View File

@@ -0,0 +1,43 @@
import { describe, expect, it } from 'vitest';
import {
DEFAULT_QUERY_EDITOR_EDITOR_HEIGHT_RATIO,
MAX_QUERY_EDITOR_EDITOR_HEIGHT_RATIO,
MIN_QUERY_EDITOR_EDITOR_HEIGHT,
MIN_QUERY_EDITOR_EDITOR_HEIGHT_RATIO,
MIN_QUERY_EDITOR_RESULT_HEIGHT,
clampQueryEditorEditorHeight,
resolveQueryEditorEditorHeightFromRatio,
resolveQueryEditorEditorHeightRatio,
sanitizeQueryEditorEditorHeightRatio,
} from './queryEditorSplitLayout';
describe('query editor split layout', () => {
it('sanitizes persisted editor height ratios', () => {
expect(sanitizeQueryEditorEditorHeightRatio(undefined)).toBe(DEFAULT_QUERY_EDITOR_EDITOR_HEIGHT_RATIO);
expect(sanitizeQueryEditorEditorHeightRatio(Number.NaN)).toBe(DEFAULT_QUERY_EDITOR_EDITOR_HEIGHT_RATIO);
expect(sanitizeQueryEditorEditorHeightRatio(0.01)).toBe(MIN_QUERY_EDITOR_EDITOR_HEIGHT_RATIO);
expect(sanitizeQueryEditorEditorHeightRatio(0.99)).toBe(MAX_QUERY_EDITOR_EDITOR_HEIGHT_RATIO);
expect(sanitizeQueryEditorEditorHeightRatio(0.62)).toBe(0.62);
});
it('resolves editor height from a persisted ratio while leaving room for results', () => {
expect(resolveQueryEditorEditorHeightFromRatio(0.5, 800)).toBe(400);
expect(resolveQueryEditorEditorHeightFromRatio(0.9, 800)).toBe(680);
expect(resolveQueryEditorEditorHeightFromRatio(0.01, 800)).toBe(144);
expect(resolveQueryEditorEditorHeightFromRatio(0.5, 0)).toBe(MIN_QUERY_EDITOR_EDITOR_HEIGHT);
});
it('clamps editor height to valid split bounds', () => {
expect(clampQueryEditorEditorHeight(20, 800)).toBe(MIN_QUERY_EDITOR_EDITOR_HEIGHT);
expect(clampQueryEditorEditorHeight(760, 800)).toBe(800 - MIN_QUERY_EDITOR_RESULT_HEIGHT);
expect(clampQueryEditorEditorHeight(360, 800)).toBe(360);
});
it('resolves persisted ratio from the final editor height', () => {
expect(resolveQueryEditorEditorHeightRatio(480, 800)).toBe(0.6);
expect(resolveQueryEditorEditorHeightRatio(20, 800)).toBe(MIN_QUERY_EDITOR_EDITOR_HEIGHT_RATIO);
expect(resolveQueryEditorEditorHeightRatio(760, 800)).toBe(MAX_QUERY_EDITOR_EDITOR_HEIGHT_RATIO);
expect(resolveQueryEditorEditorHeightRatio(480, 0)).toBe(DEFAULT_QUERY_EDITOR_EDITOR_HEIGHT_RATIO);
});
});

View File

@@ -0,0 +1,66 @@
export const DEFAULT_QUERY_EDITOR_EDITOR_HEIGHT_RATIO = 0.5;
export const MIN_QUERY_EDITOR_EDITOR_HEIGHT_RATIO = 0.18;
export const MAX_QUERY_EDITOR_EDITOR_HEIGHT_RATIO = 0.85;
export const MIN_QUERY_EDITOR_EDITOR_HEIGHT = 100;
export const MIN_QUERY_EDITOR_RESULT_HEIGHT = 120;
export const sanitizeQueryEditorEditorHeightRatio = (value: unknown): number => {
const ratio = Number(value);
if (!Number.isFinite(ratio)) {
return DEFAULT_QUERY_EDITOR_EDITOR_HEIGHT_RATIO;
}
return Math.min(
MAX_QUERY_EDITOR_EDITOR_HEIGHT_RATIO,
Math.max(MIN_QUERY_EDITOR_EDITOR_HEIGHT_RATIO, ratio),
);
};
export const clampQueryEditorEditorHeight = (
height: unknown,
availableHeight: unknown,
): number => {
const rawHeight = Number(height);
const rawAvailableHeight = Number(availableHeight);
const normalizedHeight = Number.isFinite(rawHeight) ? rawHeight : MIN_QUERY_EDITOR_EDITOR_HEIGHT;
if (!Number.isFinite(rawAvailableHeight) || rawAvailableHeight <= 0) {
return Math.max(MIN_QUERY_EDITOR_EDITOR_HEIGHT, Math.round(normalizedHeight));
}
const maxEditorHeight = Math.max(
MIN_QUERY_EDITOR_EDITOR_HEIGHT,
rawAvailableHeight - MIN_QUERY_EDITOR_RESULT_HEIGHT,
);
return Math.round(Math.max(
MIN_QUERY_EDITOR_EDITOR_HEIGHT,
Math.min(maxEditorHeight, normalizedHeight),
));
};
export const resolveQueryEditorEditorHeightFromRatio = (
ratio: unknown,
availableHeight: unknown,
): number => {
const rawAvailableHeight = Number(availableHeight);
if (!Number.isFinite(rawAvailableHeight) || rawAvailableHeight <= 0) {
return MIN_QUERY_EDITOR_EDITOR_HEIGHT;
}
return clampQueryEditorEditorHeight(
rawAvailableHeight * sanitizeQueryEditorEditorHeightRatio(ratio),
rawAvailableHeight,
);
};
export const resolveQueryEditorEditorHeightRatio = (
editorHeight: unknown,
availableHeight: unknown,
): number => {
const rawEditorHeight = Number(editorHeight);
const rawAvailableHeight = Number(availableHeight);
if (
!Number.isFinite(rawEditorHeight)
|| !Number.isFinite(rawAvailableHeight)
|| rawAvailableHeight <= 0
) {
return DEFAULT_QUERY_EDITOR_EDITOR_HEIGHT_RATIO;
}
return sanitizeQueryEditorEditorHeightRatio(rawEditorHeight / rawAvailableHeight);
};