From 0f32dce69c9ca504c459fb53384fd8a6753a3189 Mon Sep 17 00:00:00 2001 From: Loic Huder Date: Wed, 10 Jun 2026 16:55:11 +0200 Subject: [PATCH 1/2] Move from react-router to wouter --- package.json | 2 +- pnpm-lock.yaml | 60 ++++++++----------- src/App.tsx | 54 +++++------------ src/edition/EditPage.tsx | 7 +-- .../TopAppBar/execution/ExecutionDialog.tsx | 4 +- src/edition/TopAppBar/menu/DeleteMenuItem.tsx | 2 +- .../menu/OpenNewWorkflowMenuItem.tsx | 2 +- src/execution/WorkflowList.tsx | 2 +- src/general/QuickOpen.tsx | 2 +- src/general/forms/GraphFormDialog.tsx | 2 +- src/navbar/NavBar.tsx | 24 ++++---- src/navbar/NavLink.tsx | 27 +++++++++ 12 files changed, 87 insertions(+), 101 deletions(-) create mode 100644 src/navbar/NavLink.tsx diff --git a/package.json b/package.json index 29acfa58..8cda0d63 100644 --- a/package.json +++ b/package.json @@ -44,9 +44,9 @@ "react-hook-form": "7.78.0", "react-is": "18.3.1", "react-resizable-panels": "4.11.2", - "react-router-dom": "6.22.0", "react-timeago": "8.3.0", "socket.io-client": "4.8.3", + "wouter": "3.10.0", "zustand": "4.4.3" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 71a2d6cd..b093c3e2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,15 +65,15 @@ importers: react-resizable-panels: specifier: 4.11.2 version: 4.11.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - react-router-dom: - specifier: 6.22.0 - version: 6.22.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) react-timeago: specifier: 8.3.0 version: 8.3.0(react@18.2.0) socket.io-client: specifier: 4.8.3 version: 4.8.3 + wouter: + specifier: 3.10.0 + version: 3.10.0(react@18.2.0) zustand: specifier: 4.4.3 version: 4.4.3(@types/react@18.3.28)(react@18.2.0) @@ -701,10 +701,6 @@ packages: js-cookie: optional: true - '@remix-run/router@1.15.0': - resolution: {integrity: sha512-HOil5aFtme37dVQTB6M34G95kPM3MMuqSmIRVCC52eKV+Y/tGSqw9P3rWhlAx6A+mz+MoX+XxsGsNJbaI5qCgQ==} - engines: {node: '>=14.0.0'} - '@rolldown/binding-android-arm64@1.0.3': resolution: {integrity: sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2435,6 +2431,9 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -2672,19 +2671,6 @@ packages: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - react-router-dom@6.22.0: - resolution: {integrity: sha512-z2w+M4tH5wlcLmH3BMMOMdrtrJ9T3oJJNsAlBJbwk+8Syxd5WFJ7J5dxMEW0/GEXD1BBis4uXRrNIz3mORr0ag==} - engines: {node: '>=14.0.0'} - peerDependencies: - react: '>=16.8' - react-dom: '>=16.8' - - react-router@6.22.0: - resolution: {integrity: sha512-q2yemJeg6gw/YixRlRnVx6IRJWZD6fonnfZhN1JIOhV2iJCPeRNSH3V1ISwHf+JWcESzLC3BOLD1T07tmO5dmg==} - engines: {node: '>=14.0.0'} - peerDependencies: - react: '>=16.8' - react-textarea-autosize@8.5.9: resolution: {integrity: sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==} engines: {node: '>=10'} @@ -2734,6 +2720,10 @@ packages: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} + regexparam@3.0.0: + resolution: {integrity: sha512-RSYAtP31mvYLkAHrOlh25pCNQ5hWnT106VukGaaFfuJrZFkGRX5GhUAdPqpSDXxOhA2c4akmRuplv1mRqnBn6Q==} + engines: {node: '>=8'} + regjsparser@0.13.1: resolution: {integrity: sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==} hasBin: true @@ -3227,6 +3217,11 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wouter@3.10.0: + resolution: {integrity: sha512-zTfddD80zc2/J5l8JKcdvzOK6AwP0kpyHEI3DxRN2bn8U1oJPnrSVm8v+X3WwDamvLAOxTO7ZvkxkpRWlyeJ1Q==} + peerDependencies: + react: '>=16.8.0' + wrap-ansi@9.0.2: resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} engines: {node: '>=18'} @@ -3925,8 +3920,6 @@ snapshots: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - '@remix-run/router@1.15.0': {} - '@rolldown/binding-android-arm64@1.0.3': optional: true @@ -5837,6 +5830,8 @@ snapshots: minimist@1.2.8: {} + mitt@3.0.1: {} + ms@2.1.3: {} nanoid@3.3.12: {} @@ -6062,18 +6057,6 @@ snapshots: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-router-dom@6.22.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0): - dependencies: - '@remix-run/router': 1.15.0 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-router: 6.22.0(react@18.2.0) - - react-router@6.22.0(react@18.2.0): - dependencies: - '@remix-run/router': 1.15.0 - react: 18.2.0 - react-textarea-autosize@8.5.9(@types/react@18.3.28)(react@18.2.0): dependencies: '@babel/runtime': 7.29.7 @@ -6144,6 +6127,8 @@ snapshots: gopd: 1.2.0 set-function-name: 2.0.2 + regexparam@3.0.0: {} + regjsparser@0.13.1: dependencies: jsesc: 3.1.0 @@ -6714,6 +6699,13 @@ snapshots: word-wrap@1.2.5: {} + wouter@3.10.0(react@18.2.0): + dependencies: + mitt: 3.0.1 + react: 18.2.0 + regexparam: 3.0.0 + use-sync-external-store: 1.6.0(react@18.2.0) + wrap-ansi@9.0.2: dependencies: ansi-styles: 6.2.3 diff --git a/src/App.tsx b/src/App.tsx index 662913a7..eb475926 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,8 +1,6 @@ import { CssBaseline } from '@mui/material'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { Navigate, Outlet } from 'react-router-dom'; -import { createBrowserRouter, RouterProvider } from 'react-router-dom'; - +import { Route, Router, Switch, Redirect } from 'wouter'; import { apiSuffix, baseUrl } from './api/client'; import EditRoute from './EditRoute'; import SimpleSnackbar from './general/Snackbar'; @@ -12,46 +10,24 @@ import SocketClientProvider from './SocketClientProvider'; const queryClient = new QueryClient(); -const router = createBrowserRouter( - [ - { - path: '/', - element: , - children: [ - { - path: 'edit', - element: , - }, - { - path: 'monitor', - element: , - }, - { - path: '/', - element: , - }, - ], - }, - ], - { basename: import.meta.env.VITE_ROUTER_BASE_DIR }, -); - -function Layout() { - return ( - <> - - - - - - ); -} - export default function App() { return ( - + + + + + + + + + + + + + + ); diff --git a/src/edition/EditPage.tsx b/src/edition/EditPage.tsx index 257e1858..9652579f 100644 --- a/src/edition/EditPage.tsx +++ b/src/edition/EditPage.tsx @@ -2,10 +2,9 @@ import { useDebouncedCallback } from '@react-hookz/web'; import { useStoreApi } from '@xyflow/react'; import { useEffect } from 'react'; import { Group, Panel, Separator } from 'react-resizable-panels'; -import { useSearchParams } from 'react-router-dom'; +import { useSearchParams } from 'wouter'; import ErrorFallback from '../general/ErrorFallback'; -import { useWorkflowHasChanges } from '../store/graph-hooks'; import useEdgeDataStore from '../store/useEdgeDataStore'; import useNodeDataStore from '../store/useNodeDataStore'; import useWorkflowHistory from '../store/useWorkflowHistory'; @@ -13,16 +12,12 @@ import useWorkflowStore from '../store/useWorkflowStore'; import SuspenseBoundary from '../suspense/SuspenseBoundary'; import Canvas from './Canvas/Canvas'; import styles from './EditPage.module.css'; -import { useWarningPrompt } from './hooks'; import EditSidebar from './Sidebar/EditSidebar'; import OverflowDrawer from './TaskDrawer/TaskDrawer'; import TopAppBar from './TopAppBar/TopAppBar'; export default function EditPage() { const [searchParams] = useSearchParams(); - const workflowHasChanges = useWorkflowHasChanges(); - - useWarningPrompt(workflowHasChanges); const workflowId = searchParams.get('workflow'); const pushToWorkflowHistory = useWorkflowHistory( diff --git a/src/edition/TopAppBar/execution/ExecutionDialog.tsx b/src/edition/TopAppBar/execution/ExecutionDialog.tsx index dcd7f92b..9adc9970 100644 --- a/src/edition/TopAppBar/execution/ExecutionDialog.tsx +++ b/src/edition/TopAppBar/execution/ExecutionDialog.tsx @@ -8,7 +8,7 @@ import DialogTitle from '@mui/material/DialogTitle'; import { useMap } from '@react-hookz/web'; import { nanoid } from 'nanoid'; import { useState } from 'react'; -import { useNavigate } from 'react-router-dom'; +import { useLocation } from 'wouter'; import commonStrings from '../../../commonStrings.json'; import GraphFormDialog from '../../../general/forms/GraphFormDialog'; @@ -40,7 +40,7 @@ export default function ExecutionDialog(props: Props) { const [engine, setEngine] = useState('default'); const [queue, setQueue] = useState(''); const { isDialogOpen, setDialogOpen, handleSave } = useSaveWorkflow(); - const navigate = useNavigate(); + const [_, navigate] = useLocation(); async function handleSaveExecute() { try { diff --git a/src/edition/TopAppBar/menu/DeleteMenuItem.tsx b/src/edition/TopAppBar/menu/DeleteMenuItem.tsx index 02b0d2ea..ac5a936d 100644 --- a/src/edition/TopAppBar/menu/DeleteMenuItem.tsx +++ b/src/edition/TopAppBar/menu/DeleteMenuItem.tsx @@ -1,6 +1,6 @@ import { Delete } from '@mui/icons-material'; import { useState } from 'react'; -import { useSearchParams } from 'react-router-dom'; +import { useSearchParams } from 'wouter'; import { deleteWorkflow, diff --git a/src/edition/TopAppBar/menu/OpenNewWorkflowMenuItem.tsx b/src/edition/TopAppBar/menu/OpenNewWorkflowMenuItem.tsx index d93f6dc8..a1eac1c2 100644 --- a/src/edition/TopAppBar/menu/OpenNewWorkflowMenuItem.tsx +++ b/src/edition/TopAppBar/menu/OpenNewWorkflowMenuItem.tsx @@ -1,6 +1,6 @@ import { FiberNew } from '@mui/icons-material'; import { useKeyboardEvent } from '@react-hookz/web'; -import { useSearchParams } from 'react-router-dom'; +import { useSearchParams } from 'wouter'; import ActionMenuItem from './ActionMenuItem'; diff --git a/src/execution/WorkflowList.tsx b/src/execution/WorkflowList.tsx index c94c1f5e..a41d3b45 100644 --- a/src/execution/WorkflowList.tsx +++ b/src/execution/WorkflowList.tsx @@ -1,5 +1,5 @@ import { ErrorBoundary } from 'react-error-boundary'; -import { Link } from 'react-router-dom'; +import { Link } from 'wouter'; import type { EwoksJob } from '../api/models'; import styles from './MonitorPage.module.css'; diff --git a/src/general/QuickOpen.tsx b/src/general/QuickOpen.tsx index 1b154219..94a1dc3a 100644 --- a/src/general/QuickOpen.tsx +++ b/src/general/QuickOpen.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { useSearchParams } from 'react-router-dom'; +import { useSearchParams } from 'wouter'; import useSnackbarStore from '../store/useSnackbarStore'; import type { WorkflowDescription } from '../types'; diff --git a/src/general/forms/GraphFormDialog.tsx b/src/general/forms/GraphFormDialog.tsx index d6b422d7..693f1170 100644 --- a/src/general/forms/GraphFormDialog.tsx +++ b/src/general/forms/GraphFormDialog.tsx @@ -9,7 +9,7 @@ import { import { useReactFlow } from '@xyflow/react'; import { flushSync } from 'react-dom'; import { Controller, useForm } from 'react-hook-form'; -import { useSearchParams } from 'react-router-dom'; +import { useSearchParams } from 'wouter'; import { postWorkflow, diff --git a/src/navbar/NavBar.tsx b/src/navbar/NavBar.tsx index 62ded5b9..96b8837d 100644 --- a/src/navbar/NavBar.tsx +++ b/src/navbar/NavBar.tsx @@ -1,11 +1,13 @@ -import { NavLink, useLocation, useSearchParams } from 'react-router-dom'; +import { Link, useSearchParams } from 'wouter'; import useWorkflowStore from '../store/useWorkflowStore'; import styles from './NavBar.module.css'; import useNavBarElementStore from './useNavBarElementStore'; +import { useHistoryState } from 'wouter/use-browser-location'; +import NavLink from './NavLink'; function NavBar() { - const { state } = useLocation(); + const state = useHistoryState(); const [searchParams] = useSearchParams(); const setElement = useNavBarElementStore((st) => st.setElement); @@ -18,26 +20,20 @@ function NavBar() { > diff --git a/src/navbar/NavLink.tsx b/src/navbar/NavLink.tsx new file mode 100644 index 00000000..27fe15fa --- /dev/null +++ b/src/navbar/NavLink.tsx @@ -0,0 +1,27 @@ +import { Link, type LinkProps, useRoute } from 'wouter'; +import styles from './NavBar.module.css'; +import { type PropsWithChildren } from 'react'; + +interface Props { + to: string; + state?: LinkProps['state']; +} + +function NavLink(props: PropsWithChildren) { + const { children, to, state } = props; + const paths = to.split('?'); + const [isActive] = useRoute(paths[0]); + + return ( + + {children} + + ); +} + +export default NavLink; From 3bc7ff1e6b463155812287f369503753a71801cb Mon Sep 17 00:00:00 2001 From: Loic Huder Date: Mon, 29 Jun 2026 14:36:23 +0200 Subject: [PATCH 2/2] Move from react-router to wouter --- cypress/e2e/pages.cy.ts | 8 ++++---- src/App.tsx | 4 +++- src/edition/hooks.ts | 18 ------------------ src/navbar/NavBar.tsx | 14 ++++++-------- src/navbar/NavLink.tsx | 13 +++++++------ 5 files changed, 20 insertions(+), 37 deletions(-) diff --git a/cypress/e2e/pages.cy.ts b/cypress/e2e/pages.cy.ts index 94dec749..8fb7e9ca 100644 --- a/cypress/e2e/pages.cy.ts +++ b/cypress/e2e/pages.cy.ts @@ -7,8 +7,8 @@ it('should land on the edit page', () => { expect(loc.pathname).to.eq('/edit'); }); cy.findByRole('link', { name: 'Edit' }).should(($link) => { - const classes = $link.attr('class'); - expect(classes).to.include('active'); + const ariaCurrent = $link.attr('aria-current'); + expect(ariaCurrent).to.equal('true'); }); cy.get('.react-flow').should('be.visible'); }); @@ -19,8 +19,8 @@ it('should switch to monitor page', () => { expect(loc.pathname).to.eq('/monitor'); }); cy.findByRole('link', { name: 'Monitor' }).should(($link) => { - const classes = $link.attr('class'); - expect(classes).to.include('active'); + const ariaCurrent = $link.attr('aria-current'); + expect(ariaCurrent).to.equal('true'); }); }); diff --git a/src/App.tsx b/src/App.tsx index eb475926..48132590 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -25,7 +25,9 @@ export default function App() { - + + + diff --git a/src/edition/hooks.ts b/src/edition/hooks.ts index 86480bee..c79b222f 100644 --- a/src/edition/hooks.ts +++ b/src/edition/hooks.ts @@ -1,8 +1,6 @@ -import { useEventListener } from '@react-hookz/web'; import { useReactFlow } from '@xyflow/react'; import type { RefCallback } from 'react'; import { useCallback, useState } from 'react'; -import { unstable_usePrompt } from 'react-router-dom'; import { useNodesIds } from '../store/graph-hooks'; import useNodeDataStore from '../store/useNodeDataStore'; @@ -11,22 +9,6 @@ import { getNodeData } from '../utils'; import { assertDefined, assertNodeDataDefined } from '../utils/typeGuards'; import { generateNewNodeId } from './utils'; -export function useWarningPrompt(displayWarning: boolean) { - useEventListener(window, 'beforeunload', (event: BeforeUnloadEvent) => { - if (displayWarning) { - event.preventDefault(); - - // Included for legacy support, e.g. Chrome/Edge < 119 - event.returnValue = true; - } - }); - - unstable_usePrompt({ - message: 'There are unsaved changes. Continue without saving?', - when: displayWarning, - }); -} - export function useCloneNode() { const rfInstance = useReactFlow(); const nodesIds = useNodesIds(); diff --git a/src/navbar/NavBar.tsx b/src/navbar/NavBar.tsx index 96b8837d..83ec6be6 100644 --- a/src/navbar/NavBar.tsx +++ b/src/navbar/NavBar.tsx @@ -5,6 +5,7 @@ import styles from './NavBar.module.css'; import useNavBarElementStore from './useNavBarElementStore'; import { useHistoryState } from 'wouter/use-browser-location'; import NavLink from './NavLink'; +import { isString } from '../utils/typeGuards'; function NavBar() { const state = useHistoryState(); @@ -13,6 +14,8 @@ function NavBar() { const setElement = useNavBarElementStore((st) => st.setElement); const displayedWorkflowInfo = useWorkflowStore((st) => st.workflowInfo); + const workflow = state?.workflow || searchParams.get('workflow'); + return (
EwoksWeb
Edit - + Monitor diff --git a/src/navbar/NavLink.tsx b/src/navbar/NavLink.tsx index 27fe15fa..ac671631 100644 --- a/src/navbar/NavLink.tsx +++ b/src/navbar/NavLink.tsx @@ -3,20 +3,21 @@ import styles from './NavBar.module.css'; import { type PropsWithChildren } from 'react'; interface Props { - to: string; + href: string; + searchParams?: Record; state?: LinkProps['state']; } function NavLink(props: PropsWithChildren) { - const { children, to, state } = props; - const paths = to.split('?'); - const [isActive] = useRoute(paths[0]); + const { children, href, searchParams = {}, state } = props; + const [isActive] = useRoute(href); + const query = new URLSearchParams(searchParams).toString(); return ( {children}