From 672d05d12405aeb86edd4fd61a12da7178028911 Mon Sep 17 00:00:00 2001 From: Syngnat Date: Wed, 24 Jun 2026 22:31:13 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(sidebar):=20=E6=81=A2?= =?UTF-8?q?=E5=A4=8D=E8=BF=9E=E6=8E=A5=E5=8A=A0=E8=BD=BD=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=E8=BD=AC=E5=9C=88=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 扩展连接状态为 loading/success/error - 在连接根节点加载开始时显示 pending,成功后才置绿 - 同步 V2 active host 与 legacy Badge 状态显示并补测试 --- .../Sidebar.locate-toolbar.test.tsx | 17 +++++++++++++ frontend/src/components/Sidebar.tsx | 5 ++-- .../sidebar/useSidebarTitleRender.tsx | 11 +++++---- .../sidebar/useSidebarTreeLoaders.tsx | 4 +++- .../sidebar/useSidebarV2ActionHandlers.tsx | 3 ++- .../sidebar/useSidebarV2ContextMenu.tsx | 10 ++++---- frontend/src/components/sidebarV2Utils.ts | 2 ++ frontend/src/v2-theme.css | 24 +++++++++++++++++++ 8 files changed, 63 insertions(+), 13 deletions(-) diff --git a/frontend/src/components/Sidebar.locate-toolbar.test.tsx b/frontend/src/components/Sidebar.locate-toolbar.test.tsx index 44f3f9d..6bc6cce 100644 --- a/frontend/src/components/Sidebar.locate-toolbar.test.tsx +++ b/frontend/src/components/Sidebar.locate-toolbar.test.tsx @@ -1009,6 +1009,23 @@ describe('Sidebar locate toolbar', () => { expect(css).toMatch(/\.gn-v2-explorer-filter-tabs button \{[^}]*flex: 0 0 auto;[^}]*white-space: nowrap;/s); }); + it('shows a pending state while a connection root is loading', () => { + const css = readV2ThemeCss(); + const source = readSidebarSource(); + const treeLoaderSource = readSourceFile('./sidebar/useSidebarTreeLoaders.tsx'); + const titleRenderSource = readSourceFile('./sidebar/useSidebarTitleRender.tsx'); + const v2ContextMenuSource = readSourceFile('./sidebar/useSidebarV2ContextMenu.tsx'); + + expect(source).toContain("export type SidebarConnectionState = 'loading' | 'success' | 'error';"); + expect(treeLoaderSource).toContain("setConnectionStates(prev => ({ ...prev, [conn.id]: 'loading' }));"); + expect(titleRenderSource).toContain("let status: 'loading' | 'success' | 'error' | 'default' = 'default';"); + expect(titleRenderSource).toContain("if (connectionStates[node.key] === 'loading') status = 'loading';"); + expect(v2ContextMenuSource).toContain("const statusMap = new Map();"); + expect(v2ContextMenuSource).toContain("value === 'loading' ? 'loading'"); + expect(css).toMatch(/\.gn-v2-tree-status\.is-loading::before \{[^}]*border: 2px solid rgba\(37, 99, 235, 0\.24\);[^}]*animation: gn-v2-tree-status-spin 0\.8s linear infinite;/s); + expect(css).toMatch(/@keyframes gn-v2-tree-status-spin \{[^}]*to \{ transform: rotate\(360deg\); \}/s); + }); + it('keeps v2 tree status dots circular while using virtual horizontal scroll for long labels', () => { const css = readV2ThemeCss(); const source = readSidebarSource(); diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index 4142c45..002b7b9 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -175,6 +175,7 @@ import { shouldCloseV2CommandSearchOnGlobalKey, shouldRunV2CommandSearchEnter, sortSidebarTableEntries, + type SidebarConnectionState, type SidebarTreeNode as TreeNode, type V2CommandSearchItem, } from './sidebarV2Utils'; @@ -704,8 +705,8 @@ const Sidebar: React.FC<{ return () => window.removeEventListener('keydown', handleV2CommandSearchGlobalKeyDown, true); }, [closeV2CommandSearch, isV2CommandSearchOpen]); - // Connection Status State: key -> 'success' | 'error' - const [connectionStates, setConnectionStates] = useState>({}); + // Connection Status State: key -> 'loading' | 'success' | 'error' + const [connectionStates, setConnectionStates] = useState>({}); const [isTreeDragging, setIsTreeDragging] = useState(false); // Create Database Modal diff --git a/frontend/src/components/sidebar/useSidebarTitleRender.tsx b/frontend/src/components/sidebar/useSidebarTitleRender.tsx index f2b40c8..750e6d0 100644 --- a/frontend/src/components/sidebar/useSidebarTitleRender.tsx +++ b/frontend/src/components/sidebar/useSidebarTitleRender.tsx @@ -9,6 +9,7 @@ import { SIDEBAR_SQL_EDITOR_DRAG_MIME, encodeSidebarSqlEditorDragPayload } from import { resolveSidebarObjectDragText, } from '../sidebarCoreUtils'; +import type { SidebarConnectionState } from '../sidebarV2Utils'; import { shouldHideSchemaPrefix, splitQualifiedName, @@ -16,7 +17,7 @@ import { import { resolveV2ObjectGroupTitle } from './sidebarHelpers'; type UseSidebarTitleRenderArgs = { - connectionStates: Record; + connectionStates: Record; isV2Ui: boolean; renderV2TreeTitle: (node: any, hoverTitle: string, statusBadge: React.ReactNode) => React.ReactNode; handleAddExternalSQLDirectory: (node: any) => Promise; @@ -36,16 +37,18 @@ export const useSidebarTitleRender = ({ treeDragSelectSuppressUntilRef, setIsTreeDragging, }: UseSidebarTitleRenderArgs) => useCallback((node: any) => { - let status: 'success' | 'error' | 'default' = 'default'; + let status: 'loading' | 'success' | 'error' | 'default' = 'default'; if (node.type === 'connection' || node.type === 'database') { - if (connectionStates[node.key] === 'success') status = 'success'; + if (connectionStates[node.key] === 'loading') status = 'loading'; + else if (connectionStates[node.key] === 'success') status = 'success'; else if (connectionStates[node.key] === 'error') status = 'error'; } + const legacyBadgeStatus = status === 'loading' ? 'processing' : status; const statusBadge = node.type === 'connection' || node.type === 'database' ? ( isV2Ui ?