diff --git a/frontend/src/components/Sidebar.locate-toolbar.test.tsx b/frontend/src/components/Sidebar.locate-toolbar.test.tsx
index 3c2a9000..786cf0f8 100644
--- a/frontend/src/components/Sidebar.locate-toolbar.test.tsx
+++ b/frontend/src/components/Sidebar.locate-toolbar.test.tsx
@@ -70,6 +70,7 @@ const readSidebarSource = () => [
readSourceFile('./sidebar/SidebarExternalSqlWorkflow.tsx'),
readSourceFile('./sidebar/useSidebarTreeLoaders.tsx'),
readSourceFile('./sidebar/SidebarEntityModals.tsx'),
+ readSourceFile('./sidebar/SidebarTreeTitle.tsx'),
readSourceFile('./sidebarV2Utils.ts'),
].join('\n');
const readLegacyNodeMenuSource = () => readSourceFile('./sidebar/sidebarLegacyNodeMenu.tsx');
@@ -2340,9 +2341,9 @@ describe('Sidebar locate toolbar', () => {
const externalSqlFlowStart = source.indexOf('const handleAddExternalSQLDirectory = async (node: any) => {');
const externalSqlFlowEnd = source.indexOf('const cancelSQLFileExecution = () => {', externalSqlFlowStart);
const externalSqlFlowSource = source.slice(externalSqlFlowStart, externalSqlFlowEnd);
- const treeTitleStart = source.indexOf('const renderV2TreeTitle = (node: any, hoverTitle: string, statusBadge: React.ReactNode) => {');
- const treeTitleEnd = source.indexOf('const selectConnectionFromRail', treeTitleStart);
- const treeTitleSource = source.slice(treeTitleStart, treeTitleEnd);
+ const treeTitleSource = readSourceFile('./sidebar/SidebarTreeTitle.tsx');
+ const treeTitleStart = 0;
+ const treeTitleEnd = treeTitleSource.length;
const externalSqlMenuStart = legacyMenuSource.indexOf("if (node.type === 'external-sql-root') {", legacyMenuSource.indexOf('// 已存查询节点的右键菜单'));
const externalSqlMenuEnd = legacyMenuSource.indexOf("if (node.type === 'external-sql-directory') {", externalSqlMenuStart);
const externalSqlMenuSource = legacyMenuSource.slice(externalSqlMenuStart, externalSqlMenuEnd);
@@ -2593,11 +2594,12 @@ describe('Sidebar locate toolbar', () => {
expect(tableGroupCallSource).not.toContain(rawSnippet);
});
- const treeTitleStart = sidebarSource.indexOf('const renderV2TreeTitle');
- const treeTitleEnd = sidebarSource.indexOf('const selectConnectionFromRail', treeTitleStart);
+ const treeTitleModuleSource = readSourceFile('./sidebar/SidebarTreeTitle.tsx');
+ const treeTitleStart = treeTitleModuleSource.indexOf('export const renderSidebarV2TreeTitle');
+ const treeTitleEnd = treeTitleModuleSource.length;
expect(treeTitleStart).toBeGreaterThanOrEqual(0);
expect(treeTitleEnd).toBeGreaterThan(treeTitleStart);
- const treeTitleSource = sidebarSource.slice(treeTitleStart, treeTitleEnd);
+ const treeTitleSource = treeTitleModuleSource.slice(treeTitleStart, treeTitleEnd);
expect(treeTitleSource).toContain('const objectGroupTitle = resolveV2ObjectGroupTitle(node);');
expect(treeTitleSource).toContain('if (objectGroupTitle) return objectGroupTitle;');
diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx
index 41176a82..e87f1931 100644
--- a/frontend/src/components/Sidebar.tsx
+++ b/frontend/src/components/Sidebar.tsx
@@ -19,6 +19,7 @@ import {
} from './sidebar/useSidebarBatchExport';
import { SidebarBatchExportModals } from './sidebar/SidebarBatchExportModals';
import { SidebarEntityModals } from './sidebar/SidebarEntityModals';
+import { renderSidebarV2TreeTitle } from './sidebar/SidebarTreeTitle';
import {
normalizeDriverType,
useSidebarTreeLoaders,
@@ -108,9 +109,7 @@ import { Tree, message, Dropdown, MenuProps, Input, Button, Form, Badge, Checkbo
MoreOutlined,
ToolOutlined,
SettingOutlined,
- BarsOutlined,
- StarFilled,
- StarOutlined
+ BarsOutlined
} from '@ant-design/icons';
import {
buildSidebarRootConnectionToken,
@@ -4151,129 +4150,17 @@ const Sidebar: React.FC<{
void fetchV2TableContextMenuStats(node);
};
- const renderV2TreeTitle = (node: any, hoverTitle: string, statusBadge: React.ReactNode) => {
- const rawTitle = String(node.title ?? '');
- const groupKey = String(node?.dataRef?.groupKey || '');
- const dragText = resolveSidebarObjectDragText(node);
- if (node.type === 'v2-table-section') {
- return (
-
- {rawTitle}
-
- );
- }
- const displayTitle = (() => {
- if (node.type === 'queries-folder') return t('sidebar.tree.saved_queries');
- if (node.type === 'external-sql-root') return t('sidebar.external_sql.root');
- if (node.type === 'object-group') {
- const objectGroupTitle = resolveV2ObjectGroupTitle(node);
- if (objectGroupTitle) return objectGroupTitle;
- }
- return rawTitle;
- })();
- const metaText = getV2TreeMetaText(node);
- const isMono = node.type === 'table'
- || node.type === 'view'
- || node.type === 'materialized-view'
- || node.type === 'db-trigger'
- || node.type === 'db-event'
- || node.type === 'routine'
- || node.type === 'saved-query'
- || node.type === 'external-sql-file';
- const titleClassName = [
- 'gn-v2-tree-title',
- isMono ? 'is-mono' : '',
- node.type === 'object-group' ? 'is-group' : '',
- node.type === 'table' && node?.dataRef?.pinnedSidebarTable ? 'is-pinned-table' : '',
- ].filter(Boolean).join(' ');
- const tablePinAction = node.type === 'table' ? (
-
- ) : null;
- if (node.type === 'connection') {
- return (
-
- {statusBadge}
-
- {displayTitle}
-
-
- );
- }
- return (
- <>
- {
- snapshotTreeSelectionBeforeDrag();
- treeDragSelectSuppressUntilRef.current = Date.now() + 600;
- setIsTreeDragging(true);
- event.stopPropagation();
- event.dataTransfer.effectAllowed = 'copy';
- event.dataTransfer.setData('text/plain', dragText);
- event.dataTransfer.setData(
- SIDEBAR_SQL_EDITOR_DRAG_MIME,
- encodeSidebarSqlEditorDragPayload({
- text: dragText,
- nodeType: node.type,
- connectionId: String(node?.dataRef?.id || ''),
- dbName: String(node?.dataRef?.dbName || ''),
- }),
- );
- } : undefined}
- onDragEnd={dragText ? () => {
- restoreTreeSelectionAfterDrag();
- setIsTreeDragging(false);
- } : undefined}
- >
- {statusBadge}
- {displayTitle}
- {metaText && {metaText}}
-
- {tablePinAction}
- >
- );
- };
+ const renderV2TreeTitle = (node: any, hoverTitle: string, statusBadge: React.ReactNode) => renderSidebarV2TreeTitle({
+ node,
+ hoverTitle,
+ statusBadge,
+ getV2TreeMetaText,
+ toggleSidebarTablePinned,
+ snapshotTreeSelectionBeforeDrag,
+ restoreTreeSelectionAfterDrag,
+ treeDragSelectSuppressUntilRef,
+ setIsTreeDragging,
+ });
const selectConnectionFromRail = useCallback((conn: SavedConnection) => {
const key = conn.id;
diff --git a/frontend/src/components/sidebar/SidebarTreeTitle.tsx b/frontend/src/components/sidebar/SidebarTreeTitle.tsx
new file mode 100644
index 00000000..3db6508c
--- /dev/null
+++ b/frontend/src/components/sidebar/SidebarTreeTitle.tsx
@@ -0,0 +1,152 @@
+import React from 'react';
+import { StarFilled, StarOutlined } from '@ant-design/icons';
+import { t } from '../../i18n';
+import { SIDEBAR_SQL_EDITOR_DRAG_MIME, encodeSidebarSqlEditorDragPayload } from '../../utils/sidebarSqlDrag';
+import { resolveSidebarObjectDragText } from '../sidebarCoreUtils';
+import { resolveV2ObjectGroupTitle } from './sidebarHelpers';
+
+type SidebarV2TreeTitleOptions = {
+ node: any;
+ hoverTitle: string;
+ statusBadge: React.ReactNode;
+ getV2TreeMetaText: (node: any) => string;
+ toggleSidebarTablePinned: (node: any) => void;
+ snapshotTreeSelectionBeforeDrag: () => void;
+ restoreTreeSelectionAfterDrag: () => void;
+ treeDragSelectSuppressUntilRef: React.MutableRefObject;
+ setIsTreeDragging: (dragging: boolean) => void;
+};
+
+export const renderSidebarV2TreeTitle = ({
+ node,
+ hoverTitle,
+ statusBadge,
+ getV2TreeMetaText,
+ toggleSidebarTablePinned,
+ snapshotTreeSelectionBeforeDrag,
+ restoreTreeSelectionAfterDrag,
+ treeDragSelectSuppressUntilRef,
+ setIsTreeDragging,
+}: SidebarV2TreeTitleOptions): React.ReactNode => {
+ const rawTitle = String(node.title ?? '');
+ const groupKey = String(node?.dataRef?.groupKey || '');
+ const dragText = resolveSidebarObjectDragText(node);
+ if (node.type === 'v2-table-section') {
+ return (
+
+ {rawTitle}
+
+ );
+ }
+ const displayTitle = (() => {
+ if (node.type === 'queries-folder') return t('sidebar.tree.saved_queries');
+ if (node.type === 'external-sql-root') return t('sidebar.external_sql.root');
+ if (node.type === 'object-group') {
+ const objectGroupTitle = resolveV2ObjectGroupTitle(node);
+ if (objectGroupTitle) return objectGroupTitle;
+ }
+ return rawTitle;
+ })();
+ const metaText = getV2TreeMetaText(node);
+ const isMono = node.type === 'table'
+ || node.type === 'view'
+ || node.type === 'materialized-view'
+ || node.type === 'db-trigger'
+ || node.type === 'db-event'
+ || node.type === 'routine'
+ || node.type === 'saved-query'
+ || node.type === 'external-sql-file';
+ const titleClassName = [
+ 'gn-v2-tree-title',
+ isMono ? 'is-mono' : '',
+ node.type === 'object-group' ? 'is-group' : '',
+ node.type === 'table' && node?.dataRef?.pinnedSidebarTable ? 'is-pinned-table' : '',
+ ].filter(Boolean).join(' ');
+ const tablePinAction = node.type === 'table' ? (
+
+ ) : null;
+ if (node.type === 'connection') {
+ return (
+
+ {statusBadge}
+
+ {displayTitle}
+
+
+ );
+ }
+ return (
+ <>
+ {
+ snapshotTreeSelectionBeforeDrag();
+ treeDragSelectSuppressUntilRef.current = Date.now() + 600;
+ setIsTreeDragging(true);
+ event.stopPropagation();
+ event.dataTransfer.effectAllowed = 'copy';
+ event.dataTransfer.setData('text/plain', dragText);
+ event.dataTransfer.setData(
+ SIDEBAR_SQL_EDITOR_DRAG_MIME,
+ encodeSidebarSqlEditorDragPayload({
+ text: dragText,
+ nodeType: node.type,
+ connectionId: String(node?.dataRef?.id || ''),
+ dbName: String(node?.dataRef?.dbName || ''),
+ }),
+ );
+ } : undefined}
+ onDragEnd={dragText ? () => {
+ restoreTreeSelectionAfterDrag();
+ setIsTreeDragging(false);
+ } : undefined}
+ >
+ {statusBadge}
+ {displayTitle}
+ {metaText && {metaText}}
+
+ {tablePinAction}
+ >
+ );
+};