diff --git a/.circleci/config.yml b/.circleci/config.yml index 733977edb..8661f26ba 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,9 +4,10 @@ jobs: working_directory: ~/html-reporter docker: - image: yinfra/html-reporter-browsers + resource_class: medium+ environment: SERVER_HOST: localhost - + steps: - checkout diff --git a/.gitignore b/.gitignore index 7166e954a..0bcb0a704 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ test/func/**/report-backup test/func/**/reports test/func/packages/*/plugin.js test/func/fixtures/playwright/test-results +@ diff --git a/lib/gui/server.ts b/lib/gui/server.ts index 231904b20..8cdbb2c9a 100644 --- a/lib/gui/server.ts +++ b/lib/gui/server.ts @@ -36,6 +36,7 @@ export const start = async (args: ServerArgs): Promise => { server.use(express.static(path.join(process.cwd(), reporterConfig.path))); server.get('/', (_req, res) => res.sendFile(path.join(__dirname, '../static', 'gui.html'))); + server.get('/new-ui', (_req, res) => res.sendFile(path.join(__dirname, '../static', 'new-ui-gui.html'))); server.get('/events', (_req, res) => { res.writeHead(OK, {'Content-Type': 'text/event-stream'}); diff --git a/lib/server-utils.ts b/lib/server-utils.ts index 96833b13c..bfce2e393 100644 --- a/lib/server-utils.ts +++ b/lib/server-utils.ts @@ -133,8 +133,9 @@ export async function saveStaticFilesToReportDir(htmlReporter: HtmlReporter, plu prepareCommonJSData(getDataForStaticFile(htmlReporter, pluginConfig)), 'utf8' ), - copyToReportDir(destPath, ['report.min.js', 'report.min.css'], staticFolder), + copyToReportDir(destPath, ['report.min.js', 'report.min.css', 'newReport.min.js', 'newReport.min.css'], staticFolder), fs.copy(path.resolve(staticFolder, 'index.html'), path.resolve(destPath, 'index.html')), + fs.copy(path.resolve(staticFolder, 'new-ui-report.html'), path.resolve(destPath, 'new-ui.html')), fs.copy(path.resolve(staticFolder, 'icons'), path.resolve(destPath, 'icons')), fs.copy(require.resolve('@gemini-testing/sql.js/dist/sql-wasm.js'), path.resolve(destPath, 'sql-wasm.js')), fs.copy(require.resolve('@gemini-testing/sql.js/dist/sql-wasm.wasm'), path.resolve(destPath, 'sql-wasm.wasm')), diff --git a/lib/static/icons/testplane.svg b/lib/static/icons/testplane.svg new file mode 100644 index 000000000..c69092c26 --- /dev/null +++ b/lib/static/icons/testplane.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/static/modules/action-names.ts b/lib/static/modules/action-names.ts index 34a0b334c..47bbc83ee 100644 --- a/lib/static/modules/action-names.ts +++ b/lib/static/modules/action-names.ts @@ -59,5 +59,6 @@ export default { TOGGLE_GROUP_CHECKBOX: 'TOGGLE_GROUP_CHECKBOX', UPDATE_BOTTOM_PROGRESS_BAR: 'UPDATE_BOTTOM_PROGRESS_BAR', GROUP_TESTS_BY_KEY: 'GROUP_TESTS_BY_KEY', - TOGGLE_BROWSER_CHECKBOX: 'TOGGLE_BROWSER_CHECKBOX' + TOGGLE_BROWSER_CHECKBOX: 'TOGGLE_BROWSER_CHECKBOX', + SUITES_PAGE_SET_CURRENT_SUITE: 'SUITES_PAGE_SET_CURRENT_SUITE' } as const; diff --git a/lib/static/modules/actions/index.js b/lib/static/modules/actions/index.js index 17cd6953c..f1048d19a 100644 --- a/lib/static/modules/actions/index.js +++ b/lib/static/modules/actions/index.js @@ -13,6 +13,7 @@ import * as plugins from '../plugins'; import performanceMarks from '../../../constants/performance-marks'; export * from './static-accepter'; +export * from './suites-page'; export const createNotification = (id, status, message, props = {}) => { const notificationProps = { diff --git a/lib/static/modules/actions/suites-page.ts b/lib/static/modules/actions/suites-page.ts new file mode 100644 index 000000000..38bc211b0 --- /dev/null +++ b/lib/static/modules/actions/suites-page.ts @@ -0,0 +1,6 @@ +import actionNames from '../action-names'; +import {SuitesPageSetCurrentSuiteAction} from '@/static/modules/reducers/suites-page'; + +export const suitesPageSetCurrentSuite = (suiteId: string): SuitesPageSetCurrentSuiteAction => { + return {type: actionNames.SUITES_PAGE_SET_CURRENT_SUITE, payload: {suiteId}}; +}; diff --git a/lib/static/modules/default-state.ts b/lib/static/modules/default-state.ts index e8172817a..e0600715c 100644 --- a/lib/static/modules/default-state.ts +++ b/lib/static/modules/default-state.ts @@ -4,6 +4,7 @@ import {DiffModes} from '../../constants/diff-modes'; import {EXPAND_ERRORS} from '../../constants/expand-modes'; import {RESULT_KEYS} from '../../constants/group-tests'; import {ToolName} from '../../constants'; +import {State} from '@/static/new-ui/types/store'; export default Object.assign({config: configDefaults}, { gui: true, @@ -28,7 +29,8 @@ export default Object.assign({config: configDefaults}, { byId: {}, allIds: [], allRootIds: [], - failedRootIds: [] + failedRootIds: [], + stateById: {} }, browsers: { byId: {}, @@ -87,5 +89,9 @@ export default Object.assign({config: configDefaults}, { acceptableImages: {}, accepterDelayedImages: [] as {imageId: string; stateName: string; stateNameImageId: string}[], imagesToCommitCount: 0 + }, + app: { + isInitialized: false, + currentSuiteId: null } -}); +}) satisfies State; diff --git a/lib/static/modules/reducers/index.js b/lib/static/modules/reducers/index.js index 1945fb461..6867e4cbf 100644 --- a/lib/static/modules/reducers/index.js +++ b/lib/static/modules/reducers/index.js @@ -26,6 +26,8 @@ import groupedTests from './grouped-tests'; import stopping from './stopping'; import progressBar from './bottom-progress-bar'; import staticImageAccepter from './static-image-accepter'; +import suitesPage from './suites-page'; +import isInitialized from './is-initialized'; // The order of specifying reducers is important. // At the top specify reducers that does not depend on other state fields. @@ -56,5 +58,7 @@ export default reduceReducers( tree, groupedTests, plugins, - progressBar + progressBar, + suitesPage, + isInitialized ); diff --git a/lib/static/modules/reducers/is-initialized.js b/lib/static/modules/reducers/is-initialized.js new file mode 100644 index 000000000..00a9bb564 --- /dev/null +++ b/lib/static/modules/reducers/is-initialized.js @@ -0,0 +1,12 @@ +import actionNames from '../action-names'; + +export default (state, action) => { + switch (action.type) { + case actionNames.INIT_GUI_REPORT: + case actionNames.INIT_STATIC_REPORT: + return {...state, app: {...state.app, isInitialized: true}}; + + default: + return state; + } +}; diff --git a/lib/static/modules/reducers/suites-page.ts b/lib/static/modules/reducers/suites-page.ts new file mode 100644 index 000000000..426350894 --- /dev/null +++ b/lib/static/modules/reducers/suites-page.ts @@ -0,0 +1,16 @@ +import actionNames from '../action-names'; +import {State} from '@/static/new-ui/types/store'; +import {Action} from '@/static/modules/actions/types'; + +export type SuitesPageSetCurrentSuiteAction = Action; + +export default (state: State, action: SuitesPageSetCurrentSuiteAction): State => { + switch (action.type) { + case actionNames.SUITES_PAGE_SET_CURRENT_SUITE: + return {...state, app: {...state.app, currentSuiteId: action.payload.suiteId}}; + default: + return state; + } +}; diff --git a/lib/static/modules/selectors/tree.js b/lib/static/modules/selectors/tree.js index 950ba5776..5200124e2 100644 --- a/lib/static/modules/selectors/tree.js +++ b/lib/static/modules/selectors/tree.js @@ -4,16 +4,12 @@ import {getViewMode} from './view'; import {ViewMode} from '../../../constants/view-modes'; import {isIdleStatus} from '../../../common-utils'; import {isNodeFailed, isNodeSuccessful, isAcceptable, iterateSuites} from '../utils'; +import {getAllRootSuiteIds, getBrowsers, getImages, getResults, getSuites} from '@/static/new-ui/store/selectors'; -const getSuites = (state) => state.tree.suites.byId; const getSuitesStates = (state) => state.tree.suites.stateById; -const getBrowsers = (state) => state.tree.browsers.byId; const getBrowserIds = (state) => state.tree.browsers.allIds; const getBrowsersStates = (state) => state.tree.browsers.stateById; -const getResults = (state) => state.tree.results.byId; -const getImages = (state) => state.tree.images.byId; const getImagesStates = (state) => state.tree.images.stateById; -const getAllRootSuiteIds = (state) => state.tree.suites.allRootIds; const getFailedRootSuiteIds = (state) => state.tree.suites.failedRootIds; const getRootSuiteIds = (state) => { const viewMode = getViewMode(state); diff --git a/lib/static/new-ui.css b/lib/static/new-ui.css new file mode 100644 index 000000000..d0063f5c7 --- /dev/null +++ b/lib/static/new-ui.css @@ -0,0 +1,7 @@ +.g-root { + --g-font-family-sans: 'Jost', sans-serif; +} + +.report { + font-family: var(--g-font-family-sans), sans-serif !important; +} diff --git a/lib/static/new-ui/app/App.tsx b/lib/static/new-ui/app/App.tsx new file mode 100644 index 000000000..e5db30ba8 --- /dev/null +++ b/lib/static/new-ui/app/App.tsx @@ -0,0 +1,43 @@ +import {ThemeProvider} from '@gravity-ui/uikit'; +import React, {ReactNode, StrictMode} from 'react'; +import {MainLayout} from '../components/MainLayout'; +import {HashRouter, Navigate, Route, Routes} from 'react-router-dom'; +import {CircleInfo, Eye, ListCheck} from '@gravity-ui/icons'; +import {SuitesPage} from '../features/suites/components/SuitesPage'; +import {VisualChecksPage} from '../features/visual-checks/components/VisualChecksPage'; +import {InfoPage} from '../features/info/components/InfoPage'; + +import '@gravity-ui/uikit/styles/fonts.css'; +import '@gravity-ui/uikit/styles/styles.css'; +import '../../new-ui.css'; +import {Provider} from 'react-redux'; +import store from '../../modules/store'; + +export function App(): ReactNode { + const pages = [ + { + title: 'Suites', + url: '/suites', + icon: ListCheck, + element: , + children: [} />] + }, + {title: 'Visual Checks', url: '/visual-checks', icon: Eye, element: }, + {title: 'Info', url: '/info', icon: CircleInfo, element: } + ]; + + return + + + + + + } path={'/'}/> + {pages.map(page => {page.children})} + + + + + + ; +} diff --git a/lib/static/new-ui/app/gui.tsx b/lib/static/new-ui/app/gui.tsx new file mode 100644 index 000000000..5c9f3682f --- /dev/null +++ b/lib/static/new-ui/app/gui.tsx @@ -0,0 +1,22 @@ +import React, {ReactNode, useEffect} from 'react'; +import {createRoot} from 'react-dom/client'; +import {App} from './App'; +import store from '../../modules/store'; +import {finGuiReport, initGuiReport} from '../../modules/actions'; + +const rootEl = document.getElementById('app') as HTMLDivElement; +const root = createRoot(rootEl); + +function Gui(): ReactNode { + useEffect(() => { + store.dispatch(initGuiReport()); + + return () => { + store.dispatch(finGuiReport()); + }; + }, []); + + return ; +} + +root.render(); diff --git a/lib/static/new-ui/app/report.tsx b/lib/static/new-ui/app/report.tsx new file mode 100644 index 000000000..6881b9605 --- /dev/null +++ b/lib/static/new-ui/app/report.tsx @@ -0,0 +1,22 @@ +import React, {ReactNode, useEffect} from 'react'; +import {createRoot} from 'react-dom/client'; +import {App} from './App'; +import store from '../../modules/store'; +import {initStaticReport, finStaticReport} from '../../modules/actions'; + +const rootEl = document.getElementById('app') as HTMLDivElement; +const root = createRoot(rootEl); + +function Report(): ReactNode { + useEffect(() => { + store.dispatch(initStaticReport()); + + return () => { + store.dispatch(finStaticReport()); + }; + }, []); + + return ; +} + +root.render(); diff --git a/lib/static/new-ui/components/ImageWithMagnifier/index.module.css b/lib/static/new-ui/components/ImageWithMagnifier/index.module.css new file mode 100644 index 000000000..fa3275874 --- /dev/null +++ b/lib/static/new-ui/components/ImageWithMagnifier/index.module.css @@ -0,0 +1,10 @@ +.magnifier { + background-color: white; + background-repeat: no-repeat; + border: 1px solid lightgrey; + border-radius: 5px; + opacity: 1; + pointer-events: none; + position: fixed; + z-index: 1000 +} diff --git a/lib/static/new-ui/components/ImageWithMagnifier/index.tsx b/lib/static/new-ui/components/ImageWithMagnifier/index.tsx new file mode 100644 index 000000000..155cee4cd --- /dev/null +++ b/lib/static/new-ui/components/ImageWithMagnifier/index.tsx @@ -0,0 +1,122 @@ +import classnames from 'classnames'; +import React, {ReactNode, useEffect, useRef, useState} from 'react'; +import {createPortal} from 'react-dom'; +import styles from './index.module.css'; + +const DEFAULT_ZOOM_LEVEL = 3; + +interface ImageWithMagnifierProps { + src: string; + alt: string; + className?: string; + style?: React.CSSProperties; + magnifierHeight?: number; + magnifierWidth?: number; + zoomLevel?: number; + // Used to detect parent container scrolling and update the magnifier state + scrollContainerRef?: React.RefObject; +} + +export function ImageWithMagnifier({ + src, + alt, + className = '', + style, + magnifierHeight = 150, + magnifierWidth = 150, + zoomLevel = DEFAULT_ZOOM_LEVEL, + scrollContainerRef +}: ImageWithMagnifierProps): ReactNode { + const [showMagnifier, setShowMagnifier] = useState(false); + const [[imgWidth, imgHeight], setSize] = useState([0, 0]); + const [[x, y], setXY] = useState([0, 0]); + const mousePositionRef = useRef([0, 0]); + const [magnifierStyle, setMagnifierStyle] = useState({}); + const imgRef = useRef(null); + + const mouseEnter = (e: React.MouseEvent): void => { + const el = e.currentTarget; + + const {width, height} = el.getBoundingClientRect(); + setSize([width, height]); + setShowMagnifier(true); + mousePositionRef.current = [e.clientX, e.clientY]; + }; + + const mouseLeave = (e: React.MouseEvent): void => { + e.preventDefault(); + setShowMagnifier(false); + mousePositionRef.current = [e.clientX, e.clientY]; + }; + + const mouseMove = (e: React.MouseEvent): void => { + const el = e.currentTarget; + const {top, left} = el.getBoundingClientRect(); + + const x = e.clientX - left - window.scrollX; + const y = e.clientY - top - window.scrollY; + + setXY([x, y]); + mousePositionRef.current = [e.clientX, e.clientY]; + }; + + useEffect(() => { + if (showMagnifier && scrollContainerRef?.current && imgRef?.current) { + const handleScroll = (): void => { + if (!imgRef.current) { + return; + } + const [mouseX, mouseY] = mousePositionRef.current; + + const el = imgRef.current; + const {top, left} = el.getBoundingClientRect(); + + const x = mouseX - left - window.scrollX; + const y = mouseY - top - window.scrollY; + + setXY([x, y]); + }; + + scrollContainerRef.current.addEventListener('scroll', handleScroll); + + return () => { + scrollContainerRef.current?.removeEventListener('scroll', handleScroll); + }; + } + + return undefined; + }, [showMagnifier, scrollContainerRef]); + + useEffect(() => { + const [mouseX, mouseY] = mousePositionRef.current; + + setMagnifierStyle({ + display: showMagnifier ? '' : 'none', + height: `${magnifierHeight}px`, + width: `${magnifierWidth}px`, + backgroundImage: `url('${src}')`, + top: `${mouseY - magnifierHeight / 2}px`, + left: `${mouseX - magnifierWidth / 2}px`, + backgroundSize: `${imgWidth * zoomLevel}px ${imgHeight * zoomLevel}px`, + backgroundPositionX: `${-x * zoomLevel + magnifierWidth / 2}px`, + backgroundPositionY: `${-y * zoomLevel + magnifierHeight / 2}px` + }); + }, [showMagnifier, imgWidth, imgHeight, x, y]); + + return
+ {alt} mouseEnter(e)} + onMouseLeave={(e): void => mouseLeave(e)} + onMouseMove={(e): void => mouseMove(e)} + ref={imgRef} + /> + {createPortal(
, document.body)} +
; +} diff --git a/lib/static/new-ui/components/MainLayout.tsx b/lib/static/new-ui/components/MainLayout.tsx new file mode 100644 index 000000000..f0cd44f2f --- /dev/null +++ b/lib/static/new-ui/components/MainLayout.tsx @@ -0,0 +1,33 @@ +import {AsideHeader, MenuItem as GravityMenuItem} from '@gravity-ui/navigation'; +import React from 'react'; +import {useNavigate, matchPath, useLocation} from 'react-router-dom'; +import TestplaneIcon from '../../icons/testplane.svg'; + +interface MenuItem { + title: string; + url: string; + icon: GravityMenuItem['icon']; +} + +export interface MainLayoutProps { + children: React.ReactNode; + menuItems: MenuItem[]; +} + +export function MainLayout(props: MainLayoutProps): JSX.Element { + const navigate = useNavigate(); + const location = useLocation(); + + const gravityMenuItems: GravityMenuItem[] = props.menuItems.map(item => ({ + id: item.url, + title: item.title, + icon: item.icon, + current: Boolean(matchPath(`${item.url.replace(/\/$/, '')}/*`, location.pathname)), + onItemClick: () => navigate(item.url) + })); + + return navigate('/')}} compact={true} + headerDecoration={true} menuItems={gravityMenuItems} + renderContent={(): React.ReactNode => props.children} hideCollapseButton={true} + />; +} diff --git a/lib/static/new-ui/components/SplitViewLayout.module.css b/lib/static/new-ui/components/SplitViewLayout.module.css new file mode 100644 index 000000000..01e7b0a9c --- /dev/null +++ b/lib/static/new-ui/components/SplitViewLayout.module.css @@ -0,0 +1,20 @@ +.split { + display: flex; + flex-direction: row; + height: 100vh; +} + + .split > div { + overflow-y: scroll; + } + + .split :global(.gutter) { + background-color: #eee; + background-repeat: no-repeat; + background-position: 50%; + } + + .split :global(.gutter.gutter-horizontal) { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg=='); + cursor: col-resize; + } diff --git a/lib/static/new-ui/components/SplitViewLayout.tsx b/lib/static/new-ui/components/SplitViewLayout.tsx new file mode 100644 index 000000000..24da06db4 --- /dev/null +++ b/lib/static/new-ui/components/SplitViewLayout.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import Split from 'react-split'; +import styles from './SplitViewLayout.module.css'; + +interface SplitViewLayoutProps { + children?: React.ReactNode; +} + +export function SplitViewLayout(props: SplitViewLayoutProps): JSX.Element { + return + {props.children} + ; +} diff --git a/lib/static/new-ui/features/info/components/InfoPage.tsx b/lib/static/new-ui/features/info/components/InfoPage.tsx new file mode 100644 index 000000000..7f354a06b --- /dev/null +++ b/lib/static/new-ui/features/info/components/InfoPage.tsx @@ -0,0 +1,8 @@ +import React, {ReactNode} from 'react'; +import {Box} from '@gravity-ui/uikit'; + +export function InfoPage(): ReactNode { + return +

Info

+
; +} diff --git a/lib/static/new-ui/features/suites/components/SuitesPage/index.module.css b/lib/static/new-ui/features/suites/components/SuitesPage/index.module.css new file mode 100644 index 000000000..2d0823863 --- /dev/null +++ b/lib/static/new-ui/features/suites/components/SuitesPage/index.module.css @@ -0,0 +1,6 @@ +.controls-row { + align-items: center; + display: flex; + height: 28px; + margin-top: var(--g-spacing-2); +} diff --git a/lib/static/new-ui/features/suites/components/SuitesPage/index.tsx b/lib/static/new-ui/features/suites/components/SuitesPage/index.tsx new file mode 100644 index 000000000..9349935d0 --- /dev/null +++ b/lib/static/new-ui/features/suites/components/SuitesPage/index.tsx @@ -0,0 +1,25 @@ +import {Flex} from '@gravity-ui/uikit'; +import React, {ReactNode} from 'react'; +import {connect} from 'react-redux'; + +import {SplitViewLayout} from '@/static/new-ui/components/SplitViewLayout'; +import {TestNameFilter} from '@/static/new-ui/features/suites/components/TestNameFilter'; +import {SuitesTreeView} from '@/static/new-ui/features/suites/components/SuitesTreeView'; +import styles from './index.module.css'; + +function SuitesPageInternal(): ReactNode { + return +
+ +

Suites

+
+ +
+ +
+
+
+
; +} + +export const SuitesPage = connect()(SuitesPageInternal); diff --git a/lib/static/new-ui/features/suites/components/SuitesPage/types.ts b/lib/static/new-ui/features/suites/components/SuitesPage/types.ts new file mode 100644 index 000000000..a7ae0e80b --- /dev/null +++ b/lib/static/new-ui/features/suites/components/SuitesPage/types.ts @@ -0,0 +1,31 @@ +import {TestStatus} from '@/constants'; +import {ImageFile} from '@/types'; + +export interface TreeViewItem { + data: T; + children?: TreeViewItem[]; +} + +export enum TreeViewItemType { + Suite, + Browser, +} + +export interface TreeViewSuiteData { + type: TreeViewItemType.Suite; + title: string; + fullTitle: string; + status: TestStatus; +} + +export interface TreeViewBrowserData { + type: TreeViewItemType.Browser; + title: string; + fullTitle: string; + status: TestStatus; + errorTitle?: string; + errorStack?: string; + diffImg?: ImageFile; +} + +export type TreeViewData = TreeViewSuiteData | TreeViewBrowserData; diff --git a/lib/static/new-ui/features/suites/components/SuitesTreeView/index.module.css b/lib/static/new-ui/features/suites/components/SuitesTreeView/index.module.css new file mode 100644 index 000000000..84242f152 --- /dev/null +++ b/lib/static/new-ui/features/suites/components/SuitesTreeView/index.module.css @@ -0,0 +1,80 @@ +.tree-view { + margin-top: var(--g-spacing-2); +} + + .tree-view :global(.g-text_ellipsis) { + white-space: normal; + } + + .tree-view__container { + height: 100%; + width: 100%; + overflow-y: auto; + contain: strict; + } + + .tree-view__total-size-wrapper { + width: 100%; + position: relative; + } + + .tree-view__visible-window { + position: absolute; + top: 0; + left: 0; + width: 100%; + } + + .tree-view__item { + --g-text-subheader-1-font-size: 18px; + --g-text-subheader-1-line-height: 22px; + --g-text-subheader-font-weight: normal; + + --g-text-body-1-font-size: 18px; + --g-text-body-1-line-height: 22px; + --g-text-body-font-weight: normal; + + padding: 4px 0; + font-size: 18px; + + user-select: none; + } + + .tree-view__item svg { + flex-shrink: 0; + } + + .tree-view__item > div > div { + align-items: start !important; + } + + .tree-view__item--current { + background: #a28aff !important; + color: #fff !important; + } + + .tree-view__item.tree-view__item--current:hover { + background: #af9aff !important; + } + + .tree-view__item--current svg { + color: #fff !important; + } + + .tree-view__item--browser { + padding-left: 8px; + } + + .tree-view__item--error { + background: var(--g-color-private-red-100); + color: var(--g-color-private-red-600-solid); + } + + .tree-view__item--error:hover { + background: var(--g-color-private-red-50) !important; + } + + + .tree-view__item--error svg { + color: var(--g-color-private-red-600-solid); + } diff --git a/lib/static/new-ui/features/suites/components/SuitesTreeView/index.tsx b/lib/static/new-ui/features/suites/components/SuitesTreeView/index.tsx new file mode 100644 index 000000000..f0a175191 --- /dev/null +++ b/lib/static/new-ui/features/suites/components/SuitesTreeView/index.tsx @@ -0,0 +1,151 @@ +import {Box} from '@gravity-ui/uikit'; +import { + unstable_getItemRenderState as getItemRenderState, + unstable_ListContainerView as ListContainerView, + unstable_ListItemView as ListItemView, + unstable_useList as useList +} from '@gravity-ui/uikit/unstable'; +import {useVirtualizer} from '@tanstack/react-virtual'; +import classNames from 'classnames'; +import React, {ReactNode, useCallback, useEffect} from 'react'; +import {connect} from 'react-redux'; +import {useNavigate, useParams} from 'react-router-dom'; +import {bindActionCreators} from 'redux'; + +import * as actions from '@/static/modules/actions'; +import { + TreeViewBrowserData, + TreeViewItem, + TreeViewItemType, + TreeViewSuiteData +} from '@/static/new-ui/features/suites/components/SuitesPage/types'; +import {getIconByStatus} from '@/static/new-ui/utils'; +import {TreeViewItemTitle} from '@/static/new-ui/features/suites/components/TreeViewItemTitle'; +import {TreeViewItemSubtitle} from '@/static/new-ui/features/suites/components/TreeViewItemSubtitle'; +import {State} from '@/static/new-ui/types/store'; +import { + getTreeViewExpandedById, + getTreeViewItems +} from '@/static/new-ui/features/suites/components/SuitesTreeView/selectors'; +import styles from './index.module.css'; +import {TestStatus} from '@/constants'; + +interface SuitesTreeViewProps { + treeViewItems: TreeViewItem[]; + treeViewExpandedById: Record; + actions: typeof actions; + isInitialized: boolean; + currentSuiteId: string | null; +} + +function SuitesTreeViewInternal(props: SuitesTreeViewProps): ReactNode { + const navigate = useNavigate(); + const {suiteId} = useParams(); + + const list = useList({ + items: props.treeViewItems, + withExpandedState: true, + getItemId: item => { + return item.fullTitle; + }, + controlledState: { + expandedById: props.treeViewExpandedById + } + }); + + const parentRef = React.useRef(null); + + const virtualizer = useVirtualizer({ + count: list.structure.visibleFlattenIds.length, + getScrollElement: () => parentRef.current, + estimateSize: () => 28, + getItemKey: useCallback((index: number) => list.structure.visibleFlattenIds[index], [list]), + overscan: 200 + }); + + const virtualizedItems = virtualizer.getVirtualItems(); + + // Effects + useEffect(() => { + if (!props.isInitialized) { + return; + } + + props.actions.setStrictMatchFilter(false); + + if (suiteId) { + props.actions.suitesPageSetCurrentSuite(suiteId); + virtualizer.scrollToIndex(list.structure.visibleFlattenIds.indexOf(suiteId), {align: 'start'}); + } + }, [props.isInitialized]); + + // Event handlers + const onItemClick = useCallback(({id}: {id: string}): void => { + const item = list.structure.itemsById[id]; + + if (item.type === TreeViewItemType.Suite) { + props.actions.toggleSuiteSection({suiteId: item.fullTitle, shouldBeOpened: !props.treeViewExpandedById[item.fullTitle]}); + } else if (item.type === TreeViewItemType.Browser) { + props.actions.suitesPageSetCurrentSuite(id); + + navigate(encodeURIComponent(item.fullTitle)); + } + }, [list, props.actions, props.treeViewExpandedById]); + + return +
+
+
+ {virtualizedItems.map((virtualRow) => { + const item = list.structure.itemsById[virtualRow.key]; + const classes = [ + styles['tree-view__item'], + { + [styles['tree-view__item--current']]: item.fullTitle === props.currentSuiteId, + [styles['tree-view__item--browser']]: item.type === TreeViewItemType.Browser, + [styles['tree-view__item--error']]: item.type === TreeViewItemType.Browser && (item.status === TestStatus.FAIL || item.status === TestStatus.ERROR) + } + ]; + + return + { + return { + startSlot: getIconByStatus(x.status), + title: , + subtitle: + }; + } + }).props}/> + ; + })} +
+
+
+
; +} + +export const SuitesTreeView = connect((state: State) => ({ + isInitialized: state.app.isInitialized, + currentSuiteId: state.app.currentSuiteId, + treeViewItems: getTreeViewItems(state), + treeViewExpandedById: getTreeViewExpandedById(state) +}), +(dispatch) => ({actions: bindActionCreators(actions, dispatch)}))(SuitesTreeViewInternal); diff --git a/lib/static/new-ui/features/suites/components/SuitesTreeView/selectors.ts b/lib/static/new-ui/features/suites/components/SuitesTreeView/selectors.ts new file mode 100644 index 000000000..3f32120cf --- /dev/null +++ b/lib/static/new-ui/features/suites/components/SuitesTreeView/selectors.ts @@ -0,0 +1,113 @@ +import {createSelector} from 'reselect'; +import {get, last} from 'lodash'; +import { + isImageEntityFail, + isResultEntityError, + isSuiteEntityLeaf, + BrowserEntity, + SuiteEntity +} from '@/static/new-ui/types/store'; +import { + TreeViewBrowserData, + TreeViewItem, + TreeViewItemType, + TreeViewSuiteData +} from '@/static/new-ui/features/suites/components/SuitesPage/types'; +import {TestStatus} from '@/constants'; +import { + getAllRootSuiteIds, + getBrowsers, + getImages, + getResults, + getSuites, + getSuitesState +} from '@/static/new-ui/store/selectors'; +import {trimArray} from '@/common-utils'; +import {ImageFile} from '@/types'; +import {getFullTitleByTitleParts} from '@/static/new-ui/utils'; + +// Converts the existing store structure to the one that can be consumed by GravityUI +export const getTreeViewItems = createSelector( + [getSuites, getSuitesState, getAllRootSuiteIds, getBrowsers, getResults, getImages], + (suites, suitesState, rootSuiteIds, browsers, results, images): TreeViewItem[] => { + const EMPTY_SUITE: TreeViewSuiteData = { + type: TreeViewItemType.Suite, + title: '', + fullTitle: '', + status: TestStatus.IDLE + }; + + const formatBrowser = (browserData: BrowserEntity, parentSuite: TreeViewSuiteData): TreeViewItem => { + // Assuming test in concrete browser always has at least one result, even never launched (idle result) + const lastResult = results[last(browserData.resultIds) as string]; + + const diffImgId = lastResult.imageIds.find(imageId => isImageEntityFail(images[imageId])); + const diffImg = get(images, [diffImgId as string, 'diffImg']) as ImageFile | undefined; + + let errorTitle, errorStack; + if (isResultEntityError(lastResult) && lastResult.error?.stack) { + errorTitle = lastResult.error?.name; + + const stackLines = trimArray(lastResult.error.stack.split('\n')); + errorStack = stackLines.slice(0, 3).join('\n'); + } + + const data: TreeViewBrowserData = { + type: TreeViewItemType.Browser, + title: browserData.name, + fullTitle: getFullTitleByTitleParts([parentSuite.fullTitle, browserData.name]), + status: lastResult.status, + errorTitle, + errorStack, + diffImg + }; + + return {data}; + }; + + const formatSuite = (suiteData: SuiteEntity, parentSuite: TreeViewSuiteData): TreeViewItem | null => { + const data: TreeViewSuiteData = { + type: TreeViewItemType.Suite, + title: suiteData.name, + fullTitle: getFullTitleByTitleParts([parentSuite.fullTitle, suiteData.name]), + status: suiteData.status + }; + + if (!suitesState[data.fullTitle].shouldBeShown) { + return null; + } + + if (isSuiteEntityLeaf(suiteData)) { + return { + data, + children: suiteData.browserIds.map((browserId) => formatBrowser(browsers[browserId], data)) + }; + } else { + return { + data, + children: suiteData.suiteIds + .map((suiteId) => formatSuite(suites[suiteId], data)) + .filter(Boolean) as TreeViewItem[] + }; + } + }; + + return rootSuiteIds + .map((rootId) => { + return formatSuite(suites[rootId], EMPTY_SUITE); + }) + .filter(Boolean) as TreeViewItem[]; + }); + +export const getTreeViewExpandedById = createSelector( + [getSuites, getSuitesState], + (suites, suitesState) => { + const result: Record = {}; + + for (const key of Object.keys(suites)) { + result[key] = suitesState[key].shouldBeOpened; + } + + return result; + } +); diff --git a/lib/static/new-ui/features/suites/components/TestNameFilter/index.tsx b/lib/static/new-ui/features/suites/components/TestNameFilter/index.tsx new file mode 100644 index 000000000..bc5679d71 --- /dev/null +++ b/lib/static/new-ui/features/suites/components/TestNameFilter/index.tsx @@ -0,0 +1,34 @@ +import {TextInput} from '@gravity-ui/uikit'; +import React, {ChangeEvent, ReactNode, useCallback, useState} from 'react'; +import {debounce} from 'lodash'; +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import * as actions from '@/static/modules/actions'; +import {State} from '@/static/new-ui/types/store'; + +interface TestNameFilterProps { + testNameFilter: string; + actions: typeof actions; +} + +function TestNameFilterInternal(props: TestNameFilterProps): ReactNode { + const [testNameFilter, setTestNameFilter] = useState(props.testNameFilter); + + const updateTestNameFilter = useCallback(debounce( + (testName) => props.actions.updateTestNameFilter(testName), + 500, + {maxWait: 3000} + ), []); + + const onChange = useCallback((event: ChangeEvent): void => { + setTestNameFilter(event.target.value); + updateTestNameFilter(event.target.value); + }, [setTestNameFilter, updateTestNameFilter]); + + return ; +} + +export const TestNameFilter = connect( + (state: State) => ({testNameFilter: state.view.testNameFilter}), + (dispatch) => ({actions: bindActionCreators(actions, dispatch)}) +)(TestNameFilterInternal); diff --git a/lib/static/new-ui/features/suites/components/TreeViewItemSubtitle/index.module.css b/lib/static/new-ui/features/suites/components/TreeViewItemSubtitle/index.module.css new file mode 100644 index 000000000..e5e9a3cf6 --- /dev/null +++ b/lib/static/new-ui/features/suites/components/TreeViewItemSubtitle/index.module.css @@ -0,0 +1,7 @@ +.tree-view-item-subtitle__error-stack { + white-space: pre; + overflow: hidden; + text-overflow: ellipsis; + line-height: 22px; + margin-top: 8px; +} diff --git a/lib/static/new-ui/features/suites/components/TreeViewItemSubtitle/index.tsx b/lib/static/new-ui/features/suites/components/TreeViewItemSubtitle/index.tsx new file mode 100644 index 000000000..bc7bb3c9a --- /dev/null +++ b/lib/static/new-ui/features/suites/components/TreeViewItemSubtitle/index.tsx @@ -0,0 +1,22 @@ +import React, {ReactNode} from 'react'; +import {TreeViewData, TreeViewItemType} from '@/static/new-ui/features/suites/components/SuitesPage/types'; +import styles from './index.module.css'; +import {ImageWithMagnifier} from '@/static/new-ui/components/ImageWithMagnifier'; + +interface TreeViewItemSubtitleProps { + item: TreeViewData; + // Passed to image with magnifier to detect parent container scrolling and update magnifier position + scrollContainerRef: React.RefObject; +} + +export function TreeViewItemSubtitle(props: TreeViewItemSubtitleProps): ReactNode { + if (props.item.type === TreeViewItemType.Browser && props.item.diffImg) { + return ; + } else if (props.item.type === TreeViewItemType.Browser && props.item.errorStack) { + return
+ {props.item.errorStack} +
; + } + + return null; +} diff --git a/lib/static/new-ui/features/suites/components/TreeViewItemTitle/index.module.css b/lib/static/new-ui/features/suites/components/TreeViewItemTitle/index.module.css new file mode 100644 index 000000000..3c82eeb78 --- /dev/null +++ b/lib/static/new-ui/features/suites/components/TreeViewItemTitle/index.module.css @@ -0,0 +1,4 @@ +.tree-view-item__error-title { + color: var(--g-color-private-red-600-solid); + margin-left: var(--g-spacing-1); +} diff --git a/lib/static/new-ui/features/suites/components/TreeViewItemTitle/index.tsx b/lib/static/new-ui/features/suites/components/TreeViewItemTitle/index.tsx new file mode 100644 index 000000000..3255938ca --- /dev/null +++ b/lib/static/new-ui/features/suites/components/TreeViewItemTitle/index.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import {TreeViewData, TreeViewItemType} from '@/static/new-ui/features/suites/components/SuitesPage/types'; +import styles from './index.module.css'; + +export function TreeViewItemTitle(props: {item: TreeViewData}): React.JSX.Element { + return
+ {props.item.title} + { + props.item.type === TreeViewItemType.Browser && + props.item.errorTitle && + {props.item.errorTitle} + } +
; +} diff --git a/lib/static/new-ui/features/visual-checks/components/VisualChecksPage.tsx b/lib/static/new-ui/features/visual-checks/components/VisualChecksPage.tsx new file mode 100644 index 000000000..1ff92dc0a --- /dev/null +++ b/lib/static/new-ui/features/visual-checks/components/VisualChecksPage.tsx @@ -0,0 +1,8 @@ +import React, {ReactNode} from 'react'; +import {Box} from '@gravity-ui/uikit'; + +export function VisualChecksPage(): ReactNode { + return +

Visual Checks

+
; +} diff --git a/lib/static/new-ui/store/selectors.ts b/lib/static/new-ui/store/selectors.ts new file mode 100644 index 000000000..b184daf1b --- /dev/null +++ b/lib/static/new-ui/store/selectors.ts @@ -0,0 +1,8 @@ +import {State, BrowserEntity, ImageEntity, ResultEntity, SuiteEntity, SuiteState} from '@/static/new-ui/types/store'; + +export const getAllRootSuiteIds = (state: State): string[] => state.tree.suites.allRootIds; +export const getSuites = (state: State): Record => state.tree.suites.byId; +export const getSuitesState = (state: State): Record => state.tree.suites.stateById; +export const getBrowsers = (state: State): Record => state.tree.browsers.byId; +export const getResults = (state: State): Record => state.tree.results.byId; +export const getImages = (state: State): Record => state.tree.images.byId; diff --git a/lib/static/new-ui/types/store.ts b/lib/static/new-ui/types/store.ts new file mode 100644 index 000000000..7e2901853 --- /dev/null +++ b/lib/static/new-ui/types/store.ts @@ -0,0 +1,81 @@ +import {TestStatus} from '@/constants'; +import {ImageFile} from '@/types'; + +export interface SuiteEntityNode { + name: string; + status: TestStatus; + suiteIds: string[]; +} + +export interface SuiteEntityLeaf { + name: string; + status: TestStatus; + browserIds: string[]; +} + +export type SuiteEntity = SuiteEntityNode | SuiteEntityLeaf; + +export const isSuiteEntityLeaf = (suite: SuiteEntity): suite is SuiteEntityLeaf => Boolean((suite as SuiteEntityLeaf).browserIds); + +export interface BrowserEntity { + name: string; + resultIds: string[]; +} + +export interface ResultEntityCommon { + imageIds: string[]; + status: TestStatus; +} + +export interface ResultEntityError extends ResultEntityCommon { + error: Error; + status: TestStatus.ERROR; +} + +export type ResultEntity = ResultEntityCommon | ResultEntityError; + +export const isResultEntityError = (result: ResultEntity): result is ResultEntityError => result.status === TestStatus.ERROR; + +export interface ImageEntityError { + status: TestStatus.ERROR; +} + +export interface ImageEntityFail { + stateName: string; + diffImg: ImageFile; +} + +export type ImageEntity = ImageEntityError | ImageEntityFail; + +export const isImageEntityFail = (image: ImageEntity): image is ImageEntityFail => Boolean((image as ImageEntityFail).stateName); + +export interface SuiteState { + shouldBeOpened: boolean; + shouldBeShown: boolean; +} + +export interface State { + app: { + isInitialized: boolean; + currentSuiteId: string | null; + } + tree: { + browsers: { + byId: Record + }; + images: { + byId: Record; + } + results: { + byId: Record; + }; + suites: { + allRootIds: string[]; + byId: Record; + stateById: Record; + }; + } + view: { + testNameFilter: string; + } +} diff --git a/lib/static/new-ui/types/typings.d.ts b/lib/static/new-ui/types/typings.d.ts new file mode 100644 index 000000000..f76e77928 --- /dev/null +++ b/lib/static/new-ui/types/typings.d.ts @@ -0,0 +1,9 @@ +declare module '*.svg' { + const value: string; + export = value; +} + +declare module '*.module.css' { + const classes: {[key: string]: string}; + export default classes; +} diff --git a/lib/static/new-ui/utils/index.tsx b/lib/static/new-ui/utils/index.tsx new file mode 100644 index 000000000..d21f0fc64 --- /dev/null +++ b/lib/static/new-ui/utils/index.tsx @@ -0,0 +1,21 @@ +import {TestStatus} from '@/constants'; +import {CircleCheck, CircleDashed, CircleXmark, CircleMinus} from '@gravity-ui/icons'; +import React from 'react'; + +export const getIconByStatus = (status: TestStatus): React.JSX.Element => { + if (status === TestStatus.FAIL || status === TestStatus.ERROR) { + return ; + } else if (status === TestStatus.SUCCESS) { + return ; + } else if (status === TestStatus.SKIPPED) { + return ; + } + + return ; +}; + +export const getFullTitleByTitleParts = (titleParts: string[]): string => { + const DELIMITER = ' '; + + return titleParts.join(DELIMITER).trim(); +}; diff --git a/lib/static/styles.css b/lib/static/styles.css index 22ba9d433..115df6d69 100644 --- a/lib/static/styles.css +++ b/lib/static/styles.css @@ -946,3 +946,124 @@ a:active { flex-grow: 1; height: 25px; } + +.g-root_theme_light { + --g-color-base-brand: var(--g-color-private-color-400); + --g-color-base-brand-hover: var(--g-color-private-color-150); + --g-color-base-selection: var(--g-color-private-color-100); + --g-color-base-selection-hover: var(--g-color-private-color-150); + --g-color-line-brand: var(--g-color-private-color-200); + --g-color-text-brand: var(--g-color-private-color-300); + --g-color-text-link: var(--g-color-private-color-300); + --g-color-text-link-hover: var(--g-color-private-color-500); + --gn-aside-header-decoration-collapsed-background-color: var(--g-color-private-color-150); + + --g-color-private-color-50: rgba(108,71,255,0.1); + --g-color-private-color-100: rgba(108,71,255,0.15); + --g-color-private-color-150: rgba(108,71,255,0.2); + --g-color-private-color-200: rgba(108,71,255,0.3); + --g-color-private-color-250: rgba(108,71,255,0.4); + --g-color-private-color-300: rgba(108,71,255,0.5); + --g-color-private-color-350: rgba(108,71,255,0.6); + --g-color-private-color-400: rgba(108,71,255,0.7); + --g-color-private-color-450: rgba(108,71,255,0.8); + --g-color-private-color-500: rgba(108,71,255,0.9); + + --g-color-private-color-50-solid: rgb(240,237,255); + --g-color-private-color-100-solid: rgb(233,227,255); + --g-color-private-color-150-solid: rgb(226,218,255); + --g-color-private-color-200-solid: rgb(211,200,255); + --g-color-private-color-250-solid: rgb(196,181,255); + --g-color-private-color-300-solid: rgb(182,163,255); + --g-color-private-color-350-solid: rgb(167,145,255); + --g-color-private-color-400-solid: rgb(152,126,255); + --g-color-private-color-450-solid: rgb(137,108,255); + --g-color-private-color-500-solid: rgb(123,89,255); + --g-color-private-color-550-solid: rgb(108,71,255); + --g-color-private-color-600-solid: rgb(102,68,235); + --g-color-private-color-650-solid: rgb(95,66,214); + --g-color-private-color-700-solid: rgb(89,63,194); + --g-color-private-color-750-solid: rgb(83,60,173); + --g-color-private-color-800-solid: rgb(77,58,153); + --g-color-private-color-850-solid: rgb(70,55,133); + --g-color-private-color-900-solid: rgb(64,52,112); + --g-color-private-color-950-solid: rgb(58,49,92); + --g-color-private-color-1000-solid: rgb(54,48,82); +} + +.split { + display: flex; + flex-direction: row; + height: 100vh; +} + +.split > div { + overflow: scroll; +} + +.gutter { + background-color: #eee; + background-repeat: no-repeat; + background-position: 50%; +} + +.gutter.gutter-horizontal { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg=='); + cursor: col-resize; +} + +/* Gravity UI Font Styles */ +.text-display-1 { + font-family: Jost, sans-serif; + font-weight: 600; + font-size: var(--g-text-display-1-font-size); + line-height: var(--g-text-display-1-line-height); + margin: 0; +} + +.text-display-2 { + font-family: Jost, sans-serif; + font-weight: 600; + font-size: 32px; + line-height: 40px; +} + +.text-display-3 { + font-family: Jost, sans-serif; + font-weight: 600; + font-size: var(--g-text-display-3-font-size); + line-height: var(--g-text-display-3-line-height); +} + +.text-header-1 { + font-family: Jost, sans-serif; + font-weight: 600; + font-size: var(--g-text-header-1-font-size); + line-height: var(--g-text-header-1-line-height); +} + +.text-subheader-1 { + font-family: Jost, sans-serif; + font-weight: 600; + font-size: var(--g-text-subheader-1-font-size); + line-height: var(--g-text-subheader-1-line-height); +} + +.text-subheader-3 { + font-family: Jost, sans-serif; + font-weight: 600; + font-size: var(--g-text-subheader-3-font-size); + line-height: var(--g-text-subheader-3-line-height); +} + +.icon-fail { + color: var(--g-color-private-red-600-solid); +} + +.icon-success { + color: var(--g-color-private-green-600-solid); +} + +.icon-skip { + color: var(--g-color-private-cool-grey-600-solid); +} diff --git a/lib/static/template.html b/lib/static/template.html index 03fc91e82..bfa502904 100644 --- a/lib/static/template.html +++ b/lib/static/template.html @@ -8,6 +8,7 @@ +
diff --git a/lib/static/tsconfig.json b/lib/static/tsconfig.json index 9daed60a8..a14167cb0 100644 --- a/lib/static/tsconfig.json +++ b/lib/static/tsconfig.json @@ -3,6 +3,9 @@ "include": ["."], "compilerOptions": { "jsx": "react", - "noEmit": true + "noEmit": true, + "paths": { + "@/*": ["../*"] + } } } diff --git a/package-lock.json b/package-lock.json index b6bcc6b56..ca693fe33 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "html-reporter", - "version": "10.6.0", + "version": "10.6.2", "license": "MIT", "workspaces": [ "test/func/fixtures/*", @@ -17,6 +17,7 @@ "@babel/runtime": "^7.22.5", "@gemini-testing/commander": "^2.15.3", "@gemini-testing/sql.js": "^2.0.0", + "@gravity-ui/navigation": "^2.16.0", "ansi-html-community": "^0.0.8", "axios": "1.6.3", "better-sqlite3": "^10.0.0", @@ -43,6 +44,9 @@ "ora": "^5.4.1", "p-queue": "^5.0.0", "qs": "^6.9.1", + "react-split": "^2.0.14", + "react-virtualized-auto-sizer": "^1.0.24", + "react-window": "^1.8.10", "signal-exit": "^4.1.0", "strip-ansi": "^6.0.1", "tmp": "^0.1.0", @@ -59,10 +63,11 @@ "@babel/preset-react": "^7.22.5", "@babel/preset-typescript": "^7.22.5", "@gravity-ui/components": "^3.7.0", - "@gravity-ui/uikit": "^6.20.0", + "@gravity-ui/uikit": "^6.23.0", "@playwright/test": "^1.44.1", "@react-hook/resize-observer": "^2.0.1", "@swc/core": "^1.3.64", + "@tanstack/react-virtual": "^3.8.3", "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.5.2", "@types/babel__core": "^7.20.5", @@ -81,6 +86,9 @@ "@types/npm-which": "^3.0.3", "@types/opener": "^1.4.0", "@types/proxyquire": "^1.3.28", + "@types/react-dom": "^18.3.0", + "@types/react-virtualized": "^9.21.30", + "@types/react-window": "^1.8.8", "@types/sinon": "^4.3.3", "@types/tmp": "^0.1.0", "@types/urijs": "^1.19.19", @@ -142,6 +150,7 @@ "react-hotkeys": "^2.0.0", "react-markdown": "^6.0.3", "react-redux": "^7.2.1", + "react-router-dom": "^6.25.1", "react-virtualized": "^9.22.5", "reapop": "^4.2.2", "reduce-reducers": "^1.0.4", @@ -2178,8 +2187,7 @@ "node_modules/@bem-react/classname": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@bem-react/classname/-/classname-1.6.0.tgz", - "integrity": "sha512-SFBwUHMcb7TFFK5ld88+JhecoEun3/kHZ6KvLDjj3w5hv/tfRV8mtGHA8N42uMctXLF4bPEcr96xwXXcRFuweg==", - "dev": true + "integrity": "sha512-SFBwUHMcb7TFFK5ld88+JhecoEun3/kHZ6KvLDjj3w5hv/tfRV8mtGHA8N42uMctXLF4bPEcr96xwXXcRFuweg==" }, "node_modules/@bundled-es-modules/cookie": { "version": "2.0.0", @@ -2828,7 +2836,6 @@ "version": "3.7.0", "resolved": "https://registry.npmjs.org/@gravity-ui/components/-/components-3.7.0.tgz", "integrity": "sha512-cWmVOa6rErZY+F3Ws9zD6vvKlU2nn4eUFzS17R0TlVa4uaIcT2WsPGwAracby/o3Ys1gpSK71AkpW/c8lgcvog==", - "dev": true, "dependencies": { "@bem-react/classname": "^1.6.0", "@gravity-ui/date-utils": "^2.1.0", @@ -2848,7 +2855,6 @@ "version": "2.5.3", "resolved": "https://registry.npmjs.org/@gravity-ui/date-utils/-/date-utils-2.5.3.tgz", "integrity": "sha512-WetzttqlW454yGsh/LBo0AxuqIFT0erENCMYAfemhu6qLiuqy35NdDGP6VA4iDkWiSPFuQ1sRoVU8tC+OwiPEg==", - "dev": true, "dependencies": { "dayjs": "1.11.10", "lodash": "^4.17.0" @@ -2857,14 +2863,12 @@ "node_modules/@gravity-ui/i18n": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/@gravity-ui/i18n/-/i18n-1.5.1.tgz", - "integrity": "sha512-ZvaQtRUf4Yl9zi0+SMzjlDeHp9+p5IXkNu2k6RtW04c+RYKA1jX+umeKNwzft4iR3+KxDlpLX2trTFEW6W7HKQ==", - "dev": true + "integrity": "sha512-ZvaQtRUf4Yl9zi0+SMzjlDeHp9+p5IXkNu2k6RtW04c+RYKA1jX+umeKNwzft4iR3+KxDlpLX2trTFEW6W7HKQ==" }, "node_modules/@gravity-ui/icons": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/@gravity-ui/icons/-/icons-2.10.0.tgz", "integrity": "sha512-xS0G4+TM7cD2cCKS4wVc01c4lLe/OreKjm4sHwrOtJWH4EawaRbpkuwtgUDcUvY2EryIcI6lgV+8o714m6lcyQ==", - "dev": true, "peerDependencies": { "react": "*" }, @@ -2874,11 +2878,27 @@ } } }, + "node_modules/@gravity-ui/navigation": { + "version": "2.17.2", + "resolved": "https://registry.npmjs.org/@gravity-ui/navigation/-/navigation-2.17.2.tgz", + "integrity": "sha512-cuYSAJYHgj2RCqwxyoq9nWeHXbHX/uXF3zNuowsUrs4P+gVp2oWP2F5fmbwX89Uafg8WMQ+MrIAAmEGSnz/b9A==", + "dependencies": { + "react-transition-group": "^4.4.1", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@bem-react/classname": "^1.6.0", + "@gravity-ui/components": "^3.0.0", + "@gravity-ui/icons": "^2.2.0", + "@gravity-ui/uikit": "^6.15.0", + "react": "^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/@gravity-ui/uikit": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@gravity-ui/uikit/-/uikit-6.20.0.tgz", - "integrity": "sha512-ngeFZH0CgF+6cNwWTrtwecYkb1Rb7TWFg3eUg87TGLvXiEemepJuwLvM4t9WX3yZFzFCzIoHML2h/SM6VXhjyQ==", - "dev": true, + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/@gravity-ui/uikit/-/uikit-6.23.0.tgz", + "integrity": "sha512-v+1QPL+IdaGxv3tuEUHaRSzK5Hp/XNJv3EQxMIMFSV4ojtvio6sQAlzLIYALGN/IULWRwlfF0cqG/TavOo9yog==", "dependencies": { "@bem-react/classname": "^1.6.0", "@gravity-ui/i18n": "^1.3.0", @@ -3385,7 +3405,6 @@ "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "dev": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -3550,6 +3569,15 @@ "react": ">=18" } }, + "node_modules/@remix-run/router": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.18.0.tgz", + "integrity": "sha512-L3jkqmqoSVBVKHfpGZmLrex0lxR5SucGA0sUfFzGctehw+S/ggL9L/0NnC5mw6P8HUWpFZ3nQw3cRApjjWx9Sw==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@semantic-ui-react/event-stack": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/@semantic-ui-react/event-stack/-/event-stack-3.1.3.tgz", @@ -3823,6 +3851,33 @@ "node": ">=14.16" } }, + "node_modules/@tanstack/react-virtual": { + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.8.4.tgz", + "integrity": "sha512-Dq0VQr3QlTS2qL35g360QaJWBt7tCn/0xw4uZ0dHXPLO1Ak4Z4nVX4vuj1Npg1b/jqNMDToRtR5OIxM2NXRBWg==", + "dev": true, + "dependencies": { + "@tanstack/virtual-core": "3.8.4" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.8.4.tgz", + "integrity": "sha512-iO5Ujgw3O1yIxWDe9FgUPNkGjyT657b1WNX52u+Wv1DyBFEpdCdGkuVaky0M3hHFqNWjAmHWTn4wgj9rTr7ZQg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@testing-library/dom": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", @@ -4088,8 +4143,7 @@ "node_modules/@types/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", - "dev": true + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" }, "node_modules/@types/debug": { "version": "4.1.8", @@ -4187,7 +4241,6 @@ "version": "3.3.5", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz", "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==", - "dev": true, "dependencies": { "@types/react": "*", "hoist-non-react-statics": "^3.3.0" @@ -4358,8 +4411,7 @@ "node_modules/@types/prop-types": { "version": "15.7.4", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", - "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==", - "dev": true + "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==" }, "node_modules/@types/proxyquire": { "version": "1.3.28", @@ -4383,17 +4435,24 @@ "version": "18.3.3", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", - "dev": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" } }, + "node_modules/@types/react-dom": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", + "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-redux": { "version": "7.1.33", "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.33.tgz", "integrity": "sha512-NF8m5AjWCkert+fosDsN3hAlHzpjSiXlVy9EgQEmLoBhaNXbmyeGs/aj5dQzKuF+/q+S7JQagorGDW8pJ28Hmg==", - "dev": true, "dependencies": { "@types/hoist-non-react-statics": "^3.3.0", "@types/react": "*", @@ -4401,6 +4460,25 @@ "redux": "^4.0.0" } }, + "node_modules/@types/react-virtualized": { + "version": "9.21.30", + "resolved": "https://registry.npmjs.org/@types/react-virtualized/-/react-virtualized-9.21.30.tgz", + "integrity": "sha512-4l2TFLQ8BCjNDQlvH85tU6gctuZoEdgYzENQyZHpgTHU7hoLzYgPSOALMAeA58LOWua8AzC6wBivPj1lfl6JgQ==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "@types/react": "*" + } + }, + "node_modules/@types/react-window": { + "version": "1.8.8", + "resolved": "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.8.tgz", + "integrity": "sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/responselike": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", @@ -8681,8 +8759,7 @@ "node_modules/blueimp-md5": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz", - "integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==", - "dev": true + "integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==" }, "node_modules/bn.js": { "version": "5.2.0", @@ -9730,8 +9807,7 @@ "node_modules/classnames": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", - "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==", - "dev": true + "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" }, "node_modules/clean-css": { "version": "5.3.2", @@ -11497,7 +11573,6 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", - "dev": true, "dependencies": { "toggle-selection": "^1.0.6" } @@ -11951,7 +12026,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", - "dev": true, "dependencies": { "tiny-invariant": "^1.0.6" } @@ -12271,8 +12345,7 @@ "node_modules/csstype": { "version": "3.0.10", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz", - "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==", - "dev": true + "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==" }, "node_modules/currently-unhandled": { "version": "0.4.1", @@ -12405,8 +12478,7 @@ "node_modules/dayjs": { "version": "1.11.10", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", - "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==", - "dev": true + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" }, "node_modules/debug": { "version": "4.3.4", @@ -13356,7 +13428,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "dev": true, "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" @@ -15441,7 +15512,6 @@ "version": "7.5.4", "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.4.tgz", "integrity": "sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==", - "dev": true, "dependencies": { "tabbable": "^6.2.0" } @@ -17075,7 +17145,6 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "dev": true, "dependencies": { "react-is": "^16.7.0" } @@ -19020,8 +19089,7 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { "version": "3.14.1", @@ -19781,7 +19849,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -20021,8 +20088,7 @@ "node_modules/memoize-one": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", - "dev": true + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" }, "node_modules/memory-fs": { "version": "0.4.1", @@ -21594,7 +21660,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -23414,7 +23479,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -23720,8 +23784,7 @@ "node_modules/raf-schd": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", - "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==", - "dev": true + "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==" }, "node_modules/randombytes": { "version": "2.1.0", @@ -23783,7 +23846,6 @@ "version": "10.6.2", "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-10.6.2.tgz", "integrity": "sha512-FjkoFjyvUQWcBo1F3RgSglky3ar0+qHLM41PlFVYB4Bj3RD8E/Mv7kqMouLFBU+3aFglMzzctAIWRwajEuueSw==", - "dev": true, "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "^2.2.5", @@ -23801,7 +23863,6 @@ "version": "5.43.0", "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.43.0.tgz", "integrity": "sha512-AzC7KKOXFqAdIBqdGWepL9Xn7cm3vnAmjlHqUnoQaTMZYhM4VlXGLkkHHxj/BZ7Td0+SOPKB4RGPboBVKT9htw==", - "dev": true, "dependencies": { "@babel/runtime": "^7.18.3", "react-is": "^18.2.0" @@ -23814,14 +23875,12 @@ "node_modules/rc-util/node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "dev": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -23833,7 +23892,6 @@ "version": "13.1.1", "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz", "integrity": "sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==", - "dev": true, "dependencies": { "@babel/runtime": "^7.9.2", "css-box-model": "^1.2.0", @@ -23880,7 +23938,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz", "integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==", - "dev": true, "dependencies": { "copy-to-clipboard": "^3.3.1", "prop-types": "^15.8.1" @@ -23893,7 +23950,6 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", - "dev": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -23905,8 +23961,7 @@ "node_modules/react-fast-compare": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", - "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", - "dev": true + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" }, "node_modules/react-hotkeys": { "version": "2.0.0", @@ -23923,8 +23978,7 @@ "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/react-lifecycles-compat": { "version": "3.0.4", @@ -23980,7 +24034,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", "integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==", - "dev": true, "dependencies": { "react-fast-compare": "^3.0.1", "warning": "^4.0.2" @@ -24001,7 +24054,6 @@ "version": "7.2.9", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==", - "dev": true, "dependencies": { "@babel/runtime": "^7.15.4", "@types/react-redux": "^7.1.20", @@ -24025,14 +24077,56 @@ "node_modules/react-redux/node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, + "node_modules/react-router": { + "version": "6.25.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.25.1.tgz", + "integrity": "sha512-u8ELFr5Z6g02nUtpPAggP73Jigj1mRePSwhS/2nkTrlPU5yEkH1vYzWNyvSnSzeeE2DNqWdH+P8OhIh9wuXhTw==", + "dev": true, + "dependencies": { + "@remix-run/router": "1.18.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.25.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.25.1.tgz", + "integrity": "sha512-0tUDpbFvk35iv+N89dWNrJp+afLgd+y4VtorJZuOCXK0kkCWjEvb3vTJM++SYvMEpbVwXKf3FjeVveVEb6JpDQ==", + "dev": true, + "dependencies": { + "@remix-run/router": "1.18.0", + "react-router": "6.25.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-split": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/react-split/-/react-split-2.0.14.tgz", + "integrity": "sha512-bKWydgMgaKTg/2JGQnaJPg51T6dmumTWZppFgEbbY0Fbme0F5TuatAScCLaqommbGQQf/ZT1zaejuPDriscISA==", + "dependencies": { + "prop-types": "^15.5.7", + "split.js": "^1.6.0" + }, + "peerDependencies": { + "react": "*" + } }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", - "dev": true, "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", @@ -24066,7 +24160,6 @@ "version": "1.0.24", "resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.24.tgz", "integrity": "sha512-3kCn7N9NEb3FlvJrSHWGQ4iVl+ydQObq2fHMn12i5wbtm74zHOPhz/i64OL3c1S1vi9i2GXtZqNqUJTQ+BnNfg==", - "dev": true, "peerDependencies": { "react": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0", "react-dom": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0" @@ -24076,7 +24169,6 @@ "version": "1.8.10", "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.10.tgz", "integrity": "sha512-Y0Cx+dnU6NLa5/EvoHukUD0BklJ8qITCtVEPY1C/nL8wwoZ0b5aEw8Ff1dOVHw7fCzMt55XfJDd8S8W8LCaUCg==", - "dev": true, "dependencies": { "@babel/runtime": "^7.0.0", "memoize-one": ">=3.1.1 <6" @@ -24266,7 +24358,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/redux/-/redux-4.1.2.tgz", "integrity": "sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==", - "dev": true, "dependencies": { "@babel/runtime": "^7.9.2" } @@ -24488,8 +24579,7 @@ "node_modules/resize-observer-polyfill": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", - "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", - "dev": true + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" }, "node_modules/resolve": { "version": "1.20.0", @@ -24799,7 +24889,6 @@ "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "dev": true, "dependencies": { "loose-envify": "^1.1.0" } @@ -25885,6 +25974,11 @@ "node": ">=0.10.0" } }, + "node_modules/split.js": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/split.js/-/split.js-1.6.5.tgz", + "integrity": "sha512-mPTnGCiS/RiuTNsVhCm9De9cCAUsrNFFviRbADdKiiV+Kk8HKp/0fWu7Kr8pi3/yBmsqLFHuXGT9UUZ+CNLwFw==" + }, "node_modules/split2": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz", @@ -27026,8 +27120,7 @@ "node_modules/tabbable": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", - "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", - "dev": true + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" }, "node_modules/tapable": { "version": "2.2.1", @@ -28160,8 +28253,7 @@ "node_modules/tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", - "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", - "dev": true + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==" }, "node_modules/tmp": { "version": "0.1.0", @@ -28248,8 +28340,7 @@ "node_modules/toggle-selection": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", - "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", - "dev": true + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" }, "node_modules/toidentifier": { "version": "1.0.0", @@ -28962,7 +29053,6 @@ "version": "6.1.3", "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-6.1.3.tgz", "integrity": "sha512-AETYRrhpRgl9T1YtnODmQE32G81U3A+f3HO3ZeK7efbXqe3x+RNOW4RTpV0iff7zJWhGYJA6EI0Mm+w50aFTAw==", - "dev": true, "dependencies": { "@types/cookie": "^0.6.0", "cookie": "^0.6.0" @@ -28972,7 +29062,6 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -29190,7 +29279,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==", - "dev": true, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } @@ -29353,7 +29441,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "dev": true, "dependencies": { "loose-envify": "^1.0.0" } @@ -33248,8 +33335,7 @@ "@bem-react/classname": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@bem-react/classname/-/classname-1.6.0.tgz", - "integrity": "sha512-SFBwUHMcb7TFFK5ld88+JhecoEun3/kHZ6KvLDjj3w5hv/tfRV8mtGHA8N42uMctXLF4bPEcr96xwXXcRFuweg==", - "dev": true + "integrity": "sha512-SFBwUHMcb7TFFK5ld88+JhecoEun3/kHZ6KvLDjj3w5hv/tfRV8mtGHA8N42uMctXLF4bPEcr96xwXXcRFuweg==" }, "@bundled-es-modules/cookie": { "version": "2.0.0", @@ -33624,7 +33710,6 @@ "version": "3.7.0", "resolved": "https://registry.npmjs.org/@gravity-ui/components/-/components-3.7.0.tgz", "integrity": "sha512-cWmVOa6rErZY+F3Ws9zD6vvKlU2nn4eUFzS17R0TlVa4uaIcT2WsPGwAracby/o3Ys1gpSK71AkpW/c8lgcvog==", - "dev": true, "requires": { "@bem-react/classname": "^1.6.0", "@gravity-ui/date-utils": "^2.1.0", @@ -33639,7 +33724,6 @@ "version": "2.5.3", "resolved": "https://registry.npmjs.org/@gravity-ui/date-utils/-/date-utils-2.5.3.tgz", "integrity": "sha512-WetzttqlW454yGsh/LBo0AxuqIFT0erENCMYAfemhu6qLiuqy35NdDGP6VA4iDkWiSPFuQ1sRoVU8tC+OwiPEg==", - "dev": true, "requires": { "dayjs": "1.11.10", "lodash": "^4.17.0" @@ -33648,21 +33732,27 @@ "@gravity-ui/i18n": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/@gravity-ui/i18n/-/i18n-1.5.1.tgz", - "integrity": "sha512-ZvaQtRUf4Yl9zi0+SMzjlDeHp9+p5IXkNu2k6RtW04c+RYKA1jX+umeKNwzft4iR3+KxDlpLX2trTFEW6W7HKQ==", - "dev": true + "integrity": "sha512-ZvaQtRUf4Yl9zi0+SMzjlDeHp9+p5IXkNu2k6RtW04c+RYKA1jX+umeKNwzft4iR3+KxDlpLX2trTFEW6W7HKQ==" }, "@gravity-ui/icons": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/@gravity-ui/icons/-/icons-2.10.0.tgz", "integrity": "sha512-xS0G4+TM7cD2cCKS4wVc01c4lLe/OreKjm4sHwrOtJWH4EawaRbpkuwtgUDcUvY2EryIcI6lgV+8o714m6lcyQ==", - "dev": true, "requires": {} }, + "@gravity-ui/navigation": { + "version": "2.17.2", + "resolved": "https://registry.npmjs.org/@gravity-ui/navigation/-/navigation-2.17.2.tgz", + "integrity": "sha512-cuYSAJYHgj2RCqwxyoq9nWeHXbHX/uXF3zNuowsUrs4P+gVp2oWP2F5fmbwX89Uafg8WMQ+MrIAAmEGSnz/b9A==", + "requires": { + "react-transition-group": "^4.4.1", + "tslib": "^2.4.0" + } + }, "@gravity-ui/uikit": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@gravity-ui/uikit/-/uikit-6.20.0.tgz", - "integrity": "sha512-ngeFZH0CgF+6cNwWTrtwecYkb1Rb7TWFg3eUg87TGLvXiEemepJuwLvM4t9WX3yZFzFCzIoHML2h/SM6VXhjyQ==", - "dev": true, + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/@gravity-ui/uikit/-/uikit-6.23.0.tgz", + "integrity": "sha512-v+1QPL+IdaGxv3tuEUHaRSzK5Hp/XNJv3EQxMIMFSV4ojtvio6sQAlzLIYALGN/IULWRwlfF0cqG/TavOo9yog==", "requires": { "@bem-react/classname": "^1.6.0", "@gravity-ui/i18n": "^1.3.0", @@ -34054,8 +34144,7 @@ "@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", - "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "dev": true + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==" }, "@puppeteer/browsers": { "version": "1.3.0", @@ -34173,6 +34262,12 @@ "@react-hook/passive-layout-effect": "^1.2.0" } }, + "@remix-run/router": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.18.0.tgz", + "integrity": "sha512-L3jkqmqoSVBVKHfpGZmLrex0lxR5SucGA0sUfFzGctehw+S/ggL9L/0NnC5mw6P8HUWpFZ3nQw3cRApjjWx9Sw==", + "dev": true + }, "@semantic-ui-react/event-stack": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/@semantic-ui-react/event-stack/-/event-stack-3.1.3.tgz", @@ -34327,6 +34422,21 @@ "defer-to-connect": "^2.0.1" } }, + "@tanstack/react-virtual": { + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.8.4.tgz", + "integrity": "sha512-Dq0VQr3QlTS2qL35g360QaJWBt7tCn/0xw4uZ0dHXPLO1Ak4Z4nVX4vuj1Npg1b/jqNMDToRtR5OIxM2NXRBWg==", + "dev": true, + "requires": { + "@tanstack/virtual-core": "3.8.4" + } + }, + "@tanstack/virtual-core": { + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.8.4.tgz", + "integrity": "sha512-iO5Ujgw3O1yIxWDe9FgUPNkGjyT657b1WNX52u+Wv1DyBFEpdCdGkuVaky0M3hHFqNWjAmHWTn4wgj9rTr7ZQg==", + "dev": true + }, "@testing-library/dom": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", @@ -34549,8 +34659,7 @@ "@types/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", - "dev": true + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" }, "@types/debug": { "version": "4.1.8", @@ -34648,7 +34757,6 @@ "version": "3.3.5", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz", "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==", - "dev": true, "requires": { "@types/react": "*", "hoist-non-react-statics": "^3.3.0" @@ -34819,8 +34927,7 @@ "@types/prop-types": { "version": "15.7.4", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", - "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==", - "dev": true + "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==" }, "@types/proxyquire": { "version": "1.3.28", @@ -34844,17 +34951,24 @@ "version": "18.3.3", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", - "dev": true, "requires": { "@types/prop-types": "*", "csstype": "^3.0.2" } }, + "@types/react-dom": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", + "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/react-redux": { "version": "7.1.33", "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.33.tgz", "integrity": "sha512-NF8m5AjWCkert+fosDsN3hAlHzpjSiXlVy9EgQEmLoBhaNXbmyeGs/aj5dQzKuF+/q+S7JQagorGDW8pJ28Hmg==", - "dev": true, "requires": { "@types/hoist-non-react-statics": "^3.3.0", "@types/react": "*", @@ -34862,6 +34976,25 @@ "redux": "^4.0.0" } }, + "@types/react-virtualized": { + "version": "9.21.30", + "resolved": "https://registry.npmjs.org/@types/react-virtualized/-/react-virtualized-9.21.30.tgz", + "integrity": "sha512-4l2TFLQ8BCjNDQlvH85tU6gctuZoEdgYzENQyZHpgTHU7hoLzYgPSOALMAeA58LOWua8AzC6wBivPj1lfl6JgQ==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "@types/react": "*" + } + }, + "@types/react-window": { + "version": "1.8.8", + "resolved": "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.8.tgz", + "integrity": "sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/responselike": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", @@ -38069,8 +38202,7 @@ "blueimp-md5": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz", - "integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==", - "dev": true + "integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==" }, "bn.js": { "version": "5.2.0", @@ -38911,8 +39043,7 @@ "classnames": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", - "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==", - "dev": true + "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" }, "clean-css": { "version": "5.3.2", @@ -40341,7 +40472,6 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", - "dev": true, "requires": { "toggle-selection": "^1.0.6" } @@ -40702,7 +40832,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", - "dev": true, "requires": { "tiny-invariant": "^1.0.6" } @@ -40924,8 +41053,7 @@ "csstype": { "version": "3.0.10", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz", - "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==", - "dev": true + "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==" }, "currently-unhandled": { "version": "0.4.1", @@ -41018,8 +41146,7 @@ "dayjs": { "version": "1.11.10", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", - "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==", - "dev": true + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" }, "debug": { "version": "4.3.4", @@ -41740,7 +41867,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "dev": true, "requires": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" @@ -43355,7 +43481,6 @@ "version": "7.5.4", "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.4.tgz", "integrity": "sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==", - "dev": true, "requires": { "tabbable": "^6.2.0" } @@ -44590,7 +44715,6 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "dev": true, "requires": { "react-is": "^16.7.0" } @@ -46033,8 +46157,7 @@ "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { "version": "3.14.1", @@ -46637,7 +46760,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "requires": { "js-tokens": "^3.0.0 || ^4.0.0" } @@ -46827,8 +46949,7 @@ "memoize-one": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", - "dev": true + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" }, "memory-fs": { "version": "0.4.1", @@ -48039,8 +48160,7 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-copy": { "version": "0.1.0", @@ -49418,7 +49538,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "requires": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -49663,8 +49782,7 @@ "raf-schd": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", - "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==", - "dev": true + "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==" }, "randombytes": { "version": "2.1.0", @@ -49717,7 +49835,6 @@ "version": "10.6.2", "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-10.6.2.tgz", "integrity": "sha512-FjkoFjyvUQWcBo1F3RgSglky3ar0+qHLM41PlFVYB4Bj3RD8E/Mv7kqMouLFBU+3aFglMzzctAIWRwajEuueSw==", - "dev": true, "requires": { "@babel/runtime": "^7.10.1", "classnames": "^2.2.5", @@ -49728,7 +49845,6 @@ "version": "5.43.0", "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.43.0.tgz", "integrity": "sha512-AzC7KKOXFqAdIBqdGWepL9Xn7cm3vnAmjlHqUnoQaTMZYhM4VlXGLkkHHxj/BZ7Td0+SOPKB4RGPboBVKT9htw==", - "dev": true, "requires": { "@babel/runtime": "^7.18.3", "react-is": "^18.2.0" @@ -49737,8 +49853,7 @@ "react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" } } }, @@ -49746,7 +49861,6 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "dev": true, "requires": { "loose-envify": "^1.1.0" } @@ -49755,7 +49869,6 @@ "version": "13.1.1", "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz", "integrity": "sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==", - "dev": true, "requires": { "@babel/runtime": "^7.9.2", "css-box-model": "^1.2.0", @@ -49792,7 +49905,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz", "integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==", - "dev": true, "requires": { "copy-to-clipboard": "^3.3.1", "prop-types": "^15.8.1" @@ -49802,7 +49914,6 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", - "dev": true, "requires": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -49811,8 +49922,7 @@ "react-fast-compare": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", - "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", - "dev": true + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" }, "react-hotkeys": { "version": "2.0.0", @@ -49826,8 +49936,7 @@ "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "react-lifecycles-compat": { "version": "3.0.4", @@ -49877,7 +49986,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", "integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==", - "dev": true, "requires": { "react-fast-compare": "^3.0.1", "warning": "^4.0.2" @@ -49893,7 +50001,6 @@ "version": "7.2.9", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==", - "dev": true, "requires": { "@babel/runtime": "^7.15.4", "@types/react-redux": "^7.1.20", @@ -49906,16 +50013,42 @@ "react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" } } }, + "react-router": { + "version": "6.25.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.25.1.tgz", + "integrity": "sha512-u8ELFr5Z6g02nUtpPAggP73Jigj1mRePSwhS/2nkTrlPU5yEkH1vYzWNyvSnSzeeE2DNqWdH+P8OhIh9wuXhTw==", + "dev": true, + "requires": { + "@remix-run/router": "1.18.0" + } + }, + "react-router-dom": { + "version": "6.25.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.25.1.tgz", + "integrity": "sha512-0tUDpbFvk35iv+N89dWNrJp+afLgd+y4VtorJZuOCXK0kkCWjEvb3vTJM++SYvMEpbVwXKf3FjeVveVEb6JpDQ==", + "dev": true, + "requires": { + "@remix-run/router": "1.18.0", + "react-router": "6.25.1" + } + }, + "react-split": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/react-split/-/react-split-2.0.14.tgz", + "integrity": "sha512-bKWydgMgaKTg/2JGQnaJPg51T6dmumTWZppFgEbbY0Fbme0F5TuatAScCLaqommbGQQf/ZT1zaejuPDriscISA==", + "requires": { + "prop-types": "^15.5.7", + "split.js": "^1.6.0" + } + }, "react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", - "dev": true, "requires": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", @@ -49941,14 +50074,12 @@ "version": "1.0.24", "resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.24.tgz", "integrity": "sha512-3kCn7N9NEb3FlvJrSHWGQ4iVl+ydQObq2fHMn12i5wbtm74zHOPhz/i64OL3c1S1vi9i2GXtZqNqUJTQ+BnNfg==", - "dev": true, "requires": {} }, "react-window": { "version": "1.8.10", "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.10.tgz", "integrity": "sha512-Y0Cx+dnU6NLa5/EvoHukUD0BklJ8qITCtVEPY1C/nL8wwoZ0b5aEw8Ff1dOVHw7fCzMt55XfJDd8S8W8LCaUCg==", - "dev": true, "requires": { "@babel/runtime": "^7.0.0", "memoize-one": ">=3.1.1 <6" @@ -50107,7 +50238,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/redux/-/redux-4.1.2.tgz", "integrity": "sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==", - "dev": true, "requires": { "@babel/runtime": "^7.9.2" } @@ -50286,8 +50416,7 @@ "resize-observer-polyfill": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", - "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", - "dev": true + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" }, "resolve": { "version": "1.20.0", @@ -50534,7 +50663,6 @@ "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "dev": true, "requires": { "loose-envify": "^1.1.0" } @@ -51426,6 +51554,11 @@ "extend-shallow": "^3.0.0" } }, + "split.js": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/split.js/-/split.js-1.6.5.tgz", + "integrity": "sha512-mPTnGCiS/RiuTNsVhCm9De9cCAUsrNFFviRbADdKiiV+Kk8HKp/0fWu7Kr8pi3/yBmsqLFHuXGT9UUZ+CNLwFw==" + }, "split2": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz", @@ -52319,8 +52452,7 @@ "tabbable": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", - "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", - "dev": true + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" }, "tapable": { "version": "2.2.1", @@ -53161,8 +53293,7 @@ "tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", - "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", - "dev": true + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==" }, "tmp": { "version": "0.1.0", @@ -53233,8 +53364,7 @@ "toggle-selection": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", - "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", - "dev": true + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" }, "toidentifier": { "version": "1.0.0", @@ -53753,7 +53883,6 @@ "version": "6.1.3", "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-6.1.3.tgz", "integrity": "sha512-AETYRrhpRgl9T1YtnODmQE32G81U3A+f3HO3ZeK7efbXqe3x+RNOW4RTpV0iff7zJWhGYJA6EI0Mm+w50aFTAw==", - "dev": true, "requires": { "@types/cookie": "^0.6.0", "cookie": "^0.6.0" @@ -53762,8 +53891,7 @@ "cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "dev": true + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" } } }, @@ -53938,7 +54066,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==", - "dev": true, "requires": {} }, "userhome": { @@ -54070,7 +54197,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "dev": true, "requires": { "loose-envify": "^1.0.0" } diff --git a/package.json b/package.json index 6b3fa89e8..b0f6db89a 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "@babel/runtime": "^7.22.5", "@gemini-testing/commander": "^2.15.3", "@gemini-testing/sql.js": "^2.0.0", + "@gravity-ui/navigation": "^2.16.0", "ansi-html-community": "^0.0.8", "axios": "1.6.3", "better-sqlite3": "^10.0.0", @@ -111,6 +112,9 @@ "ora": "^5.4.1", "p-queue": "^5.0.0", "qs": "^6.9.1", + "react-split": "^2.0.14", + "react-virtualized-auto-sizer": "^1.0.24", + "react-window": "^1.8.10", "signal-exit": "^4.1.0", "strip-ansi": "^6.0.1", "tmp": "^0.1.0", @@ -124,10 +128,11 @@ "@babel/preset-react": "^7.22.5", "@babel/preset-typescript": "^7.22.5", "@gravity-ui/components": "^3.7.0", - "@gravity-ui/uikit": "^6.20.0", + "@gravity-ui/uikit": "^6.23.0", "@playwright/test": "^1.44.1", "@react-hook/resize-observer": "^2.0.1", "@swc/core": "^1.3.64", + "@tanstack/react-virtual": "^3.8.3", "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.5.2", "@types/babel__core": "^7.20.5", @@ -146,6 +151,9 @@ "@types/npm-which": "^3.0.3", "@types/opener": "^1.4.0", "@types/proxyquire": "^1.3.28", + "@types/react-dom": "^18.3.0", + "@types/react-virtualized": "^9.21.30", + "@types/react-window": "^1.8.8", "@types/sinon": "^4.3.3", "@types/tmp": "^0.1.0", "@types/urijs": "^1.19.19", @@ -207,6 +215,7 @@ "react-hotkeys": "^2.0.0", "react-markdown": "^6.0.3", "react-redux": "^7.2.1", + "react-router-dom": "^6.25.1", "react-virtualized": "^9.22.5", "reapop": "^4.2.2", "reduce-reducers": "^1.0.4", diff --git a/test/setup/globals.js b/test/setup/globals.js index 717ac74f9..310b17fbf 100644 --- a/test/setup/globals.js +++ b/test/setup/globals.js @@ -19,4 +19,17 @@ chai.use(require('chai-as-promised')); chai.use(require('chai-dom')); sinon.assert.expose(chai.assert, {prefix: ''}); -require('app-module-path').addPath(path.resolve(__dirname, '..', '..')); +const projectRoot = path.resolve(__dirname, '..', '..'); + +// Resolving imports like lib/.../ +require('app-module-path').addPath(projectRoot); + +// Resolving webpack alias imports like @/.../ +try { + const fs = require('fs'); + fs.symlinkSync(path.join(projectRoot, 'lib'), path.join(projectRoot, '@')); +} catch (e) { + if (e.code !== 'EEXIST') { + throw e; + } +} diff --git a/tsconfig.spec.json b/tsconfig.spec.json index 9cf19863e..8a2555b73 100644 --- a/tsconfig.spec.json +++ b/tsconfig.spec.json @@ -6,7 +6,8 @@ "jsx": "react", "noEmit": true, "paths": { - "lib/*": ["./lib/*"] + "lib/*": ["./lib/*"], + "@/*": ["./lib/*"] } }, } diff --git a/webpack.common.js b/webpack.common.js index 925f12041..a99d9f08e 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -15,9 +15,14 @@ const staticPath = path.resolve(__dirname, 'build', 'lib', 'static'); module.exports = { entry: { report: ['./index.jsx', './variables.css', './styles.css'], - gui: ['./gui.jsx', './variables.css', './styles.css', './gui.css'] + gui: ['./gui.jsx', './variables.css', './styles.css', './gui.css'], + newReport: ['./new-ui/app/report.tsx', './variables.css', './styles.css'], + newGui: ['./new-ui/app/gui.tsx', './variables.css', './styles.css', './gui.css'] }, resolve: { + alias: { + '@': path.resolve(__dirname, 'lib') + }, extensions: ['.js', '.jsx', '.ts', '.tsx'], fallback: { buffer: require.resolve('buffer'), @@ -38,7 +43,15 @@ module.exports = { rules: [ { test: /\.css$/, - use: [MiniCssExtractPlugin.loader, 'css-loader'] + use: [MiniCssExtractPlugin.loader, { + loader: 'css-loader', + options: { + modules: { + auto: true, + exportLocalsConvention: 'camelCase' + } + } + }] }, { test: /\.less$/, @@ -62,7 +75,7 @@ module.exports = { } }, generator: { - filename: path.resolve(__dirname, 'lib', 'static', 'assets', '[contenthash][ext][query]') + filename: path.join('assets', '[contenthash][ext][query]') } } @@ -107,6 +120,28 @@ module.exports = { files: ['gui.html'], scripts: ['sql-wasm.js'], append: false + }), + new HtmlWebpackPlugin({ + title: 'HTML report (New UI)', + filename: 'new-ui-report.html', + template: 'template.html', + chunks: ['newReport'] + }), + new HtmlWebpackTagsPlugin({ + files: ['new-ui-report.html'], + scripts: ['data.js', 'sql-wasm.js'], + append: false + }), + new HtmlWebpackPlugin({ + title: 'GUI report (New UI)', + filename: 'new-ui-gui.html', + template: 'template.html', + chunks: ['newGui'] + }), + new HtmlWebpackTagsPlugin({ + files: ['new-ui-gui.html'], + scripts: ['sql-wasm.js'], + append: false }) ], optimization: {