🐛 fix(sidebar): 将表置顶星标改为只读状态指示

- 仅为已置顶表展示非交互实心星标
- 保留右键菜单中的置顶与取消置顶入口
- 补充侧边栏置顶指示器回归测试
This commit is contained in:
Syngnat
2026-07-24 20:20:23 +08:00
parent 0c9e807674
commit cbca312a50
5 changed files with 63 additions and 83 deletions

View File

@@ -2292,17 +2292,45 @@ describe('Sidebar locate toolbar', () => {
}).map((entry) => entry.tableName)).toEqual(['orders', 'users', 'audit']);
});
it('keeps the v2 table pin action on sidebar table rows', () => {
const source = readSidebarSource();
it('renders a non-interactive pin indicator only for pinned v2 sidebar tables', () => {
const source = readSourceFile('./sidebar/SidebarTreeTitle.tsx');
const css = readV2ThemeCss();
const baseOptions = {
hoverTitle: 'orders',
statusBadge: null,
getV2TreeMetaText: () => '',
sidebarTableMetadataFields: [],
snapshotTreeSelectionBeforeDrag: vi.fn(),
restoreTreeSelectionAfterDrag: vi.fn(),
treeDragSelectSuppressUntilRef: { current: 0 },
setIsTreeDragging: vi.fn(),
};
const renderTableTitle = (pinnedSidebarTable: boolean) => renderToStaticMarkup(renderSidebarV2TreeTitle({
...baseOptions,
node: {
type: 'table',
title: 'orders',
key: 'conn-main-orders',
dataRef: {
id: 'conn',
dbName: 'main',
tableName: 'orders',
pinnedSidebarTable,
},
},
}));
expect(source).toContain('data-v2-sidebar-table-pin-action="true"');
expect(source).toContain('node?.dataRef?.pinnedSidebarTable ? <StarFilled /> : <StarOutlined />');
expect(source).toContain('toggleSidebarTablePinned(node);');
expect(source).toContain("message.success(shouldPin ? t('sidebar.message.table_pinned') : t('sidebar.message.table_unpinned'));");
expect(css).toMatch(/\.gn-v2-table-pin-action \{[^}]*opacity: 0;/s);
expect(css).toMatch(/\.gn-v2-table-pin-action\.is-pinned \{[^}]*color: #f59e0b;[^}]*opacity: 1;/s);
expect(css).toMatch(/\.ant-tree-node-content-wrapper:hover \.gn-v2-table-pin-action,/s);
const unpinnedMarkup = renderTableTitle(false);
const pinnedMarkup = renderTableTitle(true);
expect(unpinnedMarkup).not.toContain('data-v2-sidebar-table-pin-indicator');
expect(pinnedMarkup).toContain('data-v2-sidebar-table-pin-indicator="true"');
expect(pinnedMarkup).toContain(`aria-label="${t('sidebar.status.pinned')}"`);
expect(pinnedMarkup).not.toContain('<button');
expect(pinnedMarkup).not.toContain('aria-pressed');
expect(source).not.toContain('toggleSidebarTablePinned');
expect(source).not.toContain('StarOutlined');
expect(css).toMatch(/\.gn-v2-table-pin-indicator \{[^}]*pointer-events: none;[^}]*cursor: default;[^}]*color: var\(--gn-warn\);/s);
expect(css).not.toContain('.gn-v2-table-pin-action');
});
it('splits v2 sidebar pinned tables into a dedicated table section', () => {
@@ -2887,23 +2915,19 @@ describe('Sidebar locate toolbar', () => {
expect(source).toContain("t('connection.sidebar.delete.failureFallback')");
});
it('localizes the sidebar table pin action title and aria-label via i18n keys', () => {
const source = readSidebarSource();
const tablePinActionStart = source.indexOf("const tablePinAction = node.type === 'table' ? (");
const tablePinActionEnd = source.indexOf('aria-pressed=', tablePinActionStart);
const tablePinActionSource = source.slice(tablePinActionStart, tablePinActionEnd);
const normalizedTablePinActionSource = tablePinActionSource.replace(/\s+/g, ' ');
it('localizes the sidebar table pin indicator via the pinned status key', () => {
const source = readSourceFile('./sidebar/SidebarTreeTitle.tsx');
const tablePinIndicatorStart = source.indexOf("const tablePinIndicator = node.type === 'table'");
const tablePinIndicatorEnd = source.indexOf('</span>', tablePinIndicatorStart);
const tablePinIndicatorSource = source.slice(tablePinIndicatorStart, tablePinIndicatorEnd);
const normalizedTablePinIndicatorSource = tablePinIndicatorSource.replace(/\s+/g, ' ');
expect(tablePinActionStart).toBeGreaterThanOrEqual(0);
expect(tablePinActionEnd).toBeGreaterThan(tablePinActionStart);
expect(normalizedTablePinActionSource).toContain(
"title={node?.dataRef?.pinnedSidebarTable ? t('sidebar.action.unpin_table') : t('sidebar.action.pin_table')}",
);
expect(normalizedTablePinActionSource).toContain(
"aria-label={node?.dataRef?.pinnedSidebarTable ? t('sidebar.action.unpin_table') : t('sidebar.action.pin_table')}",
);
expect(tablePinActionSource).not.toContain("'取消置顶表'");
expect(tablePinActionSource).not.toContain("'置顶表'");
expect(tablePinIndicatorStart).toBeGreaterThanOrEqual(0);
expect(tablePinIndicatorEnd).toBeGreaterThan(tablePinIndicatorStart);
expect(normalizedTablePinIndicatorSource).toContain("title={t('sidebar.status.pinned')}");
expect(normalizedTablePinIndicatorSource).toContain("aria-label={t('sidebar.status.pinned')}");
expect(tablePinIndicatorSource).not.toContain('sidebar.action.pin_table');
expect(tablePinIndicatorSource).not.toContain('sidebar.action.unpin_table');
});
it('localizes legacy sidebar connection and redis menu labels', () => {
@@ -3324,7 +3348,6 @@ describe('Sidebar locate toolbar', () => {
hoverTitle: 'users',
statusBadge: null,
getV2TreeMetaText: () => '',
toggleSidebarTablePinned: vi.fn(),
snapshotTreeSelectionBeforeDrag: vi.fn(),
restoreTreeSelectionAfterDrag: vi.fn(),
treeDragSelectSuppressUntilRef: { current: 0 },

View File

@@ -2388,7 +2388,6 @@ const Sidebar: React.FC<{
const {
getConnectionNodeForAction,
toggleSidebarTablePinned,
handleV2TableContextMenuAction,
handleTableGroupSortAction,
handleV2TableGroupContextMenuAction,
@@ -2577,8 +2576,6 @@ const Sidebar: React.FC<{
refreshV2TableContextMenuStatsRef.current = refreshV2TableContextMenuStats;
const getV2TreeMetaTextRef = useRef(getV2TreeMetaText);
getV2TreeMetaTextRef.current = getV2TreeMetaText;
const toggleSidebarTablePinnedRef = useRef(toggleSidebarTablePinned);
toggleSidebarTablePinnedRef.current = toggleSidebarTablePinned;
const renderV2TreeTitle = useCallback((node: any, hoverTitle: string, statusBadge: React.ReactNode) => renderSidebarV2TreeTitle({
node,
@@ -2586,7 +2583,6 @@ const Sidebar: React.FC<{
statusBadge,
getV2TreeMetaText: getV2TreeMetaTextRef.current,
sidebarTableMetadataFields,
toggleSidebarTablePinned: toggleSidebarTablePinnedRef.current,
snapshotTreeSelectionBeforeDrag,
restoreTreeSelectionAfterDrag,
treeDragSelectSuppressUntilRef,

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { Tooltip } from 'antd';
import { StarFilled, StarOutlined } from '@ant-design/icons';
import { StarFilled } from '@ant-design/icons';
import { t } from '../../i18n';
import { SIDEBAR_SQL_EDITOR_DRAG_MIME, encodeSidebarSqlEditorDragPayload } from '../../utils/sidebarSqlDrag';
import {
@@ -25,7 +25,6 @@ type SidebarV2TreeTitleOptions = {
statusBadge: React.ReactNode;
getV2TreeMetaText: (node: any) => string;
sidebarTableMetadataFields: SidebarTableMetadataField[];
toggleSidebarTablePinned: (node: any) => void;
snapshotTreeSelectionBeforeDrag: () => void;
restoreTreeSelectionAfterDrag: () => void;
treeDragSelectSuppressUntilRef: React.MutableRefObject<number>;
@@ -116,7 +115,6 @@ export const renderSidebarV2TreeTitle = ({
statusBadge,
getV2TreeMetaText,
sidebarTableMetadataFields,
toggleSidebarTablePinned,
snapshotTreeSelectionBeforeDrag,
restoreTreeSelectionAfterDrag,
treeDragSelectSuppressUntilRef,
@@ -178,33 +176,16 @@ export const renderSidebarV2TreeTitle = ({
node.type === 'redis-db' ? 'is-redis-db' : '',
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();
}}
const tablePinIndicator = node.type === 'table' && node?.dataRef?.pinnedSidebarTable ? (
<span
className="gn-v2-table-pin-indicator"
title={t('sidebar.status.pinned')}
role="img"
aria-label={t('sidebar.status.pinned')}
data-v2-sidebar-table-pin-indicator="true"
>
{node?.dataRef?.pinnedSidebarTable ? <StarFilled /> : <StarOutlined />}
</button>
<StarFilled aria-hidden="true" />
</span>
) : null;
if (node.type === 'connection') {
return (
@@ -287,7 +268,7 @@ export const renderSidebarV2TreeTitle = ({
return (
<>
{wrappedTitleNode}
{tablePinAction}
{tablePinIndicator}
</>
);
};

View File

@@ -558,7 +558,6 @@ export const useSidebarV2ActionHandlers = ({
return {
getConnectionNodeForAction,
toggleSidebarTablePinned,
handleV2TableContextMenuAction,
handleTableGroupSortAction,
handleV2TableGroupContextMenuAction,

View File

@@ -3117,7 +3117,7 @@ body[data-ui-version="v2"] .gn-v2-tree-count {
opacity: 0.82;
}
body[data-ui-version="v2"] .gn-v2-table-pin-action {
body[data-ui-version="v2"] .gn-v2-table-pin-indicator {
width: 18px;
height: 18px;
flex: 0 0 18px;
@@ -3126,28 +3126,9 @@ body[data-ui-version="v2"] .gn-v2-table-pin-action {
justify-content: center;
margin-left: 2px;
padding: 0;
border: 0;
border-radius: 4px;
background: transparent;
color: var(--gn-fg-5);
cursor: pointer;
opacity: 0;
transition: opacity 0.12s ease, color 0.12s ease, background 0.12s ease;
}
body[data-ui-version="v2"] .gn-v2-table-pin-action:hover {
background: var(--gn-bg-hover);
color: var(--gn-fg-2);
}
body[data-ui-version="v2"] .gn-v2-table-pin-action.is-pinned {
pointer-events: none;
cursor: default;
color: var(--gn-warn);
opacity: 1;
}
body[data-ui-version="v2"] .ant-tree-node-content-wrapper:hover .gn-v2-table-pin-action,
body[data-ui-version="v2"] .gn-v2-table-pin-action:focus-visible {
opacity: 1;
}
body[data-ui-version="v2"] .gn-v2-object-explorer .ant-tree .ant-tree-node-content-wrapper.ant-tree-node-selected::before {