Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/static/new-ui/components/AsidePanel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ interface AsidePanelProps {

export function AsidePanel(props: AsidePanelProps): ReactNode {
return <div className={classNames(styles.container, props.className)}>
<h2 className={classNames('text-display-1')}>{props.title}</h2>
<h2 className={classNames('text-display-1')} data-qa="aside-panel-title">{props.title}</h2>
<Divider className={styles.divider} orientation={'horizontal'} />
{props.children}
</div>;
Expand Down
2 changes: 1 addition & 1 deletion lib/static/new-ui/components/AttemptPickerItem/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,5 +90,5 @@ export const AttemptPickerItem = (props: AttemptPickerItemProps): ReactNode => {
{[styles['attempt-picker-item--non-matched']]: isGroupingEnabled && !matchedSelectedGroup}
);

return <Button {...buttonStyle} title={title} className={className} onClick={onClick} qa={'retry-switcher'}>{result.attempt + 1}</Button>;
return <Button {...buttonStyle} title={title} className={className} onClick={onClick} qa={'retry-switcher'} data-qa-active={isActive}>{result.attempt + 1}</Button>;
};
27 changes: 27 additions & 0 deletions lib/static/new-ui/components/HotkeysPanel/index.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.hotkeys-panel {
height: 100%;
}

.divider {
margin: 16px 0;
}

.hotkeys-list {
display: flex;
flex-direction: column;
gap: 12px;
margin-top: 12px;
}

.hotkey-item {
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
}

.hotkey-title {
color: var(--color-neutral-500);
font-size: 15px;
}

37 changes: 37 additions & 0 deletions lib/static/new-ui/components/HotkeysPanel/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import {Divider, Hotkey} from '@gravity-ui/uikit';
import React, {ReactNode} from 'react';

import {AsidePanel} from '@/static/new-ui/components/AsidePanel';
import {PanelSection} from '@/static/new-ui/components/PanelSection';
import {HOTKEYS_GROUPS} from '@/static/new-ui/components/MainLayout/hotkeys';
import styles from './index.module.css';

export function HotkeysPanel(): ReactNode {
const sections = HOTKEYS_GROUPS.map((group, groupIndex) => (
<PanelSection key={groupIndex} title={group.title}>
<div className={styles.hotkeysList}>
{group.items.map((item, itemIndex) => (
<div key={itemIndex} className={styles.hotkeyItem}>
<span className={styles.hotkeyTitle}>{item.title}</span>
<Hotkey value={item.value} view="light" />
</div>
))}
</div>
</PanelSection>
));

const lastSection = sections.pop();

return (
<AsidePanel title="Keyboard Shortcuts" className={styles.hotkeysPanel}>
{sections.map((section, index) => (
<React.Fragment key={index}>
{section}
<Divider orientation="horizontal" className={styles.divider} />
</React.Fragment>
))}
{lastSection}
</AsidePanel>
);
}

2 changes: 1 addition & 1 deletion lib/static/new-ui/components/IconButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React, {KeyboardEventHandler, MouseEventHandler, ReactNode} from 'react';

interface IconButtonProps {
icon: ReactNode;
tooltip: string;
tooltip: ReactNode;
onClick?: MouseEventHandler;
onKeyDown?: KeyboardEventHandler;
view?: ButtonView;
Expand Down
23 changes: 21 additions & 2 deletions lib/static/new-ui/components/MainLayout/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Gear, CircleInfo} from '@gravity-ui/icons';
import {Gear, CircleInfo, Keyboard} from '@gravity-ui/icons';
import {FooterItem, MenuItem as GravityMenuItem} from '@gravity-ui/navigation';
import {Icon} from '@gravity-ui/uikit';
import {Hotkey, Icon} from '@gravity-ui/uikit';
import classNames from 'classnames';
import React, {ReactNode, useEffect, useState} from 'react';
import {useSelector} from 'react-redux';
Expand Down Expand Up @@ -45,14 +45,32 @@ export function Footer(props: FooterProps): ReactNode {
}
}, [props.visiblePanel]);

