Browse Source

Merge pull request #477 from danditomaso/refactor/use-deno

Switch from Bun to Deno for dev environment
deno-round-2
Dan Ditomaso 1 year ago
committed by GitHub
parent
commit
b1cf4ef645
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 12
      .github/workflows/ci.yml
  2. 14
      .github/workflows/nightly.yml
  3. 14
      .github/workflows/pr.yml
  4. 14
      .github/workflows/release.yml
  5. 3
      .gitignore
  6. 9
      .vscode/settings.json
  7. 138
      README.md
  8. 27
      biome.json
  9. 107
      bun.lock
  10. 34
      deno.json
  11. 6804
      deno.lock
  12. 42
      package.json
  13. 7
      src/App.tsx
  14. 2
      src/PageRouter.tsx
  15. 14
      src/components/CommandPalette.tsx
  16. 21
      src/components/DeviceSelector.tsx
  17. 7
      src/components/DeviceSelectorButton.tsx
  18. 2
      src/components/Dialog/DeviceNameDialog.tsx
  19. 10
      src/components/Dialog/DialogManager.tsx
  20. 27
      src/components/Dialog/ImportDialog.tsx
  21. 13
      src/components/Dialog/LocationResponseDialog.tsx
  22. 30
      src/components/Dialog/NewDeviceDialog.tsx
  23. 287
      src/components/Dialog/NodeDetailsDialog.tsx
  24. 24
      src/components/Dialog/NodeOptionsDialog.tsx
  25. 10
      src/components/Dialog/PKIBackupDialog.tsx
  26. 2
      src/components/Dialog/PkiRegenerateDialog.tsx
  27. 12
      src/components/Dialog/QRDialog.tsx
  28. 2
      src/components/Dialog/RebootDialog.tsx
  29. 6
      src/components/Dialog/RemoveNodeDialog.tsx
  30. 2
      src/components/Dialog/ShutdownDialog.tsx
  31. 16
      src/components/Dialog/TracerouteResponseDialog.tsx
  32. 20
      src/components/Form/DynamicForm.tsx
  33. 14
      src/components/Form/DynamicFormField.tsx
  34. 20
      src/components/Form/FormInput.tsx
  35. 6
      src/components/Form/FormMultiSelect.tsx
  36. 18
      src/components/Form/FormPasswordGenerator.tsx
  37. 16
      src/components/Form/FormSelect.tsx
  38. 7
      src/components/KeyBackupReminder.tsx
  39. 71
      src/components/PageComponents/Channel.tsx
  40. 7
      src/components/PageComponents/Config/Bluetooth.tsx
  41. 2
      src/components/PageComponents/Config/Device.tsx
  42. 2
      src/components/PageComponents/Config/Display.tsx
  43. 2
      src/components/PageComponents/Config/LoRa.tsx
  44. 2
      src/components/PageComponents/Config/Position.tsx
  45. 2
      src/components/PageComponents/Config/Power.tsx
  46. 25
      src/components/PageComponents/Config/Security/Security.tsx
  47. 2
      src/components/PageComponents/Config/Security/securityReducer.tsx
  48. 6
      src/components/PageComponents/Config/Security/types.ts
  49. 4
      src/components/PageComponents/Connect/BLE.tsx
  50. 21
      src/components/PageComponents/Connect/HTTP.tsx
  51. 4
      src/components/PageComponents/Connect/Serial.tsx
  52. 65
      src/components/PageComponents/Map/NodeDetail.tsx
  53. 16
      src/components/PageComponents/Messages/ChannelChat.tsx
  54. 33
      src/components/PageComponents/Messages/Message.tsx
  55. 22
      src/components/PageComponents/Messages/MessageInput.tsx
  56. 41
      src/components/PageComponents/Messages/TraceRoute.tsx
  57. 4
      src/components/PageComponents/ModuleConfig/AmbientLighting.tsx
  58. 2
      src/components/PageComponents/ModuleConfig/Audio.tsx
  59. 17
      src/components/PageComponents/ModuleConfig/CannedMessage.tsx
  60. 4
      src/components/PageComponents/ModuleConfig/DetectionSensor.tsx
  61. 2
      src/components/PageComponents/ModuleConfig/ExternalNotification.tsx
  62. 55
      src/components/PageComponents/ModuleConfig/MQTT.tsx
  63. 4
      src/components/PageComponents/ModuleConfig/NeighborInfo.tsx
  64. 2
      src/components/PageComponents/ModuleConfig/Paxcounter.tsx
  65. 2
      src/components/PageComponents/ModuleConfig/RangeTest.tsx
  66. 2
      src/components/PageComponents/ModuleConfig/Serial.tsx
  67. 2
      src/components/PageComponents/ModuleConfig/StoreForward.tsx
  68. 2
      src/components/PageComponents/ModuleConfig/Telemetry.tsx
  69. 13
      src/components/PageLayout.tsx
  70. 128
      src/components/Sidebar.tsx
  71. 12
      src/components/ThemeSwitcher.tsx
  72. 4
      src/components/Toaster.tsx
  73. 2
      src/components/UI/Avatar.tsx
  74. 8
      src/components/UI/Button.tsx
  75. 6
      src/components/UI/Command.tsx
  76. 8
      src/components/UI/Dialog.tsx
  77. 12
      src/components/UI/DropdownMenu.tsx
  78. 9
      src/components/UI/ErrorPage.tsx
  79. 54
      src/components/UI/Footer.tsx
  80. 8
      src/components/UI/Generator.tsx
  81. 5
      src/components/UI/Input.tsx
  82. 16
      src/components/UI/Menubar.tsx
  83. 25
      src/components/UI/Modal.tsx
  84. 7
      src/components/UI/MultiSelect.tsx
  85. 2
      src/components/UI/Popover.tsx
  86. 8
      src/components/UI/Select.tsx
  87. 2
      src/components/UI/Sidebar/SidebarSection.tsx
  88. 5
      src/components/UI/Sidebar/sidebarButton.tsx
  89. 2
      src/components/UI/Spinner.tsx
  90. 2
      src/components/UI/Tabs.tsx
  91. 20
      src/components/UI/Toast.tsx
  92. 4
      src/components/UI/Tooltip.tsx
  93. 2
      src/components/UI/Typography/Blockquote.tsx
  94. 2
      src/components/UI/Typography/Code.tsx
  95. 3
      src/components/UI/Typography/Heading.tsx
  96. 6
      src/components/UI/Typography/Link.tsx
  97. 2
      src/components/UI/Typography/P.tsx
  98. 4
      src/components/UI/Typography/Subtle.tsx
  99. 2
      src/components/generic/Blur.tsx
  100. 2
      src/components/generic/Mono.tsx

12
.github/workflows/ci.yml

@ -16,14 +16,16 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup bun - name: Setup Deno
uses: oven-sh/setup-bun@v2 uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- name: Install Dependencies - name: Install Dependencies
run: bun install run: deno install
- name: Run tests - name: Run tests
run: bun run test:run run: deno task test
- name: Build Package - name: Build Package
run: bun run build run: deno task build

14
.github/workflows/nightly.yml

@ -15,20 +15,22 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup bun - name: Setup Deno
uses: oven-sh/setup-bun@v2 uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- name: Install Dependencies - name: Install Dependencies
run: bun install run: deno install
- name: Run tests - name: Run tests
run: bun run test:run run: deno task test
- name: Build Package - name: Build Package
run: bun run build run: deno task build
- name: Package Output - name: Package Output
run: bun run package run: deno task package
- name: Archive compressed build - name: Archive compressed build
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4

14
.github/workflows/pr.yml

@ -9,20 +9,22 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup bun - name: Setup Deno
uses: oven-sh/setup-bun@v2 uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- name: Install Dependencies - name: Install Dependencies
run: bun install run: deno install
- name: Run tests - name: Run tests
run: bun run test:run run: deno task test
- name: Build Package - name: Build Package
run: bun run build run: deno task build
- name: Compress build - name: Compress build
run: bun run package run: deno task package
- name: Archive compressed build - name: Archive compressed build
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4

14
.github/workflows/release.yml

@ -15,20 +15,22 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup bun - name: Setup Deno
uses: oven-sh/setup-bun@v2 uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- name: Install Dependencies - name: Install Dependencies
run: bun install run: deno install
- name: Run tests - name: Run tests
run: bun run test:run run: deno task test
- name: Build Package - name: Build Package
run: bun run build run: deno task build
- name: Package Output - name: Package Output
run: bun run package run: deno task package
- name: Archive compressed build - name: Archive compressed build
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4

3
.gitignore

@ -2,4 +2,5 @@ dist
node_modules node_modules
stats.html stats.html
.vercel .vercel
dev-dist .vite/deps
dev-dist

9
.vscode/settings.json

@ -1,7 +1,6 @@
{ {
"editor.defaultFormatter": "biomejs.biome", "deno.enable": true,
"editor.codeActionsOnSave": { "deno.suggest.imports.autoDiscover": true,
"quickfix.biome": "explicit" "editor.formatOnSave": true,
}, "editor.defaultFormatter": "denoland.vscode-deno"
"editor.formatOnSave": true
} }

138
README.md

