mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-07 07:03:34 +08:00
✨ feat(tab): 增加查询标签右键重命名 (#844)
## 变更说明 - 在查询 Tab 的右键菜单中增加“重命名查询”入口。 - 复用 QueryEditor 现有的重命名流程,保持保存记录、Tab 标题和未保存查询引导行为一致。 - 右键非当前查询 Tab 时先激活目标 Tab,再打开对应的重命名窗口。 - SQL 文件 Tab 保留现有不可重命名限制,表数据等非查询 Tab 不显示该菜单项。 - 通过带 tabId 的事件只通知目标 QueryEditor,避免误操作其他查询 Tab。 ## 测试 - 定向测试:325 passed - npm run build:TypeScript 和 Vite 构建通过 - Wails Windows 测试包:GoNavi-issue-835-query-tab-rename.exe - 已完成人工测试验证 Fixes #835
This commit is contained in:
@@ -10,6 +10,7 @@ import type { SavedQuery, TabData } from '../types';
|
||||
import { ORACLE_ROWID_LOCATOR_COLUMN } from '../utils/rowLocator';
|
||||
import { setGlobalImeCompositionActive } from '../utils/shortcuts';
|
||||
import { clearQueryEditorResultSession } from '../utils/queryEditorResultSessionCache';
|
||||
import { QUERY_TAB_RENAME_REQUEST_EVENT } from '../utils/queryTabTitle';
|
||||
import { clearQueryTabDraft, clearSQLFileTabDraft, getQueryTabDraft, getSQLFileTabDraft } from '../utils/sqlFileTabDrafts';
|
||||
import { clearQueryEditorInlineRuntimeReadinessCache } from './queryEditor/QueryEditorAiAssist';
|
||||
import QueryEditor, {
|
||||
@@ -10208,6 +10209,43 @@ END;`;
|
||||
expect(messageApi.success).toHaveBeenCalledWith('查询已重命名。');
|
||||
});
|
||||
|
||||
it('opens the existing rename flow for the query tab context-menu request', async () => {
|
||||
storeState.savedQueries = [
|
||||
{
|
||||
id: 'saved-1',
|
||||
name: '常用查询',
|
||||
sql: 'select 1;',
|
||||
connectionId: 'conn-1',
|
||||
dbName: 'main',
|
||||
createdAt: 100,
|
||||
},
|
||||
];
|
||||
|
||||
let renderer!: ReactTestRenderer;
|
||||
await act(async () => {
|
||||
renderer = create(<QueryEditor tab={createTab({ savedQueryId: 'saved-1' })} />);
|
||||
});
|
||||
|
||||
const renameRequestListenerCalls = (window.addEventListener as any).mock.calls
|
||||
.filter(([eventName]: [string]) => eventName === QUERY_TAB_RENAME_REQUEST_EVENT);
|
||||
const renameRequestListener = renameRequestListenerCalls[renameRequestListenerCalls.length - 1]?.[1];
|
||||
expect(renameRequestListener).toBeTypeOf('function');
|
||||
|
||||
await act(async () => {
|
||||
renameRequestListener(new CustomEvent(QUERY_TAB_RENAME_REQUEST_EVENT, {
|
||||
detail: { tabId: 'another-tab' },
|
||||
}));
|
||||
});
|
||||
expect(findExactButton(renderer!, '重命名')).toBeUndefined();
|
||||
|
||||
await act(async () => {
|
||||
renameRequestListener(new CustomEvent(QUERY_TAB_RENAME_REQUEST_EVENT, {
|
||||
detail: { tabId: 'tab-1' },
|
||||
}));
|
||||
});
|
||||
expect(findExactButton(renderer!, '重命名')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('exports the current editor SQL without changing saved query state', async () => {
|
||||
storeState.savedQueries = [
|
||||
{
|
||||
|
||||
@@ -57,7 +57,7 @@ import {
|
||||
import { resolveUniqueKeyGroupsFromIndexes } from './dataGridCopyInsert';
|
||||
import { t as translate } from '../i18n';
|
||||
import { buildSqlAnalysisWorkbenchTab } from '../utils/sqlAnalysisTab';
|
||||
import { isLocalizedUntitledQueryTitle } from '../utils/queryTabTitle';
|
||||
import { isLocalizedUntitledQueryTitle, QUERY_TAB_RENAME_REQUEST_EVENT } from '../utils/queryTabTitle';
|
||||
import { buildSqlServerObjectDefinitionQueries } from '../utils/sqlServerObjectDefinition';
|
||||
import { formatDdlForDisplay } from '../utils/ddlFormat';
|
||||
import {
|
||||
@@ -9680,6 +9680,20 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
openSaveQueryModal('rename');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleRenameQueryRequest = (event: Event) => {
|
||||
if (!(event instanceof CustomEvent) || event.detail?.tabId !== tab.id) {
|
||||
return;
|
||||
}
|
||||
handleRenameQuery();
|
||||
};
|
||||
|
||||
window.addEventListener(QUERY_TAB_RENAME_REQUEST_EVENT, handleRenameQueryRequest as EventListener);
|
||||
return () => {
|
||||
window.removeEventListener(QUERY_TAB_RENAME_REQUEST_EVENT, handleRenameQueryRequest as EventListener);
|
||||
};
|
||||
}, [handleRenameQuery, tab.id]);
|
||||
|
||||
const handleExportSQLFile = async () => {
|
||||
try {
|
||||
const res = await ExportSQLFile(currentSavedQuery?.name || resolveDefaultQueryName(), getCurrentQuery());
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
handleTabDragPointerDown,
|
||||
resolveTabHoverOpen,
|
||||
resolveTabHoverTitle,
|
||||
resolveQueryTabRenameMenuState,
|
||||
shouldShowV2ConnectionLabel,
|
||||
TabHoverInfo,
|
||||
isMiddleMouseButton,
|
||||
@@ -118,6 +119,21 @@ describe('TabManager hover info', () => {
|
||||
expect(isMiddleMouseButton(2)).toBe(false);
|
||||
});
|
||||
|
||||
it('shows query rename only for query tabs and disables SQL file tabs', () => {
|
||||
expect(resolveQueryTabRenameMenuState({ type: 'query' })).toEqual({
|
||||
visible: true,
|
||||
disabled: false,
|
||||
});
|
||||
expect(resolveQueryTabRenameMenuState({ type: 'query', filePath: 'D:/queries/report.sql' })).toEqual({
|
||||
visible: true,
|
||||
disabled: true,
|
||||
});
|
||||
expect(resolveQueryTabRenameMenuState({ type: 'table' })).toEqual({
|
||||
visible: false,
|
||||
disabled: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps the tab workbench as a full-height flex child in legacy and v2 UI', () => {
|
||||
|
||||
expect(TAB_WORKBENCH_CLASS_NAME).toBe('tab-workbench');
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Modal from './common/ResizableDraggableModal';
|
||||
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Button, Dropdown, message, Tabs, Tooltip } from 'antd';
|
||||
import { CloseOutlined, ConsoleSqlOutlined, DatabaseOutlined, FileTextOutlined, FolderOpenOutlined, HistoryOutlined, PlusOutlined, PushpinOutlined, RightOutlined, RobotOutlined, SearchOutlined, SettingOutlined } from '@ant-design/icons';
|
||||
import { CloseOutlined, ConsoleSqlOutlined, DatabaseOutlined, EditOutlined, FileTextOutlined, FolderOpenOutlined, HistoryOutlined, PlusOutlined, PushpinOutlined, RightOutlined, RobotOutlined, SearchOutlined, SettingOutlined } from '@ant-design/icons';
|
||||
import type { MenuProps, TabsProps } from 'antd';
|
||||
import { DndContext, PointerSensor, closestCenter, useSensor, useSensors } from '@dnd-kit/core';
|
||||
import type { DragEndEvent, DragMoveEvent, DragStartEvent } from '@dnd-kit/core';
|
||||
@@ -47,6 +47,7 @@ import { openNativeWorkbenchTabWindow } from '../utils/nativeDetachedWindowHost'
|
||||
import { useWorkbenchTabs } from '../hooks/useWorkbenchTabs';
|
||||
import { resolveConnectionEnvironmentPresentation } from '../utils/connectionEnvironment';
|
||||
import { createSidebarResizeAwareFrameScheduler } from '../utils/sidebarResizeLifecycle';
|
||||
import { QUERY_TAB_RENAME_REQUEST_EVENT } from '../utils/queryTabTitle';
|
||||
|
||||
const getTabKindLabel = (tab: TabData): string => {
|
||||
if (tab.type === 'query') return t('tab_manager.kind_badge.query');
|
||||
@@ -78,6 +79,13 @@ export const isBackgroundTaskWorkbenchTab = (tab: Pick<TabData, 'type'>): boolea
|
||||
tab.type === 'table-export' || tab.type === 'data-import' || tab.type === 'data-sync'
|
||||
);
|
||||
|
||||
export const resolveQueryTabRenameMenuState = (
|
||||
tab: Pick<TabData, 'type' | 'filePath'>,
|
||||
): { visible: boolean; disabled: boolean } => ({
|
||||
visible: tab.type === 'query',
|
||||
disabled: Boolean(tab.filePath),
|
||||
});
|
||||
|
||||
export const isRunningDataImportWorkbenchTab = (
|
||||
tab: Pick<TabData, 'type' | 'dataImportRunning'>,
|
||||
): boolean => tab.type === 'data-import' && tab.dataImportRunning === true;
|
||||
@@ -1254,8 +1262,23 @@ const TabManager: React.FC<TabManagerProps> = React.memo<TabManagerProps>(({ onF
|
||||
const displayTitle = displayModel.fullTitle;
|
||||
const hostSummary = resolveConnectionHostSummary(connection?.config);
|
||||
const tabIsActive = tab.id === dockedActiveTabId;
|
||||
const renameQueryMenuState = resolveQueryTabRenameMenuState(tab);
|
||||
|
||||
const menuItems: MenuProps['items'] = [
|
||||
...(renameQueryMenuState.visible ? [{
|
||||
key: 'rename-query',
|
||||
icon: <EditOutlined />,
|
||||
label: t('query_editor.action.rename_query'),
|
||||
disabled: renameQueryMenuState.disabled,
|
||||
onClick: () => {
|
||||
setActiveTab(tab.id);
|
||||
window.setTimeout(() => {
|
||||
window.dispatchEvent(new CustomEvent(QUERY_TAB_RENAME_REQUEST_EVENT, {
|
||||
detail: { tabId: tab.id },
|
||||
}));
|
||||
}, 0);
|
||||
},
|
||||
}] : []),
|
||||
{
|
||||
key: 'tab-display-settings',
|
||||
icon: <SettingOutlined />,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { SUPPORTED_LANGUAGES } from '../i18n/resolveLanguage';
|
||||
import type { I18nParams } from '../i18n/types';
|
||||
|
||||
const UNTITLED_QUERY_DATABASE_PLACEHOLDER = '__GONAVI_QUERY_DATABASE__';
|
||||
export const QUERY_TAB_RENAME_REQUEST_EVENT = 'gonavi:request-query-tab-rename';
|
||||
const UNTITLED_QUERY_TITLE_KEYS = [
|
||||
'query.new',
|
||||
'sidebar.tab.new_query',
|
||||
|
||||
Reference in New Issue
Block a user