mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-11 09:13:36 +08:00
✨ feat(titlebar): 为新建操作提示平台快捷键
- 新建查询与新建连接悬浮提示展示当前生效快捷键 - 按 macOS 和 Windows 格式化组合键并跟随用户自定义绑定 - 快捷键禁用时回退操作名称并补齐平台与隔离回归测试
This commit is contained in:
@@ -7,7 +7,9 @@ import { arrayMove, SortableContext, useSortable, verticalListSortingStrategy }
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import { BrowserOpenURL, Environment, EventsOn, WindowFullscreen, WindowGetPosition, WindowGetSize, WindowIsFullscreen, WindowIsMaximised, WindowIsMinimised, WindowIsNormal, WindowMaximise, WindowMinimise, WindowSetDarkTheme, WindowSetLightTheme, WindowSetPosition, WindowSetSize, WindowSetSystemDefaultTheme, WindowUnfullscreen, WindowUnmaximise } from '../wailsjs/runtime';
|
||||
import Sidebar from './components/Sidebar';
|
||||
import TitleBarPrimaryActions from './components/TitleBarPrimaryActions';
|
||||
import TitleBarPrimaryActions, {
|
||||
resolveTitleBarPrimaryActionShortcut,
|
||||
} from './components/TitleBarPrimaryActions';
|
||||
import TabManager from './components/TabManager';
|
||||
import FloatingWorkbenchWindows from './components/FloatingWorkbenchWindows';
|
||||
import FloatingAIChatWindow from './components/FloatingAIChatWindow';
|
||||
@@ -2446,6 +2448,16 @@ function App() {
|
||||
|| (runtimePlatform === '' && /mac/i.test(detectNavigatorPlatform()));
|
||||
const useNativeMacWindowControls = isMacRuntime;
|
||||
const activeShortcutPlatform = getShortcutPlatform(isMacRuntime);
|
||||
const titleBarNewQueryShortcut = resolveTitleBarPrimaryActionShortcut(
|
||||
shortcutOptions,
|
||||
'newQueryTab',
|
||||
activeShortcutPlatform,
|
||||
);
|
||||
const titleBarNewConnectionShortcut = resolveTitleBarPrimaryActionShortcut(
|
||||
shortcutOptions,
|
||||
'newConnection',
|
||||
activeShortcutPlatform,
|
||||
);
|
||||
const macWindowDiagnosticsEnabled = shouldEnableMacWindowDiagnostics(
|
||||
isMacRuntime,
|
||||
import.meta.env.DEV,
|
||||
@@ -7856,6 +7868,8 @@ function App() {
|
||||
<TitleBarPrimaryActions
|
||||
newQueryLabel={t('query.new')}
|
||||
newConnectionLabel={t('connection.new')}
|
||||
newQueryShortcut={titleBarNewQueryShortcut}
|
||||
newConnectionShortcut={titleBarNewConnectionShortcut}
|
||||
onNewQuery={handleNewQuery}
|
||||
onNewConnection={handleCreateConnection}
|
||||
/>
|
||||
|
||||
@@ -3,7 +3,13 @@ import { readFileSync } from 'node:fs';
|
||||
import { create } from 'react-test-renderer';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import TitleBarPrimaryActions from './TitleBarPrimaryActions';
|
||||
import TitleBarPrimaryActions, {
|
||||
resolveTitleBarPrimaryActionShortcut,
|
||||
} from './TitleBarPrimaryActions';
|
||||
import {
|
||||
cloneShortcutOptions,
|
||||
DEFAULT_SHORTCUT_OPTIONS,
|
||||
} from '../utils/shortcuts';
|
||||
|
||||
const appCss = readFileSync(new URL('../App.css', import.meta.url), 'utf8');
|
||||
|
||||
@@ -45,10 +51,13 @@ describe('TitleBarPrimaryActions', () => {
|
||||
it('shows both labels in query-first order and invokes their actions', () => {
|
||||
const onNewQuery = vi.fn();
|
||||
const onNewConnection = vi.fn();
|
||||
const shortcutOptions = cloneShortcutOptions(DEFAULT_SHORTCUT_OPTIONS);
|
||||
const renderer = create(
|
||||
<TitleBarPrimaryActions
|
||||
newQueryLabel="新建查询"
|
||||
newConnectionLabel="新建连接"
|
||||
newQueryShortcut={resolveTitleBarPrimaryActionShortcut(shortcutOptions, 'newQueryTab', 'mac')}
|
||||
newConnectionShortcut={resolveTitleBarPrimaryActionShortcut(shortcutOptions, 'newConnection', 'mac')}
|
||||
onNewQuery={onNewQuery}
|
||||
onNewConnection={onNewConnection}
|
||||
/>,
|
||||
@@ -58,6 +67,10 @@ describe('TitleBarPrimaryActions', () => {
|
||||
const buttons = actions.findAllByType('button');
|
||||
expect(actions.props['data-no-titlebar-toggle']).toBe('true');
|
||||
expect(buttons.map((button) => button.props['aria-label'])).toEqual(['新建查询', '新建连接']);
|
||||
expect(buttons.map((button) => button.props.title)).toEqual([
|
||||
'新建查询 · ⌘N',
|
||||
'新建连接 · ⌘⇧N',
|
||||
]);
|
||||
expect(buttons.map((button) => button.children[button.children.length - 1])).toEqual(['新建查询', '新建连接']);
|
||||
|
||||
buttons[0].props.onClick();
|
||||
@@ -65,4 +78,56 @@ describe('TitleBarPrimaryActions', () => {
|
||||
expect(onNewQuery).toHaveBeenCalledTimes(1);
|
||||
expect(onNewConnection).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('shows both Windows shortcut labels', () => {
|
||||
const shortcutOptions = cloneShortcutOptions(DEFAULT_SHORTCUT_OPTIONS);
|
||||
const renderer = create(
|
||||
<TitleBarPrimaryActions
|
||||
newQueryLabel="New Query"
|
||||
newConnectionLabel="New Connection"
|
||||
newQueryShortcut={resolveTitleBarPrimaryActionShortcut(shortcutOptions, 'newQueryTab', 'windows')}
|
||||
newConnectionShortcut={resolveTitleBarPrimaryActionShortcut(shortcutOptions, 'newConnection', 'windows')}
|
||||
onNewQuery={vi.fn()}
|
||||
onNewConnection={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
const buttons = renderer.root.findAllByType('button');
|
||||
expect(buttons.map((button) => button.props.title)).toEqual([
|
||||
'New Query · Ctrl+N',
|
||||
'New Connection · Ctrl+Shift+N',
|
||||
]);
|
||||
});
|
||||
|
||||
it('uses current platform custom bindings and hides disabled shortcuts', () => {
|
||||
const shortcutOptions = cloneShortcutOptions(DEFAULT_SHORTCUT_OPTIONS);
|
||||
shortcutOptions.newQueryTab.mac = { combo: 'Meta+Alt+Q', enabled: true };
|
||||
shortcutOptions.newQueryTab.windows = { combo: 'Ctrl+Alt+W', enabled: true };
|
||||
shortcutOptions.newConnection.mac = { combo: 'Meta+Shift+C', enabled: false };
|
||||
shortcutOptions.newConnection.windows = { combo: 'Ctrl+Alt+C', enabled: true };
|
||||
|
||||
expect(resolveTitleBarPrimaryActionShortcut(shortcutOptions, 'newQueryTab', 'mac')).toBe('⌘⌥Q');
|
||||
expect(resolveTitleBarPrimaryActionShortcut(shortcutOptions, 'newQueryTab', 'windows')).toBe('Ctrl+Alt+W');
|
||||
expect(resolveTitleBarPrimaryActionShortcut(shortcutOptions, 'newConnection', 'mac')).toBeUndefined();
|
||||
expect(resolveTitleBarPrimaryActionShortcut(shortcutOptions, 'newConnection', 'windows')).toBe('Ctrl+Alt+C');
|
||||
|
||||
const renderer = create(
|
||||
<TitleBarPrimaryActions
|
||||
newQueryLabel="新建查询"
|
||||
newConnectionLabel="新建连接"
|
||||
newQueryShortcut={resolveTitleBarPrimaryActionShortcut(shortcutOptions, 'newQueryTab', 'mac')}
|
||||
newConnectionShortcut={resolveTitleBarPrimaryActionShortcut(shortcutOptions, 'newConnection', 'mac')}
|
||||
onNewQuery={vi.fn()}
|
||||
onNewConnection={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
const buttons = renderer.root.findAllByType('button');
|
||||
expect(buttons.map((button) => button.props.title)).toEqual([
|
||||
'新建查询 · ⌘⌥Q',
|
||||
'新建连接',
|
||||
]);
|
||||
expect(buttons.map((button) => button.props['aria-label'])).toEqual(['新建查询', '新建连接']);
|
||||
expect(buttons.every((button) => button.props.disabled !== true)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,16 +1,43 @@
|
||||
import React from 'react';
|
||||
import { ConsoleSqlOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
getShortcutDisplayLabel,
|
||||
resolveShortcutBinding,
|
||||
type ShortcutOptions,
|
||||
type ShortcutPlatform,
|
||||
} from '../utils/shortcuts';
|
||||
|
||||
type TitleBarPrimaryShortcutAction = 'newQueryTab' | 'newConnection';
|
||||
|
||||
export const resolveTitleBarPrimaryActionShortcut = (
|
||||
shortcutOptions: Partial<ShortcutOptions> | null | undefined,
|
||||
action: TitleBarPrimaryShortcutAction,
|
||||
platform: ShortcutPlatform,
|
||||
): string | undefined => {
|
||||
const binding = resolveShortcutBinding(shortcutOptions, action, platform);
|
||||
return binding.enabled && binding.combo
|
||||
? getShortcutDisplayLabel(binding.combo, platform)
|
||||
: undefined;
|
||||
};
|
||||
|
||||
interface TitleBarPrimaryActionsProps {
|
||||
newQueryLabel: string;
|
||||
newConnectionLabel: string;
|
||||
newQueryShortcut?: string;
|
||||
newConnectionShortcut?: string;
|
||||
onNewQuery: () => void;
|
||||
onNewConnection: () => void;
|
||||
}
|
||||
|
||||
const getActionTitle = (label: string, shortcut?: string): string => (
|
||||
shortcut ? `${label} \u00b7 ${shortcut}` : label
|
||||
);
|
||||
|
||||
const TitleBarPrimaryActions: React.FC<TitleBarPrimaryActionsProps> = ({
|
||||
newQueryLabel,
|
||||
newConnectionLabel,
|
||||
newQueryShortcut,
|
||||
newConnectionShortcut,
|
||||
onNewQuery,
|
||||
onNewConnection,
|
||||
}) => (
|
||||
@@ -24,7 +51,7 @@ const TitleBarPrimaryActions: React.FC<TitleBarPrimaryActionsProps> = ({
|
||||
type="button"
|
||||
className="gonavi-titlebar-primary-action"
|
||||
aria-label={newQueryLabel}
|
||||
title={newQueryLabel}
|
||||
title={getActionTitle(newQueryLabel, newQueryShortcut)}
|
||||
data-gonavi-new-query-action="true"
|
||||
onClick={onNewQuery}
|
||||
>
|
||||
@@ -35,7 +62,7 @@ const TitleBarPrimaryActions: React.FC<TitleBarPrimaryActionsProps> = ({
|
||||
type="button"
|
||||
className="gonavi-titlebar-primary-action"
|
||||
aria-label={newConnectionLabel}
|
||||
title={newConnectionLabel}
|
||||
title={getActionTitle(newConnectionLabel, newConnectionShortcut)}
|
||||
data-gonavi-create-connection-action="true"
|
||||
onClick={onNewConnection}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user