diff --git a/lib/static/modules/action-names.ts b/lib/static/modules/action-names.ts index 1ffb5ad53..5361f5be2 100644 --- a/lib/static/modules/action-names.ts +++ b/lib/static/modules/action-names.ts @@ -62,5 +62,6 @@ export default { TOGGLE_BROWSER_CHECKBOX: 'TOGGLE_BROWSER_CHECKBOX', SUITES_PAGE_SET_CURRENT_SUITE: 'SUITES_PAGE_SET_CURRENT_SUITE', SUITES_PAGE_SET_SECTION_EXPANDED: 'SUITES_PAGE_SET_SECTION_EXPANDED', - SUITES_PAGE_SET_STEPS_EXPANDED: 'SUITES_PAGE_SET_STEPS_EXPANDED' + SUITES_PAGE_SET_STEPS_EXPANDED: 'SUITES_PAGE_SET_STEPS_EXPANDED', + VISUAL_CHECKS_PAGE_SET_CURRENT_NAMED_IMAGE: 'VISUAL_CHECKS_PAGE_SET_CURRENT_NAMED_IMAGE' } as const; diff --git a/lib/static/modules/actions/index.js b/lib/static/modules/actions/index.js index 4054b0bc2..59fd6a466 100644 --- a/lib/static/modules/actions/index.js +++ b/lib/static/modules/actions/index.js @@ -15,6 +15,7 @@ import performanceMarks from '../../../constants/performance-marks'; export * from './static-accepter'; export * from './suites-page'; export * from './suites'; +export * from './visual-checks-page'; export const createNotification = (id, status, message, props = {}) => { const notificationProps = { diff --git a/lib/static/modules/actions/visual-checks-page.ts b/lib/static/modules/actions/visual-checks-page.ts new file mode 100644 index 000000000..553335a1a --- /dev/null +++ b/lib/static/modules/actions/visual-checks-page.ts @@ -0,0 +1,13 @@ +import actionNames from '@/static/modules/action-names'; +import {Action} from '@/static/modules/actions/types'; + +export type VisualChecksPageSetCurrentNamedImageAction = Action; + +export const visualChecksPageSetCurrentNamedImage = (namedImageId: string): VisualChecksPageSetCurrentNamedImageAction => { + return {type: actionNames.VISUAL_CHECKS_PAGE_SET_CURRENT_NAMED_IMAGE, payload: {namedImageId}}; +}; + +export type VisualChecksPageAction = + | VisualChecksPageSetCurrentNamedImageAction; diff --git a/lib/static/modules/default-state.ts b/lib/static/modules/default-state.ts index b97d08a9c..9ca2b983e 100644 --- a/lib/static/modules/default-state.ts +++ b/lib/static/modules/default-state.ts @@ -94,7 +94,12 @@ export default Object.assign({config: configDefaults}, { }, app: { isInitialized: false, - currentSuiteId: null + suitesPage: { + currentBrowserId: null + }, + visualChecksPage: { + currentNamedImageId: null + } }, ui: { suitesPage: { diff --git a/lib/static/modules/reducers/index.js b/lib/static/modules/reducers/index.js index 6867e4cbf..3dc21a5cf 100644 --- a/lib/static/modules/reducers/index.js +++ b/lib/static/modules/reducers/index.js @@ -27,6 +27,7 @@ import stopping from './stopping'; import progressBar from './bottom-progress-bar'; import staticImageAccepter from './static-image-accepter'; import suitesPage from './suites-page'; +import visualChecksPage from './visual-checks-page'; import isInitialized from './is-initialized'; // The order of specifying reducers is important. @@ -60,5 +61,6 @@ export default reduceReducers( plugins, progressBar, suitesPage, + visualChecksPage, isInitialized ); diff --git a/lib/static/modules/reducers/suites-page.ts b/lib/static/modules/reducers/suites-page.ts index c6db2ef02..910e5d7a7 100644 --- a/lib/static/modules/reducers/suites-page.ts +++ b/lib/static/modules/reducers/suites-page.ts @@ -6,7 +6,7 @@ import {applyStateUpdate} from '@/static/modules/utils/state'; export default (state: State, action: SuitesPageAction): State => { switch (action.type) { case actionNames.SUITES_PAGE_SET_CURRENT_SUITE: - return applyStateUpdate(state, {app: {currentSuiteId: action.payload.suiteId}}) as State; + return applyStateUpdate(state, {app: {suitesPage: {currentBrowserId: action.payload.suiteId}}}) as State; case actionNames.SUITES_PAGE_SET_SECTION_EXPANDED: { return applyStateUpdate(state, { ui: { diff --git a/lib/static/modules/reducers/visual-checks-page.ts b/lib/static/modules/reducers/visual-checks-page.ts new file mode 100644 index 000000000..777d34772 --- /dev/null +++ b/lib/static/modules/reducers/visual-checks-page.ts @@ -0,0 +1,13 @@ +import {State} from '@/static/new-ui/types/store'; +import actionNames from '@/static/modules/action-names'; +import {applyStateUpdate} from '@/static/modules/utils/state'; +import {VisualChecksPageAction} from '@/static/modules/actions'; + +export default (state: State, action: VisualChecksPageAction): State => { + switch (action.type) { + case actionNames.VISUAL_CHECKS_PAGE_SET_CURRENT_NAMED_IMAGE: + return applyStateUpdate(state, {app: {visualChecksPage: {currentNamedImageId: action.payload.namedImageId}}}) as State; + default: + return state; + } +}; diff --git a/lib/static/new-ui/components/AttemptPicker/index.tsx b/lib/static/new-ui/components/AttemptPicker/index.tsx index e21d60667..51d314459 100644 --- a/lib/static/new-ui/components/AttemptPicker/index.tsx +++ b/lib/static/new-ui/components/AttemptPicker/index.tsx @@ -46,7 +46,7 @@ function AttemptPickerInternal(props: AttemptPickerInternalProps): ReactNode { export const AttemptPicker = connect((state: State) => { let resultIds: string[] = []; let currentResultId = ''; - const browserId = state.app.currentSuiteId; + const browserId = state.app.suitesPage.currentBrowserId; if (browserId && state.tree.browsers.byId[browserId]) { resultIds = state.tree.browsers.byId[browserId].resultIds; diff --git a/lib/static/new-ui/components/Card/TextHintCard.module.css b/lib/static/new-ui/components/Card/TextHintCard.module.css new file mode 100644 index 000000000..e3cfdfc2f --- /dev/null +++ b/lib/static/new-ui/components/Card/TextHintCard.module.css @@ -0,0 +1,11 @@ +.card { + background-color: #fff; + display: flex; + align-items: center; + justify-content: center; +} + +.hint { + color: var(--g-color-private-black-400); + font-weight: 500; +} diff --git a/lib/static/new-ui/components/Card/TextHintCard.tsx b/lib/static/new-ui/components/Card/TextHintCard.tsx new file mode 100644 index 000000000..80b67167f --- /dev/null +++ b/lib/static/new-ui/components/Card/TextHintCard.tsx @@ -0,0 +1,15 @@ +import classNames from 'classnames'; +import React, {ReactNode} from 'react'; + +import {Card, CardProps} from '.'; +import styles from './TextHintCard.module.css'; + +interface TextHintCardProps extends CardProps { + hint: string; +} + +export function TextHintCard(props: TextHintCardProps): ReactNode { + return + {props.hint} + ; +} diff --git a/lib/static/new-ui/components/Card/UiCard.module.css b/lib/static/new-ui/components/Card/UiCard.module.css new file mode 100644 index 000000000..e691bde34 --- /dev/null +++ b/lib/static/new-ui/components/Card/UiCard.module.css @@ -0,0 +1,8 @@ +.card { + background-color: #fff; + display: flex; + flex-direction: column; + padding: 0 20px 20px 20px; + overflow: scroll; + position: relative; +} diff --git a/lib/static/new-ui/components/Card/UiCard.tsx b/lib/static/new-ui/components/Card/UiCard.tsx new file mode 100644 index 000000000..6a71096af --- /dev/null +++ b/lib/static/new-ui/components/Card/UiCard.tsx @@ -0,0 +1,9 @@ +import classNames from 'classnames'; +import React, {ReactNode} from 'react'; + +import {Card, CardProps} from '.'; +import styles from './UiCard.module.css'; + +export function UiCard(props: CardProps): ReactNode { + return {props.children}; +} diff --git a/lib/static/new-ui/components/Card/index.module.css b/lib/static/new-ui/components/Card/index.module.css index 87848e2f7..29f960a1e 100644 --- a/lib/static/new-ui/components/Card/index.module.css +++ b/lib/static/new-ui/components/Card/index.module.css @@ -3,10 +3,8 @@ position: relative; } -.card { - box-shadow: rgb(255, 255, 255) 0 0 0 0, rgba(9, 9, 11, 0.05) 0 0 0 1px, rgba(0, 0, 0, 0.05) 0 1px 2px 0; -} - .wrapper { overflow: hidden; + box-shadow: rgb(255, 255, 255) 0 0 0 0, rgba(9, 9, 11, 0.05) 0 0 0 1px, rgba(0, 0, 0, 0.05) 0 1px 2px 0; + border-radius: 10px; } diff --git a/lib/static/new-ui/components/Card/index.tsx b/lib/static/new-ui/components/Card/index.tsx index fecc3b7fa..da212b11f 100644 --- a/lib/static/new-ui/components/Card/index.tsx +++ b/lib/static/new-ui/components/Card/index.tsx @@ -3,14 +3,14 @@ import classNames from 'classnames'; import styles from './index.module.css'; -interface CardProps { +export interface CardProps { className?: string; children?: React.ReactNode; } export function Card(props: CardProps): React.ReactNode { return
-
+
{props.children}
; diff --git a/lib/static/new-ui/components/SuiteTitle/index.module.css b/lib/static/new-ui/components/SuiteTitle/index.module.css new file mode 100644 index 000000000..4e9616654 --- /dev/null +++ b/lib/static/new-ui/components/SuiteTitle/index.module.css @@ -0,0 +1,73 @@ +.container { + display: flex; + align-items: baseline; + justify-content: space-between; +} + +@container (max-width: 500px) { + .container { + flex-wrap: wrap-reverse; + } +} + +.breadcrumbs { + font-weight: 500; + color: var(--g-color-private-black-400); + font-size: 15px; + display: flex; + flex-wrap: wrap; +} + +.separator { + margin: 0 4px; + display: inline-block; + width: 16px; + position: relative; +} + +.invisible-space { + position: absolute; + z-index: -1; + left: 0; +} + +.content { + margin-top: 8px; + display: flex; + flex-direction: column; + row-gap: 8px; +} + +.title-container { + display: flex; + align-items: center; + flex-wrap: wrap; + column-gap: 8px; + row-gap: 4px; +} + +.labels-container { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.label :global(.g-label__content) { + display: flex; + align-items: center; + flex-wrap: nowrap; + gap: 4px; + font-size: 15px; +} + +.pagination-container { + display: flex; + align-items: baseline; + gap: 4px; +} + +.counter { + font-weight: 500; + color: var(--g-color-private-black-400); + font-size: 15px; +} diff --git a/lib/static/new-ui/components/SuiteTitle/index.tsx b/lib/static/new-ui/components/SuiteTitle/index.tsx new file mode 100644 index 000000000..78f23814e --- /dev/null +++ b/lib/static/new-ui/components/SuiteTitle/index.tsx @@ -0,0 +1,59 @@ +import classNames from 'classnames'; +import React, {ReactNode} from 'react'; +import {Button, Icon, Label} from '@gravity-ui/uikit'; +import {Camera, ChevronRight, PlanetEarth, ChevronUp, ChevronDown} from '@gravity-ui/icons'; + +import styles from './index.module.css'; + +interface SuiteTitleProps { + className?: string; +} + +interface SuiteTitlePropsInternal extends SuiteTitleProps { + suitePath: string[]; + browserName: string; + stateName?: string; + index: number; + totalItems: number; + onPrevious: () => void; + onNext: () => void; +} + +export function SuiteTitle(props: SuiteTitlePropsInternal): ReactNode { + const suiteName = props.suitePath[props.suitePath.length - 1]; + const suitePath = props.suitePath.slice(0, -1); + + return
+
+
+ {suitePath.map((item, index) => ( +
+ {item} + {index !== suitePath.length - 1 && +
+ +   +
} +
+ ))} +
+
+

+ {suiteName ?? 'Unknown Suite'} +

+
+ + {props.stateName && } +
+
+
+
+ {props.index === -1 ? '–' : props.index + 1}/{props.totalItems} + + +
+
; +} diff --git a/lib/static/new-ui/components/TreeViewItem/index.tsx b/lib/static/new-ui/components/TreeViewItem/index.tsx index 0f3a3eccd..a2de64e7a 100644 --- a/lib/static/new-ui/components/TreeViewItem/index.tsx +++ b/lib/static/new-ui/components/TreeViewItem/index.tsx @@ -26,6 +26,9 @@ interface TreeListItemProps { } export function TreeViewItem(props: TreeListItemProps): ReactNode { + const indent = props.list.structure.itemsState[props.id].indentation; + const hasChildren = (props.list.structure.itemsById[props.id] as {hasChildren?: boolean}).hasChildren; + return @@ -34,7 +37,7 @@ export function TreeViewItem(props: TreeListItemProps): ReactNode { [styles['tree-view-item--error']]: props.isFailed }])} activeOnHover={true} - style={{'--indent': props.list.structure.itemsState[props.id].indentation} as React.CSSProperties} + style={{'--indent': indent + Number(!hasChildren)} as React.CSSProperties} {...getItemRenderState({ id: props.id, list: props.list, diff --git a/lib/static/new-ui/features/suites/components/BrowsersSelect/BrowserIcon.module.css b/lib/static/new-ui/features/suites/components/BrowsersSelect/BrowserIcon.module.css new file mode 100644 index 000000000..c7394f6f3 --- /dev/null +++ b/lib/static/new-ui/features/suites/components/BrowsersSelect/BrowserIcon.module.css @@ -0,0 +1,3 @@ +.icon { + opacity: .6; +} diff --git a/lib/static/new-ui/features/suites/components/BrowsersSelect/BrowserIcon.tsx b/lib/static/new-ui/features/suites/components/BrowsersSelect/BrowserIcon.tsx index 568f60aab..55d8f2156 100644 --- a/lib/static/new-ui/features/suites/components/BrowsersSelect/BrowserIcon.tsx +++ b/lib/static/new-ui/features/suites/components/BrowsersSelect/BrowserIcon.tsx @@ -1,6 +1,8 @@ import {isString} from 'lodash'; import React, {ReactNode} from 'react'; +import styles from './BrowserIcon.module.css'; + const valueToIcon = { google: 'chrome', chrome: 'chrome', @@ -19,7 +21,7 @@ const valueToIcon = { } as const; export function BrowserIcon({name: browser}: {name: string}): ReactNode { - const getIcon = (iconName: string): ReactNode =>