11import { AsideHeader , MenuItem as GravityMenuItem } from '@gravity-ui/navigation' ;
22import classNames from 'classnames' ;
3- import React , { ReactNode , useState } from 'react' ;
3+ import React , { ReactNode , useCallback , useState } from 'react' ;
44import { useDispatch , useSelector } from 'react-redux' ;
55import { matchPath , useLocation , useNavigate } from 'react-router-dom' ;
66
77import { getIsInitialized } from '@/static/new-ui/store/selectors' ;
88import { SettingsPanel } from '@/static/new-ui/components/SettingsPanel' ;
9+ import { HotkeysPanel } from '@/static/new-ui/components/HotkeysPanel' ;
910import TestplaneIcon from '../../../icons/testplane-mono.svg' ;
1011import styles from './index.module.css' ;
1112import { Footer } from './Footer' ;
1213import { EmptyReportCard } from '@/static/new-ui/components/Card/EmptyReportCard' ;
1314import { InfoPanel } from '@/static/new-ui/components/InfoPanel' ;
1415import { useAnalytics } from '@/static/new-ui/hooks/useAnalytics' ;
16+ import { useHotkey } from '@/static/new-ui/hooks/useHotkey' ;
1517import { setSectionSizes } from '../../../modules/actions/suites-page' ;
1618import { ArrowLeftToLine , ArrowRightFromLine } from '@gravity-ui/icons' ;
19+ import { Hotkey } from '@gravity-ui/uikit' ;
1720import { isSectionHidden } from '../../features/suites/utils' ;
1821import { Page , PathNames } from '@/constants' ;
1922
2023export enum PanelId {
24+ Hotkeys = 'hotkeys' ,
2125 Settings = 'settings' ,
2226 Info = 'info' ,
2327}
@@ -39,9 +43,15 @@ export function MainLayout(props: MainLayoutProps): ReactNode {
3943 const location = useLocation ( ) ;
4044 const analytics = useAnalytics ( ) ;
4145
46+ const pageHotkeys : Record < string , string > = {
47+ [ PathNames . suites ] : 's' ,
48+ [ PathNames . visualChecks ] : 'v'
49+ } ;
50+
4251 const menuItems : GravityMenuItem [ ] = props . pages . map ( item => ( {
4352 id : item . url ,
4453 title : item . title ,
54+ tooltipText : < > { item . title } < Hotkey value = { pageHotkeys [ item . url ] } view = "dark" /> </ > ,
4555 icon : item . icon ,
4656 current : Boolean ( matchPath ( `${ item . url . replace ( / \/ $ / , '' ) } /*` , location . pathname ) ) ,
4757 onItemClick : ( ) : void => {
@@ -55,11 +65,13 @@ export function MainLayout(props: MainLayoutProps): ReactNode {
5565 const backupSuitesPageSectionSizes = useSelector ( state => state . ui [ Page . suitesPage ] . backupSectionSizes ) ;
5666 if ( / \/ s u i t e s / . test ( location . pathname ) ) {
5767 const shouldExpandTree = isSectionHidden ( currentSuitesPageSectionSizes [ 0 ] ) ;
68+ const treeTitle = shouldExpandTree ? 'Expand tree' : 'Collapse tree' ;
5869 menuItems . push (
5970 { id : 'divider' , type : 'divider' , title : '-' } ,
6071 {
6172 id : 'expand-collapse-tree' ,
62- title : shouldExpandTree ? 'Expand tree' : 'Collapse tree' ,
73+ title : treeTitle ,
74+ tooltipText : < > { treeTitle } < Hotkey value = "t" view = "dark" /> </ > ,
6375 icon : shouldExpandTree ? ArrowRightFromLine : ArrowLeftToLine ,
6476 onItemClick : ( ) : void => {
6577 dispatch ( setSectionSizes ( { sizes : shouldExpandTree ? backupSuitesPageSectionSizes : [ 0 , 100 ] , page : Page . suitesPage } ) ) ;
@@ -73,11 +85,13 @@ export function MainLayout(props: MainLayoutProps): ReactNode {
7385 const backupVisualChecksPageSectionSizes = useSelector ( state => state . ui [ Page . visualChecksPage ] . backupSectionSizes ) ;
7486 if ( / \/ v i s u a l - c h e c k s / . test ( location . pathname ) ) {
7587 const shouldExpandTree = isSectionHidden ( currentVisualChecksPageSectionSizes [ 0 ] ) ;
88+ const treeTitle = shouldExpandTree ? 'Expand tree' : 'Collapse tree' ;
7689 menuItems . push (
7790 { id : 'divider' , type : 'divider' , title : '-' } ,
7891 {
7992 id : 'expand-collapse-tree' ,
80- title : shouldExpandTree ? 'Expand tree' : 'Collapse tree' ,
93+ title : treeTitle ,
94+ tooltipText : < > { treeTitle } < Hotkey value = "t" view = "dark" /> </ > ,
8195 icon : shouldExpandTree ? ArrowRightFromLine : ArrowLeftToLine ,
8296 onItemClick : ( ) : void => {
8397 dispatch ( setSectionSizes ( { sizes : shouldExpandTree ? backupVisualChecksPageSectionSizes : [ 0 , 100 ] , page : Page . visualChecksPage } ) ) ;
@@ -102,6 +116,36 @@ export function MainLayout(props: MainLayoutProps): ReactNode {
102116 }
103117 } ;
104118
119+ const togglePanel = useCallback ( ( panelId : PanelId ) : void => {
120+ setVisiblePanel ( prev => prev === panelId ? null : panelId ) ;
121+ } , [ ] ) ;
122+
123+ const toggleTreeSidebar = useCallback ( ( ) : void => {
124+ const isOnSuitesPage = / \/ s u i t e s / . test ( location . pathname ) ;
125+ const isOnVisualChecksPage = / \/ v i s u a l - c h e c k s / . test ( location . pathname ) ;
126+
127+ if ( isOnSuitesPage ) {
128+ const shouldExpand = isSectionHidden ( currentSuitesPageSectionSizes [ 0 ] ) ;
129+ dispatch ( setSectionSizes ( { sizes : shouldExpand ? backupSuitesPageSectionSizes : [ 0 , 100 ] , page : Page . suitesPage } ) ) ;
130+ } else if ( isOnVisualChecksPage ) {
131+ const shouldExpand = isSectionHidden ( currentVisualChecksPageSectionSizes [ 0 ] ) ;
132+ dispatch ( setSectionSizes ( { sizes : shouldExpand ? backupVisualChecksPageSectionSizes : [ 0 , 100 ] , page : Page . visualChecksPage } ) ) ;
133+ }
134+ } , [ location . pathname , currentSuitesPageSectionSizes , backupSuitesPageSectionSizes , currentVisualChecksPageSectionSizes , backupVisualChecksPageSectionSizes , dispatch ] ) ;
135+
136+ const navigateToSuites = useCallback ( ( ) => navigate ( PathNames . suites ) , [ navigate ] ) ;
137+ const navigateToVisualChecks = useCallback ( ( ) => navigate ( PathNames . visualChecks ) , [ navigate ] ) ;
138+ const toggleHotkeysPanel = useCallback ( ( ) => togglePanel ( PanelId . Hotkeys ) , [ togglePanel ] ) ;
139+ const toggleInfoPanel = useCallback ( ( ) => togglePanel ( PanelId . Info ) , [ togglePanel ] ) ;
140+ const toggleSettingsPanel = useCallback ( ( ) => togglePanel ( PanelId . Settings ) , [ togglePanel ] ) ;
141+
142+ useHotkey ( 's' , navigateToSuites ) ;
143+ useHotkey ( 'v' , navigateToVisualChecks ) ;
144+ useHotkey ( 't' , toggleTreeSidebar ) ;
145+ useHotkey ( 'mod+/' , toggleHotkeysPanel ) ;
146+ useHotkey ( 'i' , toggleInfoPanel ) ;
147+ useHotkey ( ',' , toggleSettingsPanel ) ;
148+
105149 return < AsideHeader
106150 className = { classNames ( { 'aside-header--initialized' : isInitialized } ) }
107151 logo = { { text : 'Testplane UI' , iconSrc : TestplaneIcon , iconSize : 32 , onClick : ( ) => navigate ( PathNames . suites ) } }
@@ -120,6 +164,10 @@ export function MainLayout(props: MainLayoutProps): ReactNode {
120164 hideCollapseButton = { true }
121165 renderFooter = { ( ) : ReactNode => < Footer visiblePanel = { visiblePanel } onFooterItemClick = { onFooterItemClick } /> }
122166 panelItems = { [ {
167+ id : PanelId . Hotkeys ,
168+ children : < HotkeysPanel /> ,
169+ visible : visiblePanel === PanelId . Hotkeys
170+ } , {
123171 id : PanelId . Info ,
124172 children : < InfoPanel /> ,
125173 visible : visiblePanel === PanelId . Info
0 commit comments