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
uses: actions/checkout@v4
- name: Setup bun
uses: oven-sh/setup-bun@v2
- name: Setup Deno
uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- name: Install Dependencies
run: bun install
run: deno install
- name: Run tests
run: bun run test:run
run: deno task test
- name: Build Package
run: bun run build
run: deno task build

14
.github/workflows/nightly.yml

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

14
.github/workflows/pr.yml

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

14
.github/workflows/release.yml

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

3
.gitignore

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

9
.vscode/settings.json

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

138
README.md

@ -9,7 +9,8 @@
## 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)**
@ -19,7 +20,8 @@ Official [Meshtastic](https://meshtastic.org) web interface, that can be hosted
## 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
- Access the interface offline
@ -35,8 +37,10 @@ PWA functionality works with both the hosted version and self-hosted instances.
## 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 base image used is [Nginx 1.27](https://hub.docker.com/_/nginx)
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 base image used is
[Nginx 1.27](https://hub.docker.com/_/nginx)
```bash
# 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
```
## 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
# 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
```
> [!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
> - Not recommended for production environments unless you are actively testing new features
> [!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
> - Not recommended for production environments unless you are actively testing
> new features
> - No guarantee of backward compatibility between nightly builds
### Version Information
Each nightly build is tagged with:
- The nightly tag for the latest build
- A specific SHA for build reproducibility
### 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
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
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()}`)
- Object literals (`style={{ color: "purple" }}`)
- 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
- 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
#### Usage
When experiencing slow renders, run:
```bash
bun run dev:scan
deno task dev:scan
```
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
- 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.
### 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
```
Use these insights to apply targeted optimizations like `React.memo()`,
`useCallback()`, or `useMemo()` where they'll have the most impact.

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",
},
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@tailwindcss/postcss": "^4.0.9",
"@testing-library/react": "^16.2.0",
"@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=="],
"@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=="],
"@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=="],
"@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=="],
@ -370,8 +351,6 @@
"@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/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/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=="],
@ -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/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=="],
@ -834,7 +813,7 @@
"@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=="],
@ -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-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=="],
"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=="],
"chownr": ["[email protected]", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
@ -1008,7 +985,7 @@
"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=="],
@ -1028,8 +1005,6 @@
"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=="],
"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=="],
"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=="],
@ -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=="],
"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=="],
@ -1134,7 +1109,7 @@
"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=="],
@ -1158,7 +1133,7 @@
"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=="],
@ -1188,11 +1163,9 @@
"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=="],
"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=="],
@ -1334,7 +1307,7 @@
"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=="],
@ -1378,8 +1351,6 @@
"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=="],
"marchingsquares": ["[email protected]", "", {}, "sha512-gz6nNQoVK7Lkh2pZulrT4qd4347S/toG9RXH2pyzhLgkL5mLkBoqgv4EvAGXcV0ikDW72n/OQb3Xe8bGagQZCg=="],
@ -1488,7 +1459,7 @@
"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=="],
@ -1582,7 +1553,7 @@
"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=="],
@ -1600,8 +1571,6 @@
"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-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=="],
"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=="],
@ -1788,7 +1757,7 @@
"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=="],
@ -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=="],
"@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": ["[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=="],
"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=="],

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",
"scripts": {
"build": "vite build",
"build:analyze": "BUNDLE_ANALYZE=true vite build",
"check": "biome check src/",
"check:fix": "pnpm check --write src/",
"format": "biome format --write src/",
"dev": "vite dev --open",
"dev:scan": "VITE_DEBUG_SCAN=true vite dev",
"test": "vitest",
"test:ui": "vitest --ui",
"test:run": "vitest run",
"preview": "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/)",
"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"
]
"build:analyze": "BUNDLE_ANALYZE=true deno task build",
"lint": "deno lint src/",
"lint:fix": "deno lint --fix src/",
"format": "deno fmt src/",
"dev": "deno task dev:ui",
"dev:ui": "deno run -A npm:vite dev",
"dev:scan": "VITE_DEBUG_SCAN=true deno task dev:ui",
"test": "deno run -A npm:vitest",
"test:ui": "deno task test --ui",
"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/ ."
},
"repository": {
"type": "git",
@ -35,6 +25,15 @@
"bugs": {
"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",
"dependencies": {
"@bufbuild/protobuf": "^2.2.3",
@ -80,7 +79,6 @@
"zustand": "5.0.3"
},
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@tailwindcss/postcss": "^4.0.9",
"@testing-library/react": "^16.2.0",
"@types/chrome": "^0.0.307",

7
src/App.tsx

@ -1,11 +1,10 @@
import { DeviceWrapper } from "@app/DeviceWrapper.tsx";
import { PageRouter } from "@app/PageRouter.tsx";
import { ThemeProvider } from "@app/components/generic/ThemeProvider";
import { CommandPalette } from "@components/CommandPalette.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 { KeyBackupReminder } from "@components/KeyBackupReminder";
import { KeyBackupReminder } from "@components/KeyBackupReminder.tsx";
import { Toaster } from "@components/Toaster.tsx";
import Footer from "@components/UI/Footer.tsx";
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 type { JSX } from "react";
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";

2
src/PageRouter.tsx

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

14
src/components/CommandPalette.tsx

@ -1,4 +1,4 @@
import { Avatar } from "@components/UI/Avatar";
import { Avatar } from "./UI/Avatar.tsx";
import {
CommandDialog,
CommandEmpty,
@ -117,13 +117,11 @@ export const CommandPalette = () => {
return {
label:
device.nodes.get(device.hardware.myNodeNum)?.user?.longName ??
device.hardware.myNodeNum.toString(),
device.hardware.myNodeNum.toString(),
icon: (
<Avatar
text={
device.nodes.get(device.hardware.myNodeNum)?.user
?.shortName ?? device.hardware.myNodeNum.toString()
}
text={device.nodes.get(device.hardware.myNodeNum)?.user
?.shortName ?? device.hardware.myNodeNum.toString()}
/>
),
action() {
@ -241,8 +239,8 @@ export const CommandPalette = () => {
}
};
window.addEventListener("keydown", handleKeydown);
return () => window.removeEventListener("keydown", handleKeydown);
globalThis.addEventListener("keydown", handleKeydown);
return () => globalThis.removeEventListener("keydown", handleKeydown);
}, [setCommandPaletteOpen]);
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 { useDeviceStore } from "@core/stores/deviceStore.ts";
import { HomeIcon, PlusIcon, SearchIcon } from "lucide-react";
import type { JSX } from "react";
import { Avatar } from "./UI/Avatar";
import { Avatar } from "@components/UI/Avatar.tsx";
export const DeviceSelector = (): JSX.Element => {
export const DeviceSelector = () => {
const { getDevices } = useDeviceStore();
const {
selectedDevice,
@ -38,11 +37,9 @@ export const DeviceSelector = (): JSX.Element => {
active={selectedDevice === device.id}
>
<Avatar
text={
device.nodes
.get(device.hardware.myNodeNum)
?.user?.shortName.toString() ?? "UNK"
}
text={device.nodes
.get(device.hardware.myNodeNum)
?.user?.shortName.toString() ?? "UNK"}
/>
</DeviceSelectorButton>
))}
@ -66,11 +63,13 @@ export const DeviceSelector = (): JSX.Element => {
<SearchIcon />
</button>
{/* 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 />
</button> */}
</button> */
}
<Separator />
<Code>{process.env.COMMIT_HASH}</Code>
<Code>{import.meta.env.VITE_COMMIT_HASH}</Code>
</div>
</nav>
);

7
src/components/DeviceSelectorButton.tsx

@ -5,7 +5,6 @@ export interface DeviceSelectorButtonProps {
}
export const DeviceSelectorButton = ({
active,
onClick,
children,
}: DeviceSelectorButtonProps) => (
@ -14,9 +13,11 @@ export const DeviceSelectorButton = ({
onClick={onClick}
onKeyDown={onClick}
>
{/* {active && (
{
/* {active && (
<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">
{children}
</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 { Button } from "@components/UI/Button.tsx";
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 { 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 { RebootDialog } from "@components/Dialog/RebootDialog.tsx";
import { ShutdownDialog } from "@components/Dialog/ShutdownDialog.tsx";
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();
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 { Protobuf } from "@meshtastic/core";
import { toByteArray } from "base64-js";
import { type JSX, useEffect, useState } from "react";
import { useEffect, useState } from "react";
export interface ImportDialogProps {
open: boolean;
@ -26,7 +26,7 @@ export interface ImportDialogProps {
export const ImportDialog = ({
open,
onOpenChange,
}: ImportDialogProps): JSX.Element => {
}: ImportDialogProps) => {
const [importDialogInput, setImportDialogInput] = useState<string>("");
const [channelSet, setChannelSet] = useState<Protobuf.AppOnly.ChannelSet>();
const [validUrl, setValidUrl] = useState<boolean>(false);
@ -62,7 +62,7 @@ export const ImportDialog = ({
),
);
setValidUrl(true);
} catch (error) {
} catch (_error) {
setValidUrl(false);
setChannelSet(undefined);
}
@ -73,10 +73,9 @@ export const ImportDialog = ({
connection?.setChannel(
create(Protobuf.Channel.ChannelSchema, {
index,
role:
index === 0
? Protobuf.Channel.Channel_Role.PRIMARY
: Protobuf.Channel.Channel_Role.SECONDARY,
role: index === 0
? Protobuf.Channel.Channel_Role.PRIMARY
: Protobuf.Channel.Channel_Role.SECONDARY,
settings: ch,
}),
);
@ -119,25 +118,29 @@ export const ImportDialog = ({
<div className="w-36">
<Label>Use Preset?</Label>
<Switch
disabled={true}
disabled
checked={channelSet?.loraConfig?.usePreset ?? true}
/>
</div>
{/* <Select
{
/* <Select
label="Modem Preset"
disabled
value={channelSet?.loraConfig?.modemPreset}
>
{renderOptions(Protobuf.Config_LoRaConfig_ModemPreset)}
</Select> */}
</Select> */
}
</div>
{/* <Select
{
/* <Select
label="Region"
disabled
value={channelSet?.loraConfig?.region}
>
{renderOptions(Protobuf.Config_LoRaConfig_RegionCode)}
</Select> */}
</Select> */
}
<span className="text-md block font-medium text-text-primary">
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 {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@components/UI/Dialog";
} from "../UI/Dialog.tsx";
import type { Protobuf, Types } from "@meshtastic/core";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import type { JSX } from "react";
export interface LocationResponseDialogProps {
location: Types.PacketMetadata<Protobuf.Mesh.location> | undefined;
@ -20,15 +19,13 @@ export const LocationResponseDialog = ({
location,
open,
onOpenChange,
}: LocationResponseDialogProps): JSX.Element => {
}: LocationResponseDialogProps) => {
const { nodes } = useDevice();
const from = nodes.get(location?.from ?? 0);
const longName =
from?.user?.longName ??
const longName = from?.user?.longName ??
(from ? `!${numberToHexUnpadded(from?.num)}` : "Unknown");
const shortName =
from?.user?.shortName ??
const shortName = from?.user?.shortName ??
(from ? `${numberToHexUnpadded(from?.num).substring(0, 4)}` : "UNK");
return (

30
src/components/Dialog/NewDeviceDialog.tsx

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

287
src/components/Dialog/NodeDetailsDialog.tsx

@ -1,23 +1,23 @@
import { useAppStore } from "@app/core/stores/appStore";
import { useDevice } from "@app/core/stores/deviceStore";
import { useAppStore } from "../../core/stores/appStore.ts";
import { useDevice } from "../../core/stores/deviceStore.ts";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@components/UI/Accordion";
} from "../UI/Accordion.tsx";
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@components/UI/Dialog";
} from "../UI/Dialog.tsx";
import { Protobuf } from "@meshtastic/core";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import { DeviceImage } from "../generic/DeviceImage";
import { TimeAgo } from "../generic/TimeAgo";
import { Uptime } from "../generic/Uptime";
import { DeviceImage } from "../generic/DeviceImage.tsx";
import { TimeAgo } from "../generic/TimeAgo.tsx";
import { Uptime } from "../generic/Uptime.tsx";
export interface NodeDetailsDialogProps {
open: boolean;
@ -32,134 +32,159 @@ export const NodeDetailsDialog = ({
const { nodeNumDetails } = useAppStore();
const device: Protobuf.Mesh.NodeInfo = nodes.get(nodeNumDetails);
return device ? (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>
Node Details for {device.user?.longName ?? "UNKNOWN"} (
{device.user?.shortName ?? "UNK"})
</DialogTitle>
</DialogHeader>
<DialogFooter>
<div className="w-full">
<DeviceImage
className="w-32 h-32 mx-auto rounded-lg border-4 border-slate-200 dark:border-slate-800"
deviceType={
Protobuf.Mesh.HardwareModel[device.user?.hwModel ?? 0]
}
/>
<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">
return device
? (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>
Node Details for {device.user?.longName ?? "UNKNOWN"} (
{device.user?.shortName ?? "UNK"})
</DialogTitle>
</DialogHeader>
<DialogFooter>
<div className="w-full">
<DeviceImage
className="w-32 h-32 mx-auto rounded-lg border-4 border-slate-200 dark:border-slate-800"
deviceType={Protobuf.Mesh
.HardwareModel[device.user?.hwModel ?? 0]}
/>
<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">
Position:
Details:
</p>
{device.position.latitudeI && device.position.longitudeI ? (
<p>
Coordinates:{" "}
<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>
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>
{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">
{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">
Position:
</p>
{device.position.latitudeI && device.position.longitudeI
? (
<p>
Coordinates:{" "}
<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)}
</pre>
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
) : null}
</div>
</DialogFooter>
</DialogContent>
</Dialog>
) : null;
</pre>
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
)
: null}
</div>
</DialogFooter>
</DialogContent>
</Dialog>
)
: null;
};

24
src/components/Dialog/NodeOptionsDialog.tsx

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

10
src/components/Dialog/PKIBackupDialog.tsx

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

2
src/components/Dialog/PkiRegenerateDialog.tsx

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

12
src/components/Dialog/QRDialog.tsx

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

2
src/components/Dialog/RebootDialog.tsx

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

6
src/components/Dialog/RemoveNodeDialog.tsx

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

2
src/components/Dialog/ShutdownDialog.tsx

@ -19,7 +19,7 @@ export interface ShutdownDialogProps {
export const ShutdownDialog = ({
open,
onOpenChange,
}: ShutdownDialogProps): JSX.Element => {
}: ShutdownDialogProps) => {
const { connection } = useDevice();
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 {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@components/UI/Dialog";
} from "../UI/Dialog.tsx";
import type { Protobuf, Types } from "@meshtastic/core";
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 {
traceroute: Types.PacketMetadata<Protobuf.Mesh.RouteDiscovery> | undefined;
@ -21,18 +21,16 @@ export const TracerouteResponseDialog = ({
traceroute,
open,
onOpenChange,
}: TracerouteResponseDialogProps): JSX.Element => {
}: TracerouteResponseDialogProps) => {
const { nodes } = useDevice();
const route: number[] = traceroute?.data.route ?? [];
const routeBack: number[] = traceroute?.data.routeBack ?? [];
const snrTowards = traceroute?.data.snrTowards ?? [];
const snrBack = traceroute?.data.snrBack ?? [];
const from = nodes.get(traceroute?.from ?? 0);
const longName =
from?.user?.longName ??
const longName = from?.user?.longName ??
(from ? `!${numberToHexUnpadded(from?.num)}` : "Unknown");
const shortName =
from?.user?.shortName ??
const shortName = from?.user?.shortName ??
(from ? `${numberToHexUnpadded(from?.num).substring(0, 4)}` : "UNK");
const to = nodes.get(traceroute?.to ?? 0);
return (

20
src/components/Form/DynamicForm.tsx

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

14
src/components/Form/DynamicFormField.tsx

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

20
src/components/Form/FormInput.tsx

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

6
src/components/Form/FormMultiSelect.tsx

@ -3,7 +3,7 @@ import type {
GenericFormElementProps,
} from "@components/Form/DynamicForm.tsx";
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> {
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
const optionsEnumValues = enumValue
? Object.entries(enumValue)
.filter((value) => typeof value[1] === "number")
.filter((value) => value[0] !== "UNSET")
.filter((value) => typeof value[1] === "number")
.filter((value) => value[0] !== "UNSET")
: [];
const formatName = (name: string) => {

18
src/components/Form/FormPasswordGenerator.tsx

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

16
src/components/Form/FormSelect.tsx

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

7
src/components/KeyBackupReminder.tsx

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

71
src/components/PageComponents/Channel.tsx

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

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

@ -1,7 +1,7 @@
import {
type FlagName,
usePositionFlags,
} from "@app/core/hooks/usePositionFlags";
} from "../../../core/hooks/usePositionFlags.ts";
import type { PositionValidation } from "@app/validation/config/position.tsx";
import { create } from "@bufbuild/protobuf";
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 { Protobuf } from "@meshtastic/core";
export const Power = (): JSX.Element => {
export const Power = () => {
const { config, setWorkingConfig } = useDevice();
const onSubmit = (data: PowerValidation) => {

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

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

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

@ -18,7 +18,7 @@ export type SecurityAction =
| { type: "SET_ADMIN_KEY"; payload: string }
| { type: "SHOW_PRIVATE_KEY_DIALOG"; payload: boolean }
| {
type: "REGENERATE_PRIV_PUB_KEY";
payload: { privateKey: string; publicKey: string };
}
type: "REGENERATE_PRIV_PUB_KEY";
payload: { privateKey: string; publicKey: 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 { Mono } from "@components/generic/Mono.tsx";
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 { useCallback, useEffect, useState } from "react";
export const BLE = ({ closeDialog }: TabElementProps): JSX.Element => {
export const BLE = ({ closeDialog }: TabElementProps) => {
const [bleDevices, setBleDevices] = useState<BluetoothDevice[]>([]);
const { addDevice } = useDeviceStore();
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 { Input } from "@components/UI/Input.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 { MeshDevice } from "@meshtastic/core";
import { TransportHTTP } from "@meshtastic/transport-http";
import { type JSX, useState } from "react";
import { useState } from "react";
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 { addDevice } = useDeviceStore();
const { setSelectedDevice } = useAppStore();
const { register, handleSubmit, control, watch } = useForm<{
const { register, handleSubmit, control } = useForm<{
ip: string;
tls: boolean;
}>({
defaultValues: {
ip: ["client.meshtastic.org", "localhost"].includes(
window.location.hostname,
globalThis.location.hostname,
)
? "meshtastic.local"
: window.location.host,
: globalThis.location.host,
tls: location.protocol === "https:",
},
});
@ -61,16 +61,15 @@ export const HTTP = ({ closeDialog }: TabElementProps): JSX.Element => {
<Controller
name="tls"
control={control}
render={({ field: { value, onChange, ...rest } }) => (
render={({ field: { ...rest } }) => (
<>
<Label>Use HTTPS</Label>
<Switch
onCheckedChange={(checked) => {
onCheckedChange={(checked: boolean) => {
checked ? setHTTPS(true) : setHTTPS(false);
}}
disabled={
location.protocol === "https:" || connectionInProgress
}
disabled={location.protocol === "https:" ||
connectionInProgress}
checked={https}
{...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 { Mono } from "@components/generic/Mono.tsx";
import { useAppStore } from "@core/stores/appStore.ts";
@ -9,7 +9,7 @@ import { MeshDevice } from "@meshtastic/core";
import { TransportWebSerial } from "@meshtastic/transport-web-serial";
import { useCallback, useEffect, useState } from "react";
export const Serial = ({ closeDialog }: TabElementProps): JSX.Element => {
export const Serial = ({ closeDialog }: TabElementProps) => {
const [serialPorts, setSerialPorts] = useState<SerialPort[]>([]);
const { addDevice } = useDeviceStore();
const { setSelectedDevice } = useAppStore();

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

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

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

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

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

@ -4,13 +4,13 @@ import {
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@app/components/UI/Tooltip";
} from "@components/UI/Tooltip.tsx";
import {
type MessageWithState,
useDeviceStore,
} from "@app/core/stores/deviceStore.ts";
import { cn } from "@app/core/utils/cn";
import { Avatar } from "@components/UI/Avatar";
} from "@core/stores/deviceStore.ts";
import { cn } from "@core/utils/cn.ts";
import { Avatar } from "@components/UI/Avatar.tsx";
import type { Protobuf } from "@meshtastic/core";
import { AlertCircle, CheckCircle2, CircleEllipsis } 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.WAITING]: "Waiting for delivery",
[MESSAGE_STATES.FAILED]: "Delivery failed",
} as const;
};
const STATUS_ICON_MAP: Record<MessageState, LucideIcon> = {
[MESSAGE_STATES.ACK]: CheckCircle2,
[MESSAGE_STATES.WAITING]: CircleEllipsis,
[MESSAGE_STATES.FAILED]: AlertCircle,
} as const;
};
const getStatusText = (state: MessageState): string => STATUS_TEXT_MAP[state];
@ -93,7 +93,6 @@ const StatusIcon = ({ state, className, ...otherProps }: StatusIconProps) => {
const getMessageTextStyles = (state: MessageState) => {
const isAcknowledged = state === MESSAGE_STATES.ACK;
const isFailed = state === MESSAGE_STATES.FAILED;
const isWaiting = state === MESSAGE_STATES.WAITING;
return cn(
"break-words overflow-hidden",
@ -144,16 +143,18 @@ export const Message = ({ lastMsgSameUser, message, sender }: MessageProps) => {
)}
>
<div className="flex items-center gap-2 mb-2">
{!lastMsgSameUser ? (
<div className="flex place-items-center gap-2 mb-1">
<Avatar text={messageUser?.shortName} />
<div className="flex flex-col">
<span className="font-medium text-slate-900 dark:text-white truncate">
{messageUser?.longName}
</span>
{!lastMsgSameUser
? (
<div className="flex place-items-center gap-2 mb-1">
<Avatar text={messageUser?.shortName ?? "UNK"} />
<div className="flex flex-col">
<span className="font-medium text-slate-900 dark:text-white truncate">
{messageUser?.longName}
</span>
</div>
</div>
</div>
) : null}
)
: null}
</div>
<TimeDisplay date={message.rxTime} />
<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 { Input } from "@components/UI/Input.tsx";
import { useDevice } from "@core/stores/deviceStore.ts";
import type { Types } from "@meshtastic/core";
import { SendIcon } from "lucide-react";
import {
type JSX,
startTransition,
useCallback,
useMemo,
useState,
} from "react";
import { startTransition, useCallback, useMemo, useState } from "react";
export interface MessageInputProps {
to: Types.Destination;
@ -22,7 +16,7 @@ export const MessageInput = ({
to,
channel,
maxBytes,
}: MessageInputProps): JSX.Element => {
}: MessageInputProps) => {
const {
connection,
setMessageState,
@ -43,7 +37,7 @@ export const MessageInput = ({
async (message: string) => {
await connection
?.sendText(message, to, true, channel)
.then((id) =>
.then((id: number) =>
setMessageState(
to === "broadcast" ? "broadcast" : "direct",
channel,
@ -51,7 +45,7 @@ export const MessageInput = ({
myNodeNum,
id,
"ack",
),
)
)
.catch((e: Types.PacketError) =>
setMessageState(
@ -61,7 +55,7 @@ export const MessageInput = ({
myNodeNum,
e.id,
e.error,
),
)
);
},
[channel, connection, myNodeNum, setMessageState, to],
@ -82,7 +76,7 @@ export const MessageInput = ({
<div className="flex gap-2">
<form
className="w-full"
action={async (formData: FormData) => {
action={(formData: FormData) => {
// prevent user from sending blank/empty message
if (localDraft === "") return;
const message = formData.get("messageInput") as string;
@ -97,7 +91,7 @@ export const MessageInput = ({
<div className="flex grow gap-2">
<span className="w-full">
<Input
autoFocus={true}
autoFocus
minLength={1}
name="messageInput"
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 { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import type { JSX } from "react";
export interface TraceRouteProps {
from?: Protobuf.Mesh.NodeInfo;
@ -19,7 +18,7 @@ export const TraceRoute = ({
routeBack,
snrTowards,
snrBack,
}: TraceRouteProps): JSX.Element => {
}: TraceRouteProps) => {
const { nodes } = useDevice();
return (
@ -38,23 +37,25 @@ export const TraceRoute = ({
))}
{from?.user?.longName}
</span>
{routeBack ? (
<span className="ml-4 border-l-2 border-l-background-primary pl-2 text-text-primary">
<p className="font-semibold">Route back:</p>
<p>{from?.user?.longName}</p>
<p> {snrBack?.[0] ? snrBack[0] : "??"}dB</p>
{routeBack.map((hop, i) => (
<span key={nodes.get(hop)?.num}>
<p>
{nodes.get(hop)?.user?.longName ??
`!${numberToHexUnpadded(hop)}`}
</p>
<p> {snrBack?.[i + 1] ? snrBack[i + 1] : "??"}dB</p>
</span>
))}
{to?.user?.longName}
</span>
) : null}
{routeBack
? (
<span className="ml-4 border-l-2 border-l-background-primary pl-2 text-text-primary">
<p className="font-semibold">Route back:</p>
<p>{from?.user?.longName}</p>
<p> {snrBack?.[0] ? snrBack[0] : "??"}dB</p>
{routeBack.map((hop, i) => (
<span key={nodes.get(hop)?.num}>
<p>
{nodes.get(hop)?.user?.longName ??
`!${numberToHexUnpadded(hop)}`}
</p>
<p> {snrBack?.[i + 1] ? snrBack[i + 1] : "??"}dB</p>
</span>
))}
{to?.user?.longName}
</span>
)
: null}
</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 { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { Protobuf } from "@meshtastic/core";
export const AmbientLighting = (): JSX.Element => {
export const AmbientLighting = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice();
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 { Protobuf } from "@meshtastic/core";
export const Audio = (): JSX.Element => {
export const Audio = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice();
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 { Protobuf } from "@meshtastic/core";
export const CannedMessage = (): JSX.Element => {
export const CannedMessage = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice();
const onSubmit = (data: CannedMessageValidation) => {
@ -63,9 +63,8 @@ export const CannedMessage = (): JSX.Element => {
label: "Clockwise event",
description: "Select input event.",
properties: {
enumValue:
Protobuf.ModuleConfig
.ModuleConfig_CannedMessageConfig_InputEventChar,
enumValue: Protobuf.ModuleConfig
.ModuleConfig_CannedMessageConfig_InputEventChar,
},
},
{
@ -74,9 +73,8 @@ export const CannedMessage = (): JSX.Element => {
label: "Counter Clockwise event",
description: "Select input event.",
properties: {
enumValue:
Protobuf.ModuleConfig
.ModuleConfig_CannedMessageConfig_InputEventChar,
enumValue: Protobuf.ModuleConfig
.ModuleConfig_CannedMessageConfig_InputEventChar,
},
},
{
@ -85,9 +83,8 @@ export const CannedMessage = (): JSX.Element => {
label: "Press event",
description: "Select input event",
properties: {
enumValue:
Protobuf.ModuleConfig
.ModuleConfig_CannedMessageConfig_InputEventChar,
enumValue: Protobuf.ModuleConfig
.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 { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { Protobuf } from "@meshtastic/core";
export const DetectionSensor = (): JSX.Element => {
export const DetectionSensor = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice();
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 { Protobuf } from "@meshtastic/core";
export const ExternalNotification = (): JSX.Element => {
export const ExternalNotification = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice();
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 { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { Protobuf } from "@meshtastic/core";
export const MQTT = (): JSX.Element => {
export const MQTT = () => {
const { config, moduleConfig, setWorkingModuleConfig } = useDevice();
const onSubmit = (data: MqttValidation) => {
@ -165,32 +165,31 @@ export const MQTT = (): JSX.Element => {
description:
"Position shared will be accurate within this distance",
properties: {
enumValue:
config.display?.units === 0
? {
"Within 23 km": 10,
"Within 12 km": 11,
"Within 5.8 km": 12,
"Within 2.9 km": 13,
"Within 1.5 km": 14,
"Within 700 m": 15,
"Within 350 m": 16,
"Within 200 m": 17,
"Within 90 m": 18,
"Within 50 m": 19,
}
: {
"Within 15 miles": 10,
"Within 7.3 miles": 11,
"Within 3.6 miles": 12,
"Within 1.8 miles": 13,
"Within 0.9 miles": 14,
"Within 0.5 miles": 15,
"Within 0.2 miles": 16,
"Within 600 feet": 17,
"Within 300 feet": 18,
"Within 150 feet": 19,
},
enumValue: config.display?.units === 0
? {
"Within 23 km": 10,
"Within 12 km": 11,
"Within 5.8 km": 12,
"Within 2.9 km": 13,
"Within 1.5 km": 14,
"Within 700 m": 15,
"Within 350 m": 16,
"Within 200 m": 17,
"Within 90 m": 18,
"Within 50 m": 19,
}
: {
"Within 15 miles": 10,
"Within 7.3 miles": 11,
"Within 3.6 miles": 12,
"Within 1.8 miles": 13,
"Within 0.9 miles": 14,
"Within 0.5 miles": 15,
"Within 0.2 miles": 16,
"Within 600 feet": 17,
"Within 300 feet": 18,
"Within 150 feet": 19,
},
},
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 { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { Protobuf } from "@meshtastic/core";
export const NeighborInfo = (): JSX.Element => {
export const NeighborInfo = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice();
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 { Protobuf } from "@meshtastic/core";
export const Paxcounter = (): JSX.Element => {
export const Paxcounter = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice();
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 { Protobuf } from "@meshtastic/core";
export const RangeTest = (): JSX.Element => {
export const RangeTest = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice();
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 { Protobuf } from "@meshtastic/core";
export const Serial = (): JSX.Element => {
export const Serial = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice();
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 { Protobuf } from "@meshtastic/core";
export const StoreForward = (): JSX.Element => {
export const StoreForward = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice();
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 { Protobuf } from "@meshtastic/core";
export const Telemetry = (): JSX.Element => {
export const Telemetry = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice();
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 Footer from "@components/UI/Footer.tsx";
import { Spinner } from "@components/UI/Spinner.tsx";
import { ErrorBoundary } from "react-error-boundary";
import { ErrorPage } from "./UI/ErrorPage";
import Footer from "./UI/Footer";
import { Spinner } from "./UI/Spinner";
import { ErrorPage } from "@components/UI/ErrorPage.tsx";
export interface PageLayoutProps {
label: string;
@ -46,9 +47,7 @@ export const PageLayout = ({
className="transition-all hover:text-accent"
onClick={action.onClick}
>
{action?.isLoading ? (
<Spinner />
) : (
{action?.isLoading ? <Spinner /> : (
<action.icon
className={action.iconClasses}
aria-disabled={action.disabled}

128
src/components/Sidebar.tsx

@ -23,7 +23,7 @@ export interface SidebarProps {
children?: React.ReactNode;
}
export const Sidebar = ({ children }: SidebarProps): JSX.Element => {
export const Sidebar = ({ children }: SidebarProps) => {
const { hardware, nodes, metadata } = useDevice();
const myNode = nodes.get(hardware.myNodeNum);
const myMetadata = metadata.get(0);
@ -64,69 +64,71 @@ export const Sidebar = ({ children }: SidebarProps): JSX.Element => {
},
];
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>
<span className="text-lg font-medium">
{myNode?.user?.shortName ?? "UNK"}
</span>
<Subtle>{myNode?.user?.longName ?? "UNK"}</Subtle>
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>
<span className="text-lg font-medium">
{myNode?.user?.shortName ?? "UNK"}
</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>
<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 className="px-8 pb-6">
<div className="flex items-center">
<BatteryMediumIcon size={24} viewBox={"0 0 28 24"} />
<Subtle>
{myNode?.deviceMetrics?.batteryLevel
? myNode?.deviceMetrics?.batteryLevel > 100
? "Charging"
: `${myNode?.deviceMetrics?.batteryLevel}%`
: "UNK"}
</Subtle>
</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 className="px-8 pb-6">
<div className="flex items-center">
<BatteryMediumIcon size={24} viewBox="0 0 28 24" />
<Subtle>
{myNode?.deviceMetrics?.batteryLevel
? myNode?.deviceMetrics?.batteryLevel > 100
? "Charging"
: `${myNode?.deviceMetrics?.batteryLevel}%`
: "UNK"}
</Subtle>
</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">
{pages.map((link) => (
<SidebarButton
key={link.name}
label={link.name}
Icon={link.icon}
onClick={() => {
setActivePage(link.page);
}}
active={link.page === activePage}
/>
))}
</SidebarSection>
{children}
</div>
) : (
<div className="px-1 pt-8 border-r-[0.5px] border-slate-700">
<button type="button" onClick={() => setShowSidebar(true)}>
<SidebarOpenIcon size={24} />
</button>
</div>
);
<SidebarSection label="Navigation">
{pages.map((link) => (
<SidebarButton
key={link.name}
label={link.name}
Icon={link.icon}
onClick={() => {
setActivePage(link.page);
}}
active={link.page === activePage}
/>
))}
</SidebarSection>
{children}
</div>
)
: (
<div className="px-1 pt-8 border-r-[0.5px] border-slate-700">
<button type="button" onClick={() => setShowSidebar(true)}>
<SidebarOpenIcon size={24} />
</button>
</div>
);
};

12
src/components/ThemeSwitcher.tsx

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

4
src/components/Toaster.tsx

@ -5,8 +5,8 @@ import {
ToastProvider,
ToastTitle,
ToastViewport,
} from "@components/UI/Toast";
import { useToast } from "@core/hooks/useToast";
} from "./UI/Toast.tsx";
import { useToast } from "../core/hooks/useToast.ts";
export function Toaster() {
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";
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 { 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",
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",
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: {
default: "h-10 py-2 px-4",
@ -38,7 +39,8 @@ const buttonVariants = cva(
export type ButtonVariant = VariantProps<typeof buttonVariants>["variant"];
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
extends
React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(

6
src/components/UI/Command.tsx

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

8
src/components/UI/Dialog.tsx

@ -23,7 +23,7 @@ DialogPortal.displayName = DialogPrimitive.Portal.displayName;
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, children, ...props }, ref) => (
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
className={cn(
"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 {
Dialog,
DialogTrigger,
DialogContent,
DialogHeader,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogDescription,
DialogTrigger,
};

12
src/components/UI/DropdownMenu.tsx

@ -184,18 +184,18 @@ DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
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 { Heading } from "./Typography/Heading";
import { Link } from "./Typography/Link";
import { P } from "./Typography/P";
import { Heading } from "./Typography/Heading.tsx";
import { Link } from "./Typography/Link.tsx";
import { P } from "./Typography/P.tsx";
export function ErrorPage({ error }: { error: Error }) {
if (!error) {

54
src/components/UI/Footer.tsx

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

8
src/components/UI/Generator.tsx

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

5
src/components/UI/Input.tsx

@ -1,7 +1,7 @@
import * as React from "react";
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";
const inputVariants = cva(
@ -20,7 +20,8 @@ const inputVariants = cva(
);
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement>,
extends
React.InputHTMLAttributes<HTMLInputElement>,
VariantProps<typeof inputVariants> {
prefix?: string;
suffix?: string;

16
src/components/UI/Menubar.tsx

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

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 { Check } from "lucide-react";
@ -8,9 +8,8 @@ interface MultiSelectProps {
}
const MultiSelect = ({ children, className = "" }: MultiSelectProps) => {
return (
<div className={cn("flex flex-wrap gap-2", className)}>{children}</div>
);
return <div className={cn("flex flex-wrap gap-2", className)}>{children}
</div>;
};
interface MultiSelectItemProps {

2
src/components/UI/Popover.tsx

@ -26,4 +26,4 @@ const PopoverContent = React.forwardRef<
));
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 {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectGroup,
SelectItem,
SelectLabel,
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 {
label: string;

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

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

2
src/components/UI/Tabs.tsx

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

4
src/components/UI/Tooltip.tsx

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

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

@ -2,7 +2,7 @@ export interface BlockquoteProps {
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">
{children}
</blockquote>

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

@ -2,7 +2,7 @@ export interface CodeProps {
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">
{children}
</code>

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

@ -2,7 +2,8 @@ import type React from "react";
const headingStyles = {
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",
h4: "scroll-m-20 text-xl font-semibold 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 {
href: string;
@ -6,10 +6,10 @@ export interface LinkProps {
className?: string;
}
export const Link = ({ href, children, className }: LinkProps): JSX.Element => (
export const Link = ({ href, children, className }: LinkProps) => (
<a
href={href}
target={"_blank"}
target="_blank"
rel="noopener noreferrer"
className={cn(
"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 {
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 {
className?: string;
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)}>
{children}
</p>

2
src/components/generic/Blur.tsx

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

2
src/components/generic/Mono.tsx

@ -2,7 +2,7 @@ export const Mono = ({
children,
className,
...rest
}: JSX.IntrinsicElements["span"]): JSX.Element => {
}: JSX.IntrinsicElements["span"]) => {
return (
<span
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