@ -9,7 +9,8 @@
## Overview ## Overview
Official [Meshtastic](https://meshtastic.org) web interface, that can be hosted or served from a node Official [Meshtastic](https://meshtastic.org) web interface, that can be hosted
or served from a node
**[Hosted version](https://client.meshtastic.org)** **[Hosted version](https://client.meshtastic.org)**
@ -19,7 +20,8 @@ Official [Meshtastic](https://meshtastic.org) web interface, that can be hosted
## Progress Web App Support (PWA) ## Progress Web App Support (PWA)
Meshtastic Web Client now includes Progressive Web App (PWA) functionality, allowing users to: Meshtastic Web Client now includes Progressive Web App (PWA) functionality,
allowing users to:
- Install the app on desktop and mobile devices - Install the app on desktop and mobile devices
- Access the interface offline - Access the interface offline
@ -35,8 +37,10 @@ PWA functionality works with both the hosted version and self-hosted instances.
## Self-host ## Self-host
The client can be self hosted using the precompiled container images with an OCI compatible runtime such as [Docker](https://www.docker.com/) or [Podman](https://podman.io/). The client can be self hosted using the precompiled container images with an OCI
The base image used is [Nginx 1.27](https://hub.docker.com/_/nginx) compatible runtime such as [Docker](https://www.docker.com/) or
[Podman](https://podman.io/). The base image used is
[Nginx 1.27](https://hub.docker.com/_/nginx)
```bash ```bash
# With Docker # With Docker
@ -46,9 +50,11 @@ docker run -d -p 8080:8080 --restart always --name Meshtastic-Web ghcr.io/meshta
podman run -d -p 8080:8080 --restart always --name Meshtastic-Web ghcr.io/meshtastic/web podman run -d -p 8080:8080 --restart always --name Meshtastic-Web ghcr.io/meshtastic/web
``` ```
## Nightly releases ## Nightly releases
Our nightly releases provide the latest development builds with cutting-edge features and fixes. These builds are automatically generated from the latest main branch every night and are available for testing and early adoption. Our nightly releases provide the latest development builds with cutting-edge
features and fixes. These builds are automatically generated from the latest
main branch every night and are available for testing and early adoption.
```bash ```bash
# With Docker # With Docker
@ -57,38 +63,102 @@ docker run -d -p 8080:8080 --restart always --name Meshtastic-Web ghcr.io/meshta
podman run -d -p 8080:8080 --restart always --name Meshtastic-Web ghcr.io/meshtastic/web:nightly podman run -d -p 8080:8080 --restart always --name Meshtastic-Web ghcr.io/meshtastic/web:nightly
``` ```
> [!WARNING] > [!WARNING]
> - Nightly builds represent the latest development state and may contain breaking changes >
> - These builds undergo automated testing but may be less stable than tagged release versions > - Nightly builds represent the latest development state and may contain
> - Not recommended for production environments unless you are actively testing new features > breaking changes
> - These builds undergo automated testing but may be less stable than tagged
> release versions
> - Not recommended for production environments unless you are actively testing
> new features
> - No guarantee of backward compatibility between nightly builds > - No guarantee of backward compatibility between nightly builds
### Version Information ### Version Information
Each nightly build is tagged with: Each nightly build is tagged with:
- The nightly tag for the latest build - The nightly tag for the latest build
- A specific SHA for build reproducibility - A specific SHA for build reproducibility
### Feedback ### Feedback
If you encounter any issues with nightly builds, please report them in our [issues tracker](https://github.com/meshtastic/web/issues). Your feedback helps improve the stability of future releases
If you encounter any issues with nightly builds, please report them in our
[issues tracker](https://github.com/meshtastic/web/issues). Your feedback helps
improve the stability of future releases
## Development & Building ## Development & Building
You'll need to download the package manager used with this repo. You can install it by visiting [Bun.sh](https://bun.sh/) and following the installation instructions.
### Debugging You'll need to download the package manager used with this repo. You can install
it by visiting [deno.com](https://deno.com/) and following the installation
instructions listed on the home page.
### Development
Install the dependencies.
```bash
deno i
```
Start the development server:
```bash
deno task dev
```
### Building and Packaging
Build the project:
```bash
deno task build
```
GZip the output:
```bash
deno task package
```
### Why Deno?
Meshtastic Web uses Deno as its development platform for several compelling
reasons:
- **Built-in Security**: Deno's security-first approach requires explicit
permissions for file, network, and environment access, reducing vulnerability
risks.
- **TypeScript Support**: Native TypeScript support without additional
configuration, enhancing code quality and developer experience.
- **Modern JavaScript**: First-class support for ESM imports, top-level await,
and other modern JavaScript features.
- **Simplified Tooling**: Built-in formatter, linter, test runner, and bundler
eliminate the need for multiple third-party tools.
- **Reproducible Builds**: Lockfile ensures consistent builds across all
environments.
- **Web Standard APIs**: Uses browser-compatible APIs, making code more portable
between server and client environments.
### Debugging
#### Debugging with React Scan #### Debugging with React Scan
Meshtastic Web Client has included the library [React Scan](https://github.com/aidenybai/react-scan) to help you identify and resolve render performance issues during development.
React's comparison-by-reference approach to props makes it easy to inadvertently cause unnecessary re-renders, especially with: Meshtastic Web Client has included the library
[React Scan](https://github.com/aidenybai/react-scan) to help you identify and
resolve render performance issues during development.
React's comparison-by-reference approach to props makes it easy to inadvertently
cause unnecessary re-renders, especially with:
- Inline function callbacks (`onClick={() => handleClick()}`) - Inline function callbacks (`onClick={() => handleClick()}`)
- Object literals (`style={{ color: "purple" }}`) - Object literals (`style={{ color: "purple" }}`)
- Array literals (`items={[1, 2, 3]}`) - Array literals (`items={[1, 2, 3]}`)
These are recreated on every render, causing child components to re-render even when nothing has actually changed. These are recreated on every render, causing child components to re-render even
when nothing has actually changed.
Unlike React DevTools, React Scan specifically focuses on performance optimization by: Unlike React DevTools, React Scan specifically focuses on performance
optimization by:
- Clearly distinguishing between necessary and unnecessary renders - Clearly distinguishing between necessary and unnecessary renders
- Providing render counts for components - Providing render counts for components
@ -96,10 +166,11 @@ Unlike React DevTools, React Scan specifically focuses on performance optimizati
- Offering a dedicated performance debugging experience - Offering a dedicated performance debugging experience
#### Usage #### Usage
When experiencing slow renders, run: When experiencing slow renders, run:
```bash ```bash
bun run dev:scan deno task dev:scan
``` ```
This will allow you to discover the following about your components and pages: This will allow you to discover the following about your components and pages:
@ -109,32 +180,5 @@ This will allow you to discover the following about your components and pages:
- Expensive hook operations - Expensive hook operations
- Props that change reference on every render - Props that change reference on every render
Use these insights to apply targeted optimizations like `React.memo()`, `useCallback()`, or `useMemo()` where they'll have the most impact. Use these insights to apply targeted optimizations like `React.memo()`,
`useCallback()`, or `useMemo()` where they'll have the most impact.
### Building and Packaging
Build the project:
```bash
bun run build
```
GZip the output:
```bash
bun run package
```
### Development
Install the dependencies.
```bash
bun i
```
Start the development server:
```bash
bun run dev
```

27
biome.json

@ -1,27 +0,0 @@
{
"$schema": "https://biomejs.dev/schemas/1.7.3/schema.json",
"organizeImports": {
"enabled": true
},
"files": {
"ignoreUnknown": true,
"ignore": ["vercel.json"]
},
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true,
"defaultBranch": "master"
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
}
}

107
bun.lock

@ -47,7 +47,6 @@
"zustand": "5.0.3", "zustand": "5.0.3",
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^1.9.4",
"@tailwindcss/postcss": "^4.0.9", "@tailwindcss/postcss": "^4.0.9",
"@testing-library/react": "^16.2.0", "@testing-library/react": "^16.2.0",
"@types/chrome": "^0.0.307", "@types/chrome": "^0.0.307",
@ -262,24 +261,6 @@
"@babel/types": ["@babel/[email protected]", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw=="], "@babel/types": ["@babel/[email protected]", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw=="],
"@biomejs/biome": ["@biomejs/[email protected]", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "1.9.4", "@biomejs/cli-darwin-x64": "1.9.4", "@biomejs/cli-linux-arm64": "1.9.4", "@biomejs/cli-linux-arm64-musl": "1.9.4", "@biomejs/cli-linux-x64": "1.9.4", "@biomejs/cli-linux-x64-musl": "1.9.4", "@biomejs/cli-win32-arm64": "1.9.4", "@biomejs/cli-win32-x64": "1.9.4" }, "bin": { "biome": "bin/biome" } }, "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog=="],
"@biomejs/cli-darwin-arm64": ["@biomejs/[email protected]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw=="],
"@biomejs/cli-darwin-x64": ["@biomejs/[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg=="],
"@biomejs/cli-linux-arm64": ["@biomejs/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g=="],
"@biomejs/cli-linux-arm64-musl": ["@biomejs/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA=="],
"@biomejs/cli-linux-x64": ["@biomejs/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg=="],
"@biomejs/cli-linux-x64-musl": ["@biomejs/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg=="],
"@biomejs/cli-win32-arm64": ["@biomejs/[email protected]", "", { "os": "win32", "cpu": "arm64" }, "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg=="],
"@biomejs/cli-win32-x64": ["@biomejs/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA=="],
"@bufbuild/protobuf": ["@bufbuild/[email protected]", "", {}, "sha512-tFQoXHJdkEOSwj5tRIZSPNUuXK3RaR7T1nUrPgbYX1pUbvqqaaZAsfo+NXBPsz5rZMSKVFrgK1WL8Q/MSLvprg=="], "@bufbuild/protobuf": ["@bufbuild/[email protected]", "", {}, "sha512-tFQoXHJdkEOSwj5tRIZSPNUuXK3RaR7T1nUrPgbYX1pUbvqqaaZAsfo+NXBPsz5rZMSKVFrgK1WL8Q/MSLvprg=="],
"@clack/core": ["@clack/[email protected]", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-5cfhQNH+1VQ2xLQlmzXMqUoiaH0lRBq9/CLW9lTyMbuKLC3+xEK01tHVvyut++mLOn5urSHmkm6I0Lg9MaJSTQ=="], "@clack/core": ["@clack/[email protected]", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-5cfhQNH+1VQ2xLQlmzXMqUoiaH0lRBq9/CLW9lTyMbuKLC3+xEK01tHVvyut++mLOn5urSHmkm6I0Lg9MaJSTQ=="],
@ -362,7 +343,7 @@
"@jridgewell/trace-mapping": ["@jridgewell/[email protected]", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="], "@jridgewell/trace-mapping": ["@jridgewell/[email protected]", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
"@jsr/meshtastic__core": ["@jsr/[email protected]-1", "https://npm.jsr.io/~/11/@jsr/meshtastic__core/2.6.0-1.tgz", { "dependencies": { "@bufbuild/protobuf": "^2.2.3", "@jsr/meshtastic__protobufs": "^2.6.0", "crc": "^4.3.2", "ste-simple-events": "^3.0.11", "tslog": "^4.9.3" } }, "sha512-fVlFVImuthS5oIH3J5Glue+0OizGmhtvzTWEmAJwdgmYyUn7mfA2iEvoUHFv3EiEDq6BJ9SjkLzOSqMTRPSNuw=="], "@jsr/meshtastic__core": ["@jsr/[email protected]", "https://npm.jsr.io/~/11/@jsr/meshtastic__core/2.6.0.tgz", { "dependencies": { "@bufbuild/protobuf": "^2.2.3", "@jsr/meshtastic__protobufs": "^2.6.0", "crc": "^4.3.2", "ste-simple-events": "^3.0.11", "tslog": "^4.9.3" } }, "sha512-+Ik6gzZnfi5sW+WC06bRayA6KGF2NI+zi3bqKbvA8mGDNSOPgsFhA4VZ79DKY4bSflTW170MRIUeyYo0IWQQuw=="],
"@jsr/meshtastic__protobufs": ["@jsr/[email protected]", "https://npm.jsr.io/~/11/@jsr/meshtastic__protobufs/2.6.0.tgz", { "dependencies": { "@bufbuild/protobuf": "^2.2.3" } }, "sha512-CGlgBdzAuQCZuGPrnzP8zU+EcLlmyYeeMbqFHuJ834cYfArWXDjDh1UYaPo2rI03LTjqa3MeWpfqDlzBR8kIMg=="], "@jsr/meshtastic__protobufs": ["@jsr/[email protected]", "https://npm.jsr.io/~/11/@jsr/meshtastic__protobufs/2.6.0.tgz", { "dependencies": { "@bufbuild/protobuf": "^2.2.3" } }, "sha512-CGlgBdzAuQCZuGPrnzP8zU+EcLlmyYeeMbqFHuJ834cYfArWXDjDh1UYaPo2rI03LTjqa3MeWpfqDlzBR8kIMg=="],
@ -370,8 +351,6 @@
"@mapbox/jsonlint-lines-primitives": ["@mapbox/[email protected]", "", {}, "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ=="], "@mapbox/jsonlint-lines-primitives": ["@mapbox/[email protected]", "", {}, "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ=="],
"@mapbox/mapbox-gl-supported": ["@mapbox/[email protected]", "", {}, "sha512-2XghOwu16ZwPJLOFVuIOaLbN0iKMn867evzXFyf0P22dqugezfJwLmdanAgU25ITvz1TvOfVP4jsDImlDJzcWg=="],
"@mapbox/point-geometry": ["@mapbox/[email protected]", "", {}, "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ=="], "@mapbox/point-geometry": ["@mapbox/[email protected]", "", {}, "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ=="],
"@mapbox/tiny-sdf": ["@mapbox/[email protected]", "", {}, "sha512-qMqa27TLw+ZQz5Jk+RcwZGH7BQf5G/TrutJhspsca/3SHwmgKQ1iq+d3Jxz5oysPVYTGP6aXxCo5Lk9Er6YBAA=="], "@mapbox/tiny-sdf": ["@mapbox/[email protected]", "", {}, "sha512-qMqa27TLw+ZQz5Jk+RcwZGH7BQf5G/TrutJhspsca/3SHwmgKQ1iq+d3Jxz5oysPVYTGP6aXxCo5Lk9Er6YBAA=="],
@ -388,9 +367,9 @@
"@meshtastic/js": ["@jsr/[email protected]", "https://npm.jsr.io/~/11/@jsr/meshtastic__js/2.6.0-0.tgz", { "dependencies": { "@bufbuild/protobuf": "^2.2.3", "@jsr/meshtastic__protobufs": "^2.6.0", "crc": "^4.3.2", "ste-simple-events": "^3.0.11", "tslog": "^4.9.3" } }, "sha512-+xpZpxK6oUIVOuEs7C+LyxRr2druvc7UNNNTK9Rl8ioXj63Jz1uQXlYe2Gj0xjnRAiSQLR7QVaPef21BR/YTxA=="], "@meshtastic/js": ["@jsr/[email protected]", "https://npm.jsr.io/~/11/@jsr/meshtastic__js/2.6.0-0.tgz", { "dependencies": { "@bufbuild/protobuf": "^2.2.3", "@jsr/meshtastic__protobufs": "^2.6.0", "crc": "^4.3.2", "ste-simple-events": "^3.0.11", "tslog": "^4.9.3" } }, "sha512-+xpZpxK6oUIVOuEs7C+LyxRr2druvc7UNNNTK9Rl8ioXj63Jz1uQXlYe2Gj0xjnRAiSQLR7QVaPef21BR/YTxA=="],
"@meshtastic/transport-http": ["@jsr/meshtastic__transport-http@0.1.0", "https://npm.jsr.io/~/11/@jsr/meshtastic__transport-http/0.1.0.tgz", { "dependencies": { "@jsr/meshtastic__core": "^2.6.0-1" } }, "sha512-+cICcTAowbnTz9yffUod1c2XxuPgJRw6VfXYHZgJZ3oaBXtUoUFHl9WdMdZLE0juzMRpTARZY2/bAH3brCf0rw=="], "@meshtastic/transport-http": ["@jsr/meshtastic__transport-http@0.2.1", "https://npm.jsr.io/~/11/@jsr/meshtastic__transport-http/0.2.1.tgz", { "dependencies": { "@jsr/meshtastic__core": "^2.6.0" } }, "sha512-lmQKr3aIINKvtGROU4HchmSVqbZSbkIHqajowRRC8IAjsnR0zNTyxz210QyY4pFUF9hpcW3GRjwq5h/VO2JuGg=="],
"@meshtastic/transport-web-serial": ["@jsr/[email protected].0", "https://npm.jsr.io/~/11/@jsr/meshtastic__transport-web-serial/0.2.0.tgz", { "dependencies": { "@jsr/meshtastic__core": "^2.6.0-0" } }, "sha512-mP/nxOj0syABh3FkG5iIolWhUMiFh/qtJtvqihxLkaRoxdabUyW62mOtfhCMBEjxgVnKg4Gy7GkaXfC/eFy19Q=="], "@meshtastic/transport-web-serial": ["@jsr/[email protected].1", "https://npm.jsr.io/~/11/@jsr/meshtastic__transport-web-serial/0.2.1.tgz", { "dependencies": { "@jsr/meshtastic__core": "^2.6.0" } }, "sha512-yumjEGLkAuJYOC3aWKvZzbQqi/LnqaKfNpVCY7Ki7oLtAshNiZrBLiwiFhN7+ZR9FfMdJThyBMqREBDRRWTO1Q=="],
"@noble/curves": ["@noble/[email protected]", "", { "dependencies": { "@noble/hashes": "1.7.1" } }, "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ=="], "@noble/curves": ["@noble/[email protected]", "", { "dependencies": { "@noble/hashes": "1.7.1" } }, "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ=="],
@ -500,43 +479,43 @@
"@rollup/pluginutils": ["@rollup/[email protected]", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ=="], "@rollup/pluginutils": ["@rollup/[email protected]", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/[email protected].8", "", { "os": "android", "cpu": "arm" }, "sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw=="], "@rollup/rollup-android-arm-eabi": ["@rollup/[email protected].9", "", { "os": "android", "cpu": "arm" }, "sha512-qZdlImWXur0CFakn2BJ2znJOdqYZKiedEPEVNTBrpfPjc/YuTGcaYZcdmNFTkUj3DU0ZM/AElcM8Ybww3xVLzA=="],
"@rollup/rollup-android-arm64": ["@rollup/[email protected].8", "", { "os": "android", "cpu": "arm64" }, "sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q=="], "@rollup/rollup-android-arm64": ["@rollup/[email protected].9", "", { "os": "android", "cpu": "arm64" }, "sha512-4KW7P53h6HtJf5Y608T1ISKvNIYLWRKMvfnG0c44M6In4DQVU58HZFEVhWINDZKp7FZps98G3gxwC1sb0wXUUg=="],
"@rollup/rollup-darwin-arm64": ["@rollup/[email protected].8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q=="], "@rollup/rollup-darwin-arm64": ["@rollup/[email protected].9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ=="],
"@rollup/rollup-darwin-x64": ["@rollup/[email protected].8", "", { "os": "darwin", "cpu": "x64" }, "sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw=="], "@rollup/rollup-darwin-x64": ["@rollup/[email protected].9", "", { "os": "darwin", "cpu": "x64" }, "sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/[email protected].8", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA=="], "@rollup/rollup-freebsd-arm64": ["@rollup/[email protected].9", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-2lzjQPJbN5UnHm7bHIUKFMulGTQwdvOkouJDpPysJS+QFBGDJqcfh+CxxtG23Ik/9tEvnebQiylYoazFMAgrYw=="],
"@rollup/rollup-freebsd-x64": ["@rollup/[email protected].8", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q=="], "@rollup/rollup-freebsd-x64": ["@rollup/[email protected].9", "", { "os": "freebsd", "cpu": "x64" }, "sha512-SLl0hi2Ah2H7xQYd6Qaiu01kFPzQ+hqvdYSoOtHYg/zCIFs6t8sV95kaoqjzjFwuYQLtOI0RZre/Ke0nPaQV+g=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/[email protected].8", "", { "os": "linux", "cpu": "arm" }, "sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g=="], "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/[email protected].9", "", { "os": "linux", "cpu": "arm" }, "sha512-88I+D3TeKItrw+Y/2ud4Tw0+3CxQ2kLgu3QvrogZ0OfkmX/DEppehus7L3TS2Q4lpB+hYyxhkQiYPJ6Mf5/dPg=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/[email protected].8", "", { "os": "linux", "cpu": "arm" }, "sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA=="], "@rollup/rollup-linux-arm-musleabihf": ["@rollup/[email protected].9", "", { "os": "linux", "cpu": "arm" }, "sha512-3qyfWljSFHi9zH0KgtEPG4cBXHDFhwD8kwg6xLfHQ0IWuH9crp005GfoUUh/6w9/FWGBwEHg3lxK1iHRN1MFlA=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/[email protected].8", "", { "os": "linux", "cpu": "arm64" }, "sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A=="], "@rollup/rollup-linux-arm64-gnu": ["@rollup/[email protected].9", "", { "os": "linux", "cpu": "arm64" }, "sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/[email protected].8", "", { "os": "linux", "cpu": "arm64" }, "sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q=="], "@rollup/rollup-linux-arm64-musl": ["@rollup/[email protected].9", "", { "os": "linux", "cpu": "arm64" }, "sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A=="],
"@rollup/rollup-linux-loongarch64-gnu": ["@rollup/[email protected].8", "", { "os": "linux", "cpu": "none" }, "sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ=="], "@rollup/rollup-linux-loongarch64-gnu": ["@rollup/[email protected].9", "", { "os": "linux", "cpu": "none" }, "sha512-dRAgTfDsn0TE0HI6cmo13hemKpVHOEyeciGtvlBTkpx/F65kTvShtY/EVyZEIfxFkV5JJTuQ9tP5HGBS0hfxIg=="],
"@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/[email protected].8", "", { "os": "linux", "cpu": "ppc64" }, "sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw=="], "@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/[email protected].9", "", { "os": "linux", "cpu": "ppc64" }, "sha512-PHcNOAEhkoMSQtMf+rJofwisZqaU8iQ8EaSps58f5HYll9EAY5BSErCZ8qBDMVbq88h4UxaNPlbrKqfWP8RfJA=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/[email protected].8", "", { "os": "linux", "cpu": "none" }, "sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw=="], "@rollup/rollup-linux-riscv64-gnu": ["@rollup/[email protected].9", "", { "os": "linux", "cpu": "none" }, "sha512-Z2i0Uy5G96KBYKjeQFKbbsB54xFOL5/y1P5wNBsbXB8yE+At3oh0DVMjQVzCJRJSfReiB2tX8T6HUFZ2k8iaKg=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/[email protected].8", "", { "os": "linux", "cpu": "s390x" }, "sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA=="], "@rollup/rollup-linux-s390x-gnu": ["@rollup/[email protected].9", "", { "os": "linux", "cpu": "s390x" }, "sha512-U+5SwTMoeYXoDzJX5dhDTxRltSrIax8KWwfaaYcynuJw8mT33W7oOgz0a+AaXtGuvhzTr2tVKh5UO8GVANTxyQ=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/[email protected].8", "", { "os": "linux", "cpu": "x64" }, "sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA=="], "@rollup/rollup-linux-x64-gnu": ["@rollup/[email protected].9", "", { "os": "linux", "cpu": "x64" }, "sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/[email protected].8", "", { "os": "linux", "cpu": "x64" }, "sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ=="], "@rollup/rollup-linux-x64-musl": ["@rollup/[email protected].9", "", { "os": "linux", "cpu": "x64" }, "sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/[email protected].8", "", { "os": "win32", "cpu": "arm64" }, "sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ=="], "@rollup/rollup-win32-arm64-msvc": ["@rollup/[email protected].9", "", { "os": "win32", "cpu": "arm64" }, "sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/[email protected].8", "", { "os": "win32", "cpu": "ia32" }, "sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w=="], "@rollup/rollup-win32-ia32-msvc": ["@rollup/[email protected].9", "", { "os": "win32", "cpu": "ia32" }, "sha512-KB48mPtaoHy1AwDNkAJfHXvHp24H0ryZog28spEs0V48l3H1fr4i37tiyHsgKZJnCmvxsbATdZGBpbmxTE3a9w=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/[email protected].8", "", { "os": "win32", "cpu": "x64" }, "sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g=="], "@rollup/rollup-win32-x64-msvc": ["@rollup/[email protected].9", "", { "os": "win32", "cpu": "x64" }, "sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw=="],
"@surma/rollup-plugin-off-main-thread": ["@surma/[email protected]", "", { "dependencies": { "ejs": "^3.1.6", "json5": "^2.2.0", "magic-string": "^0.25.0", "string.prototype.matchall": "^4.0.6" } }, "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ=="], "@surma/rollup-plugin-off-main-thread": ["@surma/[email protected]", "", { "dependencies": { "ejs": "^3.1.6", "json5": "^2.2.0", "magic-string": "^0.25.0", "string.prototype.matchall": "^4.0.6" } }, "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ=="],
@ -834,7 +813,7 @@
"@types/mapbox__vector-tile": ["@types/[email protected]", "", { "dependencies": { "@types/geojson": "*", "@types/mapbox__point-geometry": "*", "@types/pbf": "*" } }, "sha512-bpd8dRn9pr6xKvuEBQup8pwQfD4VUyqO/2deGjfpe6AwC8YRlyEipvefyRJUSiCJTZuCb8Pl1ciVV5ekqJ96Bg=="], "@types/mapbox__vector-tile": ["@types/[email protected]", "", { "dependencies": { "@types/geojson": "*", "@types/mapbox__point-geometry": "*", "@types/pbf": "*" } }, "sha512-bpd8dRn9pr6xKvuEBQup8pwQfD4VUyqO/2deGjfpe6AwC8YRlyEipvefyRJUSiCJTZuCb8Pl1ciVV5ekqJ96Bg=="],
"@types/node": ["@types/[email protected].7", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-oU2q+BsQldB9lYxHNp/5aZO+/Bs0Usa74Abo9mAKulz4ahQyXRHK6UVKYIN8KSC8HXwhWSi7b49JnX+txuac0w=="], "@types/node": ["@types/[email protected].9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
"@types/pbf": ["@types/[email protected]", "", {}, "sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA=="], "@types/pbf": ["@types/[email protected]", "", {}, "sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA=="],
@ -966,16 +945,14 @@
"call-bind-apply-helpers": ["[email protected]", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], "call-bind-apply-helpers": ["[email protected]", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
"call-bound": ["[email protected].3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="], "call-bound": ["[email protected].4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
"caniuse-lite": ["[email protected]0", "", {}, "sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ=="], "caniuse-lite": ["[email protected]1", "", {}, "sha512-faRs/AW3jA9nTwmJBSO1PQ6L/EOgsB5HMQQq4iCu5zhPgVVgO/pZRHlmatwijZKetFw8/Pr4q6dEN8sJuq8qTw=="],
"chai": ["[email protected]", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw=="], "chai": ["[email protected]", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw=="],
"chalk": ["[email protected]", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "chalk": ["[email protected]", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"cheap-ruler": ["[email protected]", "", {}, "sha512-0BJa8f4t141BYKQyn9NSQt1PguFQXMXwZiA5shfoaBYHAb2fFk2RAX+tiWMoQU+Agtzt3mdt0JtuyshAXqZ+Vw=="],
"check-error": ["[email protected]", "", {}, "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw=="], "check-error": ["[email protected]", "", {}, "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw=="],
"chownr": ["[email protected]", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], "chownr": ["[email protected]", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
@ -1008,7 +985,7 @@
"convert-source-map": ["[email protected]", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], "convert-source-map": ["[email protected]", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
"core-js-compat": ["[email protected]0.0", "", { "dependencies": { "browserslist": "^4.24.3" } }, "sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ=="], "core-js-compat": ["[email protected]1.0", "", { "dependencies": { "browserslist": "^4.24.4" } }, "sha512-RFsU9LySVue9RTwdDVX/T0e2Y6jRYWXERKElIjpuEOEnxaXffI0X7RUwVzfYLfzuLXSNJDYoRYUAmRUcyln20A=="],
"core-util-is": ["[email protected]", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], "core-util-is": ["[email protected]", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
@ -1028,8 +1005,6 @@
"crypto-random-string": ["[email protected]", "", { "dependencies": { "type-fest": "^2.12.2" } }, "sha512-KWjTXWwxFd6a94m5CdRGW/t82Tr8DoBc9dNnPCAbFI1EBweN6v1tv8y4Y1m7ndkp/nkIBRxUxAzpaBnR2k3bcQ=="], "crypto-random-string": ["[email protected]", "", { "dependencies": { "type-fest": "^2.12.2" } }, "sha512-KWjTXWwxFd6a94m5CdRGW/t82Tr8DoBc9dNnPCAbFI1EBweN6v1tv8y4Y1m7ndkp/nkIBRxUxAzpaBnR2k3bcQ=="],
"csscolorparser": ["[email protected]", "", {}, "sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w=="],
"csstype": ["[email protected]", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], "csstype": ["[email protected]", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"d3-array": ["[email protected]", "", {}, "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw=="], "d3-array": ["[email protected]", "", {}, "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw=="],
@ -1080,7 +1055,7 @@
"ejs": ["[email protected]", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="], "ejs": ["[email protected]", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="],
"electron-to-chromium": ["[email protected]02", "", {}, "sha512-eHhqaja8tE/FNpIiBrvBjFV/SSKpyWHLvxuR9dPTdo+3V9ppdLmFB7ZZQ98qNovcngPLYIz0oOBF9P0FfZef5Q=="], "electron-to-chromium": ["[email protected]11", "", {}, "sha512-vJyJlO95wQRAw6K2ZGF/8nol7AcbCOnp8S6H91mwOOBbXoS9seDBYxCTPYAFsvXLxl3lc0jLXXe9GLxC4nXVog=="],
"elliptic": ["[email protected]", "", { "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", "hash.js": "^1.0.0", "hmac-drbg": "^1.0.1", "inherits": "^2.0.4", "minimalistic-assert": "^1.0.1", "minimalistic-crypto-utils": "^1.0.1" } }, "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g=="], "elliptic": ["[email protected]", "", { "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", "hash.js": "^1.0.0", "hmac-drbg": "^1.0.1", "inherits": "^2.0.4", "minimalistic-assert": "^1.0.1", "minimalistic-crypto-utils": "^1.0.1" } }, "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g=="],
@ -1116,7 +1091,7 @@
"evp_bytestokey": ["[email protected]", "", { "dependencies": { "md5.js": "^1.3.4", "safe-buffer": "^5.1.1" } }, "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA=="], "evp_bytestokey": ["[email protected]", "", { "dependencies": { "md5.js": "^1.3.4", "safe-buffer": "^5.1.1" } }, "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA=="],
"expect-type": ["expect-type@1.1.0", "", {}, "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA=="], "expect-type": ["expect-type@1.2.0", "", {}, "sha512-80F22aiJ3GLyVnS/B3HzgR6RelZVumzj9jkL0Rhz4h0xYbNW9PjlQz5h3J/SShErbXBc295vseR4/MIbVmUbeA=="],
"extend-shallow": ["[email protected]", "", { "dependencies": { "is-extendable": "^0.1.0" } }, "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug=="], "extend-shallow": ["[email protected]", "", { "dependencies": { "is-extendable": "^0.1.0" } }, "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug=="],
@ -1134,7 +1109,7 @@
"for-each": ["[email protected]", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], "for-each": ["[email protected]", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
"foreground-child": ["[email protected].0", "", { "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" } }, "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg=="], "foreground-child": ["[email protected].1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
"fraction.js": ["[email protected]", "", {}, "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew=="], "fraction.js": ["[email protected]", "", {}, "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew=="],
@ -1158,7 +1133,7 @@
"geojson-vt": ["[email protected]", "", {}, "sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A=="], "geojson-vt": ["[email protected]", "", {}, "sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A=="],
"get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="], "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
"get-nonce": ["[email protected]", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="], "get-nonce": ["[email protected]", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="],
@ -1188,11 +1163,9 @@
"graceful-fs": ["[email protected]", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], "graceful-fs": ["[email protected]", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"grid-index": ["[email protected]", "", {}, "sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA=="],
"gzipper": ["[email protected]", "", { "dependencies": { "@gfx/zopfli": "^1.0.15", "commander": "^12.1.0", "simple-zstd": "^1.4.2" }, "bin": { "gzipper": "bin/index.js" } }, "sha512-JUvhzo8dHQWJp1eyYy1ShaPfcowsPbRc2rvwkD4LRyou/80UUz96bn+EOOYLWO4PG0Y5f3+UlUX9Gmu8RZhrtw=="], "gzipper": ["[email protected]", "", { "dependencies": { "@gfx/zopfli": "^1.0.15", "commander": "^12.1.0", "simple-zstd": "^1.4.2" }, "bin": { "gzipper": "bin/index.js" } }, "sha512-JUvhzo8dHQWJp1eyYy1ShaPfcowsPbRc2rvwkD4LRyou/80UUz96bn+EOOYLWO4PG0Y5f3+UlUX9Gmu8RZhrtw=="],
"happy-dom": ["[email protected].8", "", { "dependencies": { "webidl-conversions": "^7.0.0", "whatwg-mimetype": "^3.0.0" } }, "sha512-Yxbq/FG79z1rhAf/iB6YM8wO2JB/JDQBy99RiLSs+2siEAi5J05x9eW1nnASHZJbpldjJE2KuFLsLZ+AzX/IxA=="], "happy-dom": ["[email protected].9", "", { "dependencies": { "webidl-conversions": "^7.0.0", "whatwg-mimetype": "^3.0.0" } }, "sha512-HL26ajjMVe/wr3xlzjF0sCPCiAKaZJcIRFZHmG4yKHRJp4YAkHPG5X6GfWxCeDTpOmuHhNiOyNKUoZjjnm0tjw=="],
"has-bigints": ["[email protected]", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="], "has-bigints": ["[email protected]", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="],
@ -1334,7 +1307,7 @@
"leven": ["[email protected]", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="], "leven": ["[email protected]", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="],
"libphonenumber-js": ["[email protected]1.20", "", {}, "sha512-/ipwAMvtSZRdiQBHqW1qxqeYiBMzncOQLVA+62MWYr7N4m7Q2jqpJ0WgT7zlOEOpyLRSqrMXidbJpC0J77AaKA=="], "libphonenumber-js": ["[email protected]2.4", "", {}, "sha512-vLmhg7Gan7idyAKfc6pvCtNzvar4/eIzrVVk3hjNFH5+fGqyjD0gQRovdTrDl20wsmZhBtmZpcsR0tOfquwb8g=="],
"lightningcss": ["[email protected]", "", { "dependencies": { "detect-libc": "^1.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.29.1", "lightningcss-darwin-x64": "1.29.1", "lightningcss-freebsd-x64": "1.29.1", "lightningcss-linux-arm-gnueabihf": "1.29.1", "lightningcss-linux-arm64-gnu": "1.29.1", "lightningcss-linux-arm64-musl": "1.29.1", "lightningcss-linux-x64-gnu": "1.29.1", "lightningcss-linux-x64-musl": "1.29.1", "lightningcss-win32-arm64-msvc": "1.29.1", "lightningcss-win32-x64-msvc": "1.29.1" } }, "sha512-FmGoeD4S05ewj+AkhTY+D+myDvXI6eL27FjHIjoyUkO/uw7WZD1fBVs0QxeYWa7E17CUHJaYX/RUGISCtcrG4Q=="], "lightningcss": ["[email protected]", "", { "dependencies": { "detect-libc": "^1.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.29.1", "lightningcss-darwin-x64": "1.29.1", "lightningcss-freebsd-x64": "1.29.1", "lightningcss-linux-arm-gnueabihf": "1.29.1", "lightningcss-linux-arm64-gnu": "1.29.1", "lightningcss-linux-arm64-musl": "1.29.1", "lightningcss-linux-x64-gnu": "1.29.1", "lightningcss-linux-x64-musl": "1.29.1", "lightningcss-win32-arm64-msvc": "1.29.1", "lightningcss-win32-x64-msvc": "1.29.1" } }, "sha512-FmGoeD4S05ewj+AkhTY+D+myDvXI6eL27FjHIjoyUkO/uw7WZD1fBVs0QxeYWa7E17CUHJaYX/RUGISCtcrG4Q=="],
@ -1378,8 +1351,6 @@
"magic-string": ["[email protected]", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], "magic-string": ["[email protected]", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
"mapbox-gl": ["[email protected]", "", { "dependencies": { "@mapbox/jsonlint-lines-primitives": "^2.0.2", "@mapbox/mapbox-gl-supported": "^3.0.0", "@mapbox/point-geometry": "^0.1.0", "@mapbox/tiny-sdf": "^2.0.6", "@mapbox/unitbezier": "^0.0.1", "@mapbox/vector-tile": "^1.3.1", "@mapbox/whoots-js": "^3.1.0", "@types/geojson": "^7946.0.16", "@types/geojson-vt": "^3.2.5", "@types/mapbox__point-geometry": "^0.1.4", "@types/mapbox__vector-tile": "^1.3.4", "@types/pbf": "^3.0.5", "@types/supercluster": "^7.1.3", "cheap-ruler": "^4.0.0", "csscolorparser": "~1.0.3", "earcut": "^3.0.0", "geojson-vt": "^4.0.2", "gl-matrix": "^3.4.3", "grid-index": "^1.1.0", "kdbush": "^4.0.2", "murmurhash-js": "^1.0.0", "pbf": "^3.2.1", "potpack": "^2.0.0", "quickselect": "^3.0.0", "serialize-to-js": "^3.1.2", "supercluster": "^8.0.1", "tinyqueue": "^3.0.0", "vt-pbf": "^3.1.3" } }, "sha512-YnQxjlthuv/tidcxGYU2C8nRDVXMlAHa3qFhuOJeX4AfRP72OMRBf9ApL+M+k5VWcAXi2fcNOUVgphknjLumjA=="],
"maplibre-gl": ["[email protected]", "", { "dependencies": { "@mapbox/geojson-rewind": "^0.5.2", "@mapbox/jsonlint-lines-primitives": "^2.0.2", "@mapbox/point-geometry": "^0.1.0", "@mapbox/tiny-sdf": "^2.0.6", "@mapbox/unitbezier": "^0.0.1", "@mapbox/vector-tile": "^1.3.1", "@mapbox/whoots-js": "^3.1.0", "@maplibre/maplibre-gl-style-spec": "^23.1.0", "@types/geojson": "^7946.0.16", "@types/geojson-vt": "3.2.5", "@types/mapbox__point-geometry": "^0.1.4", "@types/mapbox__vector-tile": "^1.3.4", "@types/pbf": "^3.0.5", "@types/supercluster": "^7.1.3", "earcut": "^3.0.1", "geojson-vt": "^4.0.2", "gl-matrix": "^3.4.3", "global-prefix": "^4.0.0", "kdbush": "^4.0.2", "murmurhash-js": "^1.0.0", "pbf": "^3.3.0", "potpack": "^2.0.0", "quickselect": "^3.0.0", "supercluster": "^8.0.1", "tinyqueue": "^3.0.0", "vt-pbf": "^3.1.3" } }, "sha512-0Z6ODzyFu/grwT6K1eIBpv6MZE4xnJD1AV+Yq1hPzOh/YCY36r9BlSaU7d7n2/HJOaoKOy0b2YF8cS4dD+iEVQ=="], "maplibre-gl": ["[email protected]", "", { "dependencies": { "@mapbox/geojson-rewind": "^0.5.2", "@mapbox/jsonlint-lines-primitives": "^2.0.2", "@mapbox/point-geometry": "^0.1.0", "@mapbox/tiny-sdf": "^2.0.6", "@mapbox/unitbezier": "^0.0.1", "@mapbox/vector-tile": "^1.3.1", "@mapbox/whoots-js": "^3.1.0", "@maplibre/maplibre-gl-style-spec": "^23.1.0", "@types/geojson": "^7946.0.16", "@types/geojson-vt": "3.2.5", "@types/mapbox__point-geometry": "^0.1.4", "@types/mapbox__vector-tile": "^1.3.4", "@types/pbf": "^3.0.5", "@types/supercluster": "^7.1.3", "earcut": "^3.0.1", "geojson-vt": "^4.0.2", "gl-matrix": "^3.4.3", "global-prefix": "^4.0.0", "kdbush": "^4.0.2", "murmurhash-js": "^1.0.0", "pbf": "^3.3.0", "potpack": "^2.0.0", "quickselect": "^3.0.0", "supercluster": "^8.0.1", "tinyqueue": "^3.0.0", "vt-pbf": "^3.1.3" } }, "sha512-0Z6ODzyFu/grwT6K1eIBpv6MZE4xnJD1AV+Yq1hPzOh/YCY36r9BlSaU7d7n2/HJOaoKOy0b2YF8cS4dD+iEVQ=="],
"marchingsquares": ["[email protected]", "", {}, "sha512-gz6nNQoVK7Lkh2pZulrT4qd4347S/toG9RXH2pyzhLgkL5mLkBoqgv4EvAGXcV0ikDW72n/OQb3Xe8bGagQZCg=="], "marchingsquares": ["[email protected]", "", {}, "sha512-gz6nNQoVK7Lkh2pZulrT4qd4347S/toG9RXH2pyzhLgkL5mLkBoqgv4EvAGXcV0ikDW72n/OQb3Xe8bGagQZCg=="],
@ -1488,7 +1459,7 @@
"potpack": ["[email protected]", "", {}, "sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw=="], "potpack": ["[email protected]", "", {}, "sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw=="],
"preact": ["[email protected].2", "", {}, "sha512-0gNmv4qpS9HaN3+40CLBAnKe0ZfyE4ZWo5xKlC1rVrr0ckkEvJvAQqKaHANdFKsGstoxrY4AItZ7kZSGVoVjgg=="], "preact": ["[email protected].4", "", {}, "sha512-KJhO7LBFTjP71d83trW+Ilnjbo+ySsaAgCfXOXUlmGzJ4ygYPWmysm77yg4emwfmoz3b22yvH5IsVFHbhUaH5w=="],
"pretty-bytes": ["[email protected]", "", {}, "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ=="], "pretty-bytes": ["[email protected]", "", {}, "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ=="],
@ -1582,7 +1553,7 @@
"robust-predicates": ["[email protected]", "", {}, "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="], "robust-predicates": ["[email protected]", "", {}, "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="],
"rollup": ["[email protected].8", "", { "dependencies": { "@types/estree": "1.0.6" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.34.8", "@rollup/rollup-android-arm64": "4.34.8", "@rollup/rollup-darwin-arm64": "4.34.8", "@rollup/rollup-darwin-x64": "4.34.8", "@rollup/rollup-freebsd-arm64": "4.34.8", "@rollup/rollup-freebsd-x64": "4.34.8", "@rollup/rollup-linux-arm-gnueabihf": "4.34.8", "@rollup/rollup-linux-arm-musleabihf": "4.34.8", "@rollup/rollup-linux-arm64-gnu": "4.34.8", "@rollup/rollup-linux-arm64-musl": "4.34.8", "@rollup/rollup-linux-loongarch64-gnu": "4.34.8", "@rollup/rollup-linux-powerpc64le-gnu": "4.34.8", "@rollup/rollup-linux-riscv64-gnu": "4.34.8", "@rollup/rollup-linux-s390x-gnu": "4.34.8", "@rollup/rollup-linux-x64-gnu": "4.34.8", "@rollup/rollup-linux-x64-musl": "4.34.8", "@rollup/rollup-win32-arm64-msvc": "4.34.8", "@rollup/rollup-win32-ia32-msvc": "4.34.8", "@rollup/rollup-win32-x64-msvc": "4.34.8", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ=="], "rollup": ["[email protected].9", "", { "dependencies": { "@types/estree": "1.0.6" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.34.9", "@rollup/rollup-android-arm64": "4.34.9", "@rollup/rollup-darwin-arm64": "4.34.9", "@rollup/rollup-darwin-x64": "4.34.9", "@rollup/rollup-freebsd-arm64": "4.34.9", "@rollup/rollup-freebsd-x64": "4.34.9", "@rollup/rollup-linux-arm-gnueabihf": "4.34.9", "@rollup/rollup-linux-arm-musleabihf": "4.34.9", "@rollup/rollup-linux-arm64-gnu": "4.34.9", "@rollup/rollup-linux-arm64-musl": "4.34.9", "@rollup/rollup-linux-loongarch64-gnu": "4.34.9", "@rollup/rollup-linux-powerpc64le-gnu": "4.34.9", "@rollup/rollup-linux-riscv64-gnu": "4.34.9", "@rollup/rollup-linux-s390x-gnu": "4.34.9", "@rollup/rollup-linux-x64-gnu": "4.34.9", "@rollup/rollup-linux-x64-musl": "4.34.9", "@rollup/rollup-win32-arm64-msvc": "4.34.9", "@rollup/rollup-win32-ia32-msvc": "4.34.9", "@rollup/rollup-win32-x64-msvc": "4.34.9", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-nF5XYqWWp9hx/LrpC8sZvvvmq0TeTjQgaZHYmAgwysT9nh8sWnZhBnM8ZyVbbJFIQBLwHDNoMqsBZBbUo4U8sQ=="],
"rw": ["[email protected]", "", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="], "rw": ["[email protected]", "", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="],
@ -1600,8 +1571,6 @@
"serialize-javascript": ["[email protected]", "", { "dependencies": { "randombytes": "^2.1.0" } }, "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g=="], "serialize-javascript": ["[email protected]", "", { "dependencies": { "randombytes": "^2.1.0" } }, "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g=="],
"serialize-to-js": ["[email protected]", "", {}, "sha512-owllqNuDDEimQat7EPG0tH7JjO090xKNzUtYz6X+Sk2BXDnOCilDdNLwjWeFywG9xkJul1ULvtUQa9O4pUaY0w=="],
"set-function-length": ["[email protected]", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], "set-function-length": ["[email protected]", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
"set-function-name": ["[email protected]", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="], "set-function-name": ["[email protected]", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="],
@ -1660,7 +1629,7 @@
"stackback": ["[email protected]", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], "stackback": ["[email protected]", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
"std-env": ["[email protected].0", "", {}, "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w=="], "std-env": ["[email protected].1", "", {}, "sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA=="],
"ste-core": ["[email protected]", "", {}, "sha512-ivkRENMh0mdGoPlZ4xVcEaC8rXQfTEfvonRw5m8VDKV7kgcbZbaNd1TnKl08wXbcLdT7okSc63HNP8cVhy95zg=="], "ste-core": ["[email protected]", "", {}, "sha512-ivkRENMh0mdGoPlZ4xVcEaC8rXQfTEfvonRw5m8VDKV7kgcbZbaNd1TnKl08wXbcLdT7okSc63HNP8cVhy95zg=="],
@ -1788,7 +1757,7 @@
"upath": ["[email protected]", "", {}, "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg=="], "upath": ["[email protected]", "", {}, "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg=="],
"update-browserslist-db": ["[email protected].2", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg=="], "update-browserslist-db": ["[email protected].3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw=="],
"url": ["[email protected]", "", { "dependencies": { "punycode": "^1.4.1", "qs": "^6.12.3" } }, "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg=="], "url": ["[email protected]", "", { "dependencies": { "punycode": "^1.4.1", "qs": "^6.12.3" } }, "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg=="],
@ -1884,8 +1853,6 @@
"zustand": ["[email protected]", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg=="], "zustand": ["[email protected]", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg=="],
"@meshtastic/transport-web-serial/@jsr/meshtastic__core": ["@jsr/[email protected]", "https://npm.jsr.io/~/11/@jsr/meshtastic__core/2.6.0-0.tgz", { "dependencies": { "@bufbuild/protobuf": "^2.2.3", "@jsr/meshtastic__protobufs": "^2.6.0", "crc": "^4.3.2", "ste-simple-events": "^3.0.11", "tslog": "^4.9.3" } }, "sha512-Ks71sRagbBipotznULpsJZ1EMcQIqCEJQx6mf628dmCNVf2YECi2zi/i/5zErp1hGPgfbDvCz9oPogvsd/7fMA=="],
"@rollup/plugin-babel/@rollup/pluginutils": ["@rollup/[email protected]", "", { "dependencies": { "@types/estree": "0.0.39", "estree-walker": "^1.0.1", "picomatch": "^2.2.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0" } }, "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg=="], "@rollup/plugin-babel/@rollup/pluginutils": ["@rollup/[email protected]", "", { "dependencies": { "@types/estree": "0.0.39", "estree-walker": "^1.0.1", "picomatch": "^2.2.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0" } }, "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg=="],
"@rollup/plugin-babel/rollup": ["[email protected]", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ=="], "@rollup/plugin-babel/rollup": ["[email protected]", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ=="],
@ -1944,7 +1911,7 @@
"rbush/quickselect": ["[email protected]", "", {}, "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw=="], "rbush/quickselect": ["[email protected]", "", {}, "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw=="],
"react-scan/@types/node": ["@types/[email protected].19", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-LEwC7o1ifqg/6r2gn9Dns0f1rhK+fPFDoMiceTJ6kWmVk6bgXBI/9IOWfVan4WiAavK9pIVWdX0/e3J+eEUh5A=="], "react-scan/@types/node": ["@types/[email protected].23", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-8PCGZ1ZJbEZuYNTMqywO+Sj4vSKjSjT6Ua+6RFOYlEvIvKQABPtrNkoVSLSKDb4obYcMhspVKmsw8Cm10NFRUg=="],
"react-scan/esbuild": ["[email protected]", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.24.2", "@esbuild/android-arm": "0.24.2", "@esbuild/android-arm64": "0.24.2", "@esbuild/android-x64": "0.24.2", "@esbuild/darwin-arm64": "0.24.2", "@esbuild/darwin-x64": "0.24.2", "@esbuild/freebsd-arm64": "0.24.2", "@esbuild/freebsd-x64": "0.24.2", "@esbuild/linux-arm": "0.24.2", "@esbuild/linux-arm64": "0.24.2", "@esbuild/linux-ia32": "0.24.2", "@esbuild/linux-loong64": "0.24.2", "@esbuild/linux-mips64el": "0.24.2", "@esbuild/linux-ppc64": "0.24.2", "@esbuild/linux-riscv64": "0.24.2", "@esbuild/linux-s390x": "0.24.2", "@esbuild/linux-x64": "0.24.2", "@esbuild/netbsd-arm64": "0.24.2", "@esbuild/netbsd-x64": "0.24.2", "@esbuild/openbsd-arm64": "0.24.2", "@esbuild/openbsd-x64": "0.24.2", "@esbuild/sunos-x64": "0.24.2", "@esbuild/win32-arm64": "0.24.2", "@esbuild/win32-ia32": "0.24.2", "@esbuild/win32-x64": "0.24.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA=="], "react-scan/esbuild": ["[email protected]", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.24.2", "@esbuild/android-arm": "0.24.2", "@esbuild/android-arm64": "0.24.2", "@esbuild/android-x64": "0.24.2", "@esbuild/darwin-arm64": "0.24.2", "@esbuild/darwin-x64": "0.24.2", "@esbuild/freebsd-arm64": "0.24.2", "@esbuild/freebsd-x64": "0.24.2", "@esbuild/linux-arm": "0.24.2", "@esbuild/linux-arm64": "0.24.2", "@esbuild/linux-ia32": "0.24.2", "@esbuild/linux-loong64": "0.24.2", "@esbuild/linux-mips64el": "0.24.2", "@esbuild/linux-ppc64": "0.24.2", "@esbuild/linux-riscv64": "0.24.2", "@esbuild/linux-s390x": "0.24.2", "@esbuild/linux-x64": "0.24.2", "@esbuild/netbsd-arm64": "0.24.2", "@esbuild/netbsd-x64": "0.24.2", "@esbuild/openbsd-arm64": "0.24.2", "@esbuild/openbsd-x64": "0.24.2", "@esbuild/sunos-x64": "0.24.2", "@esbuild/win32-arm64": "0.24.2", "@esbuild/win32-ia32": "0.24.2", "@esbuild/win32-x64": "0.24.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA=="],

34
deno.json

@ -0,0 +1,34 @@
{
"imports": {
"@app/": "./src/",
"@pages/": "./src/pages/",
"@components/": "./src/components/",
"@core/": "./src/core/",
"@layouts/": "./src/layouts/"
},
"compilerOptions": {
"lib": [
"DOM",
"DOM.Iterable",
"ESNext",
"deno.window",
"deno.ns"
],
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"strictNullChecks": true,
"types": [
"vite/client",
"node",
"@types/web-bluetooth",
"@types/w3c-web-serial"
],
"strictPropertyInitialization": false
},
"unstable": [
"sloppy-imports"
]
}

6804
deno.lock

File diff suppressed because it is too large

42
package.json

@ -6,27 +6,17 @@
"license": "GPL-3.0-only", "license": "GPL-3.0-only",
"scripts": { "scripts": {
"build": "vite build", "build": "vite build",
"build:analyze": "BUNDLE_ANALYZE=true vite build", "build:analyze": "BUNDLE_ANALYZE=true deno task build",
"check": "biome check src/", "lint": "deno lint src/",
"check:fix": "pnpm check --write src/", "lint:fix": "deno lint --fix src/",
"format": "biome format --write src/", "format": "deno fmt src/",
"dev": "vite dev --open", "dev": "deno task dev:ui",
"dev:scan": "VITE_DEBUG_SCAN=true vite dev", "dev:ui": "deno run -A npm:vite dev",
"test": "vitest", "dev:scan": "VITE_DEBUG_SCAN=true deno task dev:ui",
"test:ui": "vitest --ui", "test": "deno run -A npm:vitest",
"test:run": "vitest run", "test:ui": "deno task test --ui",
"preview": "vite preview", "preview": "deno run -A npm:vite preview",
"package": "gzipper c -i html,js,css,png,ico,svg,webmanifest,txt dist dist/output && tar -cvf dist/build.tar -C ./dist/output/ $(ls ./dist/output/)", "package": "gzipper c -i html,js,css,png,ico,svg,webmanifest,txt dist dist/output && tar -cvf dist/build.tar -C ./dist/output/ ."
"postinstall": "npx simple-git-hooks"
},
"simple-git-hooks": {
"pre-commit": "bun run check:fix && bun run format"
},
"lint-staged": {
"*.{ts,tsx}": [
"bun run check:fix",
"bun run format"
]
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -35,6 +25,15 @@
"bugs": { "bugs": {
"url": "https://github.com/meshtastic/web/issues" "url": "https://github.com/meshtastic/web/issues"
}, },
"simple-git-hooks": {
"pre-commit": "deno task lint:fix && deno task format"
},
"lint-staged": {
"*.{ts,tsx}": [
"deno task lint:fix",
"deno task format"
]
},
"homepage": "https://meshtastic.org", "homepage": "https://meshtastic.org",
"dependencies": { "dependencies": {
"@bufbuild/protobuf": "^2.2.3", "@bufbuild/protobuf": "^2.2.3",
@ -80,7 +79,6 @@
"zustand": "5.0.3" "zustand": "5.0.3"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^1.9.4",
"@tailwindcss/postcss": "^4.0.9", "@tailwindcss/postcss": "^4.0.9",
"@testing-library/react": "^16.2.0", "@testing-library/react": "^16.2.0",
"@types/chrome": "^0.0.307", "@types/chrome": "^0.0.307",

7
src/App.tsx

@ -1,11 +1,10 @@
import { DeviceWrapper } from "@app/DeviceWrapper.tsx"; import { DeviceWrapper } from "@app/DeviceWrapper.tsx";
import { PageRouter } from "@app/PageRouter.tsx"; import { PageRouter } from "@app/PageRouter.tsx";
import { ThemeProvider } from "@app/components/generic/ThemeProvider";
import { CommandPalette } from "@components/CommandPalette.tsx"; import { CommandPalette } from "@components/CommandPalette.tsx";
import { DeviceSelector } from "@components/DeviceSelector.tsx"; import { DeviceSelector } from "@components/DeviceSelector.tsx";
import { DialogManager } from "@components/Dialog/DialogManager"; import { DialogManager } from "@components/Dialog/DialogManager.tsx";
import { NewDeviceDialog } from "@components/Dialog/NewDeviceDialog.tsx"; import { NewDeviceDialog } from "@components/Dialog/NewDeviceDialog.tsx";
import { KeyBackupReminder } from "@components/KeyBackupReminder"; import { KeyBackupReminder } from "@components/KeyBackupReminder.tsx";
import { Toaster } from "@components/Toaster.tsx"; import { Toaster } from "@components/Toaster.tsx";
import Footer from "@components/UI/Footer.tsx"; import Footer from "@components/UI/Footer.tsx";
import { useAppStore } from "@core/stores/appStore.ts"; import { useAppStore } from "@core/stores/appStore.ts";
@ -13,7 +12,7 @@ import { useDeviceStore } from "@core/stores/deviceStore.ts";
import { Dashboard } from "@pages/Dashboard/index.tsx"; import { Dashboard } from "@pages/Dashboard/index.tsx";
import type { JSX } from "react"; import type { JSX } from "react";
import { ErrorBoundary } from "react-error-boundary"; import { ErrorBoundary } from "react-error-boundary";
import { ErrorPage } from "./components/UI/ErrorPage"; import { ErrorPage } from "@components/UI/ErrorPage.tsx";
import { MapProvider } from "react-map-gl/maplibre"; import { MapProvider } from "react-map-gl/maplibre";

2
src/PageRouter.tsx

@ -5,7 +5,7 @@ import ConfigPage from "@pages/Config/index.tsx";
import MessagesPage from "@pages/Messages.tsx"; import MessagesPage from "@pages/Messages.tsx";
import NodesPage from "@pages/Nodes.tsx"; import NodesPage from "@pages/Nodes.tsx";
import { ErrorBoundary } from "react-error-boundary"; import { ErrorBoundary } from "react-error-boundary";
import { ErrorPage } from "./components/UI/ErrorPage"; import { ErrorPage } from "@components/UI/ErrorPage.tsx";
export const ErrorBoundaryWrapper = ({ export const ErrorBoundaryWrapper = ({
children, children,

14
src/components/CommandPalette.tsx

@ -1,4 +1,4 @@
import { Avatar } from "@components/UI/Avatar"; import { Avatar } from "./UI/Avatar.tsx";
import { import {
CommandDialog, CommandDialog,
CommandEmpty, CommandEmpty,
@ -117,13 +117,11 @@ export const CommandPalette = () => {
return { return {
label: label:
device.nodes.get(device.hardware.myNodeNum)?.user?.longName ?? device.nodes.get(device.hardware.myNodeNum)?.user?.longName ??
device.hardware.myNodeNum.toString(), device.hardware.myNodeNum.toString(),
icon: ( icon: (
<Avatar <Avatar
text={ text={device.nodes.get(device.hardware.myNodeNum)?.user
device.nodes.get(device.hardware.myNodeNum)?.user ?.shortName ?? device.hardware.myNodeNum.toString()}
?.shortName ?? device.hardware.myNodeNum.toString()
}
/> />
), ),
action() { action() {
@ -241,8 +239,8 @@ export const CommandPalette = () => {
} }
}; };
window.addEventListener("keydown", handleKeydown); globalThis.addEventListener("keydown", handleKeydown);
return () => window.removeEventListener("keydown", handleKeydown); return () => globalThis.removeEventListener("keydown", handleKeydown);
}, [setCommandPaletteOpen]); }, [setCommandPaletteOpen]);
return ( return (

21
src/components/DeviceSelector.tsx

@ -5,10 +5,9 @@ import { Code } from "@components/UI/Typography/Code.tsx";
import { useAppStore } from "@core/stores/appStore.ts"; import { useAppStore } from "@core/stores/appStore.ts";
import { useDeviceStore } from "@core/stores/deviceStore.ts"; import { useDeviceStore } from "@core/stores/deviceStore.ts";
import { HomeIcon, PlusIcon, SearchIcon } from "lucide-react"; import { HomeIcon, PlusIcon, SearchIcon } from "lucide-react";
import type { JSX } from "react"; import { Avatar } from "@components/UI/Avatar.tsx";
import { Avatar } from "./UI/Avatar";
export const DeviceSelector = (): JSX.Element => { export const DeviceSelector = () => {
const { getDevices } = useDeviceStore(); const { getDevices } = useDeviceStore();
const { const {
selectedDevice, selectedDevice,
@ -38,11 +37,9 @@ export const DeviceSelector = (): JSX.Element => {
active={selectedDevice === device.id} active={selectedDevice === device.id}
> >
<Avatar <Avatar
text={ text={device.nodes
device.nodes .get(device.hardware.myNodeNum)
.get(device.hardware.myNodeNum) ?.user?.shortName.toString() ?? "UNK"}
?.user?.shortName.toString() ?? "UNK"
}
/> />
</DeviceSelectorButton> </DeviceSelectorButton>
))} ))}
@ -66,11 +63,13 @@ export const DeviceSelector = (): JSX.Element => {
<SearchIcon /> <SearchIcon />
</button> </button>
{/* TODO: This is being commented out until its fixed */} {/* TODO: This is being commented out until its fixed */}
{/* <button type="button" className="transition-all hover:text-accent"> {
/* <button type="button" className="transition-all hover:text-accent">
<LanguagesIcon /> <LanguagesIcon />
</button> */} </button> */
}
<Separator /> <Separator />
<Code>{process.env.COMMIT_HASH}</Code> <Code>{import.meta.env.VITE_COMMIT_HASH}</Code>
</div> </div>
</nav> </nav>
); );

7
src/components/DeviceSelectorButton.tsx

@ -5,7 +5,6 @@ export interface DeviceSelectorButtonProps {
} }
export const DeviceSelectorButton = ({ export const DeviceSelectorButton = ({
active,
onClick, onClick,
children, children,
}: DeviceSelectorButtonProps) => ( }: DeviceSelectorButtonProps) => (
@ -14,9 +13,11 @@ export const DeviceSelectorButton = ({
onClick={onClick} onClick={onClick}
onKeyDown={onClick} onKeyDown={onClick}
> >
{/* {active && ( {
/* {active && (
<div className="absolute -left-2 h-10 w-1.5 rounded-full bg-accent" /> <div className="absolute -left-2 h-10 w-1.5 rounded-full bg-accent" />
)} */} )} */
}
<div className="flex aspect-square cursor-pointer flex-col items-center justify-center"> <div className="flex aspect-square cursor-pointer flex-col items-center justify-center">
{children} {children}
</div> </div>

2
src/components/Dialog/DeviceNameDialog.tsx

@ -1,4 +1,4 @@
import { useDevice } from "@app/core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { Button } from "@components/UI/Button.tsx"; import { Button } from "@components/UI/Button.tsx";
import { import {

10
src/components/Dialog/DialogManager.tsx

@ -1,15 +1,15 @@
import { RemoveNodeDialog } from "@app/components/Dialog/RemoveNodeDialog.tsx"; import { RemoveNodeDialog } from "@components/Dialog/RemoveNodeDialog.tsx";
import { DeviceNameDialog } from "@components/Dialog/DeviceNameDialog.tsx"; import { DeviceNameDialog } from "@components/Dialog/DeviceNameDialog.tsx";
import { ImportDialog } from "@components/Dialog/ImportDialog.tsx"; import { ImportDialog } from "@components/Dialog/ImportDialog.tsx";
import { PkiBackupDialog } from "@components/Dialog/PKIBackupDialog"; import { PkiBackupDialog } from "./PKIBackupDialog.tsx";
import { QRDialog } from "@components/Dialog/QRDialog.tsx"; import { QRDialog } from "@components/Dialog/QRDialog.tsx";
import { RebootDialog } from "@components/Dialog/RebootDialog.tsx"; import { RebootDialog } from "@components/Dialog/RebootDialog.tsx";
import { ShutdownDialog } from "@components/Dialog/ShutdownDialog.tsx"; import { ShutdownDialog } from "@components/Dialog/ShutdownDialog.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import type { JSX } from "react";
import { NodeDetailsDialog } from "./NodeDetailsDialog";
export const DialogManager = (): JSX.Element => { import { NodeDetailsDialog } from "./NodeDetailsDialog.tsx";
export const DialogManager = () => {
const { channels, config, dialog, setDialogOpen } = useDevice(); const { channels, config, dialog, setDialogOpen } = useDevice();
return ( return (
<> <>

27
src/components/Dialog/ImportDialog.tsx

@ -15,7 +15,7 @@ import { Switch } from "@components/UI/Switch.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
import { toByteArray } from "base64-js"; import { toByteArray } from "base64-js";
import { type JSX, useEffect, useState } from "react"; import { useEffect, useState } from "react";
export interface ImportDialogProps { export interface ImportDialogProps {
open: boolean; open: boolean;
@ -26,7 +26,7 @@ export interface ImportDialogProps {
export const ImportDialog = ({ export const ImportDialog = ({
open, open,
onOpenChange, onOpenChange,
}: ImportDialogProps): JSX.Element => { }: ImportDialogProps) => {
const [importDialogInput, setImportDialogInput] = useState<string>(""); const [importDialogInput, setImportDialogInput] = useState<string>("");
const [channelSet, setChannelSet] = useState<Protobuf.AppOnly.ChannelSet>(); const [channelSet, setChannelSet] = useState<Protobuf.AppOnly.ChannelSet>();
const [validUrl, setValidUrl] = useState<boolean>(false); const [validUrl, setValidUrl] = useState<boolean>(false);
@ -62,7 +62,7 @@ export const ImportDialog = ({
), ),
); );
setValidUrl(true); setValidUrl(true);
} catch (error) { } catch (_error) {
setValidUrl(false); setValidUrl(false);
setChannelSet(undefined); setChannelSet(undefined);
} }
@ -73,10 +73,9 @@ export const ImportDialog = ({
connection?.setChannel( connection?.setChannel(
create(Protobuf.Channel.ChannelSchema, { create(Protobuf.Channel.ChannelSchema, {
index, index,
role: role: index === 0
index === 0 ? Protobuf.Channel.Channel_Role.PRIMARY
? Protobuf.Channel.Channel_Role.PRIMARY : Protobuf.Channel.Channel_Role.SECONDARY,
: Protobuf.Channel.Channel_Role.SECONDARY,
settings: ch, settings: ch,
}), }),
); );
@ -119,25 +118,29 @@ export const ImportDialog = ({
<div className="w-36"> <div className="w-36">
<Label>Use Preset?</Label> <Label>Use Preset?</Label>
<Switch <Switch
disabled={true} disabled
checked={channelSet?.loraConfig?.usePreset ?? true} checked={channelSet?.loraConfig?.usePreset ?? true}
/> />
</div> </div>
{/* <Select {
/* <Select
label="Modem Preset" label="Modem Preset"
disabled disabled
value={channelSet?.loraConfig?.modemPreset} value={channelSet?.loraConfig?.modemPreset}
> >
{renderOptions(Protobuf.Config_LoRaConfig_ModemPreset)} {renderOptions(Protobuf.Config_LoRaConfig_ModemPreset)}
</Select> */} </Select> */
}
</div> </div>
{/* <Select {
/* <Select
label="Region" label="Region"
disabled disabled
value={channelSet?.loraConfig?.region} value={channelSet?.loraConfig?.region}
> >
{renderOptions(Protobuf.Config_LoRaConfig_RegionCode)} {renderOptions(Protobuf.Config_LoRaConfig_RegionCode)}
</Select> */} </Select> */
}
<span className="text-md block font-medium text-text-primary"> <span className="text-md block font-medium text-text-primary">
Channels: Channels:

13
src/components/Dialog/LocationResponseDialog.tsx

@ -1,14 +1,13 @@
import { useDevice } from "@app/core/stores/deviceStore"; import { useDevice } from "../../core/stores/deviceStore.ts";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
DialogDescription, DialogDescription,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
} from "@components/UI/Dialog"; } from "../UI/Dialog.tsx";
import type { Protobuf, Types } from "@meshtastic/core"; import type { Protobuf, Types } from "@meshtastic/core";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import type { JSX } from "react";
export interface LocationResponseDialogProps { export interface LocationResponseDialogProps {
location: Types.PacketMetadata<Protobuf.Mesh.location> | undefined; location: Types.PacketMetadata<Protobuf.Mesh.location> | undefined;
@ -20,15 +19,13 @@ export const LocationResponseDialog = ({
location, location,
open, open,
onOpenChange, onOpenChange,
}: LocationResponseDialogProps): JSX.Element => { }: LocationResponseDialogProps) => {
const { nodes } = useDevice(); const { nodes } = useDevice();
const from = nodes.get(location?.from ?? 0); const from = nodes.get(location?.from ?? 0);
const longName = const longName = from?.user?.longName ??
from?.user?.longName ??
(from ? `!${numberToHexUnpadded(from?.num)}` : "Unknown"); (from ? `!${numberToHexUnpadded(from?.num)}` : "Unknown");
const shortName = const shortName = from?.user?.shortName ??
from?.user?.shortName ??
(from ? `${numberToHexUnpadded(from?.num).substring(0, 4)}` : "UNK"); (from ? `${numberToHexUnpadded(from?.num).substring(0, 4)}` : "UNK");
return ( return (

30
src/components/Dialog/NewDeviceDialog.tsx

@ -1,7 +1,7 @@
import { import {
type BrowserFeature, type BrowserFeature,
useBrowserFeatureDetection, useBrowserFeatureDetection,
} from "@app/core/hooks/useBrowserFeatureDetection"; } from "../../core/hooks/useBrowserFeatureDetection.ts";
import { BLE } from "@components/PageComponents/Connect/BLE.tsx"; import { BLE } from "@components/PageComponents/Connect/BLE.tsx";
import { HTTP } from "@components/PageComponents/Connect/HTTP.tsx"; import { HTTP } from "@components/PageComponents/Connect/HTTP.tsx";
import { Serial } from "@components/PageComponents/Connect/Serial.tsx"; import { Serial } from "@components/PageComponents/Connect/Serial.tsx";
@ -18,9 +18,9 @@ import {
TabsTrigger, TabsTrigger,
} from "@components/UI/Tabs.tsx"; } from "@components/UI/Tabs.tsx";
import { Subtle } from "@components/UI/Typography/Subtle.tsx"; import { Subtle } from "@components/UI/Typography/Subtle.tsx";
import { AlertCircle, InfoIcon } from "lucide-react"; import { AlertCircle } from "lucide-react";
import { Fragment, type JSX } from "react/jsx-runtime"; import { Link } from "../UI/Typography/Link.tsx";
import { Link } from "../UI/Typography/Link"; import { Fragment } from "react/jsx-runtime";
export interface TabElementProps { export interface TabElementProps {
closeDialog: () => void; closeDialog: () => void;
@ -85,14 +85,16 @@ const ErrorMessage = ({ missingFeatures }: FeatureErrorProps) => {
<p className="text-sm"> <p className="text-sm">
{browserFeatures.length > 0 && ( {browserFeatures.length > 0 && (
<> <>
This application requires {formatFeatureList(browserFeatures)}. This application requires{" "}
Please use a Chromium-based browser like Chrome or Edge. {formatFeatureList(browserFeatures)}. Please use a
Chromium-based browser like Chrome or Edge.
</> </>
)} )}
{needsSecureContext && ( {needsSecureContext && (
<> <>
{browserFeatures.length > 0 && " Additionally, it"} {browserFeatures.length > 0 && " Additionally, it"}
{browserFeatures.length === 0 && "This application"} requires a{" "} {browserFeatures.length === 0 && "This application"} requires a
{" "}
<Link href={links["Secure Context"]}>secure context</Link>. <Link href={links["Secure Context"]}>secure context</Link>.
Please connect using HTTPS or localhost. Please connect using HTTPS or localhost.
</> </>
@ -107,7 +109,7 @@ const ErrorMessage = ({ missingFeatures }: FeatureErrorProps) => {
export const NewDeviceDialog = ({ export const NewDeviceDialog = ({
open, open,
onOpenChange, onOpenChange,
}: NewDeviceProps): JSX.Element => { }: NewDeviceProps) => {
const { unsupported } = useBrowserFeatureDetection(); const { unsupported } = useBrowserFeatureDetection();
const tabs: TabManifest[] = [ const tabs: TabManifest[] = [
@ -119,15 +121,13 @@ export const NewDeviceDialog = ({
{ {
label: "Bluetooth", label: "Bluetooth",
element: BLE, element: BLE,
isDisabled: isDisabled: unsupported.includes("Web Bluetooth") ||
unsupported.includes("Web Bluetooth") ||
unsupported.includes("Secure Context"), unsupported.includes("Secure Context"),
}, },
{ {
label: "Serial", label: "Serial",
element: Serial, element: Serial,
isDisabled: isDisabled: unsupported.includes("Web Serial") ||
unsupported.includes("Web Serial") ||
unsupported.includes("Secure Context"), unsupported.includes("Secure Context"),
}, },
]; ];
@ -149,9 +149,9 @@ export const NewDeviceDialog = ({
{tabs.map((tab) => ( {tabs.map((tab) => (
<TabsContent key={tab.label} value={tab.label}> <TabsContent key={tab.label} value={tab.label}>
<fieldset disabled={tab.isDisabled}> <fieldset disabled={tab.isDisabled}>
{tab.isDisabled ? ( {tab.isDisabled
<ErrorMessage missingFeatures={unsupported} /> ? <ErrorMessage missingFeatures={unsupported} />
) : null} : null}
<tab.element closeDialog={() => onOpenChange(false)} /> <tab.element closeDialog={() => onOpenChange(false)} />
</fieldset> </fieldset>
</TabsContent> </TabsContent>

287
src/components/Dialog/NodeDetailsDialog.tsx

@ -1,23 +1,23 @@
import { useAppStore } from "@app/core/stores/appStore"; import { useAppStore } from "../../core/stores/appStore.ts";
import { useDevice } from "@app/core/stores/deviceStore"; import { useDevice } from "../../core/stores/deviceStore.ts";
import { import {
Accordion, Accordion,
AccordionContent, AccordionContent,
AccordionItem, AccordionItem,
AccordionTrigger, AccordionTrigger,
} from "@components/UI/Accordion"; } from "../UI/Accordion.tsx";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
DialogFooter, DialogFooter,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
} from "@components/UI/Dialog"; } from "../UI/Dialog.tsx";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import { DeviceImage } from "../generic/DeviceImage"; import { DeviceImage } from "../generic/DeviceImage.tsx";
import { TimeAgo } from "../generic/TimeAgo"; import { TimeAgo } from "../generic/TimeAgo.tsx";
import { Uptime } from "../generic/Uptime"; import { Uptime } from "../generic/Uptime.tsx";
export interface NodeDetailsDialogProps { export interface NodeDetailsDialogProps {
open: boolean; open: boolean;
@ -32,134 +32,159 @@ export const NodeDetailsDialog = ({
const { nodeNumDetails } = useAppStore(); const { nodeNumDetails } = useAppStore();
const device: Protobuf.Mesh.NodeInfo = nodes.get(nodeNumDetails); const device: Protobuf.Mesh.NodeInfo = nodes.get(nodeNumDetails);
return device ? ( return device
<Dialog open={open} onOpenChange={onOpenChange}> ? (
<DialogContent> <Dialog open={open} onOpenChange={onOpenChange}>
<DialogHeader> <DialogContent>
<DialogTitle> <DialogHeader>
Node Details for {device.user?.longName ?? "UNKNOWN"} ( <DialogTitle>
{device.user?.shortName ?? "UNK"}) Node Details for {device.user?.longName ?? "UNKNOWN"} (
</DialogTitle> {device.user?.shortName ?? "UNK"})
</DialogHeader> </DialogTitle>
<DialogFooter> </DialogHeader>
<div className="w-full"> <DialogFooter>
<DeviceImage <div className="w-full">
className="w-32 h-32 mx-auto rounded-lg border-4 border-slate-200 dark:border-slate-800" <DeviceImage
deviceType={ className="w-32 h-32 mx-auto rounded-lg border-4 border-slate-200 dark:border-slate-800"
Protobuf.Mesh.HardwareModel[device.user?.hwModel ?? 0] deviceType={Protobuf.Mesh
} .HardwareModel[device.user?.hwModel ?? 0]}
/> />
<div className="bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3"> <div className="bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
<p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
Details:
</p>
<p>
Hardware:{" "}
{Protobuf.Mesh.HardwareModel[device.user?.hwModel ?? 0]}
</p>
<p>Node Number: {device.num}</p>
<p>Node HEX: !{numberToHexUnpadded(device.num)}</p>
<p>
Role:{" "}
{
Protobuf.Config.Config_DeviceConfig_Role[
device.user?.role ?? 0
]
}
</p>
<p>
Last Heard:{" "}
{device.lastHeard === 0 ? (
"Never"
) : (
<TimeAgo timestamp={device.lastHeard * 1000} />
)}
</p>
</div>
{device.position ? (
<div className="mt-5 bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
<p className="text-lg font-semibold text-slate-900 dark:text-slate-50"> <p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
Position: Details:
</p> </p>
{device.position.latitudeI && device.position.longitudeI ? ( <p>
<p> Hardware:{" "}
Coordinates:{" "} {Protobuf.Mesh.HardwareModel[device.user?.hwModel ?? 0]}
<a </p>
className="text-blue-500 dark:text-blue-400" <p>Node Number: {device.num}</p>
href={`https://www.openstreetmap.org/?mlat=${ <p>Node HEX: !{numberToHexUnpadded(device.num)}</p>
device.position.latitudeI / 1e7 <p>
}&mlon=${device.position.longitudeI / 1e7}&layers=N`} Role: {Protobuf.Config.Config_DeviceConfig_Role[
target="_blank" device.user?.role ?? 0
rel="noreferrer" ]}
> </p>
{device.position.latitudeI / 1e7},{" "} <p>
{device.position.longitudeI / 1e7} Last Heard: {device.lastHeard === 0
</a> ? (
</p> "Never"
) : null} )
{device.position.altitude ? ( : <TimeAgo timestamp={device.lastHeard * 1000} />}
<p>Altitude: {device.position.altitude}m</p>
) : null}
</div>
) : null}
{device.deviceMetrics ? (
<div className="mt-5 bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
<p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
Device Metrics:
</p> </p>
{device.deviceMetrics.airUtilTx ? (
<p>
Air TX utilization:{" "}
{device.deviceMetrics.airUtilTx.toFixed(2)}%
</p>
) : null}
{device.deviceMetrics.channelUtilization ? (
<p>
Channel utilization:{" "}
{device.deviceMetrics.channelUtilization.toFixed(2)}%
</p>
) : null}
{device.deviceMetrics.batteryLevel ? (
<p>
Battery level:{" "}
{device.deviceMetrics.batteryLevel.toFixed(2)}%
</p>
) : null}
{device.deviceMetrics.voltage ? (
<p>Voltage: {device.deviceMetrics.voltage.toFixed(2)}V</p>
) : null}
{device.deviceMetrics.uptimeSeconds ? (
<p>
Uptime:{" "}
<Uptime seconds={device.deviceMetrics.uptimeSeconds} />
</p>
) : null}
</div> </div>
) : null}
{device ? ( {device.position
<div className="mt-5 w-full max-w-[464px] bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3"> ? (
<Accordion className="AccordionRoot" type="single" collapsible> <div className="mt-5 bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
<AccordionItem className="AccordionItem" value="item-1"> <p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
<AccordionTrigger> Position:
<p className="text-lg font-semibold text-slate-900 dark:text-slate-50"> </p>
All Raw Metrics: {device.position.latitudeI && device.position.longitudeI
</p> ? (
</AccordionTrigger> <p>
<AccordionContent className="overflow-x-scroll"> Coordinates:{" "}
<pre className="text-xs w-full"> <a
className="text-blue-500 dark:text-blue-400"
href={`https://www.openstreetmap.org/?mlat=${
device.position.latitudeI / 1e7
}&mlon=${
device.position.longitudeI / 1e7
}&layers=N`}
target="_blank"
rel="noreferrer"
>
{device.position.latitudeI / 1e7},{" "}
{device.position.longitudeI / 1e7}
</a>
</p>
)
: null}
{device.position.altitude
? <p>Altitude: {device.position.altitude}m</p>
: null}
</div>
)
: null}
{device.deviceMetrics
? (
<div className="mt-5 bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
<p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
Device Metrics:
</p>
{device.deviceMetrics.airUtilTx
? (
<p>
Air TX utilization:{" "}
{device.deviceMetrics.airUtilTx.toFixed(2)}%
</p>
)
: null}
{device.deviceMetrics.channelUtilization
? (
<p>
Channel utilization:{" "}
{device.deviceMetrics.channelUtilization.toFixed(2)}%
</p>
)
: null}
{device.deviceMetrics.batteryLevel
? (
<p>
Battery level:{" "}
{device.deviceMetrics.batteryLevel.toFixed(2)}%
</p>
)
: null}
{device.deviceMetrics.voltage
? (
<p>
Voltage: {device.deviceMetrics.voltage.toFixed(2)}V
</p>
)
: null}
{device.deviceMetrics.uptimeSeconds
? (
<p>
Uptime:{" "}
<Uptime
seconds={device.deviceMetrics.uptimeSeconds}
/>
</p>
)
: null}
</div>
)
: null}
{device
? (
<div className="mt-5 w-full max-w-[464px] bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
<Accordion
className="AccordionRoot"
type="single"
collapsible
>
<AccordionItem className="AccordionItem" value="item-1">
<AccordionTrigger>
<p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
All Raw Metrics:
</p>
</AccordionTrigger>
<AccordionContent className="overflow-x-scroll">
<pre className="text-xs w-full">
{JSON.stringify(device, null, 2)} {JSON.stringify(device, null, 2)}
</pre> </pre>
</AccordionContent> </AccordionContent>
</AccordionItem> </AccordionItem>
</Accordion> </Accordion>
</div> </div>
) : null} )
</div> : null}
</DialogFooter> </div>
</DialogContent> </DialogFooter>
</Dialog> </DialogContent>
) : null; </Dialog>
)
: null;
}; };

24
src/components/Dialog/NodeOptionsDialog.tsx

@ -1,17 +1,17 @@
import { toast } from "@app/core/hooks/useToast"; import { toast } from "../../core/hooks/useToast.ts";
import { useAppStore } from "@app/core/stores/appStore"; import { useAppStore } from "../../core/stores/appStore.ts";
import { useDevice } from "@app/core/stores/deviceStore"; import { useDevice } from "../../core/stores/deviceStore.ts";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
} from "@components/UI/Dialog"; } from "../UI/Dialog.tsx";
import type { Protobuf } from "@meshtastic/core"; import type { Protobuf } from "@meshtastic/core";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import { TrashIcon } from "lucide-react"; import { TrashIcon } from "lucide-react";
import type { JSX } from "react";
import { Button } from "../UI/Button"; import { Button } from "../UI/Button.tsx";
export interface NodeOptionsDialogProps { export interface NodeOptionsDialogProps {
node: Protobuf.Mesh.NodeInfo | undefined; node: Protobuf.Mesh.NodeInfo | undefined;
@ -23,7 +23,7 @@ export const NodeOptionsDialog = ({
node, node,
open, open,
onOpenChange, onOpenChange,
}: NodeOptionsDialogProps): JSX.Element => { }: NodeOptionsDialogProps) => {
const { setDialogOpen, connection, setActivePage } = useDevice(); const { setDialogOpen, connection, setActivePage } = useDevice();
const { const {
setNodeNumToBeRemoved, setNodeNumToBeRemoved,
@ -31,11 +31,9 @@ export const NodeOptionsDialog = ({
setChatType, setChatType,
setActiveChat, setActiveChat,
} = useAppStore(); } = useAppStore();
const longName = const longName = node?.user?.longName ??
node?.user?.longName ??
(node ? `!${numberToHexUnpadded(node?.num)}` : "Unknown"); (node ? `!${numberToHexUnpadded(node?.num)}` : "Unknown");
const shortName = const shortName = node?.user?.shortName ??
node?.user?.shortName ??
(node ? `${numberToHexUnpadded(node?.num).substring(0, 4)}` : "UNK"); (node ? `${numberToHexUnpadded(node?.num).substring(0, 4)}` : "UNK");
function handleDirectMessage() { function handleDirectMessage() {
@ -53,7 +51,7 @@ export const NodeOptionsDialog = ({
connection?.requestPosition(node.num).then(() => connection?.requestPosition(node.num).then(() =>
toast({ toast({
title: "Position request sent.", title: "Position request sent.",
}), })
); );
onOpenChange(); onOpenChange();
} }
@ -66,7 +64,7 @@ export const NodeOptionsDialog = ({
connection?.traceRoute(node.num).then(() => connection?.traceRoute(node.num).then(() =>
toast({ toast({
title: "Traceroute sent.", title: "Traceroute sent.",
}), })
); );
onOpenChange(); onOpenChange();
} }

10
src/components/Dialog/PKIBackupDialog.tsx

@ -1,5 +1,5 @@
import { useDevice } from "@app/core/stores/deviceStore"; import { useDevice } from "../../core/stores/deviceStore.ts";
import { Button } from "@components/UI/Button"; import { Button } from "../UI/Button.tsx";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
@ -40,7 +40,7 @@ export const PkiBackupDialog = ({
const renderPrintWindow = React.useCallback(() => { const renderPrintWindow = React.useCallback(() => {
if (!privateKey || !publicKey) return; if (!privateKey || !publicKey) return;
const printWindow = window.open("", "_blank"); const printWindow = globalThis.open("", "_blank");
if (printWindow) { if (printWindow) {
printWindow.document.write(` printWindow.document.write(`
<html> <html>
@ -116,14 +116,14 @@ export const PkiBackupDialog = ({
</DialogHeader> </DialogHeader>
<DialogFooter className="mt-6"> <DialogFooter className="mt-6">
<Button <Button
variant={"default"} variant="default"
onClick={() => createDownloadKeyFile()} onClick={() => createDownloadKeyFile()}
className="" className=""
> >
<DownloadIcon size={20} className="mr-2" /> <DownloadIcon size={20} className="mr-2" />
Download Download
</Button> </Button>
<Button variant={"default"} onClick={() => renderPrintWindow()}> <Button variant="default" onClick={() => renderPrintWindow()}>
<PrinterIcon size={20} className="mr-2" /> <PrinterIcon size={20} className="mr-2" />
Print Print
</Button> </Button>

2
src/components/Dialog/PkiRegenerateDialog.tsx

@ -18,7 +18,7 @@ export const PkiRegenerateDialog = ({
open, open,
onOpenChange, onOpenChange,
onSubmit, onSubmit,
}: PkiRegenerateDialogProps): JSX.Element => { }: PkiRegenerateDialogProps) => {
return ( return (
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent> <DialogContent>

12
src/components/Dialog/QRDialog.tsx

@ -28,7 +28,7 @@ export const QRDialog = ({
onOpenChange, onOpenChange,
loraConfig, loraConfig,
channels, channels,
}: QRDialogProps): JSX.Element => { }: QRDialogProps) => {
const [selectedChannels, setSelectedChannels] = useState<number[]>([0]); const [selectedChannels, setSelectedChannels] = useState<number[]>([0]);
const [qrCodeUrl, setQrCodeUrl] = useState<string>(""); const [qrCodeUrl, setQrCodeUrl] = useState<string>("");
const [qrCodeAdd, setQrCodeAdd] = useState<boolean>(); const [qrCodeAdd, setQrCodeAdd] = useState<boolean>();
@ -77,8 +77,8 @@ export const QRDialog = ({
{channel.settings?.name.length {channel.settings?.name.length
? channel.settings.name ? channel.settings.name
: channel.role === Protobuf.Channel.Channel_Role.PRIMARY : channel.role === Protobuf.Channel.Channel_Role.PRIMARY
? "Primary" ? "Primary"
: `Channel: ${channel.index}`} : `Channel: ${channel.index}`}
</Label> </Label>
<Checkbox <Checkbox
key={channel.index} key={channel.index}
@ -86,7 +86,9 @@ export const QRDialog = ({
onCheckedChange={() => { onCheckedChange={() => {
if (selectedChannels.includes(channel.index)) { if (selectedChannels.includes(channel.index)) {
setSelectedChannels( setSelectedChannels(
selectedChannels.filter((c) => c !== channel.index), selectedChannels.filter((c) =>
c !== channel.index
),
); );
} else { } else {
setSelectedChannels([ setSelectedChannels([
@ -130,7 +132,7 @@ export const QRDialog = ({
<Label>Sharable URL</Label> <Label>Sharable URL</Label>
<Input <Input
value={qrCodeUrl} value={qrCodeUrl}
disabled={true} disabled
className="dark:text-slate-900" className="dark:text-slate-900"
action={{ action={{
icon: ClipboardIcon, icon: ClipboardIcon,

2
src/components/Dialog/RebootDialog.tsx

@ -19,7 +19,7 @@ export interface RebootDialogProps {
export const RebootDialog = ({ export const RebootDialog = ({
open, open,
onOpenChange, onOpenChange,
}: RebootDialogProps): JSX.Element => { }: RebootDialogProps) => {
const { connection } = useDevice(); const { connection } = useDevice();
const [time, setTime] = useState<number>(5); const [time, setTime] = useState<number>(5);

6
src/components/Dialog/RemoveNodeDialog.tsx

@ -1,5 +1,5 @@
import { useAppStore } from "@app/core/stores/appStore"; import { useAppStore } from "../../core/stores/appStore.ts";
import { useDevice } from "@app/core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Button } from "@components/UI/Button.tsx"; import { Button } from "@components/UI/Button.tsx";
import { import {
Dialog, Dialog,
@ -19,7 +19,7 @@ export interface RemoveNodeDialogProps {
export const RemoveNodeDialog = ({ export const RemoveNodeDialog = ({
open, open,
onOpenChange, onOpenChange,
}: RemoveNodeDialogProps): JSX.Element => { }: RemoveNodeDialogProps) => {
const { connection, nodes, removeNode } = useDevice(); const { connection, nodes, removeNode } = useDevice();
const { nodeNumToBeRemoved } = useAppStore(); const { nodeNumToBeRemoved } = useAppStore();

2
src/components/Dialog/ShutdownDialog.tsx

@ -19,7 +19,7 @@ export interface ShutdownDialogProps {
export const ShutdownDialog = ({ export const ShutdownDialog = ({
open, open,
onOpenChange, onOpenChange,
}: ShutdownDialogProps): JSX.Element => { }: ShutdownDialogProps) => {
const { connection } = useDevice(); const { connection } = useDevice();
const [time, setTime] = useState<number>(5); const [time, setTime] = useState<number>(5);

16
src/components/Dialog/TracerouteResponseDialog.tsx

@ -1,15 +1,15 @@
import { useDevice } from "@app/core/stores/deviceStore"; import { useDevice } from "../../core/stores/deviceStore.ts";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
DialogDescription, DialogDescription,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
} from "@components/UI/Dialog"; } from "../UI/Dialog.tsx";
import type { Protobuf, Types } from "@meshtastic/core"; import type { Protobuf, Types } from "@meshtastic/core";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import type { JSX } from "react";
import { TraceRoute } from "../PageComponents/Messages/TraceRoute"; import { TraceRoute } from "../PageComponents/Messages/TraceRoute.tsx";
export interface TracerouteResponseDialogProps { export interface TracerouteResponseDialogProps {
traceroute: Types.PacketMetadata<Protobuf.Mesh.RouteDiscovery> | undefined; traceroute: Types.PacketMetadata<Protobuf.Mesh.RouteDiscovery> | undefined;
@ -21,18 +21,16 @@ export const TracerouteResponseDialog = ({
traceroute, traceroute,
open, open,
onOpenChange, onOpenChange,
}: TracerouteResponseDialogProps): JSX.Element => { }: TracerouteResponseDialogProps) => {
const { nodes } = useDevice(); const { nodes } = useDevice();
const route: number[] = traceroute?.data.route ?? []; const route: number[] = traceroute?.data.route ?? [];
const routeBack: number[] = traceroute?.data.routeBack ?? []; const routeBack: number[] = traceroute?.data.routeBack ?? [];
const snrTowards = traceroute?.data.snrTowards ?? []; const snrTowards = traceroute?.data.snrTowards ?? [];
const snrBack = traceroute?.data.snrBack ?? []; const snrBack = traceroute?.data.snrBack ?? [];
const from = nodes.get(traceroute?.from ?? 0); const from = nodes.get(traceroute?.from ?? 0);
const longName = const longName = from?.user?.longName ??
from?.user?.longName ??
(from ? `!${numberToHexUnpadded(from?.num)}` : "Unknown"); (from ? `!${numberToHexUnpadded(from?.num)}` : "Unknown");
const shortName = const shortName = from?.user?.shortName ??
from?.user?.shortName ??
(from ? `${numberToHexUnpadded(from?.num).substring(0, 4)}` : "UNK"); (from ? `${numberToHexUnpadded(from?.num).substring(0, 4)}` : "UNK");
const to = nodes.get(traceroute?.to ?? 0); const to = nodes.get(traceroute?.to ?? 0);
return ( return (

20
src/components/Form/DynamicForm.tsx

@ -13,7 +13,8 @@ import {
type SubmitHandler, type SubmitHandler,
useForm, useForm,
} from "react-hook-form"; } from "react-hook-form";
import { Heading } from "../UI/Typography/Heading"; import { Heading } from "@components/UI/Typography/Heading.tsx";
interface DisabledBy<T> { interface DisabledBy<T> {
fieldName: Path<T>; fieldName: Path<T>;
@ -76,10 +77,11 @@ export function DynamicForm<T extends FieldValues>({
const value = getValues(field.fieldName); const value = getValues(field.fieldName);
if (value === "always") return true; if (value === "always") return true;
if (typeof value === "boolean") return field.invert ? value : !value; if (typeof value === "boolean") return field.invert ? value : !value;
if (typeof value === "number") if (typeof value === "number") {
return field.invert return field.invert
? field.selector !== value ? field.selector !== value
: field.selector === value; : field.selector === value;
}
return false; return false;
}); });
}; };
@ -87,11 +89,9 @@ export function DynamicForm<T extends FieldValues>({
return ( return (
<form <form
className="space-y-8" className="space-y-8"
{...(submitType === "onSubmit" {...(submitType === "onSubmit" ? { onSubmit: handleSubmit(onSubmit) } : {
? { onSubmit: handleSubmit(onSubmit) } onChange: handleSubmit(onSubmit),
: { })}
onChange: handleSubmit(onSubmit),
})}
> >
{fieldGroups.map((fieldGroup) => ( {fieldGroups.map((fieldGroup) => (
<div key={fieldGroup.label} className="space-y-8 sm:space-y-5"> <div key={fieldGroup.label} className="space-y-8 sm:space-y-5">
@ -110,10 +110,8 @@ export function DynamicForm<T extends FieldValues>({
label={field.label} label={field.label}
fieldName={field.name} fieldName={field.name}
description={field.description} description={field.description}
valid={ valid={field.validationText === undefined ||
field.validationText === undefined || field.validationText === ""}
field.validationText === ""
}
validationText={field.validationText} validationText={field.validationText}
> >
<DynamicFormField <DynamicFormField

14
src/components/Form/DynamicFormField.tsx

@ -1,7 +1,7 @@
import { import {
type MultiSelectFieldProps, type MultiSelectFieldProps,
MultiSelectInput, MultiSelectInput,
} from "@app/components/Form/FormMultiSelect"; } from "./FormMultiSelect.tsx";
import { import {
GenericInput, GenericInput,
type InputFieldProps, type InputFieldProps,
@ -48,11 +48,19 @@ export function DynamicFormField<T extends FieldValues>({
case "toggle": case "toggle":
return ( return (
<ToggleInput field={field} control={control} disabled={disabled} /> <ToggleInput
field={field}
control={control}
disabled={disabled}
/>
); );
case "select": case "select":
return ( return (
<SelectInput field={field} control={control} disabled={disabled} /> <SelectInput
field={field}
control={control}
disabled={disabled}
/>
); );
case "passwordGenerator": case "passwordGenerator":
return ( return (

20
src/components/Form/FormInput.tsx

@ -40,17 +40,15 @@ export function GenericInput<T extends FieldValues>({
control={control} control={control}
render={({ field: { value, onChange, ...rest } }) => ( render={({ field: { value, onChange, ...rest } }) => (
<Input <Input
type={ type={field.type === "password" && passwordShown
field.type === "password" && passwordShown ? "text" : field.type ? "text"
} : field.type}
action={ action={field.type === "password"
field.type === "password" ? {
? { icon: passwordShown ? EyeOff : Eye,
icon: passwordShown ? EyeOff : Eye, onClick: togglePasswordVisiblity,
onClick: togglePasswordVisiblity, }
} : undefined}
: undefined
}
step={field.properties?.step} step={field.properties?.step}
value={field.type === "number" ? Number.parseFloat(value) : value} value={field.type === "number" ? Number.parseFloat(value) : value}
id={field.name} id={field.name}

6
src/components/Form/FormMultiSelect.tsx

@ -3,7 +3,7 @@ import type {
GenericFormElementProps, GenericFormElementProps,
} from "@components/Form/DynamicForm.tsx"; } from "@components/Form/DynamicForm.tsx";
import type { FieldValues } from "react-hook-form"; import type { FieldValues } from "react-hook-form";
import { MultiSelect, MultiSelectItem } from "../UI/MultiSelect"; import { MultiSelect, MultiSelectItem } from "../UI/MultiSelect.tsx";
export interface MultiSelectFieldProps<T> extends BaseFormBuilderProps<T> { export interface MultiSelectFieldProps<T> extends BaseFormBuilderProps<T> {
type: "multiSelect"; type: "multiSelect";
@ -28,8 +28,8 @@ export function MultiSelectInput<T extends FieldValues>({
// Make sure to filter out the UNSET value, as it shouldn't be shown in the UI // Make sure to filter out the UNSET value, as it shouldn't be shown in the UI
const optionsEnumValues = enumValue const optionsEnumValues = enumValue
? Object.entries(enumValue) ? Object.entries(enumValue)
.filter((value) => typeof value[1] === "number") .filter((value) => typeof value[1] === "number")
.filter((value) => value[0] !== "UNSET") .filter((value) => value[0] !== "UNSET")
: []; : [];
const formatName = (name: string) => { const formatName = (name: string) => {

18
src/components/Form/FormPasswordGenerator.tsx

@ -2,10 +2,10 @@ import type {
BaseFormBuilderProps, BaseFormBuilderProps,
GenericFormElementProps, GenericFormElementProps,
} from "@components/Form/DynamicForm.tsx"; } from "@components/Form/DynamicForm.tsx";
import type { ButtonVariant } from "@components/UI/Button"; import type { ButtonVariant } from "../UI/Button.tsx";
import { Generator } from "@components/UI/Generator.tsx"; import { Generator } from "@components/UI/Generator.tsx";
import { Eye, EyeOff } from "lucide-react"; import { Eye, EyeOff } from "lucide-react";
import type { ChangeEventHandler, MouseEventHandler } from "react"; import type { ChangeEventHandler } from "react";
import { useState } from "react"; import { useState } from "react";
import { Controller, type FieldValues } from "react-hook-form"; import { Controller, type FieldValues } from "react-hook-form";
@ -43,14 +43,12 @@ export function PasswordGenerator<T extends FieldValues>({
<Generator <Generator
type={field.hide && !passwordShown ? "password" : "text"} type={field.hide && !passwordShown ? "password" : "text"}
id={field.id} id={field.id}
action={ action={field.hide
field.hide ? {
? { icon: passwordShown ? EyeOff : Eye,
icon: passwordShown ? EyeOff : Eye, onClick: togglePasswordVisiblity,
onClick: togglePasswordVisiblity, }
} : undefined}
: undefined
}
devicePSKBitCount={field.devicePSKBitCount} devicePSKBitCount={field.devicePSKBitCount}
bits={field.bits} bits={field.bits}
inputChange={field.inputChange} inputChange={field.inputChange}

16
src/components/Form/FormSelect.tsx

@ -36,8 +36,8 @@ export function SelectInput<T extends FieldValues>({
field.properties; field.properties;
const optionsEnumValues = enumValue const optionsEnumValues = enumValue
? Object.entries(enumValue).filter( ? Object.entries(enumValue).filter(
(value) => typeof value[1] === "number", (value) => typeof value[1] === "number",
) )
: []; : [];
return ( return (
<Select <Select
@ -58,11 +58,13 @@ export function SelectInput<T extends FieldValues>({
<SelectItem key={name} value={value.toString()}> <SelectItem key={name} value={value.toString()}>
{formatEnumName {formatEnumName
? name ? name
.replace(/_/g, " ") .replace(/_/g, " ")
.toLowerCase() .toLowerCase()
.split(" ") .split(" ")
.map((s) => s.charAt(0).toUpperCase() + s.substring(1)) .map((s) =>
.join(" ") s.charAt(0).toUpperCase() + s.substring(1)
)
.join(" ")
: name} : name}
</SelectItem> </SelectItem>
))} ))}

7
src/components/KeyBackupReminder.tsx

@ -1,7 +1,7 @@
import { useBackupReminder } from "@app/core/hooks/useKeyBackupReminder"; import { useBackupReminder } from "@core/hooks/useKeyBackupReminder.tsx";
import { useDevice } from "@app/core/stores/deviceStore"; import { useDevice } from "@core/stores/deviceStore.ts";
export const KeyBackupReminder = (): JSX.Element => { export const KeyBackupReminder = () => {
const { setDialogOpen } = useDevice(); const { setDialogOpen } = useDevice();
useBackupReminder({ useBackupReminder({
@ -15,5 +15,6 @@ export const KeyBackupReminder = (): JSX.Element => {
sameSite: "strict", sameSite: "strict",
}, },
}); });
// deno-lint-ignore jsx-no-useless-fragment
return <></>; return <></>;
}; };

71
src/components/PageComponents/Channel.tsx

@ -7,13 +7,13 @@ import { Protobuf } from "@meshtastic/core";
import { fromByteArray, toByteArray } from "base64-js"; import { fromByteArray, toByteArray } from "base64-js";
import cryptoRandomString from "crypto-random-string"; import cryptoRandomString from "crypto-random-string";
import { useState } from "react"; import { useState } from "react";
import { PkiRegenerateDialog } from "../Dialog/PkiRegenerateDialog"; import { PkiRegenerateDialog } from "../Dialog/PkiRegenerateDialog.tsx";
export interface SettingsPanelProps { export interface SettingsPanelProps {
channel: Protobuf.Channel.Channel; channel: Protobuf.Channel.Channel;
} }
export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => { export const Channel = ({ channel }: SettingsPanelProps) => {
const { config, connection, addChannel } = useDevice(); const { config, connection, addChannel } = useDevice();
const { toast } = useToast(); const { toast } = useToast();
@ -24,8 +24,9 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
channel?.settings?.psk.length ?? 16, channel?.settings?.psk.length ?? 16,
); );
const [validationText, setValidationText] = useState<string>(); const [validationText, setValidationText] = useState<string>();
const [preSharedDialogOpen, setPreSharedDialogOpen] = const [preSharedDialogOpen, setPreSharedDialogOpen] = useState<boolean>(
useState<boolean>(false); false,
);
const onSubmit = (data: ChannelValidation) => { const onSubmit = (data: ChannelValidation) => {
const channel = create(Protobuf.Channel.ChannelSchema, { const channel = create(Protobuf.Channel.ChannelSchema, {
@ -92,7 +93,7 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
<DynamicForm<ChannelValidation> <DynamicForm<ChannelValidation>
onSubmit={onSubmit} onSubmit={onSubmit}
submitType="onSubmit" submitType="onSubmit"
hasSubmitButton={true} hasSubmitButton
defaultValues={{ defaultValues={{
...channel, ...channel,
...{ ...{
@ -107,7 +108,7 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
channel?.settings?.moduleSettings?.positionPrecision === 32, channel?.settings?.moduleSettings?.positionPrecision === 32,
positionPrecision: positionPrecision:
channel?.settings?.moduleSettings?.positionPrecision === channel?.settings?.moduleSettings?.positionPrecision ===
undefined undefined
? 10 ? 10
: channel?.settings?.moduleSettings?.positionPrecision, : channel?.settings?.moduleSettings?.positionPrecision,
}, },
@ -126,10 +127,9 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
description: description:
"Device telemetry is sent over PRIMARY. Only one PRIMARY allowed", "Device telemetry is sent over PRIMARY. Only one PRIMARY allowed",
properties: { properties: {
enumValue: enumValue: channel.index === 0
channel.index === 0 ? { PRIMARY: 1 }
? { PRIMARY: 1 } : { DISABLED: 0, SECONDARY: 2 },
: { DISABLED: 0, SECONDARY: 2 },
}, },
}, },
{ {
@ -192,32 +192,31 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
description: description:
"If not sharing precise location, position shared on channel will be accurate within this distance", "If not sharing precise location, position shared on channel will be accurate within this distance",
properties: { properties: {
enumValue: enumValue: config.display?.units === 0
config.display?.units === 0 ? {
? { "Within 23 km": 10,
"Within 23 km": 10, "Within 12 km": 11,
"Within 12 km": 11, "Within 5.8 km": 12,
"Within 5.8 km": 12, "Within 2.9 km": 13,
"Within 2.9 km": 13, "Within 1.5 km": 14,
"Within 1.5 km": 14, "Within 700 m": 15,
"Within 700 m": 15, "Within 350 m": 16,
"Within 350 m": 16, "Within 200 m": 17,
"Within 200 m": 17, "Within 90 m": 18,
"Within 90 m": 18, "Within 50 m": 19,
"Within 50 m": 19, }
} : {
: { "Within 15 miles": 10,
"Within 15 miles": 10, "Within 7.3 miles": 11,
"Within 7.3 miles": 11, "Within 3.6 miles": 12,
"Within 3.6 miles": 12, "Within 1.8 miles": 13,
"Within 1.8 miles": 13, "Within 0.9 miles": 14,
"Within 0.9 miles": 14, "Within 0.5 miles": 15,
"Within 0.5 miles": 15, "Within 0.2 miles": 16,
"Within 0.2 miles": 16, "Within 600 feet": 17,
"Within 600 feet": 17, "Within 300 feet": 18,
"Within 300 feet": 18, "Within 150 feet": 19,
"Within 150 feet": 19, },
},
}, },
}, },
], ],

7
src/components/PageComponents/Config/Bluetooth.tsx

@ -1,4 +1,4 @@
import { useAppStore } from "@app/core/stores/appStore"; import { useAppStore } from "../../../core/stores/appStore.ts";
import type { BluetoothValidation } from "@app/validation/config/bluetooth.tsx"; import type { BluetoothValidation } from "@app/validation/config/bluetooth.tsx";
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
@ -113,9 +113,8 @@ export const Bluetooth = () => {
disabledBy: [ disabledBy: [
{ {
fieldName: "mode", fieldName: "mode",
selector: selector: Protobuf.Config.Config_BluetoothConfig_PairingMode
Protobuf.Config.Config_BluetoothConfig_PairingMode .FIXED_PIN,
.FIXED_PIN,
invert: true, invert: true,
}, },
{ {

2
src/components/PageComponents/Config/Device.tsx

@ -4,7 +4,7 @@ import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
export const Device = (): JSX.Element => { export const Device = () => {
const { config, setWorkingConfig } = useDevice(); const { config, setWorkingConfig } = useDevice();
const onSubmit = (data: DeviceValidation) => { const onSubmit = (data: DeviceValidation) => {

2
src/components/PageComponents/Config/Display.tsx

@ -4,7 +4,7 @@ import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
export const Display = (): JSX.Element => { export const Display = () => {
const { config, setWorkingConfig } = useDevice(); const { config, setWorkingConfig } = useDevice();
const onSubmit = (data: DisplayValidation) => { const onSubmit = (data: DisplayValidation) => {

2
src/components/PageComponents/Config/LoRa.tsx

@ -4,7 +4,7 @@ import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
export const LoRa = (): JSX.Element => { export const LoRa = () => {
const { config, setWorkingConfig } = useDevice(); const { config, setWorkingConfig } = useDevice();
const onSubmit = (data: LoRaValidation) => { const onSubmit = (data: LoRaValidation) => {

2
src/components/PageComponents/Config/Position.tsx

@ -1,7 +1,7 @@
import { import {
type FlagName, type FlagName,
usePositionFlags, usePositionFlags,
} from "@app/core/hooks/usePositionFlags"; } from "../../../core/hooks/usePositionFlags.ts";
import type { PositionValidation } from "@app/validation/config/position.tsx"; import type { PositionValidation } from "@app/validation/config/position.tsx";
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";

2
src/components/PageComponents/Config/Power.tsx

@ -4,7 +4,7 @@ import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
export const Power = (): JSX.Element => { export const Power = () => {
const { config, setWorkingConfig } = useDevice(); const { config, setWorkingConfig } = useDevice();
const onSubmit = (data: PowerValidation) => { const onSubmit = (data: PowerValidation) => {

25
src/components/PageComponents/Config/Security/Security.tsx

@ -1,18 +1,18 @@
import { PkiRegenerateDialog } from "@app/components/Dialog/PkiRegenerateDialog"; import { PkiRegenerateDialog } from "@components/Dialog/PkiRegenerateDialog.tsx";
import { DynamicForm } from "@app/components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useAppStore } from "@app/core/stores/appStore"; import { useAppStore } from "@core/stores/appStore.ts";
import { import {
getX25519PrivateKey, getX25519PrivateKey,
getX25519PublicKey, getX25519PublicKey,
} from "@app/core/utils/x25519"; } from "@core/utils/x25519.ts";
import type { SecurityValidation } from "@app/validation/config/security.tsx"; import type { SecurityValidation } from "@app/validation/config/security.ts";
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
import { fromByteArray, toByteArray } from "base64-js"; import { fromByteArray, toByteArray } from "base64-js";
import { Eye, EyeOff } from "lucide-react"; import { Eye, EyeOff } from "lucide-react";
import { useReducer } from "react"; import { useReducer } from "react";
import { securityReducer } from "./securityReducer"; import { securityReducer } from "@components/PageComponents/Config/Security/securityReducer.tsx";
export const Security = () => { export const Security = () => {
const { config, setWorkingConfig, setDialogOpen } = useDevice(); const { config, setWorkingConfig, setDialogOpen } = useDevice();
@ -58,8 +58,7 @@ export const Security = () => {
if (input.length % 4 !== 0) { if (input.length % 4 !== 0) {
addError( addError(
fieldName, fieldName,
`${ `${fieldName === "privateKey" ? "Private" : "Admin"
fieldName === "privateKey" ? "Private" : "Admin"
} Key is required to be a 256 bit pre-shared key (PSK)`, } Key is required to be a 256 bit pre-shared key (PSK)`,
); );
return; return;
@ -74,8 +73,7 @@ export const Security = () => {
console.error(e); console.error(e);
addError( addError(
fieldName, fieldName,
`Invalid ${ `Invalid ${fieldName === "privateKey" ? "Private" : "Admin"
fieldName === "privateKey" ? "Private" : "Admin"
} Key format`, } Key format`,
); );
} }
@ -85,8 +83,6 @@ export const Security = () => {
if (hasErrors()) { if (hasErrors()) {
return; return;
} }
console.log(toByteArray(state.adminKey));
setWorkingConfig( setWorkingConfig(
create(Protobuf.Config.ConfigSchema, { create(Protobuf.Config.ConfigSchema, {
payloadVariant: { payloadVariant: {
@ -248,7 +244,7 @@ export const Security = () => {
? getErrorMessage("adminKey") ? getErrorMessage("adminKey")
: "", : "",
inputChange: adminKeyInputChangeEvent, inputChange: adminKeyInputChangeEvent,
selectChange: () => {}, selectChange: () => { },
bits: [{ text: "256 bit", value: "32", key: "bit256" }], bits: [{ text: "256 bit", value: "32", key: "bit256" }],
devicePSKBitCount: state.privateKeyBitCount, devicePSKBitCount: state.privateKeyBitCount,
hide: !state.adminKeyVisible, hide: !state.adminKeyVisible,
@ -308,8 +304,7 @@ export const Security = () => {
<PkiRegenerateDialog <PkiRegenerateDialog
open={state.privateKeyDialogOpen} open={state.privateKeyDialogOpen}
onOpenChange={() => onOpenChange={() =>
dispatch({ type: "SHOW_PRIVATE_KEY_DIALOG", payload: false }) dispatch({ type: "SHOW_PRIVATE_KEY_DIALOG", payload: false })}
}
onSubmit={pkiRegenerate} onSubmit={pkiRegenerate}
/> />
</> </>

2
src/components/PageComponents/Config/Security/securityReducer.tsx

@ -1,4 +1,4 @@
import type { SecurityAction, SecurityState } from "./types"; import type { SecurityAction, SecurityState } from "./types.ts";
export function securityReducer( export function securityReducer(
state: SecurityState, state: SecurityState,

6
src/components/PageComponents/Config/Security/types.ts

@ -18,7 +18,7 @@ export type SecurityAction =
| { type: "SET_ADMIN_KEY"; payload: string } | { type: "SET_ADMIN_KEY"; payload: string }
| { type: "SHOW_PRIVATE_KEY_DIALOG"; payload: boolean } | { type: "SHOW_PRIVATE_KEY_DIALOG"; payload: boolean }
| { | {
type: "REGENERATE_PRIV_PUB_KEY"; type: "REGENERATE_PRIV_PUB_KEY";
payload: { privateKey: string; publicKey: string }; payload: { privateKey: string; publicKey: string };
} }
| { type: "REGENERATE_ADMIN_KEY"; payload: { adminKey: string } }; | { type: "REGENERATE_ADMIN_KEY"; payload: { adminKey: string } };

4
src/components/PageComponents/Connect/BLE.tsx

@ -1,4 +1,4 @@
import type { TabElementProps } from "@app/components/Dialog/NewDeviceDialog"; import type { TabElementProps } from "../../Dialog/NewDeviceDialog.tsx";
import { Button } from "@components/UI/Button.tsx"; import { Button } from "@components/UI/Button.tsx";
import { Mono } from "@components/generic/Mono.tsx"; import { Mono } from "@components/generic/Mono.tsx";
import { useAppStore } from "@core/stores/appStore.ts"; import { useAppStore } from "@core/stores/appStore.ts";
@ -8,7 +8,7 @@ import { randId } from "@core/utils/randId.ts";
import { BleConnection, Constants } from "@meshtastic/js"; import { BleConnection, Constants } from "@meshtastic/js";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
export const BLE = ({ closeDialog }: TabElementProps): JSX.Element => { export const BLE = ({ closeDialog }: TabElementProps) => {
const [bleDevices, setBleDevices] = useState<BluetoothDevice[]>([]); const [bleDevices, setBleDevices] = useState<BluetoothDevice[]>([]);
const { addDevice } = useDeviceStore(); const { addDevice } = useDeviceStore();
const { setSelectedDevice } = useAppStore(); const { setSelectedDevice } = useAppStore();

21
src/components/PageComponents/Connect/HTTP.tsx

@ -1,4 +1,4 @@
import type { TabElementProps } from "@app/components/Dialog/NewDeviceDialog"; import type { TabElementProps } from "@components/Dialog/NewDeviceDialog.tsx";
import { Button } from "@components/UI/Button.tsx"; import { Button } from "@components/UI/Button.tsx";
import { Input } from "@components/UI/Input.tsx"; import { Input } from "@components/UI/Input.tsx";
import { Label } from "@components/UI/Label.tsx"; import { Label } from "@components/UI/Label.tsx";
@ -9,23 +9,23 @@ import { subscribeAll } from "@core/subscriptions.ts";
import { randId } from "@core/utils/randId.ts"; import { randId } from "@core/utils/randId.ts";
import { MeshDevice } from "@meshtastic/core"; import { MeshDevice } from "@meshtastic/core";
import { TransportHTTP } from "@meshtastic/transport-http"; import { TransportHTTP } from "@meshtastic/transport-http";
import { type JSX, useState } from "react"; import { useState } from "react";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
export const HTTP = ({ closeDialog }: TabElementProps): JSX.Element => { export const HTTP = ({ closeDialog }: TabElementProps) => {
const [https, setHTTPS] = useState(false); const [https, setHTTPS] = useState(false);
const { addDevice } = useDeviceStore(); const { addDevice } = useDeviceStore();
const { setSelectedDevice } = useAppStore(); const { setSelectedDevice } = useAppStore();
const { register, handleSubmit, control, watch } = useForm<{ const { register, handleSubmit, control } = useForm<{
ip: string; ip: string;
tls: boolean; tls: boolean;
}>({ }>({
defaultValues: { defaultValues: {
ip: ["client.meshtastic.org", "localhost"].includes( ip: ["client.meshtastic.org", "localhost"].includes(
window.location.hostname, globalThis.location.hostname,
) )
? "meshtastic.local" ? "meshtastic.local"
: window.location.host, : globalThis.location.host,
tls: location.protocol === "https:", tls: location.protocol === "https:",
}, },
}); });
@ -61,16 +61,15 @@ export const HTTP = ({ closeDialog }: TabElementProps): JSX.Element => {
<Controller <Controller
name="tls" name="tls"
control={control} control={control}
render={({ field: { value, onChange, ...rest } }) => ( render={({ field: { ...rest } }) => (
<> <>
<Label>Use HTTPS</Label> <Label>Use HTTPS</Label>
<Switch <Switch
onCheckedChange={(checked) => { onCheckedChange={(checked: boolean) => {
checked ? setHTTPS(true) : setHTTPS(false); checked ? setHTTPS(true) : setHTTPS(false);
}} }}
disabled={ disabled={location.protocol === "https:" ||
location.protocol === "https:" || connectionInProgress connectionInProgress}
}
checked={https} checked={https}
{...rest} {...rest}
/> />

4
src/components/PageComponents/Connect/Serial.tsx

@ -1,4 +1,4 @@
import type { TabElementProps } from "@app/components/Dialog/NewDeviceDialog"; import type { TabElementProps } from "../../Dialog/NewDeviceDialog.tsx";
import { Button } from "@components/UI/Button.tsx"; import { Button } from "@components/UI/Button.tsx";
import { Mono } from "@components/generic/Mono.tsx"; import { Mono } from "@components/generic/Mono.tsx";
import { useAppStore } from "@core/stores/appStore.ts"; import { useAppStore } from "@core/stores/appStore.ts";
@ -9,7 +9,7 @@ import { MeshDevice } from "@meshtastic/core";
import { TransportWebSerial } from "@meshtastic/transport-web-serial"; import { TransportWebSerial } from "@meshtastic/transport-web-serial";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
export const Serial = ({ closeDialog }: TabElementProps): JSX.Element => { export const Serial = ({ closeDialog }: TabElementProps) => {
const [serialPorts, setSerialPorts] = useState<SerialPort[]>([]); const [serialPorts, setSerialPorts] = useState<SerialPort[]>([]);
const { addDevice } = useDeviceStore(); const { addDevice } = useDeviceStore();
const { setSelectedDevice } = useAppStore(); const { setSelectedDevice } = useAppStore();

65
src/components/PageComponents/Map/NodeDetail.tsx

@ -1,8 +1,8 @@
import { Separator } from "@app/components/UI/Seperator"; import { Separator } from "@components/UI/Seperator.tsx";
import { Heading } from "@app/components/UI/Typography/Heading"; import { Heading } from "@components/UI/Typography/Heading.tsx";
import { Subtle } from "@app/components/UI/Typography/Subtle.tsx"; import { Subtle } from "@components/UI/Typography/Subtle.tsx";
import { formatQuantity } from "@app/core/utils/string"; import { formatQuantity } from "@core/utils/string.ts";
import { Avatar } from "@components/UI/Avatar"; import { Avatar } from "@components/UI/Avatar.tsx";
import { Mono } from "@components/generic/Mono.tsx"; import { Mono } from "@components/generic/Mono.tsx";
import { TimeAgo } from "@components/generic/TimeAgo.tsx"; import { TimeAgo } from "@components/generic/TimeAgo.tsx";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
@ -34,24 +34,26 @@ export const NodeDetail = ({ node }: NodeDetailProps) => {
<div className="dark:text-slate-900 p-1"> <div className="dark:text-slate-900 p-1">
<div className="flex gap-2"> <div className="flex gap-2">
<div className="flex flex-col items-center gap-2 min-w-6 pt-1"> <div className="flex flex-col items-center gap-2 min-w-6 pt-1">
<Avatar text={node.user?.shortName} /> <Avatar text={node.user?.shortName ?? "UNK"} />
<div> <div>
{node.user?.publicKey && node.user?.publicKey.length > 0 ? ( {node.user?.publicKey && node.user?.publicKey.length > 0
<LockIcon ? (
className="text-green-600" <LockIcon
size={12} className="text-green-600"
strokeWidth={3} size={12}
aria-label="Public Key Enabled" strokeWidth={3}
/> aria-label="Public Key Enabled"
) : ( />
<LockOpenIcon )
className="text-yellow-500" : (
size={12} <LockOpenIcon
strokeWidth={3} className="text-yellow-500"
aria-label="No Public Key" size={12}
/> strokeWidth={3}
)} aria-label="No Public Key"
/>
)}
</div> </div>
<Star <Star
@ -69,19 +71,16 @@ export const NodeDetail = ({ node }: NodeDetailProps) => {
{!!node.deviceMetrics?.batteryLevel && ( {!!node.deviceMetrics?.batteryLevel && (
<div <div
className="flex items-center gap-1" className="flex items-center gap-1"
title={`${ title={`${node.deviceMetrics?.voltage?.toPrecision(3) ?? "Unknown"
node.deviceMetrics?.voltage?.toPrecision(3) ?? "Unknown" } volts`}
} volts`}
> >
{node.deviceMetrics?.batteryLevel > 100 ? ( {node.deviceMetrics?.batteryLevel > 100
<BatteryChargingIcon size={22} /> ? <BatteryChargingIcon size={22} />
) : node.deviceMetrics?.batteryLevel > 80 ? ( : node.deviceMetrics?.batteryLevel > 80
<BatteryFullIcon size={22} /> ? <BatteryFullIcon size={22} />
) : node.deviceMetrics?.batteryLevel > 20 ? ( : node.deviceMetrics?.batteryLevel > 20
<BatteryMediumIcon size={22} /> ? <BatteryMediumIcon size={22} />
) : ( : <BatteryLowIcon size={22} />}
<BatteryLowIcon size={22} />
)}
<Subtle aria-label="Battery"> <Subtle aria-label="Battery">
{node.deviceMetrics?.batteryLevel > 100 {node.deviceMetrics?.batteryLevel > 100
? "Charging" ? "Charging"

16
src/components/PageComponents/Messages/ChannelChat.tsx

@ -1,13 +1,9 @@
import { import { type MessageWithState, useDevice } from "@core/stores/deviceStore.ts";
type MessageWithState,
useDevice,
} from "@app/core/stores/deviceStore.ts";
import { Message } from "@components/PageComponents/Messages/Message.tsx"; import { Message } from "@components/PageComponents/Messages/Message.tsx";
import { MessageInput } from "@components/PageComponents/Messages/MessageInput.tsx"; import { MessageInput } from "@components/PageComponents/Messages/MessageInput.tsx";
import type { Types } from "@meshtastic/core"; import type { Types } from "@meshtastic/core";
import { InboxIcon } from "lucide-react"; import { InboxIcon } from "lucide-react";
import { useCallback, useEffect, useRef } from "react"; import { useCallback, useEffect, useRef } from "react";
import type { JSX } from "react";
export interface ChannelChatProps { export interface ChannelChatProps {
messages?: MessageWithState[]; messages?: MessageWithState[];
@ -26,7 +22,7 @@ export const ChannelChat = ({
messages, messages,
channel, channel,
to, to,
}: ChannelChatProps): JSX.Element => { }: ChannelChatProps) => {
const { nodes } = useDevice(); const { nodes } = useDevice();
const messagesEndRef = useRef<HTMLDivElement>(null); const messagesEndRef = useRef<HTMLDivElement>(null);
const scrollContainerRef = useRef<HTMLDivElement>(null); const scrollContainerRef = useRef<HTMLDivElement>(null);
@ -34,8 +30,7 @@ export const ChannelChat = ({
const scrollToBottom = useCallback(() => { const scrollToBottom = useCallback(() => {
const scrollContainer = scrollContainerRef.current; const scrollContainer = scrollContainerRef.current;
if (scrollContainer) { if (scrollContainer) {
const isNearBottom = const isNearBottom = scrollContainer.scrollHeight -
scrollContainer.scrollHeight -
scrollContainer.scrollTop - scrollContainer.scrollTop -
scrollContainer.clientHeight < scrollContainer.clientHeight <
100; 100;
@ -72,9 +67,8 @@ export const ChannelChat = ({
key={message.id} key={message.id}
message={message} message={message}
sender={nodes.get(message.from)} sender={nodes.get(message.from)}
lastMsgSameUser={ lastMsgSameUser={index > 0 &&
index > 0 && messages[index - 1].from === message.from messages[index - 1].from === message.from}
}
/> />
); );
})} })}

33
src/components/PageComponents/Messages/Message.tsx

@ -4,13 +4,13 @@ import {
TooltipContent, TooltipContent,
TooltipProvider, TooltipProvider,
TooltipTrigger, TooltipTrigger,
} from "@app/components/UI/Tooltip"; } from "@components/UI/Tooltip.tsx";
import { import {
type MessageWithState, type MessageWithState,
useDeviceStore, useDeviceStore,
} from "@app/core/stores/deviceStore.ts"; } from "@core/stores/deviceStore.ts";
import { cn } from "@app/core/utils/cn"; import { cn } from "@core/utils/cn.ts";
import { Avatar } from "@components/UI/Avatar"; import { Avatar } from "@components/UI/Avatar.tsx";
import type { Protobuf } from "@meshtastic/core"; import type { Protobuf } from "@meshtastic/core";
import { AlertCircle, CheckCircle2, CircleEllipsis } from "lucide-react"; import { AlertCircle, CheckCircle2, CircleEllipsis } from "lucide-react";
import type { LucideIcon } from "lucide-react"; import type { LucideIcon } from "lucide-react";
@ -44,13 +44,13 @@ const STATUS_TEXT_MAP: Record<MessageState, string> = {
[MESSAGE_STATES.ACK]: "Message delivered", [MESSAGE_STATES.ACK]: "Message delivered",
[MESSAGE_STATES.WAITING]: "Waiting for delivery", [MESSAGE_STATES.WAITING]: "Waiting for delivery",
[MESSAGE_STATES.FAILED]: "Delivery failed", [MESSAGE_STATES.FAILED]: "Delivery failed",
} as const; };
const STATUS_ICON_MAP: Record<MessageState, LucideIcon> = { const STATUS_ICON_MAP: Record<MessageState, LucideIcon> = {
[MESSAGE_STATES.ACK]: CheckCircle2, [MESSAGE_STATES.ACK]: CheckCircle2,
[MESSAGE_STATES.WAITING]: CircleEllipsis, [MESSAGE_STATES.WAITING]: CircleEllipsis,
[MESSAGE_STATES.FAILED]: AlertCircle, [MESSAGE_STATES.FAILED]: AlertCircle,
} as const; };
const getStatusText = (state: MessageState): string => STATUS_TEXT_MAP[state]; const getStatusText = (state: MessageState): string => STATUS_TEXT_MAP[state];
@ -93,7 +93,6 @@ const StatusIcon = ({ state, className, ...otherProps }: StatusIconProps) => {
const getMessageTextStyles = (state: MessageState) => { const getMessageTextStyles = (state: MessageState) => {
const isAcknowledged = state === MESSAGE_STATES.ACK; const isAcknowledged = state === MESSAGE_STATES.ACK;
const isFailed = state === MESSAGE_STATES.FAILED; const isFailed = state === MESSAGE_STATES.FAILED;
const isWaiting = state === MESSAGE_STATES.WAITING;
return cn( return cn(
"break-words overflow-hidden", "break-words overflow-hidden",
@ -144,16 +143,18 @@ export const Message = ({ lastMsgSameUser, message, sender }: MessageProps) => {
)} )}
> >
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
{!lastMsgSameUser ? ( {!lastMsgSameUser
<div className="flex place-items-center gap-2 mb-1"> ? (
<Avatar text={messageUser?.shortName} /> <div className="flex place-items-center gap-2 mb-1">
<div className="flex flex-col"> <Avatar text={messageUser?.shortName ?? "UNK"} />
<span className="font-medium text-slate-900 dark:text-white truncate"> <div className="flex flex-col">
{messageUser?.longName} <span className="font-medium text-slate-900 dark:text-white truncate">
</span> {messageUser?.longName}
</span>
</div>
</div> </div>
</div> )
) : null} : null}
</div> </div>
<TimeDisplay date={message.rxTime} /> <TimeDisplay date={message.rxTime} />
<div className="flex place-items-center gap-2 pb-2"> <div className="flex place-items-center gap-2 pb-2">

22
src/components/PageComponents/Messages/MessageInput.tsx

@ -1,16 +1,10 @@
import { debounce } from "@app/core/utils/debounce"; import { debounce } from "../../../core/utils/debounce.ts";
import { Button } from "@components/UI/Button.tsx"; import { Button } from "@components/UI/Button.tsx";
import { Input } from "@components/UI/Input.tsx"; import { Input } from "@components/UI/Input.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import type { Types } from "@meshtastic/core"; import type { Types } from "@meshtastic/core";
import { SendIcon } from "lucide-react"; import { SendIcon } from "lucide-react";
import { import { startTransition, useCallback, useMemo, useState } from "react";
type JSX,
startTransition,
useCallback,
useMemo,
useState,
} from "react";
export interface MessageInputProps { export interface MessageInputProps {
to: Types.Destination; to: Types.Destination;
@ -22,7 +16,7 @@ export const MessageInput = ({
to, to,
channel, channel,
maxBytes, maxBytes,
}: MessageInputProps): JSX.Element => { }: MessageInputProps) => {
const { const {
connection, connection,
setMessageState, setMessageState,
@ -43,7 +37,7 @@ export const MessageInput = ({
async (message: string) => { async (message: string) => {
await connection await connection
?.sendText(message, to, true, channel) ?.sendText(message, to, true, channel)
.then((id) => .then((id: number) =>
setMessageState( setMessageState(
to === "broadcast" ? "broadcast" : "direct", to === "broadcast" ? "broadcast" : "direct",
channel, channel,
@ -51,7 +45,7 @@ export const MessageInput = ({
myNodeNum, myNodeNum,
id, id,
"ack", "ack",
), )
) )
.catch((e: Types.PacketError) => .catch((e: Types.PacketError) =>
setMessageState( setMessageState(
@ -61,7 +55,7 @@ export const MessageInput = ({
myNodeNum, myNodeNum,
e.id, e.id,
e.error, e.error,
), )
); );
}, },
[channel, connection, myNodeNum, setMessageState, to], [channel, connection, myNodeNum, setMessageState, to],
@ -82,7 +76,7 @@ export const MessageInput = ({
<div className="flex gap-2"> <div className="flex gap-2">
<form <form
className="w-full" className="w-full"
action={async (formData: FormData) => { action={(formData: FormData) => {
// prevent user from sending blank/empty message // prevent user from sending blank/empty message
if (localDraft === "") return; if (localDraft === "") return;
const message = formData.get("messageInput") as string; const message = formData.get("messageInput") as string;
@ -97,7 +91,7 @@ export const MessageInput = ({
<div className="flex grow gap-2"> <div className="flex grow gap-2">
<span className="w-full"> <span className="w-full">
<Input <Input
autoFocus={true} autoFocus
minLength={1} minLength={1}
name="messageInput" name="messageInput"
placeholder="Enter Message" placeholder="Enter Message"

41
src/components/PageComponents/Messages/TraceRoute.tsx

@ -1,7 +1,6 @@
import { useDevice } from "@app/core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import type { Protobuf } from "@meshtastic/core"; import type { Protobuf } from "@meshtastic/core";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import type { JSX } from "react";
export interface TraceRouteProps { export interface TraceRouteProps {
from?: Protobuf.Mesh.NodeInfo; from?: Protobuf.Mesh.NodeInfo;
@ -19,7 +18,7 @@ export const TraceRoute = ({
routeBack, routeBack,
snrTowards, snrTowards,
snrBack, snrBack,
}: TraceRouteProps): JSX.Element => { }: TraceRouteProps) => {
const { nodes } = useDevice(); const { nodes } = useDevice();
return ( return (
@ -38,23 +37,25 @@ export const TraceRoute = ({
))} ))}
{from?.user?.longName} {from?.user?.longName}
</span> </span>
{routeBack ? ( {routeBack
<span className="ml-4 border-l-2 border-l-background-primary pl-2 text-text-primary"> ? (
<p className="font-semibold">Route back:</p> <span className="ml-4 border-l-2 border-l-background-primary pl-2 text-text-primary">
<p>{from?.user?.longName}</p> <p className="font-semibold">Route back:</p>
<p> {snrBack?.[0] ? snrBack[0] : "??"}dB</p> <p>{from?.user?.longName}</p>
{routeBack.map((hop, i) => ( <p> {snrBack?.[0] ? snrBack[0] : "??"}dB</p>
<span key={nodes.get(hop)?.num}> {routeBack.map((hop, i) => (
<p> <span key={nodes.get(hop)?.num}>
{nodes.get(hop)?.user?.longName ?? <p>
`!${numberToHexUnpadded(hop)}`} {nodes.get(hop)?.user?.longName ??
</p> `!${numberToHexUnpadded(hop)}`}
<p> {snrBack?.[i + 1] ? snrBack[i + 1] : "??"}dB</p> </p>
</span> <p> {snrBack?.[i + 1] ? snrBack[i + 1] : "??"}dB</p>
))} </span>
{to?.user?.longName} ))}
</span> {to?.user?.longName}
) : null} </span>
)
: null}
</div> </div>
); );
}; };

4
src/components/PageComponents/ModuleConfig/AmbientLighting.tsx

@ -1,10 +1,10 @@
import { useDevice } from "@app/core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import type { AmbientLightingValidation } from "@app/validation/moduleConfig/ambientLighting.tsx"; import type { AmbientLightingValidation } from "@app/validation/moduleConfig/ambientLighting.tsx";
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
export const AmbientLighting = (): JSX.Element => { export const AmbientLighting = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();
const onSubmit = (data: AmbientLightingValidation) => { const onSubmit = (data: AmbientLightingValidation) => {

2
src/components/PageComponents/ModuleConfig/Audio.tsx

@ -4,7 +4,7 @@ import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
export const Audio = (): JSX.Element => { export const Audio = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();
const onSubmit = (data: AudioValidation) => { const onSubmit = (data: AudioValidation) => {

17
src/components/PageComponents/ModuleConfig/CannedMessage.tsx

@ -4,7 +4,7 @@ import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
export const CannedMessage = (): JSX.Element => { export const CannedMessage = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();
const onSubmit = (data: CannedMessageValidation) => { const onSubmit = (data: CannedMessageValidation) => {
@ -63,9 +63,8 @@ export const CannedMessage = (): JSX.Element => {
label: "Clockwise event", label: "Clockwise event",
description: "Select input event.", description: "Select input event.",
properties: { properties: {
enumValue: enumValue: Protobuf.ModuleConfig
Protobuf.ModuleConfig .ModuleConfig_CannedMessageConfig_InputEventChar,
.ModuleConfig_CannedMessageConfig_InputEventChar,
}, },
}, },
{ {
@ -74,9 +73,8 @@ export const CannedMessage = (): JSX.Element => {
label: "Counter Clockwise event", label: "Counter Clockwise event",
description: "Select input event.", description: "Select input event.",
properties: { properties: {
enumValue: enumValue: Protobuf.ModuleConfig
Protobuf.ModuleConfig .ModuleConfig_CannedMessageConfig_InputEventChar,
.ModuleConfig_CannedMessageConfig_InputEventChar,
}, },
}, },
{ {
@ -85,9 +83,8 @@ export const CannedMessage = (): JSX.Element => {
label: "Press event", label: "Press event",
description: "Select input event", description: "Select input event",
properties: { properties: {
enumValue: enumValue: Protobuf.ModuleConfig
Protobuf.ModuleConfig .ModuleConfig_CannedMessageConfig_InputEventChar,
.ModuleConfig_CannedMessageConfig_InputEventChar,
}, },
}, },
{ {

4
src/components/PageComponents/ModuleConfig/DetectionSensor.tsx

@ -1,10 +1,10 @@
import { useDevice } from "@app/core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import type { DetectionSensorValidation } from "@app/validation/moduleConfig/detectionSensor.tsx"; import type { DetectionSensorValidation } from "@app/validation/moduleConfig/detectionSensor.tsx";
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
export const DetectionSensor = (): JSX.Element => { export const DetectionSensor = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();
const onSubmit = (data: DetectionSensorValidation) => { const onSubmit = (data: DetectionSensorValidation) => {

2
src/components/PageComponents/ModuleConfig/ExternalNotification.tsx

@ -4,7 +4,7 @@ import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
export const ExternalNotification = (): JSX.Element => { export const ExternalNotification = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();
const onSubmit = (data: ExternalNotificationValidation) => { const onSubmit = (data: ExternalNotificationValidation) => {

55
src/components/PageComponents/ModuleConfig/MQTT.tsx

@ -1,10 +1,10 @@
import { useDevice } from "@app/core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import type { MqttValidation } from "@app/validation/moduleConfig/mqtt.tsx"; import type { MqttValidation } from "@app/validation/moduleConfig/mqtt.tsx";
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
export const MQTT = (): JSX.Element => { export const MQTT = () => {
const { config, moduleConfig, setWorkingModuleConfig } = useDevice(); const { config, moduleConfig, setWorkingModuleConfig } = useDevice();
const onSubmit = (data: MqttValidation) => { const onSubmit = (data: MqttValidation) => {
@ -165,32 +165,31 @@ export const MQTT = (): JSX.Element => {
description: description:
"Position shared will be accurate within this distance", "Position shared will be accurate within this distance",
properties: { properties: {
enumValue: enumValue: config.display?.units === 0
config.display?.units === 0 ? {
? { "Within 23 km": 10,
"Within 23 km": 10, "Within 12 km": 11,
"Within 12 km": 11, "Within 5.8 km": 12,
"Within 5.8 km": 12, "Within 2.9 km": 13,
"Within 2.9 km": 13, "Within 1.5 km": 14,
"Within 1.5 km": 14, "Within 700 m": 15,
"Within 700 m": 15, "Within 350 m": 16,
"Within 350 m": 16, "Within 200 m": 17,
"Within 200 m": 17, "Within 90 m": 18,
"Within 90 m": 18, "Within 50 m": 19,
"Within 50 m": 19, }
} : {
: { "Within 15 miles": 10,
"Within 15 miles": 10, "Within 7.3 miles": 11,
"Within 7.3 miles": 11, "Within 3.6 miles": 12,
"Within 3.6 miles": 12, "Within 1.8 miles": 13,
"Within 1.8 miles": 13, "Within 0.9 miles": 14,
"Within 0.9 miles": 14, "Within 0.5 miles": 15,
"Within 0.5 miles": 15, "Within 0.2 miles": 16,
"Within 0.2 miles": 16, "Within 600 feet": 17,
"Within 600 feet": 17, "Within 300 feet": 18,
"Within 300 feet": 18, "Within 150 feet": 19,
"Within 150 feet": 19, },
},
}, },
disabledBy: [ disabledBy: [
{ {

4
src/components/PageComponents/ModuleConfig/NeighborInfo.tsx

@ -1,10 +1,10 @@
import { useDevice } from "@app/core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import type { NeighborInfoValidation } from "@app/validation/moduleConfig/neighborInfo.tsx"; import type { NeighborInfoValidation } from "@app/validation/moduleConfig/neighborInfo.tsx";
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
export const NeighborInfo = (): JSX.Element => { export const NeighborInfo = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();
const onSubmit = (data: NeighborInfoValidation) => { const onSubmit = (data: NeighborInfoValidation) => {

2
src/components/PageComponents/ModuleConfig/Paxcounter.tsx

@ -4,7 +4,7 @@ import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
export const Paxcounter = (): JSX.Element => { export const Paxcounter = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();
const onSubmit = (data: PaxcounterValidation) => { const onSubmit = (data: PaxcounterValidation) => {

2
src/components/PageComponents/ModuleConfig/RangeTest.tsx

@ -4,7 +4,7 @@ import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
export const RangeTest = (): JSX.Element => { export const RangeTest = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();
const onSubmit = (data: RangeTestValidation) => { const onSubmit = (data: RangeTestValidation) => {

2
src/components/PageComponents/ModuleConfig/Serial.tsx

@ -4,7 +4,7 @@ import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
export const Serial = (): JSX.Element => { export const Serial = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();
const onSubmit = (data: SerialValidation) => { const onSubmit = (data: SerialValidation) => {

2
src/components/PageComponents/ModuleConfig/StoreForward.tsx

@ -4,7 +4,7 @@ import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
export const StoreForward = (): JSX.Element => { export const StoreForward = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();
const onSubmit = (data: StoreForwardValidation) => { const onSubmit = (data: StoreForwardValidation) => {

2
src/components/PageComponents/ModuleConfig/Telemetry.tsx

@ -4,7 +4,7 @@ import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
export const Telemetry = (): JSX.Element => { export const Telemetry = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();
const onSubmit = (data: TelemetryValidation) => { const onSubmit = (data: TelemetryValidation) => {

13
src/components/PageLayout.tsx

@ -1,9 +1,10 @@
import { cn } from "@app/core/utils/cn.ts"; import { cn } from "@core/utils/cn.ts";
import { AlignLeftIcon, type LucideIcon } from "lucide-react"; import { AlignLeftIcon, type LucideIcon } from "lucide-react";
import Footer from "@components/UI/Footer.tsx";
import { Spinner } from "@components/UI/Spinner.tsx";
import { ErrorBoundary } from "react-error-boundary"; import { ErrorBoundary } from "react-error-boundary";
import { ErrorPage } from "./UI/ErrorPage"; import { ErrorPage } from "@components/UI/ErrorPage.tsx";
import Footer from "./UI/Footer";
import { Spinner } from "./UI/Spinner";
export interface PageLayoutProps { export interface PageLayoutProps {
label: string; label: string;
@ -46,9 +47,7 @@ export const PageLayout = ({
className="transition-all hover:text-accent" className="transition-all hover:text-accent"
onClick={action.onClick} onClick={action.onClick}
> >
{action?.isLoading ? ( {action?.isLoading ? <Spinner /> : (
<Spinner />
) : (
<action.icon <action.icon
className={action.iconClasses} className={action.iconClasses}
aria-disabled={action.disabled} aria-disabled={action.disabled}

128
src/components/Sidebar.tsx

@ -23,7 +23,7 @@ export interface SidebarProps {
children?: React.ReactNode; children?: React.ReactNode;
} }
export const Sidebar = ({ children }: SidebarProps): JSX.Element => { export const Sidebar = ({ children }: SidebarProps) => {
const { hardware, nodes, metadata } = useDevice(); const { hardware, nodes, metadata } = useDevice();
const myNode = nodes.get(hardware.myNodeNum); const myNode = nodes.get(hardware.myNodeNum);
const myMetadata = metadata.get(0); const myMetadata = metadata.get(0);
@ -64,69 +64,71 @@ export const Sidebar = ({ children }: SidebarProps): JSX.Element => {
}, },
]; ];
return showSidebar ? ( return showSidebar
<div className="min-w-[280px] max-w-min flex-col overflow-y-auto border-r-[0.5px] bg-background-primary border-slate-300 dark:border-slate-700"> ? (
<div className="flex justify-between px-8 pt-6"> <div className="min-w-[280px] max-w-min flex-col overflow-y-auto border-r-[0.5px] bg-background-primary border-slate-300 dark:border-slate-700">
<div> <div className="flex justify-between px-8 pt-6">
<span className="text-lg font-medium"> <div>
{myNode?.user?.shortName ?? "UNK"} <span className="text-lg font-medium">
</span> {myNode?.user?.shortName ?? "UNK"}
<Subtle>{myNode?.user?.longName ?? "UNK"}</Subtle> </span>
<Subtle>{myNode?.user?.longName ?? "UNK"}</Subtle>
</div>
<button
type="button"
className="transition-all hover:text-accent"
onClick={() => setDialogOpen("deviceName", true)}
>
<EditIcon size={16} />
</button>
<button type="button" onClick={() => setShowSidebar(false)}>
<SidebarCloseIcon size={24} />
</button>
</div> </div>
<button <div className="px-8 pb-6">
type="button" <div className="flex items-center">
className="transition-all hover:text-accent" <BatteryMediumIcon size={24} viewBox="0 0 28 24" />
onClick={() => setDialogOpen("deviceName", true)} <Subtle>
> {myNode?.deviceMetrics?.batteryLevel
<EditIcon size={16} /> ? myNode?.deviceMetrics?.batteryLevel > 100
</button> ? "Charging"
<button type="button" onClick={() => setShowSidebar(false)}> : `${myNode?.deviceMetrics?.batteryLevel}%`
<SidebarCloseIcon size={24} /> : "UNK"}
</button> </Subtle>
</div> </div>
<div className="px-8 pb-6"> <div className="flex items-center">
<div className="flex items-center"> <ZapIcon size={24} viewBox="0 0 36 24" />
<BatteryMediumIcon size={24} viewBox={"0 0 28 24"} /> <Subtle>
<Subtle> {myNode?.deviceMetrics?.voltage?.toPrecision(3) ?? "UNK"} volts
{myNode?.deviceMetrics?.batteryLevel </Subtle>
? myNode?.deviceMetrics?.batteryLevel > 100 </div>
? "Charging" <div className="flex items-center">
: `${myNode?.deviceMetrics?.batteryLevel}%` <CpuIcon size={24} viewBox="0 0 36 24" />
: "UNK"} <Subtle>v{myMetadata?.firmwareVersion ?? "UNK"}</Subtle>
</Subtle> </div>
</div>
<div className="flex items-center">
<ZapIcon size={24} viewBox={"0 0 36 24"} />
<Subtle>
{myNode?.deviceMetrics?.voltage?.toPrecision(3) ?? "UNK"} volts
</Subtle>
</div>
<div className="flex items-center">
<CpuIcon size={24} viewBox={"0 0 36 24"} />
<Subtle>v{myMetadata?.firmwareVersion ?? "UNK"}</Subtle>
</div> </div>
</div>
<SidebarSection label="Navigation"> <SidebarSection label="Navigation">
{pages.map((link) => ( {pages.map((link) => (
<SidebarButton <SidebarButton
key={link.name} key={link.name}
label={link.name} label={link.name}
Icon={link.icon} Icon={link.icon}
onClick={() => { onClick={() => {
setActivePage(link.page); setActivePage(link.page);
}} }}
active={link.page === activePage} active={link.page === activePage}
/> />
))} ))}
</SidebarSection> </SidebarSection>
{children} {children}
</div> </div>
) : ( )
<div className="px-1 pt-8 border-r-[0.5px] border-slate-700"> : (
<button type="button" onClick={() => setShowSidebar(true)}> <div className="px-1 pt-8 border-r-[0.5px] border-slate-700">
<SidebarOpenIcon size={24} /> <button type="button" onClick={() => setShowSidebar(true)}>
</button> <SidebarOpenIcon size={24} />
</div> </button>
); </div>
);
}; };

12
src/components/ThemeSwitcher.tsx

@ -1,5 +1,5 @@
import { useTheme } from "@app/core/hooks/useTheme"; import { useTheme } from "../core/hooks/useTheme.ts";
import { cn } from "@app/core/utils/cn"; import { cn } from "../core/utils/cn.ts";
import { Monitor, Moon, Sun } from "lucide-react"; import { Monitor, Moon, Sun } from "lucide-react";
type ThemePreference = "light" | "dark" | "system"; type ThemePreference = "light" | "dark" | "system";
@ -32,11 +32,9 @@ export default function ThemeSwitcher({
className, className,
)} )}
onClick={toggleTheme} onClick={toggleTheme}
aria-label={ aria-label={preference === "system"
preference === "system" ? `System theme (currently ${theme}). Click to change theme.`
? `System theme (currently ${theme}). Click to change theme.` : `Current theme: ${theme}. Click to change theme.`}
: `Current theme: ${theme}. Click to change theme.`
}
> >
{themeIcons[preference]} {themeIcons[preference]}
</button> </button>

4
src/components/Toaster.tsx

@ -5,8 +5,8 @@ import {
ToastProvider, ToastProvider,
ToastTitle, ToastTitle,
ToastViewport, ToastViewport,
} from "@components/UI/Toast"; } from "./UI/Toast.tsx";
import { useToast } from "@core/hooks/useToast"; import { useToast } from "../core/hooks/useToast.ts";
export function Toaster() { export function Toaster() {
const { toasts } = useToast(); const { toasts } = useToast();

2
src/components/UI/Avatar.tsx

@ -1,4 +1,4 @@
import { cn } from "@app/core/utils/cn"; import { cn } from "../../core/utils/cn.ts";
import type React from "react"; import type React from "react";
type RGBColor = { type RGBColor = {

8
src/components/UI/Button.tsx

@ -1,4 +1,4 @@
import { type VariantProps, cva } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react"; import * as React from "react";
import { cn } from "@core/utils/cn.ts"; import { cn } from "@core/utils/cn.ts";
@ -20,7 +20,8 @@ const buttonVariants = cva(
"bg-slate-100 text-slate-700 hover:bg-slate-200 dark:bg-slate-500 dark:text-white dark:hover:bg-slate-400", "bg-slate-100 text-slate-700 hover:bg-slate-200 dark:bg-slate-500 dark:text-white dark:hover:bg-slate-400",
ghost: ghost:
"bg-transparent hover:bg-slate-100 dark:hover:bg-slate-800 dark:text-slate-100 dark:hover:text-slate-100 data-[state=open]:bg-transparent dark:data-[state=open]:bg-transparent", "bg-transparent hover:bg-slate-100 dark:hover:bg-slate-800 dark:text-slate-100 dark:hover:text-slate-100 data-[state=open]:bg-transparent dark:data-[state=open]:bg-transparent",
link: "bg-transparent underline-offset-4 hover:underline text-slate-900 dark:text-slate-100 hover:bg-transparent dark:hover:bg-transparent", link:
"bg-transparent underline-offset-4 hover:underline text-slate-900 dark:text-slate-100 hover:bg-transparent dark:hover:bg-transparent",
}, },
size: { size: {
default: "h-10 py-2 px-4", default: "h-10 py-2 px-4",
@ -38,7 +39,8 @@ const buttonVariants = cva(
export type ButtonVariant = VariantProps<typeof buttonVariants>["variant"]; export type ButtonVariant = VariantProps<typeof buttonVariants>["variant"];
export interface ButtonProps export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>, extends
React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {} VariantProps<typeof buttonVariants> {}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(

6
src/components/UI/Command.tsx

@ -144,11 +144,11 @@ CommandShortcut.displayName = "CommandShortcut";
export { export {
Command, Command,
CommandDialog, CommandDialog,
CommandInput,
CommandList,
CommandEmpty, CommandEmpty,
CommandGroup, CommandGroup,
CommandInput,
CommandItem, CommandItem,
CommandShortcut, CommandList,
CommandSeparator, CommandSeparator,
CommandShortcut,
}; };

8
src/components/UI/Dialog.tsx

@ -23,7 +23,7 @@ DialogPortal.displayName = DialogPrimitive.Portal.displayName;
const DialogOverlay = React.forwardRef< const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>, React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, children, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay <DialogPrimitive.Overlay
className={cn( className={cn(
"fixed inset-0 z-50 bg-black/50 backdrop-blur-xs transition-opacity animate-in fade-in", "fixed inset-0 z-50 bg-black/50 backdrop-blur-xs transition-opacity animate-in fade-in",
@ -113,10 +113,10 @@ DialogDescription.displayName = DialogPrimitive.Description.displayName;
export { export {
Dialog, Dialog,
DialogTrigger,
DialogContent, DialogContent,
DialogHeader, DialogDescription,
DialogFooter, DialogFooter,
DialogHeader,
DialogTitle, DialogTitle,
DialogDescription, DialogTrigger,
}; };

12
src/components/UI/DropdownMenu.tsx

@ -184,18 +184,18 @@ DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
export { export {
DropdownMenu, DropdownMenu,
DropdownMenuTrigger, DropdownMenuCheckboxItem,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel, DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuShortcut, DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub, DropdownMenuSub,
DropdownMenuSubContent, DropdownMenuSubContent,
DropdownMenuSubTrigger, DropdownMenuSubTrigger,
DropdownMenuRadioGroup, DropdownMenuTrigger,
}; };

9
src/components/UI/ErrorPage.tsx

@ -1,8 +1,9 @@
import newGithubIssueUrl from "@app/core/utils/github"; import newGithubIssueUrl from "../../core/utils/github.ts";
import { ExternalLink } from "lucide-react"; import { ExternalLink } from "lucide-react";
import { Heading } from "./Typography/Heading"; import { Heading } from "./Typography/Heading.tsx";
import { Link } from "./Typography/Link"; import { Link } from "./Typography/Link.tsx";
import { P } from "./Typography/P"; import { P } from "./Typography/P.tsx";
export function ErrorPage({ error }: { error: Error }) { export function ErrorPage({ error }: { error: Error }) {
if (!error) { if (!error) {

54
src/components/UI/Footer.tsx

@ -1,30 +1,32 @@
import { cn } from "@app/core/utils/cn"; import { cn } from "@core/utils/cn.ts"
import React from "react";
export interface FooterProps extends React.HTMLAttributes<HTMLElement> {} type FooterProps = {
className?: string;
}
const Footer = React.forwardRef<HTMLElement, FooterProps>( const Footer = ({ className, ...props }: FooterProps) => {
({ className, ...props }, ref) => { return (
return ( <footer
<footer className={cn("flex mt-auto justify-center p-2", className)}> className={cn("flex mt-auto justify-center p-2", className)}
<p> {...props}
<a >
href="https://vercel.com/?utm_source=meshtastic&utm_campaign=oss" <p>
className="hover:underline text-link" <a
> href="https://vercel.com/?utm_source=meshtastic&utm_campaign=oss"
Powered by Vercel className="hover:underline text-link"
</a>{" "} >
| Meshtastic® is a registered trademark of Meshtastic LLC. |{" "} Powered by Vercel
<a </a>{" "}
href="https://meshtastic.org/docs/legal" | Meshtastic® is a registered trademark of Meshtastic LLC. |{" "}
className="hover:underline text-link" <a
> href="https://meshtastic.org/docs/legal"
Legal Information className="hover:underline text-link"
</a> >
</p> Legal Information
</footer> </a>
); </p>
}, </footer>
); );
}
export default Footer; export default Footer;

8
src/components/UI/Generator.tsx

@ -33,7 +33,7 @@ export interface GeneratorProps extends React.BaseHTMLAttributes<HTMLElement> {
disabled?: boolean; disabled?: boolean;
} }
const Generator = React.forwardRef<HTMLInputElement, GeneratorProps>( const Generator =
( (
{ {
type, type,
@ -53,8 +53,7 @@ const Generator = React.forwardRef<HTMLInputElement, GeneratorProps>(
action, action,
disabled, disabled,
...props ...props
}, }: GeneratorProps
ref,
) => { ) => {
const inputRef = React.useRef<HTMLInputElement>(null); const inputRef = React.useRef<HTMLInputElement>(null);
@ -116,8 +115,7 @@ const Generator = React.forwardRef<HTMLInputElement, GeneratorProps>(
</div> </div>
</> </>
); );
}, }
);
Generator.displayName = "Button"; Generator.displayName = "Button";
export { Generator }; export { Generator };

5
src/components/UI/Input.tsx

@ -1,7 +1,7 @@
import * as React from "react"; import * as React from "react";
import { cn } from "@core/utils/cn.ts"; import { cn } from "@core/utils/cn.ts";
import { type VariantProps, cva } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import type { LucideIcon } from "lucide-react"; import type { LucideIcon } from "lucide-react";
const inputVariants = cva( const inputVariants = cva(
@ -20,7 +20,8 @@ const inputVariants = cva(
); );
export interface InputProps export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement>, extends
React.InputHTMLAttributes<HTMLInputElement>,
VariantProps<typeof inputVariants> { VariantProps<typeof inputVariants> {
prefix?: string; prefix?: string;
suffix?: string; suffix?: string;

16
src/components/UI/Menubar.tsx

@ -216,19 +216,19 @@ MenubarShortcut.displayname = "MenubarShortcut";
export { export {
Menubar, Menubar,
MenubarMenu, MenubarCheckboxItem,
MenubarTrigger,
MenubarContent, MenubarContent,
MenubarGroup,
MenubarItem, MenubarItem,
MenubarSeparator,
MenubarLabel, MenubarLabel,
MenubarCheckboxItem, MenubarMenu,
MenubarPortal,
MenubarRadioGroup, MenubarRadioGroup,
MenubarRadioItem, MenubarRadioItem,
MenubarPortal, MenubarSeparator,
MenubarShortcut,
MenubarSub,
MenubarSubContent, MenubarSubContent,
MenubarSubTrigger, MenubarSubTrigger,
MenubarGroup, MenubarTrigger,
MenubarSub,
MenubarShortcut,
}; };

25
src/components/UI/Modal.tsx

@ -1,25 +0,0 @@
export interface ModalProps {
title: string;
actions?: JSX.Element[];
children: React.ReactNode;
}
export const Modal = ({
title,
actions,
children,
}: ModalProps): JSX.Element => {
return (
<div className="rounded-md overflow-hidden w-full">
<div className="flex h-12 px-3 bg-slate-200 dark:bg-slate-700 justify-between">
<h2 className="my-auto font-semibold text-lg">{title}</h2>
{actions && (
<div className="my-auto">{actions.map((action) => action)}</div>
)}
</div>
<div className="h-full border border-slate-200 dark:border-slate-700">
{children}
</div>
</div>
);
};

7
src/components/UI/MultiSelect.tsx

@ -1,4 +1,4 @@
import { cn } from "@app/core/utils/cn"; import { cn } from "../../core/utils/cn.ts";
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
import { Check } from "lucide-react"; import { Check } from "lucide-react";
@ -8,9 +8,8 @@ interface MultiSelectProps {
} }
const MultiSelect = ({ children, className = "" }: MultiSelectProps) => { const MultiSelect = ({ children, className = "" }: MultiSelectProps) => {
return ( return <div className={cn("flex flex-wrap gap-2", className)}>{children}
<div className={cn("flex flex-wrap gap-2", className)}>{children}</div> </div>;
);
}; };
interface MultiSelectItemProps { interface MultiSelectItemProps {

2
src/components/UI/Popover.tsx

@ -26,4 +26,4 @@ const PopoverContent = React.forwardRef<
)); ));
PopoverContent.displayName = PopoverPrimitive.Content.displayName; PopoverContent.displayName = PopoverPrimitive.Content.displayName;
export { Popover, PopoverTrigger, PopoverContent }; export { Popover, PopoverContent, PopoverTrigger };

8
src/components/UI/Select.tsx

@ -101,11 +101,11 @@ SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
export { export {
Select, Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent, SelectContent,
SelectLabel, SelectGroup,
SelectItem, SelectItem,
SelectLabel,
SelectSeparator, SelectSeparator,
SelectTrigger,
SelectValue,
}; };

2
src/components/UI/Sidebar/SidebarSection.tsx

@ -1,4 +1,4 @@
import { Heading } from "../Typography/Heading"; import { Heading } from "../Typography/Heading.tsx";
export interface SidebarSectionProps { export interface SidebarSectionProps {
label: string; label: string;

5
src/components/UI/Sidebar/sidebarButton.tsx

@ -1,12 +1,11 @@
import { Button } from "@components/UI/Button.tsx"; import { Button } from "@components/UI/Button.tsx";
import type { LucideIcon } from "lucide-react"; import type { LucideIcon } from "lucide-react";
import type { JSX } from "react";
export interface SidebarButtonProps { export interface SidebarButtonProps {
label: string; label: string;
active?: boolean; active?: boolean;
Icon?: LucideIcon; Icon?: LucideIcon;
element?: JSX.Element; element?;
onClick?: () => void; onClick?: () => void;
} }
@ -16,7 +15,7 @@ export const SidebarButton = ({
Icon, Icon,
element, element,
onClick, onClick,
}: SidebarButtonProps): JSX.Element => ( }: SidebarButtonProps) => (
<Button <Button
onClick={onClick} onClick={onClick}
variant={active ? "subtle" : "ghost"} variant={active ? "subtle" : "ghost"}

2
src/components/UI/Spinner.tsx

@ -1,4 +1,4 @@
import { cn } from "@app/core/utils/cn"; import { cn } from "../../core/utils/cn.ts";
interface SpinnerProps extends React.HTMLAttributes<HTMLDivElement> { interface SpinnerProps extends React.HTMLAttributes<HTMLDivElement> {
size?: "sm" | "md" | "lg"; size?: "sm" | "md" | "lg";

2
src/components/UI/Tabs.tsx

@ -50,4 +50,4 @@ const TabsContent = React.forwardRef<
)); ));
TabsContent.displayName = TabsPrimitive.Content.displayName; TabsContent.displayName = TabsPrimitive.Content.displayName;
export { Tabs, TabsList, TabsTrigger, TabsContent }; export { Tabs, TabsContent, TabsList, TabsTrigger };

20
src/components/UI/Toast.tsx

@ -1,9 +1,9 @@
import * as ToastPrimitives from "@radix-ui/react-toast"; import * as ToastPrimitives from "@radix-ui/react-toast";
import { type VariantProps, cva } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import { X } from "lucide-react"; import { X } from "lucide-react";
import * as React from "react"; import * as React from "react";
import { cn } from "@core/utils/cn"; import { cn } from "../../core/utils/cn.ts";
const ToastProvider = ToastPrimitives.Provider; const ToastProvider = ToastPrimitives.Provider;
@ -41,8 +41,8 @@ const toastVariants = cva(
const Toast = React.forwardRef< const Toast = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Root>, React.ElementRef<typeof ToastPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> & & React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root>
VariantProps<typeof toastVariants> & VariantProps<typeof toastVariants>
>(({ className, variant, ...props }, ref) => { >(({ className, variant, ...props }, ref) => {
return ( return (
<ToastPrimitives.Root <ToastPrimitives.Root
@ -116,13 +116,13 @@ type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
type ToastActionElement = React.ReactElement<typeof ToastAction>; type ToastActionElement = React.ReactElement<typeof ToastAction>;
export { export {
type ToastProps, Toast,
ToastAction,
type ToastActionElement, type ToastActionElement,
ToastClose,
ToastDescription,
type ToastProps,
ToastProvider, ToastProvider,
ToastViewport,
Toast,
ToastTitle, ToastTitle,
ToastDescription, ToastViewport,
ToastClose,
ToastAction,
}; };

4
src/components/UI/Tooltip.tsx

@ -29,8 +29,8 @@ TooltipContent.displayName = TooltipPrimitive.Content.displayName;
export { export {
Tooltip, Tooltip,
TooltipTrigger, TooltipArrow,
TooltipContent, TooltipContent,
TooltipProvider, TooltipProvider,
TooltipArrow, TooltipTrigger,
}; };

2
src/components/UI/Typography/Blockquote.tsx

@ -2,7 +2,7 @@ export interface BlockquoteProps {
children: React.ReactNode; children: React.ReactNode;
} }
export const BlockQuote = ({ children }: BlockquoteProps): JSX.Element => ( export const BlockQuote = ({ children }: BlockquoteProps) => (
<blockquote className="mt-6 border-l-2 border-slate-300 pl-6 italic text-slate-800 dark:border-slate-600 dark:text-slate-200"> <blockquote className="mt-6 border-l-2 border-slate-300 pl-6 italic text-slate-800 dark:border-slate-600 dark:text-slate-200">
{children} {children}
</blockquote> </blockquote>

2
src/components/UI/Typography/Code.tsx

@ -2,7 +2,7 @@ export interface CodeProps {
children: React.ReactNode; children: React.ReactNode;
} }
export const Code = ({ children }: CodeProps): JSX.Element => ( export const Code = ({ children }: CodeProps) => (
<code className="relative rounded-sm bg-slate-100 px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold text-slate-900 dark:bg-slate-800 dark:text-slate-400"> <code className="relative rounded-sm bg-slate-100 px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold text-slate-900 dark:bg-slate-800 dark:text-slate-400">
{children} {children}
</code> </code>

3
src/components/UI/Typography/Heading.tsx

@ -2,7 +2,8 @@ import type React from "react";
const headingStyles = { const headingStyles = {
h1: "scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl", h1: "scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl",
h2: "scroll-m-20 border-b border-b-slate-200 pb-2 text-3xl font-semibold tracking-tight transition-colors first:mt-0 dark:border-b-slate-700", h2:
"scroll-m-20 border-b border-b-slate-200 pb-2 text-3xl font-semibold tracking-tight transition-colors first:mt-0 dark:border-b-slate-700",
h3: "scroll-m-20 text-2xl font-semibold tracking-tight", h3: "scroll-m-20 text-2xl font-semibold tracking-tight",
h4: "scroll-m-20 text-xl font-semibold tracking-tight", h4: "scroll-m-20 text-xl font-semibold tracking-tight",
h5: "scroll-m-20 text-lg font-medium tracking-tight", h5: "scroll-m-20 text-lg font-medium tracking-tight",

6
src/components/UI/Typography/Link.tsx

@ -1,4 +1,4 @@
import { cn } from "@app/core/utils/cn"; import { cn } from "../../../core/utils/cn.ts";
export interface LinkProps { export interface LinkProps {
href: string; href: string;
@ -6,10 +6,10 @@ export interface LinkProps {
className?: string; className?: string;
} }
export const Link = ({ href, children, className }: LinkProps): JSX.Element => ( export const Link = ({ href, children, className }: LinkProps) => (
<a <a
href={href} href={href}
target={"_blank"} target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className={cn( className={cn(
"font-medium text-slate-900 underline underline-offset-4 dark:text-slate-50", "font-medium text-slate-900 underline underline-offset-4 dark:text-slate-50",

2
src/components/UI/Typography/P.tsx

@ -1,4 +1,4 @@
import { cn } from "@app/core/utils/cn"; import { cn } from "@core/utils/cn.ts";
export interface PProps { export interface PProps {
children: React.ReactNode; children: React.ReactNode;

4
src/components/UI/Typography/Subtle.tsx

@ -1,11 +1,11 @@
import { cn } from "@app/core/utils/cn.ts"; import { cn } from "@core/utils/cn.ts";
export interface SubtleProps { export interface SubtleProps {
className?: string; className?: string;
children: React.ReactNode; children: React.ReactNode;
} }
export const Subtle = ({ className, children }: SubtleProps): JSX.Element => ( export const Subtle = ({ className, children }: SubtleProps) => (
<p className={cn("text-sm text-slate-500 dark:text-slate-400", className)}> <p className={cn("text-sm text-slate-500 dark:text-slate-400", className)}>
{children} {children}
</p> </p>

2
src/components/generic/Blur.tsx

@ -1,4 +1,4 @@
export const Blur = (): JSX.Element => { export const Blur = () => {
return ( return (
<div <div
className="fixed inset-0 backdrop-blur-md backdrop-brightness-press" className="fixed inset-0 backdrop-blur-md backdrop-brightness-press"

2
src/components/generic/Mono.tsx

@ -2,7 +2,7 @@ export const Mono = ({
children, children,
className, className,
...rest ...rest
}: JSX.IntrinsicElements["span"]): JSX.Element => { }: JSX.IntrinsicElements["span"]) => {
return ( return (
<span <span
className={`font-mono text-sm text-text-secondary ${className ?? ""}`} className={`font-mono text-sm text-text-secondary ${className ?? ""}`}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save