const isHotkeysCurrent = props.visiblePanel === PanelId.Hotkeys;
const isInfoCurrent = props.visiblePanel === PanelId.Info;
const isSettingsCurrent = props.visiblePanel === PanelId.Settings;

return <>
<UiModeHintNotification isVisible={isHintVisible} onClose={(): void => setIsHintVisible(false)} />
<FooterItem compact={false} item={{
id: PanelId.Hotkeys,
title: 'Keyboard Shortcuts',
tooltipText: <>Keyboard Shortcuts <Hotkey value="mod+/" view="dark" /></>,
onItemClick: props.onFooterItemClick,
current: isHotkeysCurrent,
qa: 'footer-item-hotkeys',
itemWrapper: (params, makeItem) => makeItem({
...params,
icon: <Icon className={classNames({
[styles.footerItem]: !isHotkeysCurrent,
[styles['footer-item--active']]: isHotkeysCurrent,
disabled: !isInitialized
})} data={Keyboard} />
})
}} />
<FooterItem compact={false} item={{
id: PanelId.Info,
title: 'Info',
tooltipText: <>Info <Hotkey value="i" view="dark" /></>,
onItemClick: props.onFooterItemClick,
current: isInfoCurrent,
qa: 'footer-item-info',
Expand All @@ -68,6 +86,7 @@ export function Footer(props: FooterProps): ReactNode {
<FooterItem compact={false} item={{
id: PanelId.Settings,
title: 'Settings',
tooltipText: <>Settings <Hotkey value="," view="dark" /></>,
onItemClick: props.onFooterItemClick,
current: isSettingsCurrent,
itemWrapper: (params, makeItem) => makeItem({
Expand Down
45 changes: 45 additions & 0 deletions lib/static/new-ui/components/MainLayout/hotkeys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type {HotkeysGroup} from '@gravity-ui/navigation';

export const HOTKEYS_GROUPS: HotkeysGroup[] = [
{
title: 'Navigation',
items: [
{title: 'Suites page', value: 's'},
{title: 'Visual Checks page', value: 'v'},
{title: 'Keyboard shortcuts', value: 'mod+/'},
{title: 'Info panel', value: 'i'},
{title: 'Settings panel', value: ','}
]
},
{
title: 'Tests Tree',
items: [
{title: 'Toggle tree sidebar', value: 't'},
{title: 'Focus search', value: 'mod+k'},
{title: 'Clear search', value: 'escape'}
]
},
{
title: 'Working with Tests',
items: [
{title: 'Previous test', value: '↑'},
{title: 'Next test', value: '↓'},
{title: 'Previous attempt', value: '←'},
{title: 'Next attempt', value: '→'},
{title: 'Run current test', value: 'r'},
{title: 'Run all/selected tests', value: 'shift+r'},
{title: 'Accept screenshot', value: 'a'},
{title: 'Undo accept', value: 'u'},
{title: 'Accept all/selected', value: 'shift+a'},
{title: 'Go to Suites / Visual Checks', value: 'g'}
]
},
{
title: 'Time Travel Player',
items: [
{title: 'Show/hide player', value: 'p'},
{title: 'Play/pause', value: 'k'}
]
}
];

54 changes: 51 additions & 3 deletions lib/static/new-ui/components/MainLayout/index.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
import {AsideHeader, MenuItem as GravityMenuItem} from '@gravity-ui/navigation';
import classNames from 'classnames';
import React, {ReactNode, useState} from 'react';
import React, {ReactNode, useCallback, useState} from 'react';
import {useDispatch, useSelector} from 'react-redux';
import {matchPath, useLocation, useNavigate} from 'react-router-dom';

import {getIsInitialized} from '@/static/new-ui/store/selectors';
import {SettingsPanel} from '@/static/new-ui/components/SettingsPanel';
import {HotkeysPanel} from '@/static/new-ui/components/HotkeysPanel';
import TestplaneIcon from '../../../icons/testplane-mono.svg';
import styles from './index.module.css';
import {Footer} from './Footer';
import {EmptyReportCard} from '@/static/new-ui/components/Card/EmptyReportCard';
import {InfoPanel} from '@/static/new-ui/components/InfoPanel';
import {useAnalytics} from '@/static/new-ui/hooks/useAnalytics';
import {useHotkey} from '@/static/new-ui/hooks/useHotkey';
import {setSectionSizes} from '../../../modules/actions/suites-page';
import {ArrowLeftToLine, ArrowRightFromLine} from '@gravity-ui/icons';
import {Hotkey} from '@gravity-ui/uikit';
import {isSectionHidden} from '../../features/suites/utils';
import {Page, PathNames} from '@/constants';

export enum PanelId {
Hotkeys = 'hotkeys',
Settings = 'settings',
Info = 'info',
}
Expand All @@ -39,9 +43,15 @@ export function MainLayout(props: MainLayoutProps): ReactNode {
const location = useLocation();
const analytics = useAnalytics();

const pageHotkeys: Record<string, string> = {
[PathNames.suites]: 's',
[PathNames.visualChecks]: 'v'
};

const menuItems: GravityMenuItem[] = props.pages.map(item => ({
id: item.url,
title: item.title,
tooltipText: <>{item.title} <Hotkey value={pageHotkeys[item.url]} view="dark" /></>,
icon: item.icon,
current: Boolean(matchPath(`${item.url.replace(/\/$/, '')}/*`, location.pathname)),
onItemClick: (): void => {
Expand All @@ -55,11 +65,13 @@ export function MainLayout(props: MainLayoutProps): ReactNode {
const backupSuitesPageSectionSizes = useSelector(state => state.ui[Page.suitesPage].backupSectionSizes);
if (/\/suites/.test(location.pathname)) {
const shouldExpandTree = isSectionHidden(currentSuitesPageSectionSizes[0]);
const treeTitle = shouldExpandTree ? 'Expand tree' : 'Collapse tree';
menuItems.push(
{id: 'divider', type: 'divider', title: '-'},
{
id: 'expand-collapse-tree',
title: shouldExpandTree ? 'Expand tree' : 'Collapse tree',
title: treeTitle,
tooltipText: <>{treeTitle} <Hotkey value="t" view="dark" /></>,
icon: shouldExpandTree ? ArrowRightFromLine : ArrowLeftToLine,
onItemClick: (): void => {
dispatch(setSectionSizes({sizes: shouldExpandTree ? backupSuitesPageSectionSizes : [0, 100], page: Page.suitesPage}));
Expand All @@ -73,11 +85,13 @@ export function MainLayout(props: MainLayoutProps): ReactNode {
const backupVisualChecksPageSectionSizes = useSelector(state => state.ui[Page.visualChecksPage].backupSectionSizes);
if (/\/visual-checks/.test(location.pathname)) {
const shouldExpandTree = isSectionHidden(currentVisualChecksPageSectionSizes[0]);
const treeTitle = shouldExpandTree ? 'Expand tree' : 'Collapse tree';
menuItems.push(
{id: 'divider', type: 'divider', title: '-'},
{
id: 'expand-collapse-tree',
title: shouldExpandTree ? 'Expand tree' : 'Collapse tree',
title: treeTitle,
tooltipText: <>{treeTitle} <Hotkey value="t" view="dark" /></>,
icon: shouldExpandTree ? ArrowRightFromLine : ArrowLeftToLine,
onItemClick: (): void => {
dispatch(setSectionSizes({sizes: shouldExpandTree ? backupVisualChecksPageSectionSizes : [0, 100], page: Page.visualChecksPage}));
Expand All @@ -102,6 +116,36 @@ export function MainLayout(props: MainLayoutProps): ReactNode {
}
};

const togglePanel = useCallback((panelId: PanelId): void => {
setVisiblePanel(prev => prev === panelId ? null : panelId);
}, []);

const toggleTreeSidebar = useCallback((): void => {
const isOnSuitesPage = /\/suites/.test(location.pathname);
const isOnVisualChecksPage = /\/visual-checks/.test(location.pathname);

if (isOnSuitesPage) {
const shouldExpand = isSectionHidden(currentSuitesPageSectionSizes[0]);
dispatch(setSectionSizes({sizes: shouldExpand ? backupSuitesPageSectionSizes : [0, 100], page: Page.suitesPage}));
} else if (isOnVisualChecksPage) {
const shouldExpand = isSectionHidden(currentVisualChecksPageSectionSizes[0]);
dispatch(setSectionSizes({sizes: shouldExpand ? backupVisualChecksPageSectionSizes : [0, 100], page: Page.visualChecksPage}));
}
}, [location.pathname, currentSuitesPageSectionSizes, backupSuitesPageSectionSizes, currentVisualChecksPageSectionSizes, backupVisualChecksPageSectionSizes, dispatch]);

const navigateToSuites = useCallback(() => navigate(PathNames.suites), [navigate]);
const navigateToVisualChecks = useCallback(() => navigate(PathNames.visualChecks), [navigate]);
const toggleHotkeysPanel = useCallback(() => togglePanel(PanelId.Hotkeys), [togglePanel]);
const toggleInfoPanel = useCallback(() => togglePanel(PanelId.Info), [togglePanel]);
const toggleSettingsPanel = useCallback(() => togglePanel(PanelId.Settings), [togglePanel]);

useHotkey('s', navigateToSuites);
useHotkey('v', navigateToVisualChecks);
useHotkey('t', toggleTreeSidebar);
useHotkey('mod+/', toggleHotkeysPanel);
useHotkey('i', toggleInfoPanel);
useHotkey(',', toggleSettingsPanel);

return <AsideHeader
className={classNames({'aside-header--initialized': isInitialized})}
logo={{text: 'Testplane UI', iconSrc: TestplaneIcon, iconSize: 32, onClick: () => navigate(PathNames.suites)}}
Expand All @@ -120,6 +164,10 @@ export function MainLayout(props: MainLayoutProps): ReactNode {
hideCollapseButton={true}
renderFooter={(): ReactNode => <Footer visiblePanel={visiblePanel} onFooterItemClick={onFooterItemClick}/>}
panelItems={[{
id: PanelId.Hotkeys,
children: <HotkeysPanel />,
visible: visiblePanel === PanelId.Hotkeys
}, {
id: PanelId.Info,
children: <InfoPanel />,
visible: visiblePanel === PanelId.Info
Expand Down
20 changes: 20 additions & 0 deletions lib/static/new-ui/components/NameFilter/index.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@
}

.search-input :global(.g-text-input__control) {
padding-right: 100px;
}

.search-input--with-hotkey :global(.g-text-input__control) {
padding-right: 100px;
}

.search-input--without-hotkey :global(.g-text-input__control) {
padding-right: 54px;
}

Expand All @@ -17,6 +25,7 @@
width: fit-content;
flex-direction: row;
padding: 2px 2px 2px 0;
align-items: center;
}

.buttons-wrapper :global(.g-button) {
Expand All @@ -39,3 +48,14 @@
width: var(--_--height);
font-weight: 600;
}

.hotkey {
--g-color-base-light-simple-hover: var(--color-neutral-100);
--g-color-text-light-complementary: var(--color-neutral-600);
--g-color-text-light-hint: var(--color-neutral-400);
pointer-events: none;
display: flex;
align-items: center;
margin-right: 4px;
height: 20px;
}
Loading
Loading