diff --git a/packages/scenes-app/.config/webpack/webpack.config.ts b/packages/scenes-app/.config/webpack/webpack.config.ts index 5714a1e5e..5b7e20e4a 100644 --- a/packages/scenes-app/.config/webpack/webpack.config.ts +++ b/packages/scenes-app/.config/webpack/webpack.config.ts @@ -47,7 +47,7 @@ const config = (env): Configuration => ({ 'react-redux', 'redux', 'rxjs', - 'react-router-dom', + 'react-router', 'd3', 'angular', '@grafana/ui', diff --git a/packages/scenes-app/package.json b/packages/scenes-app/package.json index e654cf7e4..1ed19433d 100644 --- a/packages/scenes-app/package.json +++ b/packages/scenes-app/package.json @@ -29,7 +29,6 @@ "@testing-library/react": "^12.1.3", "@types/jest": "^29.5.12", "@types/node": "^20.11.30", - "@types/react-router-dom": "^5.3.3", "@typescript-eslint/eslint-plugin": "^4.33.0", "@typescript-eslint/parser": "^4.33.0", "copy-webpack-plugin": "^10.0.0", @@ -70,7 +69,7 @@ "@types/lodash": "latest", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "^5.2.0" + "react-router-dom": "^6.28.0" }, "lint-staged": { "*.{js,ts,tsx}": [ diff --git a/packages/scenes-app/src/components/App/App.tsx b/packages/scenes-app/src/components/App/App.tsx index 7122d3923..c4d0e707b 100644 --- a/packages/scenes-app/src/components/App/App.tsx +++ b/packages/scenes-app/src/components/App/App.tsx @@ -1,13 +1,13 @@ import * as React from 'react'; import { AppRootProps } from '@grafana/data'; import { PluginPropsContext } from '../../utils/utils.plugin'; -import { Routes } from '../Routes'; +import { AppRoutes } from '../Routes'; export class App extends React.PureComponent { render() { return ( - + ); } diff --git a/packages/scenes-app/src/components/Routes/Routes.tsx b/packages/scenes-app/src/components/Routes/Routes.tsx index f01432fd6..33f7856b7 100644 --- a/packages/scenes-app/src/components/Routes/Routes.tsx +++ b/packages/scenes-app/src/components/Routes/Routes.tsx @@ -1,19 +1,17 @@ import * as React from 'react'; -import { Redirect, Route, Switch } from 'react-router-dom'; -import { prefixRoute } from '../../utils/utils.routing'; +import { Route, Routes } from 'react-router-dom'; import { ROUTES } from '../../constants'; import { DemoListPage } from '../../pages/DemoListPage'; import GrafanaMonitoringApp from '../../monitoring-app/GrafanaMonitoringApp'; import { ReactDemoPage } from '../../react-demo/Home'; -export const Routes = () => { +export function AppRoutes() { return ( - - {/* Default page */} - - - - - + + + + + {/* */} + ); -}; +} diff --git a/packages/scenes-app/src/demos/adhocFiltersDemo.tsx b/packages/scenes-app/src/demos/adhocFiltersDemo.tsx index 4d2e053aa..a5a7624cc 100644 --- a/packages/scenes-app/src/demos/adhocFiltersDemo.tsx +++ b/packages/scenes-app/src/demos/adhocFiltersDemo.tsx @@ -30,6 +30,7 @@ export function getAdhocFiltersDemo(defaults: SceneAppPageState) { new SceneAppPage({ title: 'Apply mode auto', url: `${defaults.url}/auto`, + routePath: `auto`, getScene: () => { return new EmbeddedScene({ ...getEmbeddedSceneDefaults(), @@ -73,6 +74,7 @@ export function getAdhocFiltersDemo(defaults: SceneAppPageState) { new SceneAppPage({ title: 'Apply mode manual', url: `${defaults.url}/manual`, + routePath: `manual`, getScene: () => { const filtersVar = new AdHocFiltersVariable({ applyMode: 'manual', @@ -128,6 +130,7 @@ export function getAdhocFiltersDemo(defaults: SceneAppPageState) { new SceneAppPage({ title: 'Vertical Variants', url: `${defaults.url}/vertical`, + routePath: `vertical`, getScene: () => { return new EmbeddedScene({ ...getEmbeddedSceneDefaults(), @@ -201,6 +204,7 @@ export function getAdhocFiltersDemo(defaults: SceneAppPageState) { }), new SceneAppPage({ title: 'New Filters UI', + routePath: `new-filters`, url: `${defaults.url}/new-filters`, getScene: () => { return new EmbeddedScene({ diff --git a/packages/scenes-app/src/demos/cursorSync.tsx b/packages/scenes-app/src/demos/cursorSync.tsx index 4b081cd58..e79619fd1 100644 --- a/packages/scenes-app/src/demos/cursorSync.tsx +++ b/packages/scenes-app/src/demos/cursorSync.tsx @@ -75,6 +75,7 @@ export function scoped() { return new SceneAppPage({ title: 'Cursor sync test', url: demoUrl('cursor-sync/scoped'), + routePath: 'scoped', getScene: () => { return new EmbeddedScene({ ...getEmbeddedSceneDefaults(), diff --git a/packages/scenes-app/src/demos/docs-examples.tsx b/packages/scenes-app/src/demos/docs-examples.tsx index ef80f419c..e65392241 100644 --- a/packages/scenes-app/src/demos/docs-examples.tsx +++ b/packages/scenes-app/src/demos/docs-examples.tsx @@ -118,6 +118,7 @@ export function getDocsExamples(defaults: SceneAppPageState) { function getDocsExample(getScene: () => EmbeddedScene, url: string, title: string) { return new SceneAppPage({ title, + routePath: url, url: demoUrl(`docs-examples/${url}`), getScene, }); diff --git a/packages/scenes-app/src/demos/dynamicPage.tsx b/packages/scenes-app/src/demos/dynamicPage.tsx index 276ed7729..3dd11fd8e 100644 --- a/packages/scenes-app/src/demos/dynamicPage.tsx +++ b/packages/scenes-app/src/demos/dynamicPage.tsx @@ -61,6 +61,7 @@ export function getDynamicPageDemo(defaults: SceneAppPageState): SceneAppPage { function getSceneAppPage(url: string, name: string) { return new SceneAppPage({ title: name, + routePath: url, url: `${demoUrl('dynamic-page')}${url}`, getScene: () => { return new EmbeddedScene({ diff --git a/packages/scenes-app/src/demos/split.tsx b/packages/scenes-app/src/demos/split.tsx index 8861d3d17..0b548ab0c 100644 --- a/packages/scenes-app/src/demos/split.tsx +++ b/packages/scenes-app/src/demos/split.tsx @@ -193,6 +193,7 @@ const dynamicSplitDemo = () => new SceneAppPage({ title: 'Dynamic split layout test', url: demoUrl('split-layout/dynamic'), + routePath: 'dynamic', getScene: getDynamicSplitScene, }); diff --git a/packages/scenes-app/src/demos/urlSyncTest.tsx b/packages/scenes-app/src/demos/urlSyncTest.tsx index 3140bd1c4..2ca55ffdf 100644 --- a/packages/scenes-app/src/demos/urlSyncTest.tsx +++ b/packages/scenes-app/src/demos/urlSyncTest.tsx @@ -34,6 +34,7 @@ export function getUrlSyncTest(defaults: SceneAppPageState) { new SceneAppPage({ title: 'First', url: `${defaults.url}/first`, + routePath: 'first', getScene: () => { return new EmbeddedScene({ controls: [new VariableValueSelectors({})], @@ -57,6 +58,7 @@ export function getUrlSyncTest(defaults: SceneAppPageState) { new SceneAppPage({ title: 'Second', url: `${defaults.url}/manual`, + routePath: 'manual', getScene: () => { return new EmbeddedScene({ controls: [new VariableValueSelectors({})], diff --git a/packages/scenes-app/src/demos/variables.tsx b/packages/scenes-app/src/demos/variables.tsx index b67a79c11..da8e7d4a5 100644 --- a/packages/scenes-app/src/demos/variables.tsx +++ b/packages/scenes-app/src/demos/variables.tsx @@ -34,6 +34,7 @@ export function getVariablesDemo(defaults: SceneAppPageState) { new SceneAppPage({ title: 'Async and chained', url: `${defaults.url}/query`, + routePath: 'query', getScene: () => { return new EmbeddedScene({ controls: [new VariableValueSelectors({})], @@ -116,6 +117,7 @@ export function getVariablesDemo(defaults: SceneAppPageState) { new SceneAppPage({ title: 'Data source and textbox', url: `${defaults.url}/ds`, + routePath: 'ds', getScene: () => { return new EmbeddedScene({ controls: [new VariableValueSelectors({})], @@ -158,6 +160,7 @@ export function getVariablesDemo(defaults: SceneAppPageState) { new SceneAppPage({ title: 'Search filter', url: `${defaults.url}/search`, + routePath: 'search', getScene: () => { return new EmbeddedScene({ controls: [new VariableValueSelectors({})], @@ -191,6 +194,7 @@ export function getVariablesDemo(defaults: SceneAppPageState) { new SceneAppPage({ title: 'Many variable options', url: `${defaults.url}/many-values`, + routePath: 'many-values', getScene: () => { return new EmbeddedScene({ controls: [new VariableValueSelectors({})], @@ -224,6 +228,7 @@ export function getVariablesDemo(defaults: SceneAppPageState) { new SceneAppPage({ title: 'Many adhoc variable values', url: `${defaults.url}/many-adhoc-values`, + routePath: 'many-adhoc-values', getScene: () => { return new EmbeddedScene({ controls: [new VariableValueSelectors({})], diff --git a/packages/scenes-app/src/demos/withDrilldown/WithDrilldown.tsx b/packages/scenes-app/src/demos/withDrilldown/WithDrilldown.tsx index afeeeed16..236aa7bf0 100644 --- a/packages/scenes-app/src/demos/withDrilldown/WithDrilldown.tsx +++ b/packages/scenes-app/src/demos/withDrilldown/WithDrilldown.tsx @@ -53,7 +53,7 @@ export function getDrilldownsAppPageScene(defaults: SceneAppPageState) { getScene, drilldowns: [ { - routePath: `${defaults.url}/room/:roomName`, + routePath: `room/:roomName/*`, getPage(routeMatch, parent) { const roomName = routeMatch.params.roomName; @@ -66,6 +66,7 @@ export function getDrilldownsAppPageScene(defaults: SceneAppPageState) { new SceneAppPage({ title: 'Temperature', titleIcon: 'dashboard', + routePath: 'temperature', tabSuffix: () => , url: `${defaults.url}/room/${roomName}/temperature`, getScene: () => getRoomDrilldownScene(roomName), @@ -73,6 +74,7 @@ export function getDrilldownsAppPageScene(defaults: SceneAppPageState) { new SceneAppPage({ title: 'Humidity', titleIcon: 'chart-line', + routePath: 'humidity', url: `${defaults.url}/room/${roomName}/humidity`, getScene: () => getHumidityOverviewScene(roomName), }), diff --git a/packages/scenes-app/src/pages/DemoListPage.tsx b/packages/scenes-app/src/pages/DemoListPage.tsx index 9fc19ea1c..083e1a832 100644 --- a/packages/scenes-app/src/pages/DemoListPage.tsx +++ b/packages/scenes-app/src/pages/DemoListPage.tsx @@ -32,7 +32,8 @@ function getDemoSceneApp() { new SceneAppPage({ title: 'Demos', key: 'SceneAppPage Demos', - url: prefixRoute(ROUTES.Demos), + url: `${prefixRoute(ROUTES.Demos)}`, + routePath: '*', preserveUrlKeys: [], getScene: () => { return new EmbeddedScene({ @@ -42,7 +43,7 @@ function getDemoSceneApp() { }, drilldowns: [ { - routePath: `${demoUrl(':demo')}`, + routePath: `:demo/*`, getPage: (routeMatch, parent) => { const demos = getDemos(); const demoSlug = decodeURIComponent(routeMatch.params.demo); @@ -54,7 +55,8 @@ function getDemoSceneApp() { return demoInfo.getPage({ title: demoInfo.title, - url: `${demoUrl(slugify(demoInfo.title))}`, + url: `${prefixRoute(ROUTES.Demos)}/${demoSlug}`, + routePath: `${slugify(demoInfo.title)}/*`, getParentPage: () => parent, }); }, diff --git a/packages/scenes-app/src/react-demo/DrilldownDemoPage.tsx b/packages/scenes-app/src/react-demo/DrilldownDemoPage.tsx index 4ecae5656..a8c469295 100644 --- a/packages/scenes-app/src/react-demo/DrilldownDemoPage.tsx +++ b/packages/scenes-app/src/react-demo/DrilldownDemoPage.tsx @@ -4,7 +4,7 @@ import React from 'react'; import { DATASOURCE_REF } from '../constants'; import { PageWrapper } from './PageWrapper'; import { DemoVizLayout, urlBase } from './utils'; -import { Route, Switch, useParams } from 'react-router-dom'; +import { Route, Routes, useParams } from 'react-router-dom'; import { PlainGraphWithRandomWalk } from './PlainGraphWithRandomWalk'; export function DrilldownDemoPage() { @@ -15,10 +15,10 @@ export function DrilldownDemoPage() { return ( - - - - + + } /> + } /> + ); } diff --git a/packages/scenes-app/src/react-demo/Home.tsx b/packages/scenes-app/src/react-demo/Home.tsx index 945dedc4c..15d965090 100644 --- a/packages/scenes-app/src/react-demo/Home.tsx +++ b/packages/scenes-app/src/react-demo/Home.tsx @@ -1,7 +1,7 @@ import { SceneContextProvider, CustomVariable } from '@grafana/scenes-react'; import { Stack, TextLink } from '@grafana/ui'; import React from 'react'; -import { Route, Switch } from 'react-router-dom'; +import { Route, Routes } from 'react-router-dom'; import { PlainGraphWithRandomWalk } from './PlainGraphWithRandomWalk'; import { PageWrapper } from './PageWrapper'; import { DynamicQueriesPage } from './DynamicQueriesPage'; @@ -21,20 +21,20 @@ export function ReactDemoPage() { return ( - - - - - - - - - - - - - - + + + + + + + + + + + + + + ); diff --git a/packages/scenes-react/package.json b/packages/scenes-react/package.json index 1e59653e5..2504214f3 100644 --- a/packages/scenes-react/package.json +++ b/packages/scenes-react/package.json @@ -48,7 +48,8 @@ "@grafana/schema": "^11.0.0", "@grafana/ui": "^11.0.0", "react": "^18.0.0", - "react-dom": "^18.0.0" + "react-dom": "^18.0.0", + "react-router-dom": "^6.28.0" }, "devDependencies": { "@emotion/css": "11.10.5", @@ -71,7 +72,6 @@ "@types/react": "18.2.74", "@types/react-dom": "18.2.24", "@types/react-grid-layout": "1.3.2", - "@types/react-router-dom": "5.3.3", "@types/react-virtualized-auto-sizer": "1.0.1", "@types/uuid": "8.3.4", "@typescript-eslint/eslint-plugin": "^4.33.0", @@ -89,7 +89,6 @@ "jest-matcher-utils": "29.7.0", "lodash": "4.17.21", "prettier": "2.5.1", - "react-router-dom": "^5.2.0", "react-select-event": "^5.5.1", "rimraf": "^3.0.2", "rollup": "2.79.1", diff --git a/packages/scenes-react/src/contexts/SceneContextProvider.test.tsx b/packages/scenes-react/src/contexts/SceneContextProvider.test.tsx index 7a5bec84c..40d135016 100644 --- a/packages/scenes-react/src/contexts/SceneContextProvider.test.tsx +++ b/packages/scenes-react/src/contexts/SceneContextProvider.test.tsx @@ -75,10 +75,11 @@ interface SetupProps extends Partial {} function setup(props: SetupProps) { const result: SetupResult = {} as SetupResult; + const history = locationService.getHistory(); result.renderResult = render( - + (result.context = c)}> diff --git a/packages/scenes/jest.config.ts b/packages/scenes/jest.config.ts index 9404b59b7..f2eacdd45 100644 --- a/packages/scenes/jest.config.ts +++ b/packages/scenes/jest.config.ts @@ -5,7 +5,8 @@ module.exports = { '\\.css$': '/utils/test/__mocks__/style.ts', }, testEnvironment: 'jsdom', - setupFilesAfterEnv: ['./utils/setupTests.ts'], + setupFiles: ['./utils/setupTests.ts'], + setupFilesAfterEnv: ['./utils/setupTestsAfterEnv.ts'], testMatch: ['/src/**/*.{spec,test,jest}.{js,jsx,ts,tsx}'], transform: { '^.+\\.(t|j)sx?$': [ diff --git a/packages/scenes/package.json b/packages/scenes/package.json index 7cf142338..b1758d809 100644 --- a/packages/scenes/package.json +++ b/packages/scenes/package.json @@ -40,9 +40,10 @@ "@floating-ui/react": "^0.26.16", "@leeoniya/ufuzzy": "^1.0.16", "@tanstack/react-virtual": "^3.9.0", - "react-grid-layout": "^1.3.4", - "react-use": "^17.5.0", - "react-virtualized-auto-sizer": "^1.0.24", + "react-grid-layout": "1.3.4", + "react-router-dom": "^6.28.0", + "react-use": "17.5.0", + "react-virtualized-auto-sizer": "1.0.24", "uuid": "^9.0.0" }, "peerDependencies": { @@ -73,7 +74,6 @@ "@types/react": "18.2.74", "@types/react-dom": "18.2.24", "@types/react-grid-layout": "1.3.2", - "@types/react-router-dom": "5.3.3", "@types/react-virtualized-auto-sizer": "1.0.1", "@types/uuid": "8.3.4", "@typescript-eslint/eslint-plugin": "^4.33.0", @@ -91,7 +91,6 @@ "jest-matcher-utils": "29.7.0", "lodash": "4.17.21", "prettier": "2.5.1", - "react-router-dom": "^5.2.0", "react-select-event": "^5.5.1", "rimraf": "^3.0.2", "rollup": "2.79.1", diff --git a/packages/scenes/src/components/SceneApp/SceneApp.test.tsx b/packages/scenes/src/components/SceneApp/SceneApp.test.tsx index a69a85b3a..5235af1c2 100644 --- a/packages/scenes/src/components/SceneApp/SceneApp.test.tsx +++ b/packages/scenes/src/components/SceneApp/SceneApp.test.tsx @@ -1,9 +1,8 @@ import { NavModelItem } from '@grafana/data'; -import { locationService, PluginPageProps } from '@grafana/runtime'; +import { locationService, PluginPage, PluginPageProps } from '@grafana/runtime'; import { screen, render } from '@testing-library/react'; -import { createMemoryHistory } from 'history'; import React from 'react'; -import { renderAppInsideRouterWithStartingUrl } from '../../../utils/test/utils'; +import { renderAppInsideRouterWithStartingUrl } from '../../../utils/test/renderAppInsideRoutingWithStartingUrl'; import { SceneObject } from '../../core/types'; import { EmbeddedScene } from '../EmbeddedScene'; import { SceneFlexItem, SceneFlexLayout } from '../layout/SceneFlexLayout'; @@ -13,17 +12,12 @@ import { SceneAppPage } from './SceneAppPage'; import { SceneRouteMatch } from './types'; import { SceneReactObject } from '../SceneReactObject'; -let history = createMemoryHistory(); -let pluginPageProps: PluginPageProps | undefined; - jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), - PluginPage: function PluginPageMock(props: PluginPageProps) { - pluginPageProps = props; + PluginPage: jest.fn().mockImplementation((props: PluginPageProps) => { return
{props.children}
; - }, + }), })); - jest.mock('../../utils/utils', () => ({ ...jest.requireActual('../../utils/utils'), useLocationServiceSafe: () => locationService, @@ -34,6 +28,7 @@ describe('SceneApp', () => { beforeEach(() => { console.error = jest.fn(); + jest.mocked(PluginPage).mockClear(); }); afterEach(() => { @@ -47,6 +42,7 @@ describe('SceneApp', () => { new SceneAppPage({ title: 'Test', url: '/test', + routePath: 'test/*', getScene: () => { return page1Scene; }, @@ -55,7 +51,7 @@ describe('SceneApp', () => { }); expect(() => render()).toThrowErrorMatchingInlineSnapshot( - `"Invariant failed: You should not use outside a "` + `"useRoutes() may be used only in the context of a component."` ); }); @@ -70,6 +66,7 @@ describe('SceneApp', () => { new SceneAppPage({ title: 'Test', url: '/test', + routePath: 'test/*', getScene: () => { return page1Scene; }, @@ -77,6 +74,7 @@ describe('SceneApp', () => { new SceneAppPage({ title: 'Test', url: '/test1', + routePath: 'test1/*', getScene: () => { return page2Scene; }, @@ -84,7 +82,7 @@ describe('SceneApp', () => { ], }); - beforeEach(() => renderAppInsideRouterWithStartingUrl(history, app, '/test')); + beforeEach(() => renderAppInsideRouterWithStartingUrl(app, '/test')); it('should render correct page on mount', async () => { expect(screen.queryByTestId(p1Object.state.key!)).toBeInTheDocument(); @@ -92,7 +90,7 @@ describe('SceneApp', () => { }); it('Can navigate to other page', async () => { - history.push('/test1'); + locationService.push('/test1'); expect(await screen.findByTestId(p2Object.state.key!)).toBeInTheDocument(); expect(screen.queryByTestId(p1Object.state.key!)).not.toBeInTheDocument(); @@ -110,17 +108,20 @@ describe('SceneApp', () => { new SceneAppPage({ title: 'Container page', url: '/test', + routePath: 'test/*', tabs: [ new SceneAppPage({ title: 'Tab1', titleIcon: 'grafana', tabSuffix: () => tab1 suffix, url: '/test/tab1', + routePath: 'tab1/*', getScene: () => setupScene(t1Object, 'tab1-scene'), }), new SceneAppPage({ title: 'Tab2', url: '/test/tab2', + routePath: 'tab2/*', getScene: () => setupScene(t2Object, 'tab2-scene'), }), ], @@ -128,15 +129,18 @@ describe('SceneApp', () => { new SceneAppPage({ title: 'Test', url: '/test1', + routePath: 'test1/*', getScene: () => setupScene(p2Object), }), ], }); - beforeEach(() => renderAppInsideRouterWithStartingUrl(history, app, '/test')); + beforeEach(() => { + renderAppInsideRouterWithStartingUrl(app, '/test'); + }); it('should render correct breadcrumbs', async () => { - expect(flattenPageNav(pluginPageProps?.pageNav!)).toEqual(['Container page']); + expect(flattenPageNav(jest.mocked(PluginPage).mock.calls[0][0].pageNav!)).toEqual(['Container page']); }); it('Inner embedded scene should be active and connected to containerPage', async () => { @@ -145,8 +149,11 @@ describe('SceneApp', () => { }); it('should render tab title with icon and suffix', async () => { - expect(pluginPageProps?.pageNav?.children?.[0].icon).toEqual('grafana'); - const suffix = pluginPageProps?.pageNav?.children?.[0].tabSuffix; + const pageNav = jest.mocked(PluginPage).mock.calls[0][0].pageNav!; + + expect(pageNav?.children?.[0].icon).toEqual('grafana'); + expect(pageNav?.children?.[0].icon).toEqual('grafana'); + const suffix = pageNav?.children?.[0].tabSuffix; expect((suffix as any)()).toEqual(tab1 suffix); }); @@ -157,12 +164,12 @@ describe('SceneApp', () => { }); it('Render first tab with its own url', async () => { - history.push('/test/tab1'); + locationService.push('/test/tab1'); expect(screen.queryByTestId(t1Object.state.key!)).toBeInTheDocument(); }); it('Can render second tab', async () => { - history.push('/test/tab2'); + locationService.push('/test/tab2'); expect(await screen.findByTestId(t2Object.state.key!)).toBeInTheDocument(); expect(screen.queryByTestId(p2Object.state.key!)).not.toBeInTheDocument(); @@ -186,12 +193,13 @@ describe('SceneApp', () => { key: 'top-level-page', title: 'Top level page', url: '/test-drilldown', + routePath: 'test-drilldown/*', getScene: () => { return page1Scene; }, drilldowns: [ { - routePath: '/test-drilldown/:id', + routePath: ':id/*', getPage: (match: SceneRouteMatch<{ id: string }>, parent) => { return new SceneAppPage({ key: 'drilldown-page', @@ -207,12 +215,12 @@ describe('SceneApp', () => { ], }); - beforeEach(() => renderAppInsideRouterWithStartingUrl(history, app, '/test-drilldown')); + beforeEach(() => renderAppInsideRouterWithStartingUrl(app, '/test-drilldown')); it('should render a drilldown page', async () => { expect(screen.queryByTestId(p1Object.state.key!)).toBeInTheDocument(); - history.push('/test-drilldown/some-id'); + locationService.push('/test-drilldown/some-id'); expect(await screen.findByText('some-id drilldown!')).toBeInTheDocument(); expect(screen.queryByTestId(p1Object.state.key!)).not.toBeInTheDocument(); @@ -222,9 +230,12 @@ describe('SceneApp', () => { expect((window as any).__grafanaSceneContext.parent.state.key).toBe('drilldown-page'); // Verify pageNav is correct - expect(flattenPageNav(pluginPageProps?.pageNav!)).toEqual(['Drilldown some-id', 'Top level page']); + expect(flattenPageNav(jest.mocked(PluginPage).mock.calls[1][0].pageNav!)).toEqual([ + 'Drilldown some-id', + 'Top level page', + ]); - history.push('/test-drilldown/some-other-id'); + locationService.push('/test-drilldown/some-other-id'); expect(await screen.findByText('some-other-id drilldown!')).toBeInTheDocument(); expect(screen.queryByTestId(p1Object.state.key!)).not.toBeInTheDocument(); @@ -236,7 +247,7 @@ describe('SceneApp', () => { }); it('When url does not match any drilldown sub page show fallback route', async () => { - history.push('/test-drilldown/some-id/does-not-exist'); + locationService.push('/test-drilldown/some-id/does-not-exist'); expect(await screen.findByTestId('default-fallback-content')).toBeInTheDocument(); }); @@ -250,10 +261,12 @@ describe('SceneApp', () => { new SceneAppPage({ title: 'Top level page', url: '/main', + routePath: 'main/*', tabs: [ new SceneAppPage({ title: 'Tab ', url: '/main/tab', + routePath: 'tab/*', getScene: () => { return page1Scene; }, @@ -261,7 +274,7 @@ describe('SceneApp', () => { ], drilldowns: [ { - routePath: '/main/drilldown/:id', + routePath: 'drilldown/:id/*', getPage: (match: SceneRouteMatch<{ id: string }>, parent) => { return new SceneAppPage({ title: `Drilldown ${match.params.id}`, @@ -276,14 +289,17 @@ describe('SceneApp', () => { ], }); - beforeEach(() => renderAppInsideRouterWithStartingUrl(history, app, '/main/drilldown/10')); + beforeEach(() => renderAppInsideRouterWithStartingUrl(app, '/main/drilldown/10')); it('should render a drilldown page', async () => { expect(await screen.findByText('10 drilldown!')).toBeInTheDocument(); expect(screen.queryByTestId(p1Object.state.key!)).not.toBeInTheDocument(); // Verify pageNav is correct - expect(flattenPageNav(pluginPageProps?.pageNav!)).toEqual(['Drilldown 10', 'Top level page']); + expect(flattenPageNav(jest.mocked(PluginPage).mock.calls[1][0].pageNav!)).toEqual([ + 'Drilldown 10', + 'Top level page', + ]); }); }); }); @@ -300,16 +316,18 @@ describe('SceneApp', () => { new SceneAppPage({ title: 'Container page', url: '/test', + routePath: 'test/*', tabs: [ new SceneAppPage({ title: 'Tab ', url: '/test/tab', + routePath: 'tab/*', getScene: () => { return tab1Scene; }, drilldowns: [ { - routePath: '/test/tab/:id', + routePath: 'tab/:id/*', getPage: (match: SceneRouteMatch<{ id: string }>) => { drillDownScenesGenerated++; @@ -327,24 +345,24 @@ describe('SceneApp', () => { ], }); - beforeEach(() => renderAppInsideRouterWithStartingUrl(history, app, '/test/tab')); + beforeEach(() => renderAppInsideRouterWithStartingUrl(app, '/test/tab')); it('should render a drilldown that is part of tab page', async () => { expect(screen.queryByTestId(t1Object.state.key!)).toBeInTheDocument(); - history.push('/test/tab/some-id'); + locationService.push('/test/tab/some-id'); expect(await screen.findByText('some-id drilldown!')).toBeInTheDocument(); expect(screen.queryByTestId(p1Object.state.key!)).not.toBeInTheDocument(); - history.push('/test/tab/some-other-id'); + locationService.push('/test/tab/some-other-id'); expect(await screen.findByText('some-other-id drilldown!')).toBeInTheDocument(); expect(screen.queryByTestId(p1Object.state.key!)).not.toBeInTheDocument(); expect(screen.queryByText('some-id drilldown!')).not.toBeInTheDocument(); // go back to the first drilldown - history.push('/test/tab/some-id'); + locationService.push('/test/tab/some-id'); expect(await screen.findByText('some-id drilldown!')).toBeInTheDocument(); // Verify that drilldown page was cached (getPage should not have been called again) @@ -352,7 +370,7 @@ describe('SceneApp', () => { }); it('When url does not match any drilldown sub page show fallback route', async () => { - history.push('/test/tab/drilldown-id/does-not-exist'); + locationService.push('/test/tab/drilldown-id/does-not-exist'); expect(await screen.findByTestId('default-fallback-content')).toBeInTheDocument(); }); }); @@ -365,6 +383,7 @@ describe('SceneApp', () => { new SceneAppPage({ title: 'Test', url: '/test', + routePath: 'test/*', getScene: () => { return page1Scene; }, @@ -372,6 +391,7 @@ describe('SceneApp', () => { return new SceneAppPage({ title: 'Loading', url: '', + routePath: '*', getScene: () => new EmbeddedScene({ body: new SceneReactObject({ @@ -385,9 +405,11 @@ describe('SceneApp', () => { }); it('should render custom fallback page if url does not match', async () => { - renderAppInsideRouterWithStartingUrl(history, app, '/test'); + renderAppInsideRouterWithStartingUrl(app, '/test'); expect(await screen.findByTestId(page1Obj.state.key!)).toBeInTheDocument(); - history.push('/test/does-not-exist'); + + locationService.push('/test/does-not-exist'); + expect(await screen.findByTestId('custom-fallback-content')).toBeInTheDocument(); }); }); diff --git a/packages/scenes/src/components/SceneApp/SceneApp.tsx b/packages/scenes/src/components/SceneApp/SceneApp.tsx index fc4583fc4..38ff66f58 100644 --- a/packages/scenes/src/components/SceneApp/SceneApp.tsx +++ b/packages/scenes/src/components/SceneApp/SceneApp.tsx @@ -1,10 +1,9 @@ import React, { createContext } from 'react'; -import { Route, Switch } from 'react-router-dom'; +import { Route, Routes } from 'react-router-dom'; import { DataRequestEnricher, SceneComponentProps } from '../../core/types'; import { SceneObjectBase } from '../../core/SceneObjectBase'; import { SceneAppState } from './types'; -import { renderSceneComponentWithRouteProps } from './utils'; /** * Responsible for top level pages routing @@ -21,16 +20,11 @@ export class SceneApp extends SceneObjectBase implements DataRequ return ( - + {pages.map((page) => ( - renderSceneComponentWithRouteProps(page, props)} - > + } /> ))} - + ); }; diff --git a/packages/scenes/src/components/SceneApp/SceneAppPage.tsx b/packages/scenes/src/components/SceneApp/SceneAppPage.tsx index 9fc3cd83b..b418c113a 100644 --- a/packages/scenes/src/components/SceneApp/SceneAppPage.tsx +++ b/packages/scenes/src/components/SceneApp/SceneAppPage.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Route, RouteComponentProps, Switch } from 'react-router-dom'; +import { Route, Routes } from 'react-router-dom'; import { SceneObjectBase } from '../../core/SceneObjectBase'; import { SceneComponentProps, SceneObject, isDataRequestEnricher } from '../../core/types'; import { EmbeddedScene } from '../EmbeddedScene'; @@ -7,7 +7,6 @@ import { SceneFlexItem, SceneFlexLayout } from '../layout/SceneFlexLayout'; import { SceneReactObject } from '../SceneReactObject'; import { SceneAppDrilldownViewRender, SceneAppPageView } from './SceneAppPageView'; import { SceneAppDrilldownView, SceneAppPageLike, SceneAppPageState, SceneRouteMatch } from './types'; -import { renderSceneComponentWithRouteProps } from './utils'; /** * Responsible for page's drilldown & tabs routing @@ -72,48 +71,32 @@ export class SceneAppPage extends SceneObjectBase implements return null; } } - -export interface SceneAppPageRendererProps extends SceneComponentProps { - routeProps: RouteComponentProps; -} - -function SceneAppPageRenderer({ model, routeProps }: SceneAppPageRendererProps) { +function SceneAppPageRenderer({ model }: SceneComponentProps) { const { tabs, drilldowns } = model.useState(); const routes: React.ReactNode[] = []; + routes.push(getFallbackRoute(model)); + if (tabs && tabs.length > 0) { for (let tabIndex = 0; tabIndex < tabs.length; tabIndex++) { const tab = tabs[tabIndex]; // Add first tab as a default route, this makes it possible for the first tab to render with the url of the parent page if (tabIndex === 0) { - routes.push( - renderSceneComponentWithRouteProps(tab, props)} - > - ); + routes.push(}>); } routes.push( - renderSceneComponentWithRouteProps(tab, props)} - > + }> ); if (tab.state.drilldowns) { for (const drilldown of tab.state.drilldowns) { routes.push( } + element={} > ); } @@ -126,57 +109,33 @@ function SceneAppPageRenderer({ model, routeProps }: SceneAppPageRendererProps) routes.push( } + Component={() => } > ); } } - if (!tabs && isCurrentPageRouteMatch(model, routeProps.match)) { - return ; + if (!tabs) { + routes.push(}>); } - routes.push(getFallbackRoute(model, routeProps)); - - return {routes}; + return {routes}; } -function getFallbackRoute(page: SceneAppPage, routeProps: RouteComponentProps) { +function getFallbackRoute(page: SceneAppPage) { return ( { + path="*" + Component={() => { const fallbackPage = page.state.getFallbackPage?.() ?? getDefaultFallbackPage(); - return ; + return ; }} > ); } -function isCurrentPageRouteMatch(page: SceneAppPage, match: SceneRouteMatch) { - if (!match.isExact) { - return false; - } - - // current page matches the route url - if (match.url === page.state.url) { - return true; - } - - // check if we are a tab and the first tab, then we should also render on the parent url - if ( - page.parent instanceof SceneAppPage && - page.parent.state.tabs![0] === page && - page.parent.state.url === match.url - ) { - return true; - } - - return false; -} - function getDefaultFallbackPage() { return new SceneAppPage({ url: '', diff --git a/packages/scenes/src/components/SceneApp/SceneAppPageView.tsx b/packages/scenes/src/components/SceneApp/SceneAppPageView.tsx index d9884fd32..c831c60df 100644 --- a/packages/scenes/src/components/SceneApp/SceneAppPageView.tsx +++ b/packages/scenes/src/components/SceneApp/SceneAppPageView.tsx @@ -2,26 +2,25 @@ import { NavModelItem, UrlQueryMap } from '@grafana/data'; import { PluginPage } from '@grafana/runtime'; import React, { useContext, useEffect, useLayoutEffect } from 'react'; -import { RouteComponentProps } from 'react-router-dom'; import { SceneObject } from '../../core/types'; import { SceneDebugger } from '../SceneDebugger/SceneDebugger'; import { SceneAppPage } from './SceneAppPage'; import { SceneAppDrilldownView, SceneAppPageLike } from './types'; -import { getUrlWithAppState, renderSceneComponentWithRouteProps, useAppQueryParams } from './utils'; +import { getUrlWithAppState, useAppQueryParams, useSceneRouteMatch } from './utils'; import { useUrlSync } from '../../services/useUrlSync'; import { SceneAppContext } from './SceneApp'; import { useLocationServiceSafe } from '../../utils/utils'; export interface Props { page: SceneAppPageLike; - routeProps: RouteComponentProps; } -export function SceneAppPageView({ page, routeProps }: Props) { +export function SceneAppPageView({ page }: Props) { + const routeMatch = useSceneRouteMatch(page.state.url); const containerPage = getParentPageIfTab(page); const containerState = containerPage.useState(); const params = useAppQueryParams(); - const scene = page.getScene(routeProps.match); + const scene = page.getScene(routeMatch); const appContext = useContext(SceneAppContext); const isInitialized = containerState.initializedScene === scene; const { layout } = page.state; @@ -130,9 +129,10 @@ function getParentBreadcrumbs( export interface SceneAppDrilldownViewRenderProps { drilldown: SceneAppDrilldownView; parent: SceneAppPageLike; - routeProps: RouteComponentProps; } -export function SceneAppDrilldownViewRender({ drilldown, parent, routeProps }: SceneAppDrilldownViewRenderProps) { - return renderSceneComponentWithRouteProps(parent.getDrilldownPage(drilldown, routeProps.match), routeProps); +export function SceneAppDrilldownViewRender({ drilldown, parent }: SceneAppDrilldownViewRenderProps) { + const routeMatch = useSceneRouteMatch(drilldown.routePath!); + const page = parent.getDrilldownPage(drilldown, routeMatch); + return ; } diff --git a/packages/scenes/src/components/SceneApp/utils.ts b/packages/scenes/src/components/SceneApp/utils.ts index 4ea5956e0..3f8aea631 100644 --- a/packages/scenes/src/components/SceneApp/utils.ts +++ b/packages/scenes/src/components/SceneApp/utils.ts @@ -1,8 +1,7 @@ -import React from 'react'; -import { RouteComponentProps, useLocation } from 'react-router-dom'; +import { matchPath, useLocation, useParams } from 'react-router-dom'; import { UrlQueryMap, locationUtil, urlUtil } from '@grafana/data'; import { locationSearchToObject } from '@grafana/runtime'; -import { SceneObject } from '../../core/types'; +import { SceneRouteMatch } from './types'; export function useAppQueryParams(): UrlQueryMap { const location = useLocation(); @@ -32,7 +31,24 @@ export function getUrlWithAppState(path: string, searchObject: UrlQueryMap, pres return urlUtil.renderUrl(locationUtil.assureBaseUrl(path), paramsCopy); } -export function renderSceneComponentWithRouteProps(sceneObject: SceneObject, routeProps: RouteComponentProps) { - // @ts-ignore - return React.createElement(sceneObject.Component, { model: sceneObject, routeProps: routeProps }); +export function useSceneRouteMatch(path: string) { + const params = useParams(); + const location = useLocation(); + const isExact = matchPath( + { + path, + caseSensitive: false, + end: true, + }, + location.pathname + ); + + const match: SceneRouteMatch = { + params, + isExact: isExact !== null, + path: location.pathname, + url: location.pathname, + }; + + return match; } diff --git a/packages/scenes/src/utils/utils.ts b/packages/scenes/src/utils/utils.ts index 9d9e0dc7f..bed7d9a58 100644 --- a/packages/scenes/src/utils/utils.ts +++ b/packages/scenes/src/utils/utils.ts @@ -1,8 +1,5 @@ import { SceneObject, SceneObjectState } from '../core/types'; -import { locationService as locationServiceRuntime } from '@grafana/runtime'; -// @ts-ignore -// eslint-disable-next-line no-duplicate-imports -import { useLocationService } from '@grafana/runtime'; +import { locationService as locationServiceRuntime, useLocationService } from '@grafana/runtime'; /** * This function works around the problem of Contravariance of the SceneObject.setState function diff --git a/packages/scenes/utils/setupTests.ts b/packages/scenes/utils/setupTests.ts index 9345da934..6907d06ba 100644 --- a/packages/scenes/utils/setupTests.ts +++ b/packages/scenes/utils/setupTests.ts @@ -1,8 +1,5 @@ -import '@testing-library/jest-dom'; import { TextEncoder, TextDecoder } from 'util'; -import { matchers } from './test'; - Object.assign(global, { TextDecoder, TextEncoder }); Object.defineProperty(global, 'matchMedia', { @@ -28,5 +25,3 @@ global.IntersectionObserver = jest.fn(() => ({ rootMargin: '', thresholds: [], })); - -expect.extend(matchers); diff --git a/packages/scenes/utils/setupTestsAfterEnv.ts b/packages/scenes/utils/setupTestsAfterEnv.ts new file mode 100644 index 000000000..84bcca27b --- /dev/null +++ b/packages/scenes/utils/setupTestsAfterEnv.ts @@ -0,0 +1,9 @@ +import '@testing-library/jest-dom'; + +import { toEmitValues } from './test/toEmitValues'; +import { toEmitValuesWith } from './test/toEmitValuesWith'; + +expect.extend({ + toEmitValues, + toEmitValuesWith, +}); diff --git a/packages/scenes/utils/test/TestContextProvider.tsx b/packages/scenes/utils/test/TestContextProvider.tsx index 235f17415..cdb6b364d 100644 --- a/packages/scenes/utils/test/TestContextProvider.tsx +++ b/packages/scenes/utils/test/TestContextProvider.tsx @@ -1,14 +1,35 @@ import { locationService, LocationServiceProvider } from '@grafana/runtime'; -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { Router } from 'react-router-dom'; -import { SceneObject, UrlSyncContextProvider } from '../../src'; +import { SceneObject } from '../../src/core/types'; +import { UrlSyncContextProvider } from '../../src/services/UrlSyncContextProvider'; + +const history = locationService.getHistory(); + +export function TestContextProviderBase({ children }: { children: React.ReactNode }) { + const [location, setLocation] = useState(history.location); + + useEffect(() => { + // If we would like to use the same history object in the Router (e.g. what is used in the UrlSyncManager when calling `useLocation()`) + // and in scenes (when accessed via the `locationService`), then we need to dynamically update the `location={}` prop of the ``. + // It is necessary because the underlying `LocationContext` (react-router) is created from the `location` prop. + // (More info: https://github.com/remix-run/react-router/blob/5d96537148d768b304be3bea7237a12351127807/packages/react-router/lib/components.tsx#L742) + history.listen(setLocation); + }, []); -export function TestContextProvider({ children, scene }: { children: React.ReactNode; scene: SceneObject }) { return ( - - {children} + + {children} ); } + +export function TestContextProvider({ children, scene }: { children: React.ReactNode; scene: SceneObject }) { + return ( + + {children} + + ); +} diff --git a/packages/scenes/utils/test/index.ts b/packages/scenes/utils/test/index.ts deleted file mode 100644 index 4f429aa53..000000000 --- a/packages/scenes/utils/test/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { toEmitValues } from './toEmitValues'; -import { toEmitValuesWith } from './toEmitValuesWith'; - -export const matchers = { - toEmitValues, - toEmitValuesWith, -}; diff --git a/packages/scenes/utils/test/renderAppInsideRoutingWithStartingUrl.tsx b/packages/scenes/utils/test/renderAppInsideRoutingWithStartingUrl.tsx new file mode 100644 index 000000000..17036d358 --- /dev/null +++ b/packages/scenes/utils/test/renderAppInsideRoutingWithStartingUrl.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { locationService } from '@grafana/runtime'; +import { render } from '@testing-library/react'; +import { SceneApp } from '../../src/components/SceneApp/SceneApp'; +import { TestContextProviderBase } from './TestContextProvider'; + +export function renderAppInsideRouterWithStartingUrl(app: SceneApp, startingUrl: string) { + locationService.push(startingUrl); + + return render( + + + + ); +} diff --git a/packages/scenes/utils/test/updateUrlStateAndSyncState.tsx b/packages/scenes/utils/test/updateUrlStateAndSyncState.tsx index 411510d95..745151df5 100644 --- a/packages/scenes/utils/test/updateUrlStateAndSyncState.tsx +++ b/packages/scenes/utils/test/updateUrlStateAndSyncState.tsx @@ -1,6 +1,6 @@ import { UrlQueryMap } from '@grafana/data'; import { locationService } from '@grafana/runtime'; -import { UrlSyncManager } from '../../src'; +import { UrlSyncManager } from '../../src/services/UrlSyncManager'; export function updateUrlStateAndSyncState(searchParams: UrlQueryMap, urlManager: UrlSyncManager) { locationService.partial(searchParams); diff --git a/packages/scenes/utils/test/utils.tsx b/packages/scenes/utils/test/utils.tsx index 5ddc65f7e..27dcab652 100644 --- a/packages/scenes/utils/test/utils.tsx +++ b/packages/scenes/utils/test/utils.tsx @@ -1,11 +1,6 @@ -import React from 'react'; import { matcherHint, printExpected, printReceived } from 'jest-matcher-utils'; import { asapScheduler, Subscription, timer, isObservable } from 'rxjs'; -import { History } from 'history'; -import { render } from '@testing-library/react'; -import { SceneApp } from '../../src/components/SceneApp/SceneApp'; -import { Router } from 'react-router-dom'; -import { SceneObject, SceneObjectState } from '../../src'; +import { SceneObject, SceneObjectState } from '../../src/core/types'; export const OBSERVABLE_TEST_TIMEOUT_IN_MS = 1000; @@ -68,15 +63,6 @@ export function expectObservable(received: unknown): jest.CustomMatcherResult | return null; } -export function renderAppInsideRouterWithStartingUrl(history: History, app: SceneApp, startingUrl: string) { - history.push(startingUrl); - render( - - - - ); -} - export function subscribeToStateUpdates(obj: SceneObject): T[] { const stateUpdates: T[] = []; obj.subscribeToState((state) => stateUpdates.push(state)); diff --git a/yarn.lock b/yarn.lock index 20824dc17..c278c1c45 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3739,7 +3739,6 @@ __metadata: "@types/react": "npm:18.2.74" "@types/react-dom": "npm:18.2.24" "@types/react-grid-layout": "npm:1.3.2" - "@types/react-router-dom": "npm:5.3.3" "@types/react-virtualized-auto-sizer": "npm:1.0.1" "@types/uuid": "npm:8.3.4" "@typescript-eslint/eslint-plugin": "npm:^4.33.0" @@ -3758,7 +3757,6 @@ __metadata: lodash: "npm:4.17.21" lru-cache: "npm:^10.2.2" prettier: "npm:2.5.1" - react-router-dom: "npm:^5.2.0" react-select-event: "npm:^5.5.1" react-use: "npm:^17.4.0" rimraf: "npm:^3.0.2" @@ -3781,6 +3779,7 @@ __metadata: "@grafana/ui": ^11.0.0 react: ^18.0.0 react-dom: ^18.0.0 + react-router-dom: ^6.28.0 languageName: unknown linkType: soft @@ -3809,7 +3808,6 @@ __metadata: "@types/react": "npm:18.2.74" "@types/react-dom": "npm:18.2.24" "@types/react-grid-layout": "npm:1.3.2" - "@types/react-router-dom": "npm:5.3.3" "@types/react-virtualized-auto-sizer": "npm:1.0.1" "@types/uuid": "npm:8.3.4" "@typescript-eslint/eslint-plugin": "npm:^4.33.0" @@ -3827,11 +3825,11 @@ __metadata: jest-matcher-utils: "npm:29.7.0" lodash: "npm:4.17.21" prettier: "npm:2.5.1" - react-grid-layout: "npm:^1.3.4" - react-router-dom: "npm:^5.2.0" + react-grid-layout: "npm:1.3.4" + react-router-dom: "npm:^6.28.0" react-select-event: "npm:^5.5.1" - react-use: "npm:^17.5.0" - react-virtualized-auto-sizer: "npm:^1.0.24" + react-use: "npm:17.5.0" + react-virtualized-auto-sizer: "npm:1.0.24" rimraf: "npm:^3.0.2" rollup: "npm:2.79.1" rollup-plugin-dts: "npm:^5.0.0" @@ -7006,7 +7004,7 @@ __metadata: languageName: node linkType: hard -"@types/react-router-dom@npm:*, @types/react-router-dom@npm:5.3.3, @types/react-router-dom@npm:^5.3.3": +"@types/react-router-dom@npm:*": version: 5.3.3 resolution: "@types/react-router-dom@npm:5.3.3" dependencies: @@ -13421,13 +13419,6 @@ __metadata: languageName: node linkType: hard -"fast-equals@npm:^4.0.3": - version: 4.0.3 - resolution: "fast-equals@npm:4.0.3" - checksum: 10/04c1ff47b79923314e9b63ec6c81beeaa5e3b9588ec230ee6aff7ece725ff834a72abf627055055127bd0f53ae8a92cc04c3a6e187783fd932dbef743f9b13bf - languageName: node - linkType: hard - "fast-glob@npm:3.2.7": version: 3.2.7 resolution: "fast-glob@npm:3.2.7" @@ -17796,6 +17787,13 @@ __metadata: languageName: node linkType: hard +"lodash.isequal@npm:^4.0.0": + version: 4.5.0 + resolution: "lodash.isequal@npm:4.5.0" + checksum: 10/82fc58a83a1555f8df34ca9a2cd300995ff94018ac12cc47c349655f0ae1d4d92ba346db4c19bbfc90510764e0c00ddcc985a358bdcd4b3b965abf8f2a48a214 + languageName: node + linkType: hard + "lodash.ismatch@npm:^4.4.0": version: 4.4.0 resolution: "lodash.ismatch@npm:4.4.0" @@ -19483,7 +19481,7 @@ __metadata: languageName: node linkType: hard -"nano-css@npm:^5.6.2": +"nano-css@npm:^5.6.1, nano-css@npm:^5.6.2": version: 5.6.2 resolution: "nano-css@npm:5.6.2" dependencies: @@ -22417,7 +22415,7 @@ __metadata: languageName: node linkType: hard -"react-draggable@npm:^4.0.3, react-draggable@npm:^4.4.5": +"react-draggable@npm:^4.0.0, react-draggable@npm:^4.0.3": version: 4.4.6 resolution: "react-draggable@npm:4.4.6" dependencies: @@ -22477,20 +22475,19 @@ __metadata: languageName: node linkType: hard -"react-grid-layout@npm:^1.3.4": - version: 1.5.0 - resolution: "react-grid-layout@npm:1.5.0" +"react-grid-layout@npm:1.3.4": + version: 1.3.4 + resolution: "react-grid-layout@npm:1.3.4" dependencies: - clsx: "npm:^2.0.0" - fast-equals: "npm:^4.0.3" + clsx: "npm:^1.1.1" + lodash.isequal: "npm:^4.0.0" prop-types: "npm:^15.8.1" - react-draggable: "npm:^4.4.5" - react-resizable: "npm:^3.0.5" - resize-observer-polyfill: "npm:^1.5.1" + react-draggable: "npm:^4.0.0" + react-resizable: "npm:^3.0.4" peerDependencies: react: ">= 16.3.0" react-dom: ">= 16.3.0" - checksum: 10/c20cf0120b0aed06fad0e3f535df3cf35fc7c6e9aca4a7e64bc5f453dc7298e037cc71dc0934a4b89b9d5c024967cf55b9f630c5f44afc03f8a1c055a01bf3c0 + checksum: 10/944ab133e59bfaa5633625f57be9f69133b5cec2de0232d9581e2c988e257ebafe010ee9bbbff6c2754f9d7d8bb0053072bac103f20fc232be2a58e15d14fc64 languageName: node linkType: hard @@ -22674,7 +22671,7 @@ __metadata: languageName: node linkType: hard -"react-resizable@npm:^3.0.5": +"react-resizable@npm:^3.0.4": version: 3.0.5 resolution: "react-resizable@npm:3.0.5" dependencies: @@ -22713,7 +22710,7 @@ __metadata: languageName: node linkType: hard -"react-router-dom@npm:^5.2.0, react-router-dom@npm:^5.3.4": +"react-router-dom@npm:^5.3.4": version: 5.3.4 resolution: "react-router-dom@npm:5.3.4" dependencies: @@ -22730,6 +22727,19 @@ __metadata: languageName: node linkType: hard +"react-router-dom@npm:^6.28.0": + version: 6.28.0 + resolution: "react-router-dom@npm:6.28.0" + dependencies: + "@remix-run/router": "npm:1.21.0" + react-router: "npm:6.28.0" + peerDependencies: + react: ">=16.8" + react-dom: ">=16.8" + checksum: 10/e637825132ea96c3514ef7b8322f9bf0b752a942d6b4ffc4c20e389b5911726adf3dba8208ed4b97bf5b9c3bd465d9d1a1db1a58a610a8d528f18d890e0b143f + languageName: node + linkType: hard + "react-router@npm:5.3.4, react-router@npm:^5.3.4": version: 5.3.4 resolution: "react-router@npm:5.3.4" @@ -22823,7 +22833,32 @@ __metadata: languageName: node linkType: hard -"react-use@npm:17.5.1, react-use@npm:^17.4.0, react-use@npm:^17.5.0": +"react-use@npm:17.5.0": + version: 17.5.0 + resolution: "react-use@npm:17.5.0" + dependencies: + "@types/js-cookie": "npm:^2.2.6" + "@xobotyi/scrollbar-width": "npm:^1.9.5" + copy-to-clipboard: "npm:^3.3.1" + fast-deep-equal: "npm:^3.1.3" + fast-shallow-equal: "npm:^1.0.0" + js-cookie: "npm:^2.2.1" + nano-css: "npm:^5.6.1" + react-universal-interface: "npm:^0.6.2" + resize-observer-polyfill: "npm:^1.5.1" + screenfull: "npm:^5.1.0" + set-harmonic-interval: "npm:^1.0.1" + throttle-debounce: "npm:^3.0.1" + ts-easing: "npm:^0.2.0" + tslib: "npm:^2.1.0" + peerDependencies: + react: "*" + react-dom: "*" + checksum: 10/5d81fe0902303d3ed7810cdd56c6cae12b08124a3d4fcbfa3924327105b81447b039ea9d6aff20aac3c13999f949000870a7a2fa29fe20ed844ac26606462fa0 + languageName: node + linkType: hard + +"react-use@npm:17.5.1, react-use@npm:^17.4.0": version: 17.5.1 resolution: "react-use@npm:17.5.1" dependencies: @@ -22848,7 +22883,7 @@ __metadata: languageName: node linkType: hard -"react-virtualized-auto-sizer@npm:^1.0.24": +"react-virtualized-auto-sizer@npm:1.0.24": version: 1.0.24 resolution: "react-virtualized-auto-sizer@npm:1.0.24" peerDependencies: @@ -23957,7 +23992,6 @@ __metadata: "@types/jest": "npm:^29.5.12" "@types/lodash": "npm:latest" "@types/node": "npm:^20.11.30" - "@types/react-router-dom": "npm:^5.3.3" "@typescript-eslint/eslint-plugin": "npm:^4.33.0" "@typescript-eslint/parser": "npm:^4.33.0" copy-webpack-plugin: "npm:^10.0.0" @@ -23973,7 +24007,7 @@ __metadata: prettier: "npm:^2.5.0" react: "npm:18.2.0" react-dom: "npm:18.2.0" - react-router-dom: "npm:^5.2.0" + react-router-dom: "npm:^6.28.0" replace-in-file-webpack-plugin: "npm:^1.0.6" swc-loader: "npm:^0.1.15" ts-node: "npm:^10.9.2" @@ -25497,21 +25531,7 @@ __metadata: languageName: node linkType: hard -"terser@npm:^5.10.0": - version: 5.19.1 - resolution: "terser@npm:5.19.1" - dependencies: - "@jridgewell/source-map": "npm:^0.3.3" - acorn: "npm:^8.8.2" - commander: "npm:^2.20.0" - source-map-support: "npm:~0.5.20" - bin: - terser: bin/terser - checksum: 10/d4f7b07fa9242d2b65676a373a2eecf9b78b7b186472cdb0f3ce990aae9ca0cbdbee0d6f5b5c0e7aacd6441858526798ae55d4166f9010a2128ab9bf372a4dd7 - languageName: node - linkType: hard - -"terser@npm:^5.15.1, terser@npm:^5.26.0": +"terser@npm:^5.10.0, terser@npm:^5.15.1, terser@npm:^5.26.0": version: 5.34.1 resolution: "terser@npm:5.34.1" dependencies: