From 9053ec65a6bd303df31f4122c7645a86b1ff5252 Mon Sep 17 00:00:00 2001 From: maze Date: Sun, 8 Mar 2026 22:22:51 +0100 Subject: [PATCH] Checkpoint --- ui/AGENTS.md | 73 --- ui/index.html | 4 +- ui/package-lock.json | 319 ++++++++++ ui/package.json | 1 + ui/public/digiview-icon.svg | 5 + ui/public/digiview.svg | 4 + ui/public/vite.svg | 1 - ui/src/App.scss | 21 +- ui/src/App.tsx | 45 +- ui/src/components/Full.tsx | 2 +- ui/src/components/Layout.scss | 1 + ui/src/components/Layout.tsx | 35 +- ui/src/components/LoadingFallback.scss | 60 ++ ui/src/components/LoadingFallback.tsx | 16 + ui/src/components/PacketDissectionViewer.tsx | 1 + ui/src/components/SignalGrade.scss | 46 ++ ui/src/components/SignalGrade.tsx | 47 ++ ui/src/components/VerticalSplit.tsx | 1 + ui/src/components/protocol/KPICard.tsx | 22 + ui/src/components/protocol/ProtocolHero.tsx | 53 ++ ui/src/components/protocol/RadioListItem.tsx | 64 ++ ui/src/components/protocol/StatusPill.tsx | 19 + ui/src/contexts/KeyboardNavigationContext.tsx | 22 +- ui/src/hooks/useKeyboardListNavigation.ts | 57 +- ui/src/pages/ADSB.scss | 13 + ui/src/pages/ADSB.tsx | 38 ++ ui/src/pages/APRS.scss | 3 + ui/src/pages/APRS.tsx | 7 + ui/src/pages/MeshCore.scss | 558 ++++++++++++++++ ui/src/pages/MeshCore.tsx | 18 + ui/src/pages/Overview.scss | 31 + ui/src/pages/Overview.tsx | 102 +-- ui/src/pages/adsb/ADSBContext.tsx | 25 + ui/src/pages/adsb/ADSBData.tsx | 83 +++ ui/src/pages/adsb/ADSBPacketsView.tsx | 357 +++++++++++ ui/src/pages/aprs/APRSView.tsx | 311 +++++++++ ui/src/pages/meshcore/MeshCoreContext.tsx | 19 +- ui/src/pages/meshcore/MeshCoreData.tsx | 199 ++---- .../pages/meshcore/MeshCoreGroupChatView.tsx | 130 +++- ui/src/pages/meshcore/MeshCoreNodesView.tsx | 368 +++++++++++ .../meshcore/MeshCorePacketDetailsPane.tsx | 127 ++-- ui/src/pages/meshcore/MeshCorePacketRows.tsx | 316 +++++++++- ui/src/pages/meshcore/MeshCorePacketTable.tsx | 45 +- ui/src/pages/meshcore/MeshCorePacketsView.tsx | 4 +- ui/src/pages/meshcore/MeshCoreStatsView.tsx | 596 ++++++++++++++++++ ui/src/pages/meshcore/MeshCoreView.tsx | 516 +++++++++++++++ ui/src/protocols/adsb.ts | 358 +++++++++++ ui/src/protocols/aprs.ts | 4 +- ui/src/protocols/meshcore.test.ts | 36 +- ui/src/protocols/meshcore.ts | 342 ++++++---- ui/src/services/ADSBService.ts | 37 ++ ui/src/services/API.ts | 14 + ui/src/services/MeshCoreService.ts | 201 +++++- ui/src/services/MeshCoreStream.ts | 76 +-- ui/src/services/Stream.ts | 12 +- ui/src/styles/ProtocolBriefing.scss | 277 ++++++++ ui/src/styles/_theme.scss | 18 +- ui/src/styles/theme/_bootstrap-overrides.scss | 69 ++ ui/src/styles/theme/_charts.scss | 47 ++ ui/src/styles/theme/_tables.scss | 39 +- ui/src/types/layout.types.tsx | 2 +- ui/src/types/protocol.types.ts | 16 +- ui/src/types/protocol/adsb.types.ts | 129 ++++ ui/src/types/protocol/meshcore.types.ts | 111 +++- ui/vite.config.ts | 9 + 65 files changed, 5874 insertions(+), 708 deletions(-) delete mode 100644 ui/AGENTS.md create mode 100644 ui/public/digiview-icon.svg create mode 100644 ui/public/digiview.svg delete mode 100644 ui/public/vite.svg create mode 100644 ui/src/components/LoadingFallback.scss create mode 100644 ui/src/components/LoadingFallback.tsx create mode 100644 ui/src/components/SignalGrade.scss create mode 100644 ui/src/components/SignalGrade.tsx create mode 100644 ui/src/components/protocol/KPICard.tsx create mode 100644 ui/src/components/protocol/ProtocolHero.tsx create mode 100644 ui/src/components/protocol/RadioListItem.tsx create mode 100644 ui/src/components/protocol/StatusPill.tsx create mode 100644 ui/src/pages/ADSB.scss create mode 100644 ui/src/pages/ADSB.tsx create mode 100644 ui/src/pages/adsb/ADSBContext.tsx create mode 100644 ui/src/pages/adsb/ADSBData.tsx create mode 100644 ui/src/pages/adsb/ADSBPacketsView.tsx create mode 100644 ui/src/pages/aprs/APRSView.tsx create mode 100644 ui/src/pages/meshcore/MeshCoreNodesView.tsx create mode 100644 ui/src/pages/meshcore/MeshCoreStatsView.tsx create mode 100644 ui/src/pages/meshcore/MeshCoreView.tsx create mode 100644 ui/src/protocols/adsb.ts create mode 100644 ui/src/services/ADSBService.ts create mode 100644 ui/src/styles/ProtocolBriefing.scss create mode 100644 ui/src/styles/theme/_bootstrap-overrides.scss create mode 100644 ui/src/styles/theme/_charts.scss create mode 100644 ui/src/types/protocol/adsb.types.ts diff --git a/ui/AGENTS.md b/ui/AGENTS.md deleted file mode 100644 index bbaa582..0000000 --- a/ui/AGENTS.md +++ /dev/null @@ -1,73 +0,0 @@ -# AGENTS - -This document provides context for AI agents working on this codebase. - -## Project Overview - -HAMView is an online Amateur Radio digital protocol live viewer. It features: -- Displaying online radio receivers in near real-time -- Streaming of popular Amateur Radio protocols such as APRS, MeshCore, etc. -- A live packet stream for each of the protocols -- Packet inspection - -## Tech Stack - -Used technologies: -- **Framework**: React 19 with TypeScript -- **Build Tool**: Vite 7 -- **User Interface**: React-Bootstrap with Bootstrap version 5 -- **Code Editor**: Visual Studio Code -- **Backend**: Go with labstack echo router -- **Libraries used**: Axios for API requests, mqtt.js for streaming -- **Testing**: use `npm run build` - -Relevant documents: -- API documentation is in `../server` - -## Testing Requirements - -**Always run tests before completing a task.** - -Run `npm run build` and run `pre-commit run --files changed files...` - -## Coding Guidelines - -### General -- Prefer ESM imports (`import`/`export`) -- Use builtins from React, React-Boostrap where possible -- Follow existing code patterns in the code base -- Look for opportunities to create reusable styles in `src/styles` or reusable components in `src/components` -- Never make changes outside of the project directory, if you think this is necessary prompt me for approval -- Only add things related to the prompted instructions, unless it is required to make the requested changes -- When adding imports, apply the import styling rules from the next section. - -### Styling -- Use React-Bootstrap components where appropriate -- Follow existing CSS patterns -- Add reusable style elements to the `src/App.scss` -- Explicit imports are better than implicit exports, be as specific as possible to minimize code size -- Order imports: - - React import first; then any react plugin - - Third-party libraries; - - Services; - - Local types imports; - - Local imports; - - Stylesheets -- Long import statements (> 3 imports) should use multiline import -- Sort import imports alphabetically - -## Protected files - -**Never modify files inside the `data/` directory.** This directory contains game data that should remain unchanged. - -Never add secrets to code. - -## Modifying code - -Prefer the patching strategy over running shell commands where possible. -Prevent using temporary files and shell commands where possible. - -## Addressing - -Don't call me "the user", refer to me as "the developer". -Refrain from using hyperbolic expressions like "excellent" and "perfect", "ok" or "good" is good enough. diff --git a/ui/index.html b/ui/index.html index 5d5f0b6..8d316ea 100644 --- a/ui/index.html +++ b/ui/index.html @@ -2,9 +2,9 @@ - + - HAMView + DigiView [PD0MZ]
diff --git a/ui/package-lock.json b/ui/package-lock.json index 2fbb9ed..7c017b4 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@emotion/styled": "^11.14.1", "@mui/icons-material": "^7.3.8", + "@mui/x-charts": "^8.27.4", "@noble/ciphers": "^2.1.1", "@noble/curves": "^2.0.1", "@noble/ed25519": "^3.0.0", @@ -1223,6 +1224,7 @@ "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.8.tgz", "integrity": "sha512-QKd1RhDXE1hf2sQDNayA9ic9jGkEgvZOf0tTkJxlBPG8ns8aS4rS8WwYURw2x5y3739p0HauUXX9WbH7UufFLw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.28.6", "@mui/core-downloads-tracker": "^7.3.8", @@ -1339,6 +1341,7 @@ "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.8.tgz", "integrity": "sha512-hoFRj4Zw2Km8DPWZp/nKG+ao5Jw5LSk2m/e4EGc6M3RRwXKEkMSG4TgtfVJg7dS2homRwtdXSMW+iRO0ZJ4+IA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.28.6", "@mui/private-theming": "^7.3.8", @@ -1427,6 +1430,105 @@ "integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==", "license": "MIT" }, + "node_modules/@mui/x-charts": { + "version": "8.27.4", + "resolved": "https://registry.npmjs.org/@mui/x-charts/-/x-charts-8.27.4.tgz", + "integrity": "sha512-T/vgCoETWiq3ODslAiGogjcqCt8dpjLcdC03l/FROPGHMV9mZ0Fyd9gwmMWTy/UZ+NYiQR/2yqhHEhSPZHQn4w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/utils": "^7.3.5", + "@mui/x-charts-vendor": "8.26.0", + "@mui/x-internal-gestures": "0.4.0", + "@mui/x-internals": "8.26.0", + "bezier-easing": "^2.1.0", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "reselect": "^5.1.1", + "use-sync-external-store": "^1.6.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0", + "@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/x-charts-vendor": { + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@mui/x-charts-vendor/-/x-charts-vendor-8.26.0.tgz", + "integrity": "sha512-R//+WSWvsLJRTjTRN90EKX9sgRzAb4HQBvtUA3cTQpkGrmEjmatD4BJAm3IdRdkSagf6yKWF+ypESctyRhbwnA==", + "license": "MIT AND ISC", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@types/d3-array": "^3.2.2", + "@types/d3-color": "^3.1.3", + "@types/d3-format": "^3.0.4", + "@types/d3-interpolate": "^3.0.4", + "@types/d3-path": "^3.1.1", + "@types/d3-scale": "^4.0.9", + "@types/d3-shape": "^3.1.7", + "@types/d3-time": "^3.0.4", + "@types/d3-time-format": "^4.0.3", + "@types/d3-timer": "^3.0.2", + "d3-array": "^3.2.4", + "d3-color": "^3.1.0", + "d3-format": "^3.1.0", + "d3-interpolate": "^3.0.1", + "d3-path": "^3.1.0", + "d3-scale": "^4.0.2", + "d3-shape": "^3.2.0", + "d3-time": "^3.1.0", + "d3-time-format": "^4.1.0", + "d3-timer": "^3.0.1", + "flatqueue": "^3.0.0", + "internmap": "^2.0.3" + } + }, + "node_modules/@mui/x-internal-gestures": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@mui/x-internal-gestures/-/x-internal-gestures-0.4.0.tgz", + "integrity": "sha512-i0W6v9LoiNY8Yf1goOmaygtz/ncPJGBedhpDfvNg/i8BvzPwJcBaeW4rqPucJfVag9KQ8MSssBBrvYeEnrQmhw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4" + } + }, + "node_modules/@mui/x-internals": { + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-8.26.0.tgz", + "integrity": "sha512-B9OZau5IQUvIxwpJZhoFJKqRpmWf5r0yMmSXjQuqb5WuqM755EuzWJOenY48denGoENzMLT8hQpA0hRTeU2IPA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/utils": "^7.3.5", + "reselect": "^5.1.1", + "use-sync-external-store": "^1.6.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/@noble/ciphers": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.1.1.tgz", @@ -2319,6 +2421,75 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, "node_modules/@types/deep-eql": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", @@ -3057,6 +3228,12 @@ "node": ">=6.0.0" } }, + "node_modules/bezier-easing": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz", + "integrity": "sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig==", + "license": "MIT" + }, "node_modules/bl": { "version": "6.1.6", "resolved": "https://registry.npmjs.org/bl/-/bl-6.1.6.tgz", @@ -3454,6 +3631,118 @@ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/date-fns": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", @@ -4018,6 +4307,12 @@ "node": ">=16" } }, + "node_modules/flatqueue": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/flatqueue/-/flatqueue-3.0.0.tgz", + "integrity": "sha512-y1deYaVt+lIc/d2uIcWDNd0CrdQTO5xoCjeFdhX0kSXvm2Acm0o+3bAOiYklTEoRyzwio3sv3/IiBZdusbAe2Q==", + "license": "ISC" + }, "node_modules/flatted": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.4.tgz", @@ -4349,6 +4644,15 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -5203,6 +5507,12 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", @@ -5723,6 +6033,15 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/ui/package.json b/ui/package.json index 92b0a55..79b3c90 100644 --- a/ui/package.json +++ b/ui/package.json @@ -15,6 +15,7 @@ "dependencies": { "@emotion/styled": "^11.14.1", "@mui/icons-material": "^7.3.8", + "@mui/x-charts": "^8.27.4", "@noble/ciphers": "^2.1.1", "@noble/curves": "^2.0.1", "@noble/ed25519": "^3.0.0", diff --git a/ui/public/digiview-icon.svg b/ui/public/digiview-icon.svg new file mode 100644 index 0000000..8f014c8 --- /dev/null +++ b/ui/public/digiview-icon.svg @@ -0,0 +1,5 @@ + diff --git a/ui/public/digiview.svg b/ui/public/digiview.svg new file mode 100644 index 0000000..736805d --- /dev/null +++ b/ui/public/digiview.svg @@ -0,0 +1,4 @@ + diff --git a/ui/public/vite.svg b/ui/public/vite.svg deleted file mode 100644 index ee9fada..0000000 --- a/ui/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/ui/src/App.scss b/ui/src/App.scss index a4081af..7d2e056 100644 --- a/ui/src/App.scss +++ b/ui/src/App.scss @@ -1,6 +1,6 @@ /* Import theme configuration */ -@import './styles/variables'; -@import './styles/theme'; +@use './styles/variables' as *; +@use './styles/theme' as *; html, body, @@ -24,6 +24,10 @@ body { color: var(--app-text); } +.text-secondary { + color: var(--app-blue-light) !important; +} + a { color: var(--app-accent); } @@ -33,10 +37,13 @@ a:hover { } .full-view { + flex: 1 1 auto; width: 100%; height: 100%; min-width: 0; min-height: 0; + overflow-y: auto; + overflow-x: hidden; } .split-root { @@ -117,6 +124,16 @@ a:hover { } } +.vertical-split-25-75 { + .split-pane-primary { + flex: 1 1 0; + } + + .split-pane-secondary { + flex: 3 1 0; + } +} + /* List Group Styles */ .list-item { padding: 0.75rem; diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 85ea28e..eaf294a 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -1,22 +1,31 @@ import { BrowserRouter, Navigate, Route, Routes } from 'react-router' +import { Suspense, lazy } from 'react' +import LoadingFallback from './components/LoadingFallback' import { RadiosProvider } from './contexts/RadiosContext' -import Overview from './pages/Overview' -import APRS from './pages/APRS' -import APRSPacketsView from './pages/aprs/APRSPacketsView' -import MeshCore from './pages/MeshCore' -import MeshCoreGroupChatView from './pages/meshcore/MeshCoreGroupChatView' -import MeshCoreMapView from './pages/meshcore/MeshCoreMapView' -import MeshCorePacketsView from './pages/meshcore/MeshCorePacketsView' -import StyleGuide from './pages/StyleGuide' -import NotFound from './pages/NotFound' import { KeyboardNavigationProvider } from './contexts/KeyboardNavigationContext' import './App.scss' +import Overview from './pages/Overview' +import APRS from './pages/APRS' +import MeshCore from './pages/MeshCore' + +const APRSView = lazy(() => import('./pages/aprs/APRSView')) +const APRSPacketsView = lazy(() => import('./pages/aprs/APRSPacketsView')) +const ADSB = lazy(() => import('./pages/ADSB')) +const ADSBPacketsView = lazy(() => import('./pages/adsb/ADSBPacketsView')) +const MeshCoreStatsView = lazy(() => import('./pages/meshcore/MeshCoreStatsView')) +const MeshCoreView = lazy(() => import('./pages/meshcore/MeshCoreView')) +const MeshCoreGroupChatView = lazy(() => import('./pages/meshcore/MeshCoreGroupChatView')) +const MeshCoreMapView = lazy(() => import('./pages/meshcore/MeshCoreMapView')) +const MeshCoreNodesView = lazy(() => import('./pages/meshcore/MeshCoreNodesView')) +const MeshCorePacketsView = lazy(() => import('./pages/meshcore/MeshCorePacketsView')) +const StyleGuide = lazy(() => import('./pages/StyleGuide')) +const NotFound = lazy(() => import('./pages/NotFound')) + const navLinks = [ { label: 'Radios', to: '/' }, { label: 'APRS', to: '/aprs' }, - { label: 'ADSB', to: '/adsb' }, { label: 'MeshCore', to: '/meshcore' }, ]; @@ -31,21 +40,29 @@ function App() { return ( - + }> + + + } /> + } /> + - } /> + } /> } /> - } /> + } /> + } /> } /> } /> + } /> } /> } /> } /> - + + ) diff --git a/ui/src/components/Full.tsx b/ui/src/components/Full.tsx index e571698..ae72811 100644 --- a/ui/src/components/Full.tsx +++ b/ui/src/components/Full.tsx @@ -4,7 +4,7 @@ import type { FullProps } from '../types/layout.types'; const Full: React.FC = ({ children, className = '' }) => { return ( - + {children} ); diff --git a/ui/src/components/Layout.scss b/ui/src/components/Layout.scss index b57b883..bde9064 100644 --- a/ui/src/components/Layout.scss +++ b/ui/src/components/Layout.scss @@ -92,6 +92,7 @@ box-sizing: border-box; background-color: var(--app-bg); color: var(--app-text); + padding-top: var(--layout-gutter); padding-left: var(--layout-gutter); padding-right: var(--layout-gutter); padding-bottom: var(--layout-gutter); diff --git a/ui/src/components/Layout.tsx b/ui/src/components/Layout.tsx index 2e11400..ed0074f 100644 --- a/ui/src/components/Layout.tsx +++ b/ui/src/components/Layout.tsx @@ -4,20 +4,21 @@ import { Link, NavLink, Outlet, useLocation } from 'react-router'; import KeyboardIcon from '@mui/icons-material/Keyboard'; import { useKeyboardNavigationActivity } from '../contexts/KeyboardNavigationContext'; import KeyboardShortcutsModal from './KeyboardShortcutsModal'; +import Full from './Full'; import { defaultKeyboardShortcuts } from '../hooks/useKeyboardListNavigation'; import './Layout.scss'; import type { LayoutProps, NavLinkItem } from '../types/layout.types'; const Layout: React.FC = ({ children, - brandText = 'PD0MZ HAM View', + brandText = 'PD0MZ Digi View', brandTo = '/', buttonGroup, navLinks = [], gutterSize = 16 }) => { const location = useLocation(); - const { isKeyboardNavigationActive, showGlobalShortcuts, setShowGlobalShortcuts } = useKeyboardNavigationActivity(); + const { isKeyboardNavigationActive, showGlobalShortcuts, setShowGlobalShortcuts, registeredShortcuts } = useKeyboardNavigationActivity(); const isActive = (link: NavLinkItem): boolean => { return location.pathname.startsWith(link.to); @@ -30,7 +31,8 @@ const Layout: React.FC = ({
- + + DigiView {brandText} {buttonGroup &&
{buttonGroup}
} @@ -72,15 +74,28 @@ const Layout: React.FC = ({ - + {children || } - + - setShowGlobalShortcuts(false)} - shortcuts={defaultKeyboardShortcuts} - /> + {(() => { + const merged = [...registeredShortcuts, ...defaultKeyboardShortcuts]; + const seen = new Set(); + const unique = merged.filter((s) => { + const key = `${s.keys}||${s.description}`; + if (seen.has(key)) return false; + seen.add(key); + return true; + }); + + return ( + setShowGlobalShortcuts(false)} + shortcuts={unique} + /> + ); + })()}