mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-15 11:14:31 +08:00
⚡️ perf(sidebar): 优化大型资源树折叠动画
- 延迟隐藏资源树内容,使侧栏宽度收起动画保持连续 - 稳定 Sidebar 属性并保留已加载节点,避免大量对象触发重复渲染 - 覆盖 V2、旧版与减少动态效果场景,补充折叠回归测试 - 关联问题:#697
This commit is contained in:
@@ -1005,6 +1005,10 @@ body[data-ui-version="legacy"] .ant-layout-sider {
|
||||
max-width: min(960px, calc(100vw - 360px)) !important;
|
||||
}
|
||||
|
||||
body[data-ui-version] .ant-layout-sider[data-sidebar-panel='true'] {
|
||||
--gonavi-sidebar-collapse-duration: 200ms;
|
||||
}
|
||||
|
||||
body[data-ui-version] .ant-layout-sider[data-sidebar-collapsed='true'] {
|
||||
min-width: var(--gonavi-sidebar-collapsed-width, 0px) !important;
|
||||
max-width: var(--gonavi-sidebar-collapsed-width, 0px) !important;
|
||||
@@ -1013,6 +1017,29 @@ body[data-ui-version] .ant-layout-sider[data-sidebar-collapsed='true'] {
|
||||
border-right: 0 !important;
|
||||
}
|
||||
|
||||
body[data-ui-version='legacy'] .ant-layout-sider[data-sidebar-collapsed='true'] [data-sidebar-content='true'],
|
||||
body[data-ui-version='v2'] .ant-layout-sider[data-sidebar-collapsed='true'] [data-sidebar-tree-panel='true'] {
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
transition: visibility 0s linear var(--gonavi-sidebar-collapse-duration);
|
||||
}
|
||||
|
||||
body[data-ui-version='legacy'] .ant-layout-sider[data-sidebar-collapsed='false'] [data-sidebar-content='true'],
|
||||
body[data-ui-version='v2'] .ant-layout-sider[data-sidebar-collapsed='false'] [data-sidebar-tree-panel='true'] {
|
||||
visibility: visible;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
body[data-ui-version='v2'] .ant-layout-sider[data-sidebar-collapsed='false'] .gn-v2-rail-sidebar-toggle-slot {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
body[data-ui-version] .ant-layout-sider[data-sidebar-panel='true'] {
|
||||
--gonavi-sidebar-collapse-duration: 0ms;
|
||||
}
|
||||
}
|
||||
|
||||
body[data-ui-version] .gonavi-sidebar-collapse-trigger.ant-btn {
|
||||
display: inline-flex;
|
||||
width: 26px;
|
||||
|
||||
@@ -19,14 +19,31 @@ describe('app sidebar tree panel collapse', () => {
|
||||
expect(appSource).not.toContain('setSidebarWidth(0)');
|
||||
});
|
||||
|
||||
it('keeps loaded tree nodes mounted and Sidebar props stable during the width transition', () => {
|
||||
expect(appSource).toContain("const sidebarPanelCollapseLabel = t('app.sidebar.collapse');");
|
||||
expect(appSource).toContain("const sidebarPanelExpandLabel = t('app.sidebar.expand');");
|
||||
expect(appSource).toContain('onCollapseSidebar={isV2Ui ? handleCollapseSidebarPanel : undefined}');
|
||||
expect(appSource).toContain('onExpandSidebar={isV2Ui ? handleExpandSidebarPanel : undefined}');
|
||||
expect(appSource).not.toContain('isTreePanelCollapsed={isV2Ui && isSidebarCollapsed}');
|
||||
expect(sidebarSource).not.toContain('isTreePanelCollapsed?: boolean;');
|
||||
expect(sidebarSource).not.toContain("display: isV2Ui && isTreePanelCollapsed ? 'none' : 'flex'");
|
||||
expect(appSource).toContain('data-sidebar-content="true"');
|
||||
expect(appCssSource).toContain('--gonavi-sidebar-collapse-duration: 200ms;');
|
||||
expect(appCssSource).toMatch(
|
||||
/\.ant-layout-sider\[data-sidebar-collapsed='true'\]\s+\[data-sidebar-tree-panel='true'\][^{]*\{[^}]*visibility:\s*hidden;[^}]*transition:\s*visibility 0s linear var\(--gonavi-sidebar-collapse-duration\);/s,
|
||||
);
|
||||
expect(appCssSource).toMatch(
|
||||
/\.ant-layout-sider\[data-sidebar-collapsed='false'\]\s+\.gn-v2-rail-sidebar-toggle-slot\s*\{[^}]*display:\s*none;/s,
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps the fixed rail visible and hides only the v2 explorer tree panel', () => {
|
||||
expect(sidebarSource).toContain("id={isV2Ui ? 'gonavi-sidebar-tree-panel' : undefined}");
|
||||
expect(sidebarSource).toContain("data-sidebar-tree-panel={isV2Ui ? 'true' : undefined}");
|
||||
expect(sidebarSource).toContain('aria-hidden={isV2Ui ? isTreePanelCollapsed : undefined}');
|
||||
expect(sidebarSource).toContain("display: isV2Ui && isTreePanelCollapsed ? 'none' : 'flex'");
|
||||
expect(sidebarSource).toContain("style={{ display: 'flex', flexDirection: 'column'");
|
||||
expect(connectionRailSource).toContain('data-sidebar-fixed-rail="true"');
|
||||
expect(appSource).toContain('isTreePanelCollapsed={isV2Ui && isSidebarCollapsed}');
|
||||
expect(appSource).toContain("visibility: !isV2Ui && isSidebarCollapsed ? 'hidden' : 'visible'");
|
||||
expect(appSource).not.toContain('isTreePanelCollapsed={isV2Ui && isSidebarCollapsed}');
|
||||
expect(appSource).not.toContain("visibility: !isV2Ui && isSidebarCollapsed ? 'hidden' : 'visible'");
|
||||
expect(appSource).toContain('data-sidebar-collapse-trigger="true"');
|
||||
expect(appSource).toContain('data-titlebar-brand-region="true"');
|
||||
expect(appSource).toContain('data-sidebar-toggle-placement="titlebar"');
|
||||
@@ -42,8 +59,8 @@ describe('app sidebar tree panel collapse', () => {
|
||||
expect(appSource).toContain('handleFocusSidebarSearch();');
|
||||
expect(appSource).toContain('<TabManager onFocusSidebarSearch={handleFocusSidebarSearch} />');
|
||||
expect(appSource).toContain('{!isV2Ui && (');
|
||||
expect(appSource).toContain('onCollapseSidebar={isV2Ui && !isSidebarCollapsed ? handleCollapseSidebarPanel : undefined}');
|
||||
expect(appSource).toContain('onExpandSidebar={isV2Ui && isSidebarCollapsed ? handleExpandSidebarPanel : undefined}');
|
||||
expect(appSource).toContain('onCollapseSidebar={isV2Ui ? handleCollapseSidebarPanel : undefined}');
|
||||
expect(appSource).toContain('onExpandSidebar={isV2Ui ? handleExpandSidebarPanel : undefined}');
|
||||
expect(appSource).toContain('collapseSidebarButtonRef={sidebarExplorerToggleRef}');
|
||||
expect(appSource).toContain('expandSidebarButtonRef={sidebarCollapsedToggleRef}');
|
||||
expect(appSource).toContain('ref={sidebarCollapsedToggleRef}');
|
||||
@@ -76,7 +93,7 @@ describe('app sidebar tree panel collapse', () => {
|
||||
expect(triggerSource).toContain('type="text"');
|
||||
expect(triggerSource).toContain("WebkitAppRegion: 'no-drag'");
|
||||
expect(triggerSource).toContain("'--wails-draggable': 'no-drag'");
|
||||
expect(siderSource).toContain('onCollapseSidebar={isV2Ui && !isSidebarCollapsed ? handleCollapseSidebarPanel : undefined}');
|
||||
expect(siderSource).toContain('onCollapseSidebar={isV2Ui ? handleCollapseSidebarPanel : undefined}');
|
||||
expect(fixedRailIndex).toBeGreaterThan(-1);
|
||||
expect(explorerPanelIndex).toBeGreaterThan(fixedRailIndex);
|
||||
expect(explorerToggleIndex).toBeGreaterThan(connectionMenuIndex);
|
||||
@@ -100,7 +117,9 @@ describe('app sidebar tree panel collapse', () => {
|
||||
expect(appCssSource).toMatch(
|
||||
/body\[data-ui-version='v2'\]\s+\.gonavi-sidebar-collapse-trigger\.ant-btn\[data-sidebar-toggle-placement='explorer-header'\]\s*\{[^}]*width:\s*24px;[^}]*height:\s*24px(?:\s*!important)?;[^}]*border:\s*1px solid var\(--gn-br-2\)\s*!important;[^}]*border-radius:\s*7px\s*!important;/s,
|
||||
);
|
||||
expect(appCssSource).not.toContain(".ant-layout-sider[data-sidebar-panel='true']");
|
||||
expect(appCssSource).not.toMatch(
|
||||
/\.ant-layout-sider\[data-sidebar-panel='true'\]\s*\{[^}]*(?:min-width|max-width|\bwidth\s*:|\bflex\s*:)/s,
|
||||
);
|
||||
expect(appCssSource).not.toMatch(/\.gonavi-sidebar-collapse-trigger[^}]*position:\s*absolute/s);
|
||||
expect(appCssSource).not.toMatch(/\.gonavi-sidebar-collapse-trigger[^}]*right:\s*-\d+px/s);
|
||||
expect(appCssSource).not.toMatch(/\.gonavi-sidebar-collapse-trigger[^}]*translateY/s);
|
||||
|
||||
@@ -7454,7 +7454,9 @@ function App() {
|
||||
return null;
|
||||
};
|
||||
|
||||
const sidebarPanelToggleLabel = t(isSidebarCollapsed ? 'app.sidebar.expand' : 'app.sidebar.collapse');
|
||||
const sidebarPanelCollapseLabel = t('app.sidebar.collapse');
|
||||
const sidebarPanelExpandLabel = t('app.sidebar.expand');
|
||||
const sidebarPanelToggleLabel = isSidebarCollapsed ? sidebarPanelExpandLabel : sidebarPanelCollapseLabel;
|
||||
|
||||
return (
|
||||
<ConfigProvider
|
||||
@@ -7622,13 +7624,13 @@ function App() {
|
||||
>
|
||||
<div
|
||||
id={isV2Ui ? undefined : 'gonavi-sidebar-tree-panel'}
|
||||
data-sidebar-content="true"
|
||||
aria-hidden={!isV2Ui ? isSidebarCollapsed : undefined}
|
||||
style={{
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflow: 'hidden',
|
||||
visibility: !isV2Ui && isSidebarCollapsed ? 'hidden' : 'visible',
|
||||
}}
|
||||
>
|
||||
{!isV2Ui && (
|
||||
@@ -7665,13 +7667,12 @@ function App() {
|
||||
onToggleLogPanel={handleToggleLogPanel}
|
||||
uiVersion={appearance.uiVersion}
|
||||
onFocusCommandSearch={handleFocusSidebarSearch}
|
||||
onCollapseSidebar={isV2Ui && !isSidebarCollapsed ? handleCollapseSidebarPanel : undefined}
|
||||
onExpandSidebar={isV2Ui && isSidebarCollapsed ? handleExpandSidebarPanel : undefined}
|
||||
collapseSidebarLabel={sidebarPanelToggleLabel}
|
||||
onCollapseSidebar={isV2Ui ? handleCollapseSidebarPanel : undefined}
|
||||
onExpandSidebar={isV2Ui ? handleExpandSidebarPanel : undefined}
|
||||
collapseSidebarLabel={isV2Ui ? sidebarPanelCollapseLabel : undefined}
|
||||
collapseSidebarButtonRef={sidebarExplorerToggleRef}
|
||||
expandSidebarLabel={sidebarPanelToggleLabel}
|
||||
expandSidebarLabel={isV2Ui ? sidebarPanelExpandLabel : undefined}
|
||||
expandSidebarButtonRef={sidebarCollapsedToggleRef}
|
||||
isTreePanelCollapsed={isV2Ui && isSidebarCollapsed}
|
||||
/>
|
||||
</div>
|
||||
{!connectionWorkbenchState.ready && (
|
||||
|
||||
@@ -1000,7 +1000,6 @@ describe('Sidebar locate toolbar', () => {
|
||||
|
||||
it('renders the fixed v2 rail, explorer filters and workbench actions', () => {
|
||||
const markup = renderSidebarMarkup({ uiVersion: 'v2', onCreateConnection: mocks.noop });
|
||||
const collapsedMarkup = renderSidebarMarkup({ uiVersion: 'v2', isTreePanelCollapsed: true });
|
||||
const source = readSidebarSource();
|
||||
|
||||
expect(markup).toContain('gn-v2-sidebar-redesign');
|
||||
@@ -1008,8 +1007,8 @@ describe('Sidebar locate toolbar', () => {
|
||||
expect(markup).toContain('data-sidebar-fixed-rail="true"');
|
||||
expect(markup).toContain('gn-v2-object-explorer');
|
||||
expect(markup.indexOf('data-sidebar-fixed-rail="true"')).toBeLessThan(markup.indexOf('data-sidebar-tree-panel="true"'));
|
||||
expect(collapsedMarkup).toContain('data-sidebar-fixed-rail="true"');
|
||||
expect(collapsedMarkup).toContain('data-sidebar-tree-panel="true" aria-hidden="true" style="display:none');
|
||||
expect(markup).toContain('data-sidebar-tree-panel="true" style="display:flex');
|
||||
expect(markup).not.toContain('data-sidebar-tree-panel="true" aria-hidden="true" style="display:none');
|
||||
expect(markup).toContain('gn-v2-active-connection-header');
|
||||
expect(markup).toContain('gn-v2-explorer-search');
|
||||
expect(markup).toContain('data-v2-sidebar-search-mode="command"');
|
||||
|
||||
@@ -516,7 +516,6 @@ const Sidebar: React.FC<{
|
||||
collapseSidebarButtonRef?: React.Ref<HTMLButtonElement>;
|
||||
expandSidebarLabel?: string;
|
||||
expandSidebarButtonRef?: React.Ref<HTMLButtonElement>;
|
||||
isTreePanelCollapsed?: boolean;
|
||||
}> = React.memo(({
|
||||
onCreateConnection,
|
||||
onEditConnection,
|
||||
@@ -531,7 +530,6 @@ const Sidebar: React.FC<{
|
||||
collapseSidebarButtonRef,
|
||||
expandSidebarLabel,
|
||||
expandSidebarButtonRef,
|
||||
isTreePanelCollapsed = false,
|
||||
}) => {
|
||||
const connections = useStore(state => state.connections);
|
||||
const savedQueries = useStore(state => state.savedQueries);
|
||||
@@ -3044,8 +3042,7 @@ const Sidebar: React.FC<{
|
||||
id={isV2Ui ? 'gonavi-sidebar-tree-panel' : undefined}
|
||||
className={isV2Ui ? 'gn-v2-object-explorer' : undefined}
|
||||
data-sidebar-tree-panel={isV2Ui ? 'true' : undefined}
|
||||
aria-hidden={isV2Ui ? isTreePanelCollapsed : undefined}
|
||||
style={{ display: isV2Ui && isTreePanelCollapsed ? 'none' : 'flex', flexDirection: 'column', height: '100%', minWidth: 0, flex: 1 }}
|
||||
style={{ display: 'flex', flexDirection: 'column', height: '100%', minWidth: 0, flex: 1 }}
|
||||
>
|
||||
{isV2Ui && (
|
||||
<div className="gn-v2-active-connection-header" data-object-count={activeConnectionObjectCount}>
|
||||
|
||||
Reference in New Issue
Block a user