mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-22 13:17:10 +08:00
♻️ refactor(sidebar): 抽出 V2 树标题渲染
This commit is contained in:
@@ -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;');
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<span
|
||||
className="gn-v2-tree-section-title"
|
||||
data-section-kind={node?.dataRef?.sectionKind || undefined}
|
||||
title={rawTitle}
|
||||
>
|
||||
{rawTitle}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
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' ? (
|
||||
<button
|
||||
type="button"
|
||||
className={[
|
||||
'gn-v2-table-pin-action',
|
||||
node?.dataRef?.pinnedSidebarTable ? 'is-pinned' : '',
|
||||
].filter(Boolean).join(' ')}
|
||||
title={node?.dataRef?.pinnedSidebarTable ? t('sidebar.action.unpin_table') : t('sidebar.action.pin_table')}
|
||||
aria-label={node?.dataRef?.pinnedSidebarTable ? t('sidebar.action.unpin_table') : t('sidebar.action.pin_table')}
|
||||
aria-pressed={node?.dataRef?.pinnedSidebarTable ? true : false}
|
||||
data-v2-sidebar-table-pin-action="true"
|
||||
onMouseDown={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
toggleSidebarTablePinned(node);
|
||||
}}
|
||||
onDoubleClick={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{node?.dataRef?.pinnedSidebarTable ? <StarFilled /> : <StarOutlined />}
|
||||
</button>
|
||||
) : null;
|
||||
if (node.type === 'connection') {
|
||||
return (
|
||||
<span
|
||||
className={`${titleClassName} is-connection`}
|
||||
title={hoverTitle}
|
||||
data-node-type={node.type}
|
||||
data-sidebar-node-key={String(node.key || '')}
|
||||
data-sidebar-node-type={String(node.type || '')}
|
||||
>
|
||||
{statusBadge}
|
||||
<span className="gn-v2-tree-connection-copy">
|
||||
<span className="gn-v2-tree-label">{displayTitle}</span>
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<span
|
||||
className={titleClassName}
|
||||
title={hoverTitle}
|
||||
draggable={!!dragText}
|
||||
data-node-type={node.type}
|
||||
data-group-key={groupKey || undefined}
|
||||
data-sidebar-node-key={String(node.key || '')}
|
||||
data-sidebar-node-type={String(node.type || '')}
|
||||
onDragStart={dragText ? (event) => {
|
||||
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}
|
||||
<span className="gn-v2-tree-label">{displayTitle}</span>
|
||||
{metaText && <span className="gn-v2-tree-count">{metaText}</span>}
|
||||
</span>
|
||||
{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;
|
||||
|
||||
152
frontend/src/components/sidebar/SidebarTreeTitle.tsx
Normal file
152
frontend/src/components/sidebar/SidebarTreeTitle.tsx
Normal file
@@ -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<number>;
|
||||
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 (
|
||||
<span
|
||||
className="gn-v2-tree-section-title"
|
||||
data-section-kind={node?.dataRef?.sectionKind || undefined}
|
||||
title={rawTitle}
|
||||
>
|
||||
{rawTitle}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
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' ? (
|
||||
<button
|
||||
type="button"
|
||||
className={[
|
||||
'gn-v2-table-pin-action',
|
||||
node?.dataRef?.pinnedSidebarTable ? 'is-pinned' : '',
|
||||
].filter(Boolean).join(' ')}
|
||||
title={node?.dataRef?.pinnedSidebarTable ? t('sidebar.action.unpin_table') : t('sidebar.action.pin_table')}
|
||||
aria-label={node?.dataRef?.pinnedSidebarTable ? t('sidebar.action.unpin_table') : t('sidebar.action.pin_table')}
|
||||
aria-pressed={node?.dataRef?.pinnedSidebarTable ? true : false}
|
||||
data-v2-sidebar-table-pin-action="true"
|
||||
onMouseDown={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
toggleSidebarTablePinned(node);
|
||||
}}
|
||||
onDoubleClick={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{node?.dataRef?.pinnedSidebarTable ? <StarFilled /> : <StarOutlined />}
|
||||
</button>
|
||||
) : null;
|
||||
if (node.type === 'connection') {
|
||||
return (
|
||||
<span
|
||||
className={`${titleClassName} is-connection`}
|
||||
title={hoverTitle}
|
||||
data-node-type={node.type}
|
||||
data-sidebar-node-key={String(node.key || '')}
|
||||
data-sidebar-node-type={String(node.type || '')}
|
||||
>
|
||||
{statusBadge}
|
||||
<span className="gn-v2-tree-connection-copy">
|
||||
<span className="gn-v2-tree-label">{displayTitle}</span>
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<span
|
||||
className={titleClassName}
|
||||
title={hoverTitle}
|
||||
draggable={!!dragText}
|
||||
data-node-type={node.type}
|
||||
data-group-key={groupKey || undefined}
|
||||
data-sidebar-node-key={String(node.key || '')}
|
||||
data-sidebar-node-type={String(node.type || '')}
|
||||
onDragStart={dragText ? (event) => {
|
||||
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}
|
||||
<span className="gn-v2-tree-label">{displayTitle}</span>
|
||||
{metaText && <span className="gn-v2-tree-count">{metaText}</span>}
|
||||
</span>
|
||||
{tablePinAction}
|
||||
</>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user