From ae3d0711f684bebc1ab0336ad368a732f77ef902 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Tue, 3 Jun 2025 23:00:23 -0400 Subject: [PATCH] feat: added router --- deno.json | 2 + deno.lock | 205 ++++++++++++++++++ package.json | 3 + src/App.tsx | 9 +- src/PageRouter.tsx | 27 --- src/components/CommandPalette/index.tsx | 14 +- .../NodeDetailsDialog/NodeDetailsDialog.tsx | 11 +- .../PageComponents/Map/NodeDetail.tsx | 6 +- .../PageComponents/Messages/MessageItem.tsx | 2 +- src/components/Sidebar.tsx | 41 ++-- src/core/stores/deviceStore.ts | 13 -- src/index.tsx | 18 +- src/routeTree.gen.ts | 59 +++++ src/routes.tsx | 60 +++++ 14 files changed, 389 insertions(+), 81 deletions(-) delete mode 100644 src/PageRouter.tsx create mode 100644 src/routeTree.gen.ts create mode 100644 src/routes.tsx diff --git a/deno.json b/deno.json index c1a8d131..36466202 100644 --- a/deno.json +++ b/deno.json @@ -31,12 +31,14 @@ }, "fmt": { "exclude": [ + "src/*.gen.ts", "*.test.ts", "*.test.tsx" ] }, "lint": { "exclude": [ + "src/*.gen.ts", "*.test.ts", "*.test.tsx" ], diff --git a/deno.lock b/deno.lock index 31fdf030..7e988e49 100644 --- a/deno.lock +++ b/deno.lock @@ -27,6 +27,9 @@ "npm:@radix-ui/react-toggle-group@^1.1.9": "1.1.10_@types+react@19.1.2_@types+react-dom@19.1.3__@types+react@19.1.2_react@19.1.0_react-dom@19.1.0__react@19.1.0", "npm:@radix-ui/react-tooltip@^1.2.4": "1.2.4_@types+react@19.1.2_@types+react-dom@19.1.3__@types+react@19.1.2_react@19.1.0_react-dom@19.1.0__react@19.1.0", "npm:@tailwindcss/postcss@^4.1.5": "4.1.5", + "npm:@tanstack/react-router@^1.120.15": "1.120.15_react@19.1.0_react-dom@19.1.0__react@19.1.0", + "npm:@tanstack/router-devtools@^1.120.15": "1.120.15_@tanstack+react-router@1.120.15__react@19.1.0__react-dom@19.1.0___react@19.1.0_react@19.1.0_react-dom@19.1.0__react@19.1.0", + "npm:@tanstack/router-plugin@^1.120.15": "1.120.15_@tanstack+react-router@1.120.15__react@19.1.0__react-dom@19.1.0___react@19.1.0_vite@6.3.4__@types+node@22.15.3__picomatch@4.0.2_@babel+core@7.27.1_react@19.1.0_react-dom@19.1.0__react@19.1.0_@types+node@22.15.3", "npm:@testing-library/jest-dom@^6.6.3": "6.6.3", "npm:@testing-library/react@^16.3.0": "16.3.0_@testing-library+dom@10.4.0_@types+react@19.1.2_@types+react-dom@19.1.3__@types+react@19.1.2_react@19.1.0_react-dom@19.1.0__react@19.1.0", "npm:@testing-library/user-event@^14.6.1": "14.6.1_@testing-library+dom@10.4.0", @@ -366,6 +369,20 @@ "@babel/helper-plugin-utils" ] }, + "@babel/plugin-syntax-jsx@7.27.1_@babel+core@7.27.1": { + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dependencies": [ + "@babel/core", + "@babel/helper-plugin-utils" + ] + }, + "@babel/plugin-syntax-typescript@7.27.1_@babel+core@7.27.1": { + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dependencies": [ + "@babel/core", + "@babel/helper-plugin-utils" + ] + }, "@babel/plugin-syntax-unicode-sets-regex@7.18.6_@babel+core@7.27.1": { "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", "dependencies": [ @@ -2341,6 +2358,126 @@ "tailwindcss" ] }, + "@tanstack/history@1.115.0": { + "integrity": "sha512-K7JJNrRVvyjAVnbXOH2XLRhFXDkeP54Kt2P4FR1Kl2KDGlIbkua5VqZQD2rot3qaDrpufyUa63nuLai1kOLTsQ==" + }, + "@tanstack/react-router-devtools@1.120.15_@tanstack+react-router@1.120.15__react@19.1.0__react-dom@19.1.0___react@19.1.0_react@19.1.0_react-dom@19.1.0__react@19.1.0_solid-js@1.9.7__seroval@1.3.2": { + "integrity": "sha512-5KcUXc3fkiLo/6Y56gOM3JqmYXG1ElIH2iyUWuG5IlcegLrpXhu4OBQ+8Q4+62CD0OKy0ifUDyemrCOAEOfCvw==", + "dependencies": [ + "@tanstack/react-router", + "@tanstack/router-devtools-core", + "react", + "react-dom", + "solid-js" + ] + }, + "@tanstack/react-router@1.120.15_react@19.1.0_react-dom@19.1.0__react@19.1.0": { + "integrity": "sha512-apzBmXh4pHwqUGU3kD8y2FJMi7rVoUbRxh5oV7v8kEb6Aq5Xpdo+OcpThw8h/M2zv7v4Ef8IoY6WFCKKu3HBjQ==", + "dependencies": [ + "@tanstack/history", + "@tanstack/react-store", + "@tanstack/router-core", + "jsesc@3.1.0", + "react", + "react-dom", + "tiny-invariant", + "tiny-warning" + ] + }, + "@tanstack/react-store@0.7.1_react@19.1.0_react-dom@19.1.0__react@19.1.0": { + "integrity": "sha512-qUTEKdId6QPWGiWyKAPf/gkN29scEsz6EUSJ0C3HgLMgaqTAyBsQ2sMCfGVcqb+kkhEXAdjleCgH6LAPD6f2sA==", + "dependencies": [ + "@tanstack/store", + "react", + "react-dom", + "use-sync-external-store" + ] + }, + "@tanstack/router-core@1.120.15": { + "integrity": "sha512-soLj+mEuvSxAVFK/3b85IowkkvmSuQL6J0RSIyKJFGFgy0CmUzpcBGEO99+JNWvvvzHgIoY4F4KtLIN+rvFSFA==", + "dependencies": [ + "@tanstack/history", + "@tanstack/store", + "tiny-invariant" + ] + }, + "@tanstack/router-devtools-core@1.120.15_@tanstack+router-core@1.120.15_solid-js@1.9.7__seroval@1.3.2_tiny-invariant@1.3.3": { + "integrity": "sha512-AT9obPHKpJqnHMbwshozSy6sApg5LchiAll3blpS3MMDybUCidYHrdhe9MZJLmlC99IQiEGmuZERP3VRcuPNHg==", + "dependencies": [ + "@tanstack/router-core", + "clsx", + "goober", + "solid-js", + "tiny-invariant" + ] + }, + "@tanstack/router-devtools@1.120.15_@tanstack+react-router@1.120.15__react@19.1.0__react-dom@19.1.0___react@19.1.0_react@19.1.0_react-dom@19.1.0__react@19.1.0": { + "integrity": "sha512-u7KbvupWSppoEUYuhCBzmWkd1hcODzHhvGIuWZKoQO9q/qeNY5XptbzGqBSUooXyoF4T/pAdCRILF5zFIqexJw==", + "dependencies": [ + "@tanstack/react-router", + "@tanstack/react-router-devtools", + "clsx", + "goober", + "react", + "react-dom" + ] + }, + "@tanstack/router-generator@1.120.15_@tanstack+react-router@1.120.15__react@19.1.0__react-dom@19.1.0___react@19.1.0_react@19.1.0_react-dom@19.1.0__react@19.1.0": { + "integrity": "sha512-QwZ0rNXxzgOEUDRRAEWVjofKxuxSMIYEdYC3z20k6a7jkLC6pnlCORFx41Vf4xVCO6eElqlrUKXWLTleYSsvQw==", + "dependencies": [ + "@tanstack/react-router", + "@tanstack/virtual-file-routes", + "prettier", + "tsx", + "zod" + ], + "optionalPeers": [ + "@tanstack/react-router" + ] + }, + "@tanstack/router-plugin@1.120.15_@tanstack+react-router@1.120.15__react@19.1.0__react-dom@19.1.0___react@19.1.0_vite@6.3.4__@types+node@22.15.3__picomatch@4.0.2_@babel+core@7.27.1_react@19.1.0_react-dom@19.1.0__react@19.1.0_@types+node@22.15.3": { + "integrity": "sha512-ARuuPRKO5HzN3V0LzmkIGm0e447t5VCy2ijbUnzd08KjcJm3lG221ViC2qI+vTom1zp6yeNZHfJW1LBh1yLrTw==", + "dependencies": [ + "@babel/core", + "@babel/plugin-syntax-jsx", + "@babel/plugin-syntax-typescript", + "@babel/template", + "@babel/traverse", + "@babel/types", + "@tanstack/react-router", + "@tanstack/router-core", + "@tanstack/router-generator", + "@tanstack/router-utils", + "@tanstack/virtual-file-routes", + "@types/babel__core", + "@types/babel__template", + "@types/babel__traverse", + "babel-dead-code-elimination", + "chokidar", + "unplugin", + "vite", + "zod" + ], + "optionalPeers": [ + "@tanstack/react-router", + "vite" + ] + }, + "@tanstack/router-utils@1.115.0": { + "integrity": "sha512-Dng4y+uLR9b5zPGg7dHReHOTHQa6x+G6nCoZshsDtWrYsrdCcJEtLyhwZ5wG8OyYS6dVr/Cn+E5Bd2b6BhJ89w==", + "dependencies": [ + "@babel/generator", + "@babel/parser", + "ansis", + "diff" + ] + }, + "@tanstack/store@0.7.1": { + "integrity": "sha512-PjUQKXEXhLYj2X5/6c1Xn/0/qKY0IVFxTJweopRfF26xfjVyb14yALydJrHupDh3/d+1WKmfEgZPBVCmDkzzwg==" + }, + "@tanstack/virtual-file-routes@1.115.0": { + "integrity": "sha512-XLUh1Py3AftcERrxkxC5Y5m5mfllRH3YR6YVlyjFgI2Tc2Ssy2NKmQFQIafoxfW459UJ8Dn81nWKETEIJifE4g==" + }, "@testing-library/dom@10.4.0": { "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", "dependencies": [ @@ -3972,6 +4109,9 @@ "ansi-styles@5.2.0": { "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" }, + "ansis@3.17.0": { + "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==" + }, "anymatch@3.1.3": { "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dependencies": [ @@ -4071,6 +4211,15 @@ "possible-typed-array-names" ] }, + "babel-dead-code-elimination@1.0.10": { + "integrity": "sha512-DV5bdJZTzZ0zn0DC24v3jD7Mnidh6xhKa4GfKCbq3sfW8kaWhDdZjP3i81geA8T33tdYqWKw4D3fVv0CwEgKVA==", + "dependencies": [ + "@babel/core", + "@babel/parser", + "@babel/traverse", + "@babel/types" + ] + }, "babel-plugin-polyfill-corejs2@0.4.13_@babel+core@7.27.1": { "integrity": "sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==", "dependencies": [ @@ -4559,6 +4708,9 @@ "detect-node-es@1.1.0": { "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" }, + "diff@7.0.0": { + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==" + }, "diffie-hellman@5.0.3": { "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "dependencies": [ @@ -5018,6 +5170,12 @@ "gopd" ] }, + "goober@2.1.16_csstype@3.1.3": { + "integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==", + "dependencies": [ + "csstype" + ] + }, "gopd@1.2.0": { "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" }, @@ -5937,6 +6095,10 @@ "potpack@2.0.0": { "integrity": "sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw==" }, + "prettier@3.5.3": { + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "bin": true + }, "pretty-bytes@5.6.0": { "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==" }, @@ -6358,6 +6520,15 @@ "randombytes" ] }, + "seroval-plugins@1.3.2_seroval@1.3.2": { + "integrity": "sha512-0QvCV2lM3aj/U3YozDiVwx9zpH0q8A60CTWIv4Jszj/givcudPb48B+rkU5D51NJ0pTpweGMttHjboPa9/zoIQ==", + "dependencies": [ + "seroval" + ] + }, + "seroval@1.3.2": { + "integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==" + }, "set-function-length@1.2.2": { "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dependencies": [ @@ -6465,6 +6636,14 @@ "smob@1.5.0": { "integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==" }, + "solid-js@1.9.7_seroval@1.3.2": { + "integrity": "sha512-/saTKi8iWEM233n5OSi1YHCCuh66ZIQ7aK2hsToPe4tqGm7qAejU1SwNuTPivbWAYq7SjuHVVYxxuZQNRbICiw==", + "dependencies": [ + "csstype", + "seroval", + "seroval-plugins" + ] + }, "sort-asc@0.2.0": { "integrity": "sha512-umMGhjPeHAI6YjABoSTrFp2zaBtXBej1a0yKkuMUyjjqu6FJsTF+JYwCswWDg+zJfk/5npWUUbd33HH/WLzpaA==" }, @@ -6720,6 +6899,12 @@ "setimmediate" ] }, + "tiny-invariant@1.3.3": { + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==" + }, + "tiny-warning@1.0.3": { + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, "tinybench@2.9.0": { "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==" }, @@ -6909,6 +7094,14 @@ "universalify@2.0.1": { "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==" }, + "unplugin@2.3.5": { + "integrity": "sha512-RyWSb5AHmGtjjNQ6gIlA67sHOsWpsbWpwDokLwTcejVdOjEkJZh7QKu14J00gDDVSh8kGH4KYC/TNBceXFZhtw==", + "dependencies": [ + "acorn", + "picomatch@4.0.2", + "webpack-virtual-modules" + ] + }, "upath@1.2.0": { "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==" }, @@ -6951,6 +7144,12 @@ "@types/react" ] }, + "use-sync-external-store@1.5.0_react@19.1.0": { + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "dependencies": [ + "react" + ] + }, "util-deprecate@1.0.2": { "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, @@ -7100,6 +7299,9 @@ "webidl-conversions@7.0.0": { "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" }, + "webpack-virtual-modules@0.6.2": { + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==" + }, "whatwg-mimetype@3.0.0": { "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==" }, @@ -7390,6 +7592,9 @@ "npm:@radix-ui/react-toggle-group@^1.1.9", "npm:@radix-ui/react-tooltip@^1.2.4", "npm:@tailwindcss/postcss@^4.1.5", + "npm:@tanstack/react-router@^1.120.15", + "npm:@tanstack/router-devtools@^1.120.15", + "npm:@tanstack/router-plugin@^1.120.15", "npm:@testing-library/jest-dom@^6.6.3", "npm:@testing-library/react@^16.3.0", "npm:@testing-library/user-event@^14.6.1", diff --git a/package.json b/package.json index 4ad78d8d..0e069acd 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,8 @@ "@radix-ui/react-toast": "^1.2.11", "@radix-ui/react-toggle-group": "^1.1.9", "@radix-ui/react-tooltip": "^1.2.4", + "@tanstack/react-router": "^1.120.15", + "@tanstack/router-devtools": "^1.120.15", "@turf/turf": "^7.2.0", "base64-js": "^1.5.1", "class-validator": "^0.14.2", @@ -87,6 +89,7 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4.1.5", + "@tanstack/router-plugin": "^1.120.15", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", diff --git a/src/App.tsx b/src/App.tsx index 3c29125a..c508b89c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,4 @@ import { DeviceWrapper } from "@app/DeviceWrapper.tsx"; -import { PageRouter } from "@app/PageRouter.tsx"; import { DialogManager } from "@components/Dialog/DialogManager.tsx"; import { NewDeviceDialog } from "@components/Dialog/NewDeviceDialog.tsx"; import { KeyBackupReminder } from "@components/KeyBackupReminder.tsx"; @@ -8,15 +7,15 @@ import Footer from "@components/UI/Footer.tsx"; import { useAppStore } from "@core/stores/appStore.ts"; import { useDeviceStore } from "@core/stores/deviceStore.ts"; import { Dashboard } from "@pages/Dashboard/index.tsx"; -import type { JSX } from "react"; import { ErrorBoundary } from "react-error-boundary"; import { ErrorPage } from "@components/UI/ErrorPage.tsx"; import { MapProvider } from "react-map-gl/maplibre"; import { CommandPalette } from "@components/CommandPalette/index.tsx"; import { SidebarProvider } from "@core/stores/sidebarStore.tsx"; import { useTheme } from "@core/hooks/useTheme.ts"; +import { Outlet } from "@tanstack/react-router"; -export const App = (): JSX.Element => { +export function App() { const { getDevice } = useDeviceStore(); const { selectedDevice, setConnectDialogOpen, connectDialogOpen } = useAppStore(); @@ -49,7 +48,7 @@ export const App = (): JSX.Element => { - + ) @@ -65,4 +64,4 @@ export const App = (): JSX.Element => { ); -}; +} diff --git a/src/PageRouter.tsx b/src/PageRouter.tsx deleted file mode 100644 index 280e9277..00000000 --- a/src/PageRouter.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import MapPage from "@app/pages/Map/index.tsx"; -import { useDevice } from "@core/stores/deviceStore.ts"; -import ChannelsPage from "@pages/Channels.tsx"; -import ConfigPage from "@pages/Config/index.tsx"; -import MessagesPage from "@pages/Messages.tsx"; -import NodesPage from "@pages/Nodes.tsx"; -import { ErrorBoundary } from "react-error-boundary"; -import { ErrorPage } from "@components/UI/ErrorPage.tsx"; - -export const ErrorBoundaryWrapper = ({ - children, -}: { children: React.ReactNode }) => ( - {children} -); - -export const PageRouter = () => { - const { activePage } = useDevice(); - return ( - - {activePage === "messages" && } - {activePage === "map" && } - {activePage === "config" && } - {activePage === "channels" && } - {activePage === "nodes" && } - - ); -}; diff --git a/src/components/CommandPalette/index.tsx b/src/components/CommandPalette/index.tsx index c8cb4140..9000bd99 100644 --- a/src/components/CommandPalette/index.tsx +++ b/src/components/CommandPalette/index.tsx @@ -35,6 +35,7 @@ import { Avatar } from "@components/UI/Avatar.tsx"; import { cn } from "@core/utils/cn.ts"; import { useTranslation } from "react-i18next"; import { usePinnedItems } from "@core/hooks/usePinnedItems.ts"; +import { useNavigate } from "@tanstack/react-router"; export interface Group { id: string; @@ -63,11 +64,12 @@ export const CommandPalette = () => { setSelectedDevice, } = useAppStore(); const { getDevices } = useDeviceStore(); - const { setDialogOpen, setActivePage, getNode, connection } = useDevice(); + const { setDialogOpen, getNode, connection } = useDevice(); const { pinnedItems, togglePinnedItem } = usePinnedItems({ storageName: "pinnedCommandMenuGroups", }); const { t } = useTranslation("commandPalette"); + const navigate = useNavigate({ from: "/" }); const groups: Group[] = [ { @@ -79,21 +81,21 @@ export const CommandPalette = () => { label: t("goto.command.messages"), icon: MessageSquareIcon, action() { - setActivePage("messages"); + navigate({ to: "/messages" }); }, }, { label: t("goto.command.map"), icon: MapIcon, action() { - setActivePage("map"); + navigate({ to: "/map" }); }, }, { label: t("goto.command.config"), icon: SettingsIcon, action() { - setActivePage("config"); + navigate({ to: "/config" }); }, tags: ["settings"], }, @@ -101,14 +103,14 @@ export const CommandPalette = () => { label: t("goto.command.channels"), icon: LayersIcon, action() { - setActivePage("channels"); + navigate({ to: "/channels" }); }, }, { label: t("goto.command.nodes"), icon: UsersIcon, action() { - setActivePage("nodes"); + navigate({ to: "/nodes" }); }, }, ], diff --git a/src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx b/src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx index 3c3d6d5f..e16297a0 100644 --- a/src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx +++ b/src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx @@ -1,5 +1,4 @@ import { useEffect, useState } from "react"; - import { useAppStore } from "@core/stores/appStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts"; import { @@ -12,8 +11,8 @@ import { DeviceImage } from "@components/generic/DeviceImage.tsx"; import { TimeAgo } from "@components/generic/TimeAgo.tsx"; import { Uptime } from "@components/generic/Uptime.tsx"; import { toast } from "@core/hooks/useToast.ts"; -import { useFavoriteNode } from "../../../core/hooks/useFavoriteNode.ts"; -import { useIgnoreNode } from "../../../core/hooks/useIgnoreNode.ts"; +import { useFavoriteNode } from "@core/hooks/useFavoriteNode.ts"; +import { useIgnoreNode } from "@core/hooks/useIgnoreNode.ts"; import { cn } from "@core/utils/cn.ts"; import { @@ -49,6 +48,7 @@ import { } from "@components/UI/Tooltip.tsx"; import { Separator } from "@components/UI/Seperator.tsx"; import { useTranslation } from "react-i18next"; +import { useNavigate } from "@tanstack/react-router"; export interface NodeDetailsDialogProps { open: boolean; @@ -60,7 +60,8 @@ export const NodeDetailsDialog = ({ onOpenChange, }: NodeDetailsDialogProps) => { const { t } = useTranslation("dialog"); - const { setDialogOpen, connection, setActivePage, getNode } = useDevice(); + const { setDialogOpen, connection, getNode } = useDevice(); + const navigate = useNavigate(); const { setNodeNumToBeRemoved, nodeNumDetails } = useAppStore(); const { setChatType, setActiveChat } = useMessageStore(); const { updateFavorite } = useFavoriteNode(); @@ -88,7 +89,7 @@ export const NodeDetailsDialog = ({ setChatType(MessageType.Direct); setActiveChat(node.num); - setActivePage("messages"); + navigate({ to: "/messages" }); } function handleRequestPosition() { diff --git a/src/components/PageComponents/Map/NodeDetail.tsx b/src/components/PageComponents/Map/NodeDetail.tsx index 12dbe987..c21914c5 100644 --- a/src/components/PageComponents/Map/NodeDetail.tsx +++ b/src/components/PageComponents/Map/NodeDetail.tsx @@ -22,13 +22,13 @@ import { TooltipProvider, TooltipTrigger, } from "@radix-ui/react-tooltip"; -import { useDevice } from "@core/stores/deviceStore.ts"; import { MessageType, useMessageStore, } from "@core/stores/messageStore/index.ts"; import BatteryStatus from "@components/BatteryStatus.tsx"; import { useTranslation } from "react-i18next"; +import { useNavigate } from "@tanstack/react-router"; export interface NodeDetailProps { node: ProtobufType.Mesh.NodeInfo; @@ -36,8 +36,8 @@ export interface NodeDetailProps { export const NodeDetail = ({ node }: NodeDetailProps) => { const { setChatType, setActiveChat } = useMessageStore(); + const navigate = useNavigate(); const { t } = useTranslation("nodes"); - const { setActivePage } = useDevice(); const name = node.user?.longName ?? t("unknown.shortName"); const shortName = node.user?.shortName ?? t("unknown.shortName"); const hwModel = node.user?.hwModel ?? 0; @@ -52,7 +52,7 @@ export const NodeDetail = ({ node }: NodeDetailProps) => { function handleDirectMessage() { setChatType(MessageType.Direct); setActiveChat(node.num); - setActivePage("messages"); + navigate({ to: "/messages" }); } return ( diff --git a/src/components/PageComponents/Messages/MessageItem.tsx b/src/components/PageComponents/Messages/MessageItem.tsx index e431522f..ea8dce86 100644 --- a/src/components/PageComponents/Messages/MessageItem.tsx +++ b/src/components/PageComponents/Messages/MessageItem.tsx @@ -18,7 +18,7 @@ import { import { Protobuf, Types } from "@meshtastic/js"; import { Message } from "@core/stores/messageStore/types.ts"; import { useTranslation } from "react-i18next"; -// import { MessageActionsMenu } from "@components/PageComponents/Messages/MessageActionsMenu.tsx"; // Uncomment if needed later +// import { MessageActionsMenu } from "@components/PageComponents/Messages/MessageActionsMenu.tsx"; // TODO: Uncomment when actions menu is implemented interface MessageStatusInfo { displayText: string; diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 43dfd431..f09ebcc4 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -20,6 +20,7 @@ import { useAppStore } from "@core/stores/appStore.ts"; import { SidebarButton } from "@components/UI/Sidebar/SidebarButton.tsx"; import { useTranslation } from "react-i18next"; import { DeviceInfoPanel } from "./DeviceInfoPanel.tsx"; +import { useLocation, useNavigate } from "@tanstack/react-router"; export interface SidebarProps { children?: React.ReactNode; @@ -69,15 +70,19 @@ export const Sidebar = ({ children }: SidebarProps) => { getNode, getNodesLength, metadata, - activePage, unreadCounts, - setActivePage, setDialogOpen, } = useDevice(); const { setCommandPaletteOpen } = useAppStore(); const myNode = getNode(hardware.myNodeNum); const { isCollapsed } = useSidebar(); const { t } = useTranslation("ui"); + const navigate = useNavigate({ from: "/" }); + + const pathname = useLocation({ + select: (location) => location.pathname.replace(/^\//, ""), + }); + const myMetadata = metadata.get(0); const numUnread = [...unreadCounts.values()].reduce((sum, v) => sum + v, 0); @@ -162,21 +167,23 @@ export const Sidebar = ({ children }: SidebarProps) => { label={t("navigation.title")} className="mt-4 px-0" > - {pages.map((link) => ( - { - if (myNode !== undefined) { - setActivePage(link.page); - } - }} - active={link.page === activePage} - disabled={myNode === undefined} - /> - ))} + {pages.map((link) => { + return ( + { + if (myNode !== undefined) { + navigate({ to: `/${link.page}` }); + } + }} + active={link.page === pathname} + disabled={myNode === undefined} + /> + ); + })}
; nodeErrors: Map; connection?: MeshDevice; - activePage: Page; activeNode: number; waypoints: Protobuf.Mesh.Waypoint[]; pendingSettingsChanges: boolean; @@ -63,7 +62,6 @@ export interface Device { setWorkingConfig: (config: Protobuf.Config.Config) => void; setWorkingModuleConfig: (config: Protobuf.ModuleConfig.ModuleConfig) => void; setHardware: (hardware: Protobuf.Mesh.MyNodeInfo) => void; - setActivePage: (page: Page) => void; setActiveNode: (node: number) => void; setPendingSettingsChanges: (state: boolean) => void; addChannel: (channel: Protobuf.Channel.Channel) => void; @@ -129,7 +127,6 @@ export const useDeviceStore = createStore((set, get) => ({ metadata: new Map(), traceroutes: new Map(), connection: undefined, - activePage: "messages", activeNode: 0, waypoints: [], dialog: { @@ -318,16 +315,6 @@ export const useDeviceStore = createStore((set, get) => ({ }), ); }, - setActivePage: (page) => { - set( - produce((draft) => { - const device = draft.devices.get(id); - if (device) { - device.activePage = page; - } - }), - ); - }, setPendingSettingsChanges: (state) => { set( produce((draft) => { diff --git a/src/index.tsx b/src/index.tsx index 65f4ed87..dbaafd39 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -3,19 +3,29 @@ import { enableMapSet } from "immer"; import "maplibre-gl/dist/maplibre-gl.css"; import { StrictMode, Suspense } from "react"; import { createRoot } from "react-dom/client"; - -import { App } from "@app/App.tsx"; import "./i18n/config.ts"; +import { createRouter, RouterProvider } from "@tanstack/react-router"; +import { routeTree } from "@app/routes.tsx"; + +declare module "@tanstack/react-router" { + interface Register { + router: ReturnType; + } +} const container = document.getElementById("root") as HTMLElement; const root = createRoot(container); enableMapSet(); +const router = createRouter({ + routeTree, +}); + root.render( - - , + + , ); diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts new file mode 100644 index 00000000..78c69447 --- /dev/null +++ b/src/routeTree.gen.ts @@ -0,0 +1,59 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +// Import Routes + +import { Route as rootRoute } from './routes/__root' + +// Create/Update Routes + +// Populate the FileRoutesByPath interface + +declare module '@tanstack/react-router' { + interface FileRoutesByPath {} +} + +// Create and export the route tree + +export interface FileRoutesByFullPath {} + +export interface FileRoutesByTo {} + +export interface FileRoutesById { + __root__: typeof rootRoute +} + +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: never + fileRoutesByTo: FileRoutesByTo + to: never + id: '__root__' + fileRoutesById: FileRoutesById +} + +export interface RootRouteChildren {} + +const rootRouteChildren: RootRouteChildren = {} + +export const routeTree = rootRoute + ._addFileChildren(rootRouteChildren) + ._addFileTypes() + +/* ROUTE_MANIFEST_START +{ + "routes": { + "__root__": { + "filePath": "__root.tsx", + "children": [] + } + } +} +ROUTE_MANIFEST_END */ diff --git a/src/routes.tsx b/src/routes.tsx new file mode 100644 index 00000000..cfae0c3b --- /dev/null +++ b/src/routes.tsx @@ -0,0 +1,60 @@ +import { createRoute } from "@tanstack/react-router"; +import { Dashboard } from "@pages/Dashboard/index.tsx"; +import MessagesPage from "@pages/Messages.tsx"; +import MapPage from "@pages/Map/index.tsx"; +import ConfigPage from "@pages/Config/index.tsx"; +import ChannelsPage from "@pages/Channels.tsx"; +import NodesPage from "@pages/Nodes.tsx"; +import { createRootRoute } from "@tanstack/react-router"; +import { App } from "./App.tsx"; + +const rootRoute = createRootRoute({ + component: App, +}); + +const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: "/", + component: Dashboard, +}); + +const messagesRoute = createRoute({ + getParentRoute: () => rootRoute, + path: "/messages", + component: MessagesPage, +}); + +const mapRoute = createRoute({ + getParentRoute: () => rootRoute, + path: "/map", + component: MapPage, +}); + +const configRoute = createRoute({ + getParentRoute: () => rootRoute, + path: "/config", + component: ConfigPage, +}); + +const channelsRoute = createRoute({ + getParentRoute: () => rootRoute, + path: "/channels", + component: ChannelsPage, +}); + +const nodesRoute = createRoute({ + getParentRoute: () => rootRoute, + path: "/nodes", + component: NodesPage, +}); + +export const routeTree = rootRoute.addChildren([ + indexRoute, + messagesRoute, + mapRoute, + configRoute, + channelsRoute, + nodesRoute, +]); + +export { rootRoute };