mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-08 22:12:29 +08:00
🐛 fix(sidebar): 放宽左侧树拖拽宽度上限
- 统一侧栏宽度常量,最大宽度放宽到 960px - 拖拽时按窗口宽度预留主工作区空间 - 同步 v2 与 legacy 侧栏 CSS 最大宽度 - 持久化侧栏宽度时允许保存更宽配置并补充测试
This commit is contained in:
@@ -697,5 +697,5 @@ body[data-theme='dark'] .gonavi-query-editor-db-token {
|
||||
/* Legacy sidebar resize bounds — mirror v2 .gn-v2-app-sider so Ant Design inline width locks do not collapse drag range. */
|
||||
body[data-ui-version="legacy"] .ant-layout-sider {
|
||||
min-width: 232px !important;
|
||||
max-width: 420px !important;
|
||||
max-width: min(960px, calc(100vw - 360px)) !important;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,10 @@ const appSidebarResizeSource = readFileSync(
|
||||
fileURLToPath(new globalThis.URL('./hooks/useAppSidebarResize.ts', import.meta.url)),
|
||||
'utf8',
|
||||
);
|
||||
const sidebarLayoutSource = readFileSync(
|
||||
fileURLToPath(new globalThis.URL('./utils/sidebarLayout.ts', import.meta.url)),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
const getGlobalShortcutCaseBlock = (action: string) => {
|
||||
const caseToken = `case '${action}':`;
|
||||
@@ -285,8 +289,12 @@ describe('tool center menu entries', () => {
|
||||
expect(appSidebarResizeSource).toContain('ghostRef.current.style.left = `${startGuideLeft + (newWidth - startWidth)}px`;');
|
||||
});
|
||||
|
||||
it('keeps legacy sidebar resize bounds aligned with the v2 sider CSS limits', () => {
|
||||
expect(appCss).toMatch(/body\[data-ui-version="legacy"\]\s+\.ant-layout-sider\s*\{[^}]*min-width:\s*232px\s*!important;[^}]*max-width:\s*420px\s*!important;/s);
|
||||
it('keeps sidebar resize bounds aligned across drag logic and sider CSS limits', () => {
|
||||
expect(sidebarLayoutSource).toContain('export const SIDEBAR_RESIZE_MAX_WIDTH = 960;');
|
||||
expect(sidebarLayoutSource).toContain('export const SIDEBAR_MIN_WORKBENCH_WIDTH = 360;');
|
||||
expect(appSidebarResizeSource).toContain('resolveSidebarResizeMaxWidth(window.innerWidth, minWidth)');
|
||||
expect(appCss).toMatch(/body\[data-ui-version="legacy"\]\s+\.ant-layout-sider\s*\{[^}]*min-width:\s*232px\s*!important;[^}]*max-width:\s*min\(960px,\s*calc\(100vw - 360px\)\)\s*!important;/s);
|
||||
expect(v2ThemeCss).toMatch(/body\[data-ui-version="v2"\]\s+\.gn-v2-app-sider\s*\{[^}]*min-width:\s*232px\s*!important;[^}]*max-width:\s*min\(960px,\s*calc\(100vw - 360px\)\)\s*!important;/s);
|
||||
});
|
||||
|
||||
it('keeps connection modal warm-mounted while leaving the other heavyweight modals conditional', () => {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import React, { useRef } from 'react';
|
||||
|
||||
const SIDEBAR_RESIZE_MIN_WIDTH = 200;
|
||||
const SIDEBAR_RESIZE_MAX_WIDTH = 600;
|
||||
import {
|
||||
SIDEBAR_RESIZE_MAX_WIDTH,
|
||||
SIDEBAR_RESIZE_MIN_WIDTH,
|
||||
resolveSidebarResizeMaxWidth,
|
||||
} from '../utils/sidebarLayout';
|
||||
|
||||
type SidebarResizeBounds = { minWidth: number; maxWidth: number };
|
||||
type SidebarResizeDragState = SidebarResizeBounds & {
|
||||
@@ -23,7 +25,8 @@ const resolveSidebarResizeBounds = (siderElement: Element | null): SidebarResize
|
||||
const cssMinWidth = parseCssPixelValue(computed.minWidth);
|
||||
const cssMaxWidth = parseCssPixelValue(computed.maxWidth);
|
||||
const minWidth = Math.max(SIDEBAR_RESIZE_MIN_WIDTH, cssMinWidth && cssMinWidth > 0 ? cssMinWidth : SIDEBAR_RESIZE_MIN_WIDTH);
|
||||
const maxWidth = Math.max(minWidth, Math.min(SIDEBAR_RESIZE_MAX_WIDTH, cssMaxWidth && cssMaxWidth > 0 ? cssMaxWidth : SIDEBAR_RESIZE_MAX_WIDTH));
|
||||
const viewportMaxWidth = resolveSidebarResizeMaxWidth(window.innerWidth, minWidth);
|
||||
const maxWidth = Math.max(minWidth, Math.min(viewportMaxWidth, cssMaxWidth && cssMaxWidth > 0 ? cssMaxWidth : viewportMaxWidth));
|
||||
return { minWidth, maxWidth };
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { SIDEBAR_RESIZE_MAX_WIDTH } from './utils/sidebarLayout';
|
||||
|
||||
class MemoryStorage implements Storage {
|
||||
private data = new Map<string, string>();
|
||||
@@ -360,6 +361,30 @@ describe('store appearance persistence', () => {
|
||||
expect(appearance.v2SidebarPersistedFilter).toHaveLength(120);
|
||||
});
|
||||
|
||||
it('persists wider sidebar widths and clamps oversized restored values', async () => {
|
||||
const { useStore } = await importStore();
|
||||
|
||||
useStore.getState().setSidebarWidth(880);
|
||||
expect(useStore.getState().sidebarWidth).toBe(880);
|
||||
|
||||
let persisted = JSON.parse(storage.getItem('lite-db-storage') || '{}');
|
||||
expect(persisted.state.sidebarWidth).toBe(880);
|
||||
|
||||
useStore.getState().setSidebarWidth(1200);
|
||||
expect(useStore.getState().sidebarWidth).toBe(SIDEBAR_RESIZE_MAX_WIDTH);
|
||||
|
||||
storage.setItem('lite-db-storage', JSON.stringify({
|
||||
state: {
|
||||
sidebarWidth: 1200,
|
||||
},
|
||||
version: 13,
|
||||
}));
|
||||
|
||||
vi.resetModules();
|
||||
const reloaded = await importStore();
|
||||
expect(reloaded.useStore.getState().sidebarWidth).toBe(SIDEBAR_RESIZE_MAX_WIDTH);
|
||||
});
|
||||
|
||||
it('persists tab display appearance settings and sanitizes invalid elements', async () => {
|
||||
const { useStore } = await importStore();
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@ import {
|
||||
DEFAULT_QUERY_EDITOR_EDITOR_HEIGHT_RATIO,
|
||||
sanitizeQueryEditorEditorHeightRatio,
|
||||
} from "./utils/queryEditorSplitLayout";
|
||||
import { sanitizeSidebarWidth } from "./utils/sidebarLayout";
|
||||
import {
|
||||
DEFAULT_SIDEBAR_TABLE_METADATA_FIELDS,
|
||||
applySidebarTableMetadataFieldOrder,
|
||||
@@ -2330,12 +2331,6 @@ const sanitizeWindowState = (
|
||||
return "normal";
|
||||
};
|
||||
|
||||
const sanitizeSidebarWidth = (value: unknown): number => {
|
||||
const parsed = Number(value);
|
||||
if (!Number.isFinite(parsed)) return 330;
|
||||
return Math.max(200, Math.min(600, Math.trunc(parsed)));
|
||||
};
|
||||
|
||||
const sanitizeWindowBounds = (
|
||||
value: unknown,
|
||||
): { width: number; height: number; x: number; y: number } | null => {
|
||||
@@ -3601,7 +3596,7 @@ export const useStore = create<AppState>()(
|
||||
setWindowState: (state) => set({ windowState: state }),
|
||||
|
||||
setSidebarWidth: (width) =>
|
||||
set({ sidebarWidth: Math.max(200, Math.min(600, Math.trunc(width))) }),
|
||||
set({ sidebarWidth: sanitizeSidebarWidth(width) }),
|
||||
|
||||
// AI actions
|
||||
toggleAIPanel: () =>
|
||||
|
||||
23
frontend/src/utils/sidebarLayout.test.ts
Normal file
23
frontend/src/utils/sidebarLayout.test.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
DEFAULT_SIDEBAR_WIDTH,
|
||||
SIDEBAR_RESIZE_MAX_WIDTH,
|
||||
SIDEBAR_RESIZE_MIN_WIDTH,
|
||||
resolveSidebarResizeMaxWidth,
|
||||
sanitizeSidebarWidth,
|
||||
} from './sidebarLayout';
|
||||
|
||||
describe('sidebar layout bounds', () => {
|
||||
it('allows wider persisted sidebar widths while keeping invalid values safe', () => {
|
||||
expect(sanitizeSidebarWidth(880)).toBe(880);
|
||||
expect(sanitizeSidebarWidth(1200)).toBe(SIDEBAR_RESIZE_MAX_WIDTH);
|
||||
expect(sanitizeSidebarWidth(120)).toBe(SIDEBAR_RESIZE_MIN_WIDTH);
|
||||
expect(sanitizeSidebarWidth('bad')).toBe(DEFAULT_SIDEBAR_WIDTH);
|
||||
});
|
||||
|
||||
it('keeps enough workbench space when resolving drag width on smaller windows', () => {
|
||||
expect(resolveSidebarResizeMaxWidth(1600)).toBe(SIDEBAR_RESIZE_MAX_WIDTH);
|
||||
expect(resolveSidebarResizeMaxWidth(1180)).toBe(820);
|
||||
expect(resolveSidebarResizeMaxWidth(480)).toBe(SIDEBAR_RESIZE_MIN_WIDTH);
|
||||
});
|
||||
});
|
||||
27
frontend/src/utils/sidebarLayout.ts
Normal file
27
frontend/src/utils/sidebarLayout.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
export const DEFAULT_SIDEBAR_WIDTH = 330;
|
||||
export const SIDEBAR_RESIZE_MIN_WIDTH = 200;
|
||||
export const SIDEBAR_SIDER_MIN_WIDTH = 232;
|
||||
export const SIDEBAR_RESIZE_MAX_WIDTH = 960;
|
||||
export const SIDEBAR_MIN_WORKBENCH_WIDTH = 360;
|
||||
|
||||
export const resolveSidebarResizeMaxWidth = (
|
||||
viewportWidth: unknown,
|
||||
minWidth = SIDEBAR_RESIZE_MIN_WIDTH,
|
||||
): number => {
|
||||
const parsedViewportWidth = Number(viewportWidth);
|
||||
if (!Number.isFinite(parsedViewportWidth) || parsedViewportWidth <= 0) {
|
||||
return SIDEBAR_RESIZE_MAX_WIDTH;
|
||||
}
|
||||
|
||||
const viewportLimitedWidth = Math.trunc(parsedViewportWidth) - SIDEBAR_MIN_WORKBENCH_WIDTH;
|
||||
return Math.max(minWidth, Math.min(SIDEBAR_RESIZE_MAX_WIDTH, viewportLimitedWidth));
|
||||
};
|
||||
|
||||
export const sanitizeSidebarWidth = (value: unknown): number => {
|
||||
const parsed = Number(value);
|
||||
if (!Number.isFinite(parsed)) return DEFAULT_SIDEBAR_WIDTH;
|
||||
return Math.max(
|
||||
SIDEBAR_RESIZE_MIN_WIDTH,
|
||||
Math.min(SIDEBAR_RESIZE_MAX_WIDTH, Math.trunc(parsed)),
|
||||
);
|
||||
};
|
||||
@@ -1758,7 +1758,7 @@ body[data-ui-version="v2"] .gn-v2-sidebar-shell {
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-app-sider {
|
||||
min-width: 232px !important;
|
||||
max-width: 420px !important;
|
||||
max-width: min(960px, calc(100vw - 360px)) !important;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-sidebar-redesign {
|
||||
|
||||
Reference in New Issue
Block a user