diff --git a/src/containers/AsideNavigation/AsideNavigation.tsx b/src/containers/AsideNavigation/AsideNavigation.tsx index c17cd18d4..cec789d2a 100644 --- a/src/containers/AsideNavigation/AsideNavigation.tsx +++ b/src/containers/AsideNavigation/AsideNavigation.tsx @@ -2,10 +2,8 @@ import React from 'react'; import {CircleQuestion, Gear, Person} from '@gravity-ui/icons'; import type {MenuItem} from '@gravity-ui/navigation'; -import {AsideHeader, FooterItem, HotkeysPanel} from '@gravity-ui/navigation'; -import {Hotkey} from '@gravity-ui/uikit'; +import {AsideHeader, FooterItem} from '@gravity-ui/navigation'; import type {IconData} from '@gravity-ui/uikit'; -import hotkeys from 'hotkeys-js'; import {useHistory} from 'react-router-dom'; import {cn} from '../../utils/cn'; @@ -13,7 +11,7 @@ import {ASIDE_HEADER_COMPACT_KEY} from '../../utils/constants'; import {useSetting} from '../../utils/hooks'; import {InformationPopup} from './InformationPopup'; -import {HOTKEYS, SHORTCUTS_HOTKEY} from './constants'; +import {useHotkeysPanel} from './hooks/useHotkeysPanel'; import i18n from './i18n'; import userSecret from '../../assets/icons/user-secret.svg'; @@ -69,45 +67,6 @@ enum Panel { Hotkeys = 'Hotkeys', } -/** - * HotkeysPanelWrapper creates a render cycle separation between mounting and visibility change. - * This is necessary for smooth animations as HotkeysPanel uses CSSTransition internally. - * - * When a component is both mounted and set to visible at once, CSSTransition can't - * properly sequence its transition classes (panel → panel-active) because it's already active when mounted - * and counts transition as it has already happened. - * This wrapper ensures the component mounts first, then sets visible=true in a subsequent render cycle - * to make transition actually happen. - */ -function HotkeysPanelWrapper({ - visiblePanel, - closePanel, -}: { - visiblePanel?: Panel; - closePanel: () => void; -}) { - const [visible, setVisible] = React.useState(false); - - React.useEffect(() => { - setVisible(visiblePanel === Panel.Hotkeys); - }, [visiblePanel]); - - return ( - - {i18n('help-center.footer.shortcuts')} - - - } - onClose={closePanel} - /> - ); -} - export function AsideNavigation(props: AsideNavigationProps) { const history = useHistory(); @@ -128,23 +87,16 @@ export function AsideNavigation(props: AsideNavigationProps) { setVisiblePanel(undefined); }, []); + const {renderPanel: renderHotkeysPanel} = useHotkeysPanel({ + isPanelVisible: visiblePanel === Panel.Hotkeys, + closePanel, + openPanel: openHotkeysPanel, + }); + const renderInformationPopup = () => { return ; }; - React.useEffect(() => { - // Register hotkey for keyboard shortcuts - hotkeys(SHORTCUTS_HOTKEY, openHotkeysPanel); - - // Add listener for custom event from Monaco editor - window.addEventListener('openKeyboardShortcutsPanel', openHotkeysPanel); - - return () => { - hotkeys.unbind(SHORTCUTS_HOTKEY); - window.removeEventListener('openKeyboardShortcutsPanel', openHotkeysPanel); - }; - }, [openHotkeysPanel]); - return ( - ), + content: renderHotkeysPanel(), }, ]} onClosePanel={closePanel} diff --git a/src/containers/AsideNavigation/InformationPopup/InformationPopup.tsx b/src/containers/AsideNavigation/InformationPopup/InformationPopup.tsx index d62df84c3..e390d6221 100644 --- a/src/containers/AsideNavigation/InformationPopup/InformationPopup.tsx +++ b/src/containers/AsideNavigation/InformationPopup/InformationPopup.tsx @@ -4,7 +4,7 @@ import {Flex, Hotkey, Icon, Link, List, Text} from '@gravity-ui/uikit'; import {settingsManager} from '../../../services/settings'; import {cn} from '../../../utils/cn'; import {LANGUAGE_KEY} from '../../../utils/constants'; -import {SHORTCUTS_HOTKEY} from '../constants'; +import {SHORTCUTS_HOTKEY} from '../hooks/useHotkeysPanel'; import i18n from '../i18n'; import './InformationPopup.scss'; diff --git a/src/containers/AsideNavigation/constants.tsx b/src/containers/AsideNavigation/constants.tsx deleted file mode 100644 index 89b70451f..000000000 --- a/src/containers/AsideNavigation/constants.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import {isMac} from './utils'; - -export const SHORTCUTS_HOTKEY = isMac() ? 'cmd+K' : 'ctrl+K'; - -export const HOTKEYS = [ - { - title: 'Query Editor', - items: [ - { - title: 'Execute query', - value: isMac() ? 'cmd+enter' : 'ctrl+enter', - }, - { - title: 'Execute selected query', - value: isMac() ? 'cmd+shift+enter' : 'ctrl+shift+enter', - }, - { - title: 'Previous query', - value: isMac() ? 'cmd+arrowUp' : 'ctrl+arrowUp', - }, - { - title: 'Next query', - value: isMac() ? 'cmd+arrowDown' : 'ctrl+arrowDown', - }, - { - title: 'Save query', - value: isMac() ? 'cmd+s' : 'ctrl+s', - }, - { - title: 'Save selected query', - value: isMac() ? 'cmd+shift+s' : 'ctrl+shift+s', - }, - ], - }, -]; diff --git a/src/containers/AsideNavigation/hooks/useHotkeysPanel.tsx b/src/containers/AsideNavigation/hooks/useHotkeysPanel.tsx new file mode 100644 index 000000000..c2e4fa58b --- /dev/null +++ b/src/containers/AsideNavigation/hooks/useHotkeysPanel.tsx @@ -0,0 +1,112 @@ +import React from 'react'; + +import {HotkeysPanel as UIKitHotkeysPanel} from '@gravity-ui/navigation'; +import {Hotkey} from '@gravity-ui/uikit'; +import hotkeys from 'hotkeys-js'; + +import {cn} from '../../../utils/cn'; +import i18n from '../i18n'; + +const b = cn('kv-navigation'); + +export const isMac = () => navigator.platform.toUpperCase().includes('MAC'); + +export const SHORTCUTS_HOTKEY = isMac() ? 'cmd+K' : 'ctrl+K'; + +export const HOTKEYS = [ + { + title: 'Query Editor', + items: [ + { + title: i18n('hotkeys.execute-query'), + value: isMac() ? 'cmd+enter' : 'ctrl+enter', + }, + { + title: i18n('hotkeys.execute-selected-query'), + value: isMac() ? 'cmd+shift+enter' : 'ctrl+shift+enter', + }, + { + title: i18n('hotkeys.previous-query'), + value: isMac() ? 'cmd+arrowUp' : 'ctrl+arrowUp', + }, + { + title: i18n('hotkeys.next-query'), + value: isMac() ? 'cmd+arrowDown' : 'ctrl+arrowDown', + }, + { + title: i18n('hotkeys.save-query'), + value: isMac() ? 'cmd+s' : 'ctrl+s', + }, + { + title: i18n('hotkeys.save-selected-query'), + value: isMac() ? 'cmd+shift+s' : 'ctrl+shift+s', + }, + ], + }, +]; + +export interface HotkeysPanelProps { + visible: boolean; + closePanel: () => void; +} + +/** + * HotkeysPanelWrapper creates a render cycle separation between mounting and visibility change. + * This is necessary for smooth animations as HotkeysPanel uses CSSTransition internally. + * + * When a component is both mounted and set to visible at once, CSSTransition can't + * properly sequence its transition classes (panel → panel-active) because it's already active when mounted + * and counts transition as it has already happened. + * This wrapper ensures the component mounts first, then sets visible=true in a subsequent render cycle + * to make transition actually happen. + */ +export const HotkeysPanelWrapper = ({visible: propsVisible, closePanel}: HotkeysPanelProps) => { + const [visible, setVisible] = React.useState(false); + + React.useEffect(() => { + setVisible(propsVisible); + }, [propsVisible]); + + return ( + + {i18n('hotkeys.title')} + + + } + onClose={closePanel} + /> + ); +}; + +interface UseHotkeysPanel { + isPanelVisible: boolean; + openPanel: () => void; + closePanel: () => void; +} + +export const useHotkeysPanel = ({isPanelVisible, openPanel, closePanel}: UseHotkeysPanel) => { + React.useEffect(() => { + hotkeys(SHORTCUTS_HOTKEY, openPanel); + + window.addEventListener('openKeyboardShortcutsPanel', openPanel); + + return () => { + hotkeys.unbind(SHORTCUTS_HOTKEY); + window.removeEventListener('openKeyboardShortcutsPanel', openPanel); + }; + }, [openPanel]); + + const renderPanel = React.useCallback( + () => , + [isPanelVisible, closePanel], + ); + + return { + renderPanel, + }; +}; diff --git a/src/containers/AsideNavigation/i18n/en.json b/src/containers/AsideNavigation/i18n/en.json index 22dd6b7c8..93e70b850 100644 --- a/src/containers/AsideNavigation/i18n/en.json +++ b/src/containers/AsideNavigation/i18n/en.json @@ -1,5 +1,5 @@ { - "navigation-item.information": "information", + "navigation-item.information": "Information", "navigation-item.settings": "Settings", "navigation-item.account": "Account", @@ -10,5 +10,13 @@ "account.user": "YDB User", "account.login": "Login", - "account.logout": "Logout" + "account.logout": "Logout", + + "hotkeys.title": "Keyboard shortcuts", + "hotkeys.execute-query": "Execute query", + "hotkeys.execute-selected-query": "Execute selected query", + "hotkeys.previous-query": "Previous query", + "hotkeys.next-query": "Next query", + "hotkeys.save-query": "Save query", + "hotkeys.save-selected-query": "Save selected query" } diff --git a/src/containers/AsideNavigation/i18n/ru.json b/src/containers/AsideNavigation/i18n/ru.json new file mode 100644 index 000000000..11fc72f84 --- /dev/null +++ b/src/containers/AsideNavigation/i18n/ru.json @@ -0,0 +1,22 @@ +{ + "navigation-item.information": "Информация", + "navigation-item.settings": "Настройки", + "navigation-item.account": "Аккаунт", + + "help-center.header.title": "Документация", + "help-center.item.documentation": "Посмотреть документацию", + "help-center.footer.shortcuts": "Горячие клавиши", + + "account.user": "Пользователь YDB", + + "account.login": "Войти", + "account.logout": "Выйти", + + "hotkeys.title": "Быстрые клавиши", + "hotkeys.execute-query": "Выполнить запрос", + "hotkeys.execute-selected-query": "Выполнить выбранный запрос", + "hotkeys.previous-query": "Предыдущий запрос", + "hotkeys.next-query": "Следующий запрос", + "hotkeys.save-query": "Сохранить запрос", + "hotkeys.save-selected-query": "Сохранить выбранный запрос" +} diff --git a/src/containers/AsideNavigation/utils.ts b/src/containers/AsideNavigation/utils.ts deleted file mode 100644 index 6125308da..000000000 --- a/src/containers/AsideNavigation/utils.ts +++ /dev/null @@ -1 +0,0 @@ -export const isMac = () => navigator.platform.toUpperCase().includes('MAC');