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 };