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

Switch from Bun to Deno for dev environment
This commit is contained in:
Dan Ditomaso
2025-03-03 20:56:00 -05:00
committed by GitHub
147 changed files with 8045 additions and 1371 deletions
+7 -5
View File
@@ -16,14 +16,16 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup bun - name: Setup Deno
uses: oven-sh/setup-bun@v2 uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- name: Install Dependencies - name: Install Dependencies
run: bun install run: deno install
- name: Run tests - name: Run tests
run: bun run test:run run: deno task test
- name: Build Package - name: Build Package
run: bun run build run: deno task build
+8 -6
View File
@@ -15,20 +15,22 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup bun - name: Setup Deno
uses: oven-sh/setup-bun@v2 uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- name: Install Dependencies - name: Install Dependencies
run: bun install run: deno install
- name: Run tests - name: Run tests
run: bun run test:run run: deno task test
- name: Build Package - name: Build Package
run: bun run build run: deno task build
- name: Package Output - name: Package Output
run: bun run package run: deno task package
- name: Archive compressed build - name: Archive compressed build
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
+8 -6
View File
@@ -9,20 +9,22 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup bun - name: Setup Deno
uses: oven-sh/setup-bun@v2 uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- name: Install Dependencies - name: Install Dependencies
run: bun install run: deno install
- name: Run tests - name: Run tests
run: bun run test:run run: deno task test
- name: Build Package - name: Build Package
run: bun run build run: deno task build
- name: Compress build - name: Compress build
run: bun run package run: deno task package
- name: Archive compressed build - name: Archive compressed build
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
+8 -6
View File
@@ -15,20 +15,22 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup bun - name: Setup Deno
uses: oven-sh/setup-bun@v2 uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- name: Install Dependencies - name: Install Dependencies
run: bun install run: deno install
- name: Run tests - name: Run tests
run: bun run test:run run: deno task test
- name: Build Package - name: Build Package
run: bun run build run: deno task build
- name: Package Output - name: Package Output
run: bun run package run: deno task package
- name: Archive compressed build - name: Archive compressed build
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
+1
View File
@@ -2,4 +2,5 @@ dist
node_modules node_modules
stats.html stats.html
.vercel .vercel
.vite/deps
dev-dist dev-dist
+4 -5
View File
@@ -1,7 +1,6 @@
{ {
"editor.defaultFormatter": "biomejs.biome", "deno.enable": true,
"editor.codeActionsOnSave": { "deno.suggest.imports.autoDiscover": true,
"quickfix.biome": "explicit" "editor.formatOnSave": true,
}, "editor.defaultFormatter": "denoland.vscode-deno"
"editor.formatOnSave": true
} }
+88 -44
View File
@@ -9,7 +9,8 @@
## Overview ## Overview
Official [Meshtastic](https://meshtastic.org) web interface, that can be hosted or served from a node Official [Meshtastic](https://meshtastic.org) web interface, that can be hosted
or served from a node
**[Hosted version](https://client.meshtastic.org)** **[Hosted version](https://client.meshtastic.org)**
@@ -19,7 +20,8 @@ Official [Meshtastic](https://meshtastic.org) web interface, that can be hosted
## Progress Web App Support (PWA) ## Progress Web App Support (PWA)
Meshtastic Web Client now includes Progressive Web App (PWA) functionality, allowing users to: Meshtastic Web Client now includes Progressive Web App (PWA) functionality,
allowing users to:
- Install the app on desktop and mobile devices - Install the app on desktop and mobile devices
- Access the interface offline - Access the interface offline
@@ -35,8 +37,10 @@ PWA functionality works with both the hosted version and self-hosted instances.
## Self-host ## Self-host
The client can be self hosted using the precompiled container images with an OCI compatible runtime such as [Docker](https://www.docker.com/) or [Podman](https://podman.io/). The client can be self hosted using the precompiled container images with an OCI
The base image used is [Nginx 1.27](https://hub.docker.com/_/nginx) compatible runtime such as [Docker](https://www.docker.com/) or
[Podman](https://podman.io/). The base image used is
[Nginx 1.27](https://hub.docker.com/_/nginx)
```bash ```bash
# With Docker # With Docker
@@ -48,7 +52,9 @@ podman run -d -p 8080:8080 --restart always --name Meshtastic-Web ghcr.io/meshta
## Nightly releases ## Nightly releases
Our nightly releases provide the latest development builds with cutting-edge features and fixes. These builds are automatically generated from the latest main branch every night and are available for testing and early adoption. Our nightly releases provide the latest development builds with cutting-edge
features and fixes. These builds are automatically generated from the latest
main branch every night and are available for testing and early adoption.
```bash ```bash
# With Docker # With Docker
@@ -58,37 +64,101 @@ podman run -d -p 8080:8080 --restart always --name Meshtastic-Web ghcr.io/meshta
``` ```
> [!WARNING] > [!WARNING]
> - Nightly builds represent the latest development state and may contain breaking changes >
> - These builds undergo automated testing but may be less stable than tagged release versions > - Nightly builds represent the latest development state and may contain
> - Not recommended for production environments unless you are actively testing new features > breaking changes
> - These builds undergo automated testing but may be less stable than tagged
> release versions
> - Not recommended for production environments unless you are actively testing
> new features
> - No guarantee of backward compatibility between nightly builds > - No guarantee of backward compatibility between nightly builds
### Version Information ### Version Information
Each nightly build is tagged with: Each nightly build is tagged with:
- The nightly tag for the latest build - The nightly tag for the latest build
- A specific SHA for build reproducibility - A specific SHA for build reproducibility
### Feedback ### Feedback
If you encounter any issues with nightly builds, please report them in our [issues tracker](https://github.com/meshtastic/web/issues). Your feedback helps improve the stability of future releases
If you encounter any issues with nightly builds, please report them in our
[issues tracker](https://github.com/meshtastic/web/issues). Your feedback helps
improve the stability of future releases
## Development & Building ## Development & Building
You'll need to download the package manager used with this repo. You can install it by visiting [Bun.sh](https://bun.sh/) and following the installation instructions.
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
#### Debugging with React Scan #### Debugging with React Scan
Meshtastic Web Client has included the library [React Scan](https://github.com/aidenybai/react-scan) to help you identify and resolve render performance issues during development.
React's comparison-by-reference approach to props makes it easy to inadvertently cause unnecessary re-renders, especially with: Meshtastic Web Client has included the library
[React Scan](https://github.com/aidenybai/react-scan) to help you identify and
resolve render performance issues during development.
React's comparison-by-reference approach to props makes it easy to inadvertently
cause unnecessary re-renders, especially with:
- Inline function callbacks (`onClick={() => handleClick()}`) - Inline function callbacks (`onClick={() => handleClick()}`)
- Object literals (`style={{ color: "purple" }}`) - Object literals (`style={{ color: "purple" }}`)
- Array literals (`items={[1, 2, 3]}`) - Array literals (`items={[1, 2, 3]}`)
These are recreated on every render, causing child components to re-render even when nothing has actually changed. These are recreated on every render, causing child components to re-render even
when nothing has actually changed.
Unlike React DevTools, React Scan specifically focuses on performance optimization by: Unlike React DevTools, React Scan specifically focuses on performance
optimization by:
- Clearly distinguishing between necessary and unnecessary renders - Clearly distinguishing between necessary and unnecessary renders
- Providing render counts for components - Providing render counts for components
@@ -96,10 +166,11 @@ Unlike React DevTools, React Scan specifically focuses on performance optimizati
- Offering a dedicated performance debugging experience - Offering a dedicated performance debugging experience
#### Usage #### Usage
When experiencing slow renders, run: When experiencing slow renders, run:
```bash ```bash
bun run dev:scan deno task dev:scan
``` ```
This will allow you to discover the following about your components and pages: This will allow you to discover the following about your components and pages:
@@ -109,32 +180,5 @@ This will allow you to discover the following about your components and pages:
- Expensive hook operations - Expensive hook operations
- Props that change reference on every render - Props that change reference on every render
Use these insights to apply targeted optimizations like `React.memo()`, `useCallback()`, or `useMemo()` where they'll have the most impact. Use these insights to apply targeted optimizations like `React.memo()`,
`useCallback()`, or `useMemo()` where they'll have the most impact.
### Building and Packaging
Build the project:
```bash
bun run build
```
GZip the output:
```bash
bun run package
```
### Development
Install the dependencies.
```bash
bun i
```
Start the development server:
```bash
bun run dev
```
-27
View File
@@ -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
}
}
}
+37 -70
View File
@@ -47,7 +47,6 @@
"zustand": "5.0.3", "zustand": "5.0.3",
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^1.9.4",
"@tailwindcss/postcss": "^4.0.9", "@tailwindcss/postcss": "^4.0.9",
"@testing-library/react": "^16.2.0", "@testing-library/react": "^16.2.0",
"@types/chrome": "^0.0.307", "@types/chrome": "^0.0.307",
@@ -262,24 +261,6 @@
"@babel/types": ["@babel/types@7.26.9", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw=="], "@babel/types": ["@babel/types@7.26.9", "", { "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/biome@1.9.4", "", { "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/cli-darwin-arm64@1.9.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw=="],
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@1.9.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg=="],
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@1.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g=="],
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@1.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA=="],
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@1.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg=="],
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@1.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg=="],
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@1.9.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg=="],
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@1.9.4", "", { "os": "win32", "cpu": "x64" }, "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA=="],
"@bufbuild/protobuf": ["@bufbuild/protobuf@2.2.3", "", {}, "sha512-tFQoXHJdkEOSwj5tRIZSPNUuXK3RaR7T1nUrPgbYX1pUbvqqaaZAsfo+NXBPsz5rZMSKVFrgK1WL8Q/MSLvprg=="], "@bufbuild/protobuf": ["@bufbuild/protobuf@2.2.3", "", {}, "sha512-tFQoXHJdkEOSwj5tRIZSPNUuXK3RaR7T1nUrPgbYX1pUbvqqaaZAsfo+NXBPsz5rZMSKVFrgK1WL8Q/MSLvprg=="],
"@clack/core": ["@clack/core@0.3.5", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-5cfhQNH+1VQ2xLQlmzXMqUoiaH0lRBq9/CLW9lTyMbuKLC3+xEK01tHVvyut++mLOn5urSHmkm6I0Lg9MaJSTQ=="], "@clack/core": ["@clack/core@0.3.5", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-5cfhQNH+1VQ2xLQlmzXMqUoiaH0lRBq9/CLW9lTyMbuKLC3+xEK01tHVvyut++mLOn5urSHmkm6I0Lg9MaJSTQ=="],
@@ -362,7 +343,7 @@
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="], "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
"@jsr/meshtastic__core": ["@jsr/meshtastic__core@2.6.0-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/meshtastic__core@2.6.0", "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/meshtastic__protobufs@2.6.0", "https://npm.jsr.io/~/11/@jsr/meshtastic__protobufs/2.6.0.tgz", { "dependencies": { "@bufbuild/protobuf": "^2.2.3" } }, "sha512-CGlgBdzAuQCZuGPrnzP8zU+EcLlmyYeeMbqFHuJ834cYfArWXDjDh1UYaPo2rI03LTjqa3MeWpfqDlzBR8kIMg=="], "@jsr/meshtastic__protobufs": ["@jsr/meshtastic__protobufs@2.6.0", "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/jsonlint-lines-primitives@2.0.2", "", {}, "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ=="], "@mapbox/jsonlint-lines-primitives": ["@mapbox/jsonlint-lines-primitives@2.0.2", "", {}, "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ=="],
"@mapbox/mapbox-gl-supported": ["@mapbox/mapbox-gl-supported@3.0.0", "", {}, "sha512-2XghOwu16ZwPJLOFVuIOaLbN0iKMn867evzXFyf0P22dqugezfJwLmdanAgU25ITvz1TvOfVP4jsDImlDJzcWg=="],
"@mapbox/point-geometry": ["@mapbox/point-geometry@0.1.0", "", {}, "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ=="], "@mapbox/point-geometry": ["@mapbox/point-geometry@0.1.0", "", {}, "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ=="],
"@mapbox/tiny-sdf": ["@mapbox/tiny-sdf@2.0.6", "", {}, "sha512-qMqa27TLw+ZQz5Jk+RcwZGH7BQf5G/TrutJhspsca/3SHwmgKQ1iq+d3Jxz5oysPVYTGP6aXxCo5Lk9Er6YBAA=="], "@mapbox/tiny-sdf": ["@mapbox/tiny-sdf@2.0.6", "", {}, "sha512-qMqa27TLw+ZQz5Jk+RcwZGH7BQf5G/TrutJhspsca/3SHwmgKQ1iq+d3Jxz5oysPVYTGP6aXxCo5Lk9Er6YBAA=="],
@@ -388,9 +367,9 @@
"@meshtastic/js": ["@jsr/meshtastic__js@2.6.0-0", "https://npm.jsr.io/~/11/@jsr/meshtastic__js/2.6.0-0.tgz", { "dependencies": { "@bufbuild/protobuf": "^2.2.3", "@jsr/meshtastic__protobufs": "^2.6.0", "crc": "^4.3.2", "ste-simple-events": "^3.0.11", "tslog": "^4.9.3" } }, "sha512-+xpZpxK6oUIVOuEs7C+LyxRr2druvc7UNNNTK9Rl8ioXj63Jz1uQXlYe2Gj0xjnRAiSQLR7QVaPef21BR/YTxA=="], "@meshtastic/js": ["@jsr/meshtastic__js@2.6.0-0", "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/meshtastic__transport-web-serial@0.2.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/meshtastic__transport-web-serial@0.2.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/curves@1.8.1", "", { "dependencies": { "@noble/hashes": "1.7.1" } }, "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ=="], "@noble/curves": ["@noble/curves@1.8.1", "", { "dependencies": { "@noble/hashes": "1.7.1" } }, "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ=="],
@@ -500,43 +479,43 @@
"@rollup/pluginutils": ["@rollup/pluginutils@5.1.4", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ=="], "@rollup/pluginutils": ["@rollup/pluginutils@5.1.4", "", { "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/rollup-android-arm-eabi@4.34.8", "", { "os": "android", "cpu": "arm" }, "sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw=="], "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.34.9", "", { "os": "android", "cpu": "arm" }, "sha512-qZdlImWXur0CFakn2BJ2znJOdqYZKiedEPEVNTBrpfPjc/YuTGcaYZcdmNFTkUj3DU0ZM/AElcM8Ybww3xVLzA=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.34.8", "", { "os": "android", "cpu": "arm64" }, "sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q=="], "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.34.9", "", { "os": "android", "cpu": "arm64" }, "sha512-4KW7P53h6HtJf5Y608T1ISKvNIYLWRKMvfnG0c44M6In4DQVU58HZFEVhWINDZKp7FZps98G3gxwC1sb0wXUUg=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.34.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q=="], "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.34.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.34.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw=="], "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.34.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.34.8", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA=="], "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.34.9", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-2lzjQPJbN5UnHm7bHIUKFMulGTQwdvOkouJDpPysJS+QFBGDJqcfh+CxxtG23Ik/9tEvnebQiylYoazFMAgrYw=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.34.8", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q=="], "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.34.9", "", { "os": "freebsd", "cpu": "x64" }, "sha512-SLl0hi2Ah2H7xQYd6Qaiu01kFPzQ+hqvdYSoOtHYg/zCIFs6t8sV95kaoqjzjFwuYQLtOI0RZre/Ke0nPaQV+g=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.34.8", "", { "os": "linux", "cpu": "arm" }, "sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g=="], "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.34.9", "", { "os": "linux", "cpu": "arm" }, "sha512-88I+D3TeKItrw+Y/2ud4Tw0+3CxQ2kLgu3QvrogZ0OfkmX/DEppehus7L3TS2Q4lpB+hYyxhkQiYPJ6Mf5/dPg=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.34.8", "", { "os": "linux", "cpu": "arm" }, "sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA=="], "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.34.9", "", { "os": "linux", "cpu": "arm" }, "sha512-3qyfWljSFHi9zH0KgtEPG4cBXHDFhwD8kwg6xLfHQ0IWuH9crp005GfoUUh/6w9/FWGBwEHg3lxK1iHRN1MFlA=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.34.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A=="], "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.34.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.34.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q=="], "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.34.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A=="],
"@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.34.8", "", { "os": "linux", "cpu": "none" }, "sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ=="], "@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.34.9", "", { "os": "linux", "cpu": "none" }, "sha512-dRAgTfDsn0TE0HI6cmo13hemKpVHOEyeciGtvlBTkpx/F65kTvShtY/EVyZEIfxFkV5JJTuQ9tP5HGBS0hfxIg=="],
"@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.34.8", "", { "os": "linux", "cpu": "ppc64" }, "sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw=="], "@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.34.9", "", { "os": "linux", "cpu": "ppc64" }, "sha512-PHcNOAEhkoMSQtMf+rJofwisZqaU8iQ8EaSps58f5HYll9EAY5BSErCZ8qBDMVbq88h4UxaNPlbrKqfWP8RfJA=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.34.8", "", { "os": "linux", "cpu": "none" }, "sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw=="], "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.34.9", "", { "os": "linux", "cpu": "none" }, "sha512-Z2i0Uy5G96KBYKjeQFKbbsB54xFOL5/y1P5wNBsbXB8yE+At3oh0DVMjQVzCJRJSfReiB2tX8T6HUFZ2k8iaKg=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.34.8", "", { "os": "linux", "cpu": "s390x" }, "sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA=="], "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.34.9", "", { "os": "linux", "cpu": "s390x" }, "sha512-U+5SwTMoeYXoDzJX5dhDTxRltSrIax8KWwfaaYcynuJw8mT33W7oOgz0a+AaXtGuvhzTr2tVKh5UO8GVANTxyQ=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.34.8", "", { "os": "linux", "cpu": "x64" }, "sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA=="], "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.34.9", "", { "os": "linux", "cpu": "x64" }, "sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.34.8", "", { "os": "linux", "cpu": "x64" }, "sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ=="], "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.34.9", "", { "os": "linux", "cpu": "x64" }, "sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.34.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ=="], "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.34.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.34.8", "", { "os": "win32", "cpu": "ia32" }, "sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w=="], "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.34.9", "", { "os": "win32", "cpu": "ia32" }, "sha512-KB48mPtaoHy1AwDNkAJfHXvHp24H0ryZog28spEs0V48l3H1fr4i37tiyHsgKZJnCmvxsbATdZGBpbmxTE3a9w=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.34.8", "", { "os": "win32", "cpu": "x64" }, "sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g=="], "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.34.9", "", { "os": "win32", "cpu": "x64" }, "sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw=="],
"@surma/rollup-plugin-off-main-thread": ["@surma/rollup-plugin-off-main-thread@2.2.3", "", { "dependencies": { "ejs": "^3.1.6", "json5": "^2.2.0", "magic-string": "^0.25.0", "string.prototype.matchall": "^4.0.6" } }, "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ=="], "@surma/rollup-plugin-off-main-thread": ["@surma/rollup-plugin-off-main-thread@2.2.3", "", { "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/mapbox__vector-tile@1.3.4", "", { "dependencies": { "@types/geojson": "*", "@types/mapbox__point-geometry": "*", "@types/pbf": "*" } }, "sha512-bpd8dRn9pr6xKvuEBQup8pwQfD4VUyqO/2deGjfpe6AwC8YRlyEipvefyRJUSiCJTZuCb8Pl1ciVV5ekqJ96Bg=="], "@types/mapbox__vector-tile": ["@types/mapbox__vector-tile@1.3.4", "", { "dependencies": { "@types/geojson": "*", "@types/mapbox__point-geometry": "*", "@types/pbf": "*" } }, "sha512-bpd8dRn9pr6xKvuEBQup8pwQfD4VUyqO/2deGjfpe6AwC8YRlyEipvefyRJUSiCJTZuCb8Pl1ciVV5ekqJ96Bg=="],
"@types/node": ["@types/node@22.13.7", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-oU2q+BsQldB9lYxHNp/5aZO+/Bs0Usa74Abo9mAKulz4ahQyXRHK6UVKYIN8KSC8HXwhWSi7b49JnX+txuac0w=="], "@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
"@types/pbf": ["@types/pbf@3.0.5", "", {}, "sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA=="], "@types/pbf": ["@types/pbf@3.0.5", "", {}, "sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA=="],
@@ -966,16 +945,14 @@
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
"call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="], "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
"caniuse-lite": ["caniuse-lite@1.0.30001700", "", {}, "sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ=="], "caniuse-lite": ["caniuse-lite@1.0.30001701", "", {}, "sha512-faRs/AW3jA9nTwmJBSO1PQ6L/EOgsB5HMQQq4iCu5zhPgVVgO/pZRHlmatwijZKetFw8/Pr4q6dEN8sJuq8qTw=="],
"chai": ["chai@5.2.0", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw=="], "chai": ["chai@5.2.0", "", { "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": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"cheap-ruler": ["cheap-ruler@4.0.0", "", {}, "sha512-0BJa8f4t141BYKQyn9NSQt1PguFQXMXwZiA5shfoaBYHAb2fFk2RAX+tiWMoQU+Agtzt3mdt0JtuyshAXqZ+Vw=="],
"check-error": ["check-error@2.1.1", "", {}, "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw=="], "check-error": ["check-error@2.1.1", "", {}, "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw=="],
"chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
@@ -1008,7 +985,7 @@
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
"core-js-compat": ["core-js-compat@3.40.0", "", { "dependencies": { "browserslist": "^4.24.3" } }, "sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ=="], "core-js-compat": ["core-js-compat@3.41.0", "", { "dependencies": { "browserslist": "^4.24.4" } }, "sha512-RFsU9LySVue9RTwdDVX/T0e2Y6jRYWXERKElIjpuEOEnxaXffI0X7RUwVzfYLfzuLXSNJDYoRYUAmRUcyln20A=="],
"core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
@@ -1028,8 +1005,6 @@
"crypto-random-string": ["crypto-random-string@5.0.0", "", { "dependencies": { "type-fest": "^2.12.2" } }, "sha512-KWjTXWwxFd6a94m5CdRGW/t82Tr8DoBc9dNnPCAbFI1EBweN6v1tv8y4Y1m7ndkp/nkIBRxUxAzpaBnR2k3bcQ=="], "crypto-random-string": ["crypto-random-string@5.0.0", "", { "dependencies": { "type-fest": "^2.12.2" } }, "sha512-KWjTXWwxFd6a94m5CdRGW/t82Tr8DoBc9dNnPCAbFI1EBweN6v1tv8y4Y1m7ndkp/nkIBRxUxAzpaBnR2k3bcQ=="],
"csscolorparser": ["csscolorparser@1.0.3", "", {}, "sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"d3-array": ["d3-array@1.2.4", "", {}, "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw=="], "d3-array": ["d3-array@1.2.4", "", {}, "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw=="],
@@ -1080,7 +1055,7 @@
"ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="], "ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="],
"electron-to-chromium": ["electron-to-chromium@1.5.102", "", {}, "sha512-eHhqaja8tE/FNpIiBrvBjFV/SSKpyWHLvxuR9dPTdo+3V9ppdLmFB7ZZQ98qNovcngPLYIz0oOBF9P0FfZef5Q=="], "electron-to-chromium": ["electron-to-chromium@1.5.111", "", {}, "sha512-vJyJlO95wQRAw6K2ZGF/8nol7AcbCOnp8S6H91mwOOBbXoS9seDBYxCTPYAFsvXLxl3lc0jLXXe9GLxC4nXVog=="],
"elliptic": ["elliptic@6.6.1", "", { "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", "hash.js": "^1.0.0", "hmac-drbg": "^1.0.1", "inherits": "^2.0.4", "minimalistic-assert": "^1.0.1", "minimalistic-crypto-utils": "^1.0.1" } }, "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g=="], "elliptic": ["elliptic@6.6.1", "", { "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": ["evp_bytestokey@1.0.3", "", { "dependencies": { "md5.js": "^1.3.4", "safe-buffer": "^5.1.1" } }, "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA=="], "evp_bytestokey": ["evp_bytestokey@1.0.3", "", { "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": ["extend-shallow@2.0.1", "", { "dependencies": { "is-extendable": "^0.1.0" } }, "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug=="], "extend-shallow": ["extend-shallow@2.0.1", "", { "dependencies": { "is-extendable": "^0.1.0" } }, "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug=="],
@@ -1134,7 +1109,7 @@
"for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
"foreground-child": ["foreground-child@3.3.0", "", { "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" } }, "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg=="], "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
"fraction.js": ["fraction.js@4.3.7", "", {}, "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew=="], "fraction.js": ["fraction.js@4.3.7", "", {}, "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew=="],
@@ -1158,7 +1133,7 @@
"geojson-vt": ["geojson-vt@4.0.2", "", {}, "sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A=="], "geojson-vt": ["geojson-vt@4.0.2", "", {}, "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": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="], "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="],
@@ -1188,11 +1163,9 @@
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"grid-index": ["grid-index@1.1.0", "", {}, "sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA=="],
"gzipper": ["gzipper@8.2.0", "", { "dependencies": { "@gfx/zopfli": "^1.0.15", "commander": "^12.1.0", "simple-zstd": "^1.4.2" }, "bin": { "gzipper": "bin/index.js" } }, "sha512-JUvhzo8dHQWJp1eyYy1ShaPfcowsPbRc2rvwkD4LRyou/80UUz96bn+EOOYLWO4PG0Y5f3+UlUX9Gmu8RZhrtw=="], "gzipper": ["gzipper@8.2.0", "", { "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": ["happy-dom@17.1.8", "", { "dependencies": { "webidl-conversions": "^7.0.0", "whatwg-mimetype": "^3.0.0" } }, "sha512-Yxbq/FG79z1rhAf/iB6YM8wO2JB/JDQBy99RiLSs+2siEAi5J05x9eW1nnASHZJbpldjJE2KuFLsLZ+AzX/IxA=="], "happy-dom": ["happy-dom@17.1.9", "", { "dependencies": { "webidl-conversions": "^7.0.0", "whatwg-mimetype": "^3.0.0" } }, "sha512-HL26ajjMVe/wr3xlzjF0sCPCiAKaZJcIRFZHmG4yKHRJp4YAkHPG5X6GfWxCeDTpOmuHhNiOyNKUoZjjnm0tjw=="],
"has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="], "has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="],
@@ -1334,7 +1307,7 @@
"leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="], "leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="],
"libphonenumber-js": ["libphonenumber-js@1.11.20", "", {}, "sha512-/ipwAMvtSZRdiQBHqW1qxqeYiBMzncOQLVA+62MWYr7N4m7Q2jqpJ0WgT7zlOEOpyLRSqrMXidbJpC0J77AaKA=="], "libphonenumber-js": ["libphonenumber-js@1.12.4", "", {}, "sha512-vLmhg7Gan7idyAKfc6pvCtNzvar4/eIzrVVk3hjNFH5+fGqyjD0gQRovdTrDl20wsmZhBtmZpcsR0tOfquwb8g=="],
"lightningcss": ["lightningcss@1.29.1", "", { "dependencies": { "detect-libc": "^1.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.29.1", "lightningcss-darwin-x64": "1.29.1", "lightningcss-freebsd-x64": "1.29.1", "lightningcss-linux-arm-gnueabihf": "1.29.1", "lightningcss-linux-arm64-gnu": "1.29.1", "lightningcss-linux-arm64-musl": "1.29.1", "lightningcss-linux-x64-gnu": "1.29.1", "lightningcss-linux-x64-musl": "1.29.1", "lightningcss-win32-arm64-msvc": "1.29.1", "lightningcss-win32-x64-msvc": "1.29.1" } }, "sha512-FmGoeD4S05ewj+AkhTY+D+myDvXI6eL27FjHIjoyUkO/uw7WZD1fBVs0QxeYWa7E17CUHJaYX/RUGISCtcrG4Q=="], "lightningcss": ["lightningcss@1.29.1", "", { "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": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], "magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
"mapbox-gl": ["mapbox-gl@3.10.0", "", { "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": ["maplibre-gl@5.1.1", "", { "dependencies": { "@mapbox/geojson-rewind": "^0.5.2", "@mapbox/jsonlint-lines-primitives": "^2.0.2", "@mapbox/point-geometry": "^0.1.0", "@mapbox/tiny-sdf": "^2.0.6", "@mapbox/unitbezier": "^0.0.1", "@mapbox/vector-tile": "^1.3.1", "@mapbox/whoots-js": "^3.1.0", "@maplibre/maplibre-gl-style-spec": "^23.1.0", "@types/geojson": "^7946.0.16", "@types/geojson-vt": "3.2.5", "@types/mapbox__point-geometry": "^0.1.4", "@types/mapbox__vector-tile": "^1.3.4", "@types/pbf": "^3.0.5", "@types/supercluster": "^7.1.3", "earcut": "^3.0.1", "geojson-vt": "^4.0.2", "gl-matrix": "^3.4.3", "global-prefix": "^4.0.0", "kdbush": "^4.0.2", "murmurhash-js": "^1.0.0", "pbf": "^3.3.0", "potpack": "^2.0.0", "quickselect": "^3.0.0", "supercluster": "^8.0.1", "tinyqueue": "^3.0.0", "vt-pbf": "^3.1.3" } }, "sha512-0Z6ODzyFu/grwT6K1eIBpv6MZE4xnJD1AV+Yq1hPzOh/YCY36r9BlSaU7d7n2/HJOaoKOy0b2YF8cS4dD+iEVQ=="], "maplibre-gl": ["maplibre-gl@5.1.1", "", { "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": ["marchingsquares@1.3.3", "", {}, "sha512-gz6nNQoVK7Lkh2pZulrT4qd4347S/toG9RXH2pyzhLgkL5mLkBoqgv4EvAGXcV0ikDW72n/OQb3Xe8bGagQZCg=="], "marchingsquares": ["marchingsquares@1.3.3", "", {}, "sha512-gz6nNQoVK7Lkh2pZulrT4qd4347S/toG9RXH2pyzhLgkL5mLkBoqgv4EvAGXcV0ikDW72n/OQb3Xe8bGagQZCg=="],
@@ -1488,7 +1459,7 @@
"potpack": ["potpack@2.0.0", "", {}, "sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw=="], "potpack": ["potpack@2.0.0", "", {}, "sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw=="],
"preact": ["preact@10.26.2", "", {}, "sha512-0gNmv4qpS9HaN3+40CLBAnKe0ZfyE4ZWo5xKlC1rVrr0ckkEvJvAQqKaHANdFKsGstoxrY4AItZ7kZSGVoVjgg=="], "preact": ["preact@10.26.4", "", {}, "sha512-KJhO7LBFTjP71d83trW+Ilnjbo+ySsaAgCfXOXUlmGzJ4ygYPWmysm77yg4emwfmoz3b22yvH5IsVFHbhUaH5w=="],
"pretty-bytes": ["pretty-bytes@6.1.1", "", {}, "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ=="], "pretty-bytes": ["pretty-bytes@6.1.1", "", {}, "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ=="],
@@ -1582,7 +1553,7 @@
"robust-predicates": ["robust-predicates@3.0.2", "", {}, "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="], "robust-predicates": ["robust-predicates@3.0.2", "", {}, "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="],
"rollup": ["rollup@4.34.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": ["rollup@4.34.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": ["rw@1.3.3", "", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="], "rw": ["rw@1.3.3", "", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="],
@@ -1600,8 +1571,6 @@
"serialize-javascript": ["serialize-javascript@6.0.2", "", { "dependencies": { "randombytes": "^2.1.0" } }, "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g=="], "serialize-javascript": ["serialize-javascript@6.0.2", "", { "dependencies": { "randombytes": "^2.1.0" } }, "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g=="],
"serialize-to-js": ["serialize-to-js@3.1.2", "", {}, "sha512-owllqNuDDEimQat7EPG0tH7JjO090xKNzUtYz6X+Sk2BXDnOCilDdNLwjWeFywG9xkJul1ULvtUQa9O4pUaY0w=="],
"set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], "set-function-length": ["set-function-length@1.2.2", "", { "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": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="], "set-function-name": ["set-function-name@2.0.2", "", { "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": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
"std-env": ["std-env@3.8.0", "", {}, "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w=="], "std-env": ["std-env@3.8.1", "", {}, "sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA=="],
"ste-core": ["ste-core@3.0.11", "", {}, "sha512-ivkRENMh0mdGoPlZ4xVcEaC8rXQfTEfvonRw5m8VDKV7kgcbZbaNd1TnKl08wXbcLdT7okSc63HNP8cVhy95zg=="], "ste-core": ["ste-core@3.0.11", "", {}, "sha512-ivkRENMh0mdGoPlZ4xVcEaC8rXQfTEfvonRw5m8VDKV7kgcbZbaNd1TnKl08wXbcLdT7okSc63HNP8cVhy95zg=="],
@@ -1788,7 +1757,7 @@
"upath": ["upath@1.2.0", "", {}, "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg=="], "upath": ["upath@1.2.0", "", {}, "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg=="],
"update-browserslist-db": ["update-browserslist-db@1.1.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": ["update-browserslist-db@1.1.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": ["url@0.11.4", "", { "dependencies": { "punycode": "^1.4.1", "qs": "^6.12.3" } }, "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg=="], "url": ["url@0.11.4", "", { "dependencies": { "punycode": "^1.4.1", "qs": "^6.12.3" } }, "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg=="],
@@ -1884,8 +1853,6 @@
"zustand": ["zustand@5.0.3", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg=="], "zustand": ["zustand@5.0.3", "", { "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/meshtastic__core@2.6.0-0", "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/pluginutils@3.1.0", "", { "dependencies": { "@types/estree": "0.0.39", "estree-walker": "^1.0.1", "picomatch": "^2.2.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0" } }, "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg=="], "@rollup/plugin-babel/@rollup/pluginutils": ["@rollup/pluginutils@3.1.0", "", { "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": ["rollup@2.79.2", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ=="], "@rollup/plugin-babel/rollup": ["rollup@2.79.2", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ=="],
@@ -1944,7 +1911,7 @@
"rbush/quickselect": ["quickselect@2.0.0", "", {}, "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw=="], "rbush/quickselect": ["quickselect@2.0.0", "", {}, "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw=="],
"react-scan/@types/node": ["@types/node@20.17.19", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-LEwC7o1ifqg/6r2gn9Dns0f1rhK+fPFDoMiceTJ6kWmVk6bgXBI/9IOWfVan4WiAavK9pIVWdX0/e3J+eEUh5A=="], "react-scan/@types/node": ["@types/node@20.17.23", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-8PCGZ1ZJbEZuYNTMqywO+Sj4vSKjSjT6Ua+6RFOYlEvIvKQABPtrNkoVSLSKDb4obYcMhspVKmsw8Cm10NFRUg=="],
"react-scan/esbuild": ["esbuild@0.24.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.24.2", "@esbuild/android-arm": "0.24.2", "@esbuild/android-arm64": "0.24.2", "@esbuild/android-x64": "0.24.2", "@esbuild/darwin-arm64": "0.24.2", "@esbuild/darwin-x64": "0.24.2", "@esbuild/freebsd-arm64": "0.24.2", "@esbuild/freebsd-x64": "0.24.2", "@esbuild/linux-arm": "0.24.2", "@esbuild/linux-arm64": "0.24.2", "@esbuild/linux-ia32": "0.24.2", "@esbuild/linux-loong64": "0.24.2", "@esbuild/linux-mips64el": "0.24.2", "@esbuild/linux-ppc64": "0.24.2", "@esbuild/linux-riscv64": "0.24.2", "@esbuild/linux-s390x": "0.24.2", "@esbuild/linux-x64": "0.24.2", "@esbuild/netbsd-arm64": "0.24.2", "@esbuild/netbsd-x64": "0.24.2", "@esbuild/openbsd-arm64": "0.24.2", "@esbuild/openbsd-x64": "0.24.2", "@esbuild/sunos-x64": "0.24.2", "@esbuild/win32-arm64": "0.24.2", "@esbuild/win32-ia32": "0.24.2", "@esbuild/win32-x64": "0.24.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA=="], "react-scan/esbuild": ["esbuild@0.24.2", "", { "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
View File
@@ -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"
]
}
Generated
+6804
View File
File diff suppressed because it is too large Load Diff
+20 -22
View File
@@ -6,27 +6,17 @@
"license": "GPL-3.0-only", "license": "GPL-3.0-only",
"scripts": { "scripts": {
"build": "vite build", "build": "vite build",
"build:analyze": "BUNDLE_ANALYZE=true vite build", "build:analyze": "BUNDLE_ANALYZE=true deno task build",
"check": "biome check src/", "lint": "deno lint src/",
"check:fix": "pnpm check --write src/", "lint:fix": "deno lint --fix src/",
"format": "biome format --write src/", "format": "deno fmt src/",
"dev": "vite dev --open", "dev": "deno task dev:ui",
"dev:scan": "VITE_DEBUG_SCAN=true vite dev", "dev:ui": "deno run -A npm:vite dev",
"test": "vitest", "dev:scan": "VITE_DEBUG_SCAN=true deno task dev:ui",
"test:ui": "vitest --ui", "test": "deno run -A npm:vitest",
"test:run": "vitest run", "test:ui": "deno task test --ui",
"preview": "vite preview", "preview": "deno run -A npm:vite preview",
"package": "gzipper c -i html,js,css,png,ico,svg,webmanifest,txt dist dist/output && tar -cvf dist/build.tar -C ./dist/output/ $(ls ./dist/output/)", "package": "gzipper c -i html,js,css,png,ico,svg,webmanifest,txt dist dist/output && tar -cvf dist/build.tar -C ./dist/output/ ."
"postinstall": "npx simple-git-hooks"
},
"simple-git-hooks": {
"pre-commit": "bun run check:fix && bun run format"
},
"lint-staged": {
"*.{ts,tsx}": [
"bun run check:fix",
"bun run format"
]
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -35,6 +25,15 @@
"bugs": { "bugs": {
"url": "https://github.com/meshtastic/web/issues" "url": "https://github.com/meshtastic/web/issues"
}, },
"simple-git-hooks": {
"pre-commit": "deno task lint:fix && deno task format"
},
"lint-staged": {
"*.{ts,tsx}": [
"deno task lint:fix",
"deno task format"
]
},
"homepage": "https://meshtastic.org", "homepage": "https://meshtastic.org",
"dependencies": { "dependencies": {
"@bufbuild/protobuf": "^2.2.3", "@bufbuild/protobuf": "^2.2.3",
@@ -80,7 +79,6 @@
"zustand": "5.0.3" "zustand": "5.0.3"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^1.9.4",
"@tailwindcss/postcss": "^4.0.9", "@tailwindcss/postcss": "^4.0.9",
"@testing-library/react": "^16.2.0", "@testing-library/react": "^16.2.0",
"@types/chrome": "^0.0.307", "@types/chrome": "^0.0.307",
+3 -4
View File
@@ -1,11 +1,10 @@
import { DeviceWrapper } from "@app/DeviceWrapper.tsx"; import { DeviceWrapper } from "@app/DeviceWrapper.tsx";
import { PageRouter } from "@app/PageRouter.tsx"; import { PageRouter } from "@app/PageRouter.tsx";
import { ThemeProvider } from "@app/components/generic/ThemeProvider";
import { CommandPalette } from "@components/CommandPalette.tsx"; import { CommandPalette } from "@components/CommandPalette.tsx";
import { DeviceSelector } from "@components/DeviceSelector.tsx"; import { DeviceSelector } from "@components/DeviceSelector.tsx";
import { DialogManager } from "@components/Dialog/DialogManager"; import { DialogManager } from "@components/Dialog/DialogManager.tsx";
import { NewDeviceDialog } from "@components/Dialog/NewDeviceDialog.tsx"; import { NewDeviceDialog } from "@components/Dialog/NewDeviceDialog.tsx";
import { KeyBackupReminder } from "@components/KeyBackupReminder"; import { KeyBackupReminder } from "@components/KeyBackupReminder.tsx";
import { Toaster } from "@components/Toaster.tsx"; import { Toaster } from "@components/Toaster.tsx";
import Footer from "@components/UI/Footer.tsx"; import Footer from "@components/UI/Footer.tsx";
import { useAppStore } from "@core/stores/appStore.ts"; import { useAppStore } from "@core/stores/appStore.ts";
@@ -13,7 +12,7 @@ import { useDeviceStore } from "@core/stores/deviceStore.ts";
import { Dashboard } from "@pages/Dashboard/index.tsx"; import { Dashboard } from "@pages/Dashboard/index.tsx";
import type { JSX } from "react"; import type { JSX } from "react";
import { ErrorBoundary } from "react-error-boundary"; import { ErrorBoundary } from "react-error-boundary";
import { ErrorPage } from "./components/UI/ErrorPage"; import { ErrorPage } from "@components/UI/ErrorPage.tsx";
import { MapProvider } from "react-map-gl/maplibre"; import { MapProvider } from "react-map-gl/maplibre";
+1 -1
View File
@@ -5,7 +5,7 @@ import ConfigPage from "@pages/Config/index.tsx";
import MessagesPage from "@pages/Messages.tsx"; import MessagesPage from "@pages/Messages.tsx";
import NodesPage from "@pages/Nodes.tsx"; import NodesPage from "@pages/Nodes.tsx";
import { ErrorBoundary } from "react-error-boundary"; import { ErrorBoundary } from "react-error-boundary";
import { ErrorPage } from "./components/UI/ErrorPage"; import { ErrorPage } from "@components/UI/ErrorPage.tsx";
export const ErrorBoundaryWrapper = ({ export const ErrorBoundaryWrapper = ({
children, children,
+6 -8
View File
@@ -1,4 +1,4 @@
import { Avatar } from "@components/UI/Avatar"; import { Avatar } from "./UI/Avatar.tsx";
import { import {
CommandDialog, CommandDialog,
CommandEmpty, CommandEmpty,
@@ -117,13 +117,11 @@ export const CommandPalette = () => {
return { return {
label: label:
device.nodes.get(device.hardware.myNodeNum)?.user?.longName ?? device.nodes.get(device.hardware.myNodeNum)?.user?.longName ??
device.hardware.myNodeNum.toString(), device.hardware.myNodeNum.toString(),
icon: ( icon: (
<Avatar <Avatar
text={ text={device.nodes.get(device.hardware.myNodeNum)?.user
device.nodes.get(device.hardware.myNodeNum)?.user ?.shortName ?? device.hardware.myNodeNum.toString()}
?.shortName ?? device.hardware.myNodeNum.toString()
}
/> />
), ),
action() { action() {
@@ -241,8 +239,8 @@ export const CommandPalette = () => {
} }
}; };
window.addEventListener("keydown", handleKeydown); globalThis.addEventListener("keydown", handleKeydown);
return () => window.removeEventListener("keydown", handleKeydown); return () => globalThis.removeEventListener("keydown", handleKeydown);
}, [setCommandPaletteOpen]); }, [setCommandPaletteOpen]);
return ( return (
+10 -11
View File
@@ -5,10 +5,9 @@ import { Code } from "@components/UI/Typography/Code.tsx";
import { useAppStore } from "@core/stores/appStore.ts"; import { useAppStore } from "@core/stores/appStore.ts";
import { useDeviceStore } from "@core/stores/deviceStore.ts"; import { useDeviceStore } from "@core/stores/deviceStore.ts";
import { HomeIcon, PlusIcon, SearchIcon } from "lucide-react"; import { HomeIcon, PlusIcon, SearchIcon } from "lucide-react";
import type { JSX } from "react"; import { Avatar } from "@components/UI/Avatar.tsx";
import { Avatar } from "./UI/Avatar";
export const DeviceSelector = (): JSX.Element => { export const DeviceSelector = () => {
const { getDevices } = useDeviceStore(); const { getDevices } = useDeviceStore();
const { const {
selectedDevice, selectedDevice,
@@ -38,11 +37,9 @@ export const DeviceSelector = (): JSX.Element => {
active={selectedDevice === device.id} active={selectedDevice === device.id}
> >
<Avatar <Avatar
text={ text={device.nodes
device.nodes .get(device.hardware.myNodeNum)
.get(device.hardware.myNodeNum) ?.user?.shortName.toString() ?? "UNK"}
?.user?.shortName.toString() ?? "UNK"
}
/> />
</DeviceSelectorButton> </DeviceSelectorButton>
))} ))}
@@ -66,11 +63,13 @@ export const DeviceSelector = (): JSX.Element => {
<SearchIcon /> <SearchIcon />
</button> </button>
{/* TODO: This is being commented out until its fixed */} {/* TODO: This is being commented out until its fixed */}
{/* <button type="button" className="transition-all hover:text-accent"> {
/* <button type="button" className="transition-all hover:text-accent">
<LanguagesIcon /> <LanguagesIcon />
</button> */} </button> */
}
<Separator /> <Separator />
<Code>{process.env.COMMIT_HASH}</Code> <Code>{import.meta.env.VITE_COMMIT_HASH}</Code>
</div> </div>
</nav> </nav>
); );
+4 -3
View File
@@ -5,7 +5,6 @@ export interface DeviceSelectorButtonProps {
} }
export const DeviceSelectorButton = ({ export const DeviceSelectorButton = ({
active,
onClick, onClick,
children, children,
}: DeviceSelectorButtonProps) => ( }: DeviceSelectorButtonProps) => (
@@ -14,9 +13,11 @@ export const DeviceSelectorButton = ({
onClick={onClick} onClick={onClick}
onKeyDown={onClick} onKeyDown={onClick}
> >
{/* {active && ( {
/* {active && (
<div className="absolute -left-2 h-10 w-1.5 rounded-full bg-accent" /> <div className="absolute -left-2 h-10 w-1.5 rounded-full bg-accent" />
)} */} )} */
}
<div className="flex aspect-square cursor-pointer flex-col items-center justify-center"> <div className="flex aspect-square cursor-pointer flex-col items-center justify-center">
{children} {children}
</div> </div>
+1 -1
View File
@@ -1,4 +1,4 @@
import { useDevice } from "@app/core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { Button } from "@components/UI/Button.tsx"; import { Button } from "@components/UI/Button.tsx";
import { import {
+5 -5
View File
@@ -1,15 +1,15 @@
import { RemoveNodeDialog } from "@app/components/Dialog/RemoveNodeDialog.tsx"; import { RemoveNodeDialog } from "@components/Dialog/RemoveNodeDialog.tsx";
import { DeviceNameDialog } from "@components/Dialog/DeviceNameDialog.tsx"; import { DeviceNameDialog } from "@components/Dialog/DeviceNameDialog.tsx";
import { ImportDialog } from "@components/Dialog/ImportDialog.tsx"; import { ImportDialog } from "@components/Dialog/ImportDialog.tsx";
import { PkiBackupDialog } from "@components/Dialog/PKIBackupDialog"; import { PkiBackupDialog } from "./PKIBackupDialog.tsx";
import { QRDialog } from "@components/Dialog/QRDialog.tsx"; import { QRDialog } from "@components/Dialog/QRDialog.tsx";
import { RebootDialog } from "@components/Dialog/RebootDialog.tsx"; import { RebootDialog } from "@components/Dialog/RebootDialog.tsx";
import { ShutdownDialog } from "@components/Dialog/ShutdownDialog.tsx"; import { ShutdownDialog } from "@components/Dialog/ShutdownDialog.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import type { JSX } from "react";
import { NodeDetailsDialog } from "./NodeDetailsDialog";
export const DialogManager = (): JSX.Element => { import { NodeDetailsDialog } from "./NodeDetailsDialog.tsx";
export const DialogManager = () => {
const { channels, config, dialog, setDialogOpen } = useDevice(); const { channels, config, dialog, setDialogOpen } = useDevice();
return ( return (
<> <>
+15 -12
View File
@@ -15,7 +15,7 @@ import { Switch } from "@components/UI/Switch.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
import { toByteArray } from "base64-js"; import { toByteArray } from "base64-js";
import { type JSX, useEffect, useState } from "react"; import { useEffect, useState } from "react";
export interface ImportDialogProps { export interface ImportDialogProps {
open: boolean; open: boolean;
@@ -26,7 +26,7 @@ export interface ImportDialogProps {
export const ImportDialog = ({ export const ImportDialog = ({
open, open,
onOpenChange, onOpenChange,
}: ImportDialogProps): JSX.Element => { }: ImportDialogProps) => {
const [importDialogInput, setImportDialogInput] = useState<string>(""); const [importDialogInput, setImportDialogInput] = useState<string>("");
const [channelSet, setChannelSet] = useState<Protobuf.AppOnly.ChannelSet>(); const [channelSet, setChannelSet] = useState<Protobuf.AppOnly.ChannelSet>();
const [validUrl, setValidUrl] = useState<boolean>(false); const [validUrl, setValidUrl] = useState<boolean>(false);
@@ -62,7 +62,7 @@ export const ImportDialog = ({
), ),
); );
setValidUrl(true); setValidUrl(true);
} catch (error) { } catch (_error) {
setValidUrl(false); setValidUrl(false);
setChannelSet(undefined); setChannelSet(undefined);
} }
@@ -73,10 +73,9 @@ export const ImportDialog = ({
connection?.setChannel( connection?.setChannel(
create(Protobuf.Channel.ChannelSchema, { create(Protobuf.Channel.ChannelSchema, {
index, index,
role: role: index === 0
index === 0 ? Protobuf.Channel.Channel_Role.PRIMARY
? Protobuf.Channel.Channel_Role.PRIMARY : Protobuf.Channel.Channel_Role.SECONDARY,
: Protobuf.Channel.Channel_Role.SECONDARY,
settings: ch, settings: ch,
}), }),
); );
@@ -119,25 +118,29 @@ export const ImportDialog = ({
<div className="w-36"> <div className="w-36">
<Label>Use Preset?</Label> <Label>Use Preset?</Label>
<Switch <Switch
disabled={true} disabled
checked={channelSet?.loraConfig?.usePreset ?? true} checked={channelSet?.loraConfig?.usePreset ?? true}
/> />
</div> </div>
{/* <Select {
/* <Select
label="Modem Preset" label="Modem Preset"
disabled disabled
value={channelSet?.loraConfig?.modemPreset} value={channelSet?.loraConfig?.modemPreset}
> >
{renderOptions(Protobuf.Config_LoRaConfig_ModemPreset)} {renderOptions(Protobuf.Config_LoRaConfig_ModemPreset)}
</Select> */} </Select> */
}
</div> </div>
{/* <Select {
/* <Select
label="Region" label="Region"
disabled disabled
value={channelSet?.loraConfig?.region} value={channelSet?.loraConfig?.region}
> >
{renderOptions(Protobuf.Config_LoRaConfig_RegionCode)} {renderOptions(Protobuf.Config_LoRaConfig_RegionCode)}
</Select> */} </Select> */
}
<span className="text-md block font-medium text-text-primary"> <span className="text-md block font-medium text-text-primary">
Channels: Channels:
@@ -1,14 +1,13 @@
import { useDevice } from "@app/core/stores/deviceStore"; import { useDevice } from "../../core/stores/deviceStore.ts";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
DialogDescription, DialogDescription,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
} from "@components/UI/Dialog"; } from "../UI/Dialog.tsx";
import type { Protobuf, Types } from "@meshtastic/core"; import type { Protobuf, Types } from "@meshtastic/core";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import type { JSX } from "react";
export interface LocationResponseDialogProps { export interface LocationResponseDialogProps {
location: Types.PacketMetadata<Protobuf.Mesh.location> | undefined; location: Types.PacketMetadata<Protobuf.Mesh.location> | undefined;
@@ -20,15 +19,13 @@ export const LocationResponseDialog = ({
location, location,
open, open,
onOpenChange, onOpenChange,
}: LocationResponseDialogProps): JSX.Element => { }: LocationResponseDialogProps) => {
const { nodes } = useDevice(); const { nodes } = useDevice();
const from = nodes.get(location?.from ?? 0); const from = nodes.get(location?.from ?? 0);
const longName = const longName = from?.user?.longName ??
from?.user?.longName ??
(from ? `!${numberToHexUnpadded(from?.num)}` : "Unknown"); (from ? `!${numberToHexUnpadded(from?.num)}` : "Unknown");
const shortName = const shortName = from?.user?.shortName ??
from?.user?.shortName ??
(from ? `${numberToHexUnpadded(from?.num).substring(0, 4)}` : "UNK"); (from ? `${numberToHexUnpadded(from?.num).substring(0, 4)}` : "UNK");
return ( return (
+15 -15
View File
@@ -1,7 +1,7 @@
import { import {
type BrowserFeature, type BrowserFeature,
useBrowserFeatureDetection, useBrowserFeatureDetection,
} from "@app/core/hooks/useBrowserFeatureDetection"; } from "../../core/hooks/useBrowserFeatureDetection.ts";
import { BLE } from "@components/PageComponents/Connect/BLE.tsx"; import { BLE } from "@components/PageComponents/Connect/BLE.tsx";
import { HTTP } from "@components/PageComponents/Connect/HTTP.tsx"; import { HTTP } from "@components/PageComponents/Connect/HTTP.tsx";
import { Serial } from "@components/PageComponents/Connect/Serial.tsx"; import { Serial } from "@components/PageComponents/Connect/Serial.tsx";
@@ -18,9 +18,9 @@ import {
TabsTrigger, TabsTrigger,
} from "@components/UI/Tabs.tsx"; } from "@components/UI/Tabs.tsx";
import { Subtle } from "@components/UI/Typography/Subtle.tsx"; import { Subtle } from "@components/UI/Typography/Subtle.tsx";
import { AlertCircle, InfoIcon } from "lucide-react"; import { AlertCircle } from "lucide-react";
import { Fragment, type JSX } from "react/jsx-runtime"; import { Link } from "../UI/Typography/Link.tsx";
import { Link } from "../UI/Typography/Link"; import { Fragment } from "react/jsx-runtime";
export interface TabElementProps { export interface TabElementProps {
closeDialog: () => void; closeDialog: () => void;
@@ -85,14 +85,16 @@ const ErrorMessage = ({ missingFeatures }: FeatureErrorProps) => {
<p className="text-sm"> <p className="text-sm">
{browserFeatures.length > 0 && ( {browserFeatures.length > 0 && (
<> <>
This application requires {formatFeatureList(browserFeatures)}. This application requires{" "}
Please use a Chromium-based browser like Chrome or Edge. {formatFeatureList(browserFeatures)}. Please use a
Chromium-based browser like Chrome or Edge.
</> </>
)} )}
{needsSecureContext && ( {needsSecureContext && (
<> <>
{browserFeatures.length > 0 && " Additionally, it"} {browserFeatures.length > 0 && " Additionally, it"}
{browserFeatures.length === 0 && "This application"} requires a{" "} {browserFeatures.length === 0 && "This application"} requires a
{" "}
<Link href={links["Secure Context"]}>secure context</Link>. <Link href={links["Secure Context"]}>secure context</Link>.
Please connect using HTTPS or localhost. Please connect using HTTPS or localhost.
</> </>
@@ -107,7 +109,7 @@ const ErrorMessage = ({ missingFeatures }: FeatureErrorProps) => {
export const NewDeviceDialog = ({ export const NewDeviceDialog = ({
open, open,
onOpenChange, onOpenChange,
}: NewDeviceProps): JSX.Element => { }: NewDeviceProps) => {
const { unsupported } = useBrowserFeatureDetection(); const { unsupported } = useBrowserFeatureDetection();
const tabs: TabManifest[] = [ const tabs: TabManifest[] = [
@@ -119,15 +121,13 @@ export const NewDeviceDialog = ({
{ {
label: "Bluetooth", label: "Bluetooth",
element: BLE, element: BLE,
isDisabled: isDisabled: unsupported.includes("Web Bluetooth") ||
unsupported.includes("Web Bluetooth") ||
unsupported.includes("Secure Context"), unsupported.includes("Secure Context"),
}, },
{ {
label: "Serial", label: "Serial",
element: Serial, element: Serial,
isDisabled: isDisabled: unsupported.includes("Web Serial") ||
unsupported.includes("Web Serial") ||
unsupported.includes("Secure Context"), unsupported.includes("Secure Context"),
}, },
]; ];
@@ -149,9 +149,9 @@ export const NewDeviceDialog = ({
{tabs.map((tab) => ( {tabs.map((tab) => (
<TabsContent key={tab.label} value={tab.label}> <TabsContent key={tab.label} value={tab.label}>
<fieldset disabled={tab.isDisabled}> <fieldset disabled={tab.isDisabled}>
{tab.isDisabled ? ( {tab.isDisabled
<ErrorMessage missingFeatures={unsupported} /> ? <ErrorMessage missingFeatures={unsupported} />
) : null} : null}
<tab.element closeDialog={() => onOpenChange(false)} /> <tab.element closeDialog={() => onOpenChange(false)} />
</fieldset> </fieldset>
</TabsContent> </TabsContent>
+158 -133
View File
@@ -1,23 +1,23 @@
import { useAppStore } from "@app/core/stores/appStore"; import { useAppStore } from "../../core/stores/appStore.ts";
import { useDevice } from "@app/core/stores/deviceStore"; import { useDevice } from "../../core/stores/deviceStore.ts";
import { import {
Accordion, Accordion,
AccordionContent, AccordionContent,
AccordionItem, AccordionItem,
AccordionTrigger, AccordionTrigger,
} from "@components/UI/Accordion"; } from "../UI/Accordion.tsx";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
DialogFooter, DialogFooter,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
} from "@components/UI/Dialog"; } from "../UI/Dialog.tsx";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import { DeviceImage } from "../generic/DeviceImage"; import { DeviceImage } from "../generic/DeviceImage.tsx";
import { TimeAgo } from "../generic/TimeAgo"; import { TimeAgo } from "../generic/TimeAgo.tsx";
import { Uptime } from "../generic/Uptime"; import { Uptime } from "../generic/Uptime.tsx";
export interface NodeDetailsDialogProps { export interface NodeDetailsDialogProps {
open: boolean; open: boolean;
@@ -32,134 +32,159 @@ export const NodeDetailsDialog = ({
const { nodeNumDetails } = useAppStore(); const { nodeNumDetails } = useAppStore();
const device: Protobuf.Mesh.NodeInfo = nodes.get(nodeNumDetails); const device: Protobuf.Mesh.NodeInfo = nodes.get(nodeNumDetails);
return device ? ( return device
<Dialog open={open} onOpenChange={onOpenChange}> ? (
<DialogContent> <Dialog open={open} onOpenChange={onOpenChange}>
<DialogHeader> <DialogContent>
<DialogTitle> <DialogHeader>
Node Details for {device.user?.longName ?? "UNKNOWN"} ( <DialogTitle>
{device.user?.shortName ?? "UNK"}) Node Details for {device.user?.longName ?? "UNKNOWN"} (
</DialogTitle> {device.user?.shortName ?? "UNK"})
</DialogHeader> </DialogTitle>
<DialogFooter> </DialogHeader>
<div className="w-full"> <DialogFooter>
<DeviceImage <div className="w-full">
className="w-32 h-32 mx-auto rounded-lg border-4 border-slate-200 dark:border-slate-800" <DeviceImage
deviceType={ className="w-32 h-32 mx-auto rounded-lg border-4 border-slate-200 dark:border-slate-800"
Protobuf.Mesh.HardwareModel[device.user?.hwModel ?? 0] deviceType={Protobuf.Mesh
} .HardwareModel[device.user?.hwModel ?? 0]}
/> />
<div className="bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3"> <div className="bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
<p className="text-lg font-semibold text-slate-900 dark:text-slate-50"> <p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
Details: Details:
</p> </p>
<p> <p>
Hardware:{" "} Hardware:{" "}
{Protobuf.Mesh.HardwareModel[device.user?.hwModel ?? 0]} {Protobuf.Mesh.HardwareModel[device.user?.hwModel ?? 0]}
</p> </p>
<p>Node Number: {device.num}</p> <p>Node Number: {device.num}</p>
<p>Node HEX: !{numberToHexUnpadded(device.num)}</p> <p>Node HEX: !{numberToHexUnpadded(device.num)}</p>
<p> <p>
Role:{" "} Role: {Protobuf.Config.Config_DeviceConfig_Role[
{
Protobuf.Config.Config_DeviceConfig_Role[
device.user?.role ?? 0 device.user?.role ?? 0
] ]}
}
</p>
<p>
Last Heard:{" "}
{device.lastHeard === 0 ? (
"Never"
) : (
<TimeAgo timestamp={device.lastHeard * 1000} />
)}
</p>
</div>
{device.position ? (
<div className="mt-5 bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
<p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
Position:
</p> </p>
{device.position.latitudeI && device.position.longitudeI ? ( <p>
<p> Last Heard: {device.lastHeard === 0
Coordinates:{" "} ? (
<a "Never"
className="text-blue-500 dark:text-blue-400" )
href={`https://www.openstreetmap.org/?mlat=${ : <TimeAgo timestamp={device.lastHeard * 1000} />}
device.position.latitudeI / 1e7 </p>
}&mlon=${device.position.longitudeI / 1e7}&layers=N`} </div>
target="_blank"
rel="noreferrer" {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
> >
{device.position.latitudeI / 1e7},{" "} <AccordionItem className="AccordionItem" value="item-1">
{device.position.longitudeI / 1e7} <AccordionTrigger>
</a> <p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
</p> All Raw Metrics:
) : null} </p>
{device.position.altitude ? ( </AccordionTrigger>
<p>Altitude: {device.position.altitude}m</p> <AccordionContent className="overflow-x-scroll">
) : null} <pre className="text-xs w-full">
</div>
) : null}
{device.deviceMetrics ? (
<div className="mt-5 bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
<p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
Device Metrics:
</p>
{device.deviceMetrics.airUtilTx ? (
<p>
Air TX utilization:{" "}
{device.deviceMetrics.airUtilTx.toFixed(2)}%
</p>
) : null}
{device.deviceMetrics.channelUtilization ? (
<p>
Channel utilization:{" "}
{device.deviceMetrics.channelUtilization.toFixed(2)}%
</p>
) : null}
{device.deviceMetrics.batteryLevel ? (
<p>
Battery level:{" "}
{device.deviceMetrics.batteryLevel.toFixed(2)}%
</p>
) : null}
{device.deviceMetrics.voltage ? (
<p>Voltage: {device.deviceMetrics.voltage.toFixed(2)}V</p>
) : null}
{device.deviceMetrics.uptimeSeconds ? (
<p>
Uptime:{" "}
<Uptime seconds={device.deviceMetrics.uptimeSeconds} />
</p>
) : null}
</div>
) : null}
{device ? (
<div className="mt-5 w-full max-w-[464px] bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
<Accordion className="AccordionRoot" type="single" collapsible>
<AccordionItem className="AccordionItem" value="item-1">
<AccordionTrigger>
<p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
All Raw Metrics:
</p>
</AccordionTrigger>
<AccordionContent className="overflow-x-scroll">
<pre className="text-xs w-full">
{JSON.stringify(device, null, 2)} {JSON.stringify(device, null, 2)}
</pre> </pre>
</AccordionContent> </AccordionContent>
</AccordionItem> </AccordionItem>
</Accordion> </Accordion>
</div> </div>
) : null} )
</div> : null}
</DialogFooter> </div>
</DialogContent> </DialogFooter>
</Dialog> </DialogContent>
) : null; </Dialog>
)
: null;
}; };
+11 -13
View File
@@ -1,17 +1,17 @@
import { toast } from "@app/core/hooks/useToast"; import { toast } from "../../core/hooks/useToast.ts";
import { useAppStore } from "@app/core/stores/appStore"; import { useAppStore } from "../../core/stores/appStore.ts";
import { useDevice } from "@app/core/stores/deviceStore"; import { useDevice } from "../../core/stores/deviceStore.ts";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
} from "@components/UI/Dialog"; } from "../UI/Dialog.tsx";
import type { Protobuf } from "@meshtastic/core"; import type { Protobuf } from "@meshtastic/core";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import { TrashIcon } from "lucide-react"; import { TrashIcon } from "lucide-react";
import type { JSX } from "react";
import { Button } from "../UI/Button"; import { Button } from "../UI/Button.tsx";
export interface NodeOptionsDialogProps { export interface NodeOptionsDialogProps {
node: Protobuf.Mesh.NodeInfo | undefined; node: Protobuf.Mesh.NodeInfo | undefined;
@@ -23,7 +23,7 @@ export const NodeOptionsDialog = ({
node, node,
open, open,
onOpenChange, onOpenChange,
}: NodeOptionsDialogProps): JSX.Element => { }: NodeOptionsDialogProps) => {
const { setDialogOpen, connection, setActivePage } = useDevice(); const { setDialogOpen, connection, setActivePage } = useDevice();
const { const {
setNodeNumToBeRemoved, setNodeNumToBeRemoved,
@@ -31,11 +31,9 @@ export const NodeOptionsDialog = ({
setChatType, setChatType,
setActiveChat, setActiveChat,
} = useAppStore(); } = useAppStore();
const longName = const longName = node?.user?.longName ??
node?.user?.longName ??
(node ? `!${numberToHexUnpadded(node?.num)}` : "Unknown"); (node ? `!${numberToHexUnpadded(node?.num)}` : "Unknown");
const shortName = const shortName = node?.user?.shortName ??
node?.user?.shortName ??
(node ? `${numberToHexUnpadded(node?.num).substring(0, 4)}` : "UNK"); (node ? `${numberToHexUnpadded(node?.num).substring(0, 4)}` : "UNK");
function handleDirectMessage() { function handleDirectMessage() {
@@ -53,7 +51,7 @@ export const NodeOptionsDialog = ({
connection?.requestPosition(node.num).then(() => connection?.requestPosition(node.num).then(() =>
toast({ toast({
title: "Position request sent.", title: "Position request sent.",
}), })
); );
onOpenChange(); onOpenChange();
} }
@@ -66,7 +64,7 @@ export const NodeOptionsDialog = ({
connection?.traceRoute(node.num).then(() => connection?.traceRoute(node.num).then(() =>
toast({ toast({
title: "Traceroute sent.", title: "Traceroute sent.",
}), })
); );
onOpenChange(); onOpenChange();
} }
+5 -5
View File
@@ -1,5 +1,5 @@
import { useDevice } from "@app/core/stores/deviceStore"; import { useDevice } from "../../core/stores/deviceStore.ts";
import { Button } from "@components/UI/Button"; import { Button } from "../UI/Button.tsx";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
@@ -40,7 +40,7 @@ export const PkiBackupDialog = ({
const renderPrintWindow = React.useCallback(() => { const renderPrintWindow = React.useCallback(() => {
if (!privateKey || !publicKey) return; if (!privateKey || !publicKey) return;
const printWindow = window.open("", "_blank"); const printWindow = globalThis.open("", "_blank");
if (printWindow) { if (printWindow) {
printWindow.document.write(` printWindow.document.write(`
<html> <html>
@@ -116,14 +116,14 @@ export const PkiBackupDialog = ({
</DialogHeader> </DialogHeader>
<DialogFooter className="mt-6"> <DialogFooter className="mt-6">
<Button <Button
variant={"default"} variant="default"
onClick={() => createDownloadKeyFile()} onClick={() => createDownloadKeyFile()}
className="" className=""
> >
<DownloadIcon size={20} className="mr-2" /> <DownloadIcon size={20} className="mr-2" />
Download Download
</Button> </Button>
<Button variant={"default"} onClick={() => renderPrintWindow()}> <Button variant="default" onClick={() => renderPrintWindow()}>
<PrinterIcon size={20} className="mr-2" /> <PrinterIcon size={20} className="mr-2" />
Print Print
</Button> </Button>
@@ -18,7 +18,7 @@ export const PkiRegenerateDialog = ({
open, open,
onOpenChange, onOpenChange,
onSubmit, onSubmit,
}: PkiRegenerateDialogProps): JSX.Element => { }: PkiRegenerateDialogProps) => {
return ( return (
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent> <DialogContent>
+7 -5
View File
@@ -28,7 +28,7 @@ export const QRDialog = ({
onOpenChange, onOpenChange,
loraConfig, loraConfig,
channels, channels,
}: QRDialogProps): JSX.Element => { }: QRDialogProps) => {
const [selectedChannels, setSelectedChannels] = useState<number[]>([0]); const [selectedChannels, setSelectedChannels] = useState<number[]>([0]);
const [qrCodeUrl, setQrCodeUrl] = useState<string>(""); const [qrCodeUrl, setQrCodeUrl] = useState<string>("");
const [qrCodeAdd, setQrCodeAdd] = useState<boolean>(); const [qrCodeAdd, setQrCodeAdd] = useState<boolean>();
@@ -77,8 +77,8 @@ export const QRDialog = ({
{channel.settings?.name.length {channel.settings?.name.length
? channel.settings.name ? channel.settings.name
: channel.role === Protobuf.Channel.Channel_Role.PRIMARY : channel.role === Protobuf.Channel.Channel_Role.PRIMARY
? "Primary" ? "Primary"
: `Channel: ${channel.index}`} : `Channel: ${channel.index}`}
</Label> </Label>
<Checkbox <Checkbox
key={channel.index} key={channel.index}
@@ -86,7 +86,9 @@ export const QRDialog = ({
onCheckedChange={() => { onCheckedChange={() => {
if (selectedChannels.includes(channel.index)) { if (selectedChannels.includes(channel.index)) {
setSelectedChannels( setSelectedChannels(
selectedChannels.filter((c) => c !== channel.index), selectedChannels.filter((c) =>
c !== channel.index
),
); );
} else { } else {
setSelectedChannels([ setSelectedChannels([
@@ -130,7 +132,7 @@ export const QRDialog = ({
<Label>Sharable URL</Label> <Label>Sharable URL</Label>
<Input <Input
value={qrCodeUrl} value={qrCodeUrl}
disabled={true} disabled
className="dark:text-slate-900" className="dark:text-slate-900"
action={{ action={{
icon: ClipboardIcon, icon: ClipboardIcon,
+1 -1
View File
@@ -19,7 +19,7 @@ export interface RebootDialogProps {
export const RebootDialog = ({ export const RebootDialog = ({
open, open,
onOpenChange, onOpenChange,
}: RebootDialogProps): JSX.Element => { }: RebootDialogProps) => {
const { connection } = useDevice(); const { connection } = useDevice();
const [time, setTime] = useState<number>(5); const [time, setTime] = useState<number>(5);
+3 -3
View File
@@ -1,5 +1,5 @@
import { useAppStore } from "@app/core/stores/appStore"; import { useAppStore } from "../../core/stores/appStore.ts";
import { useDevice } from "@app/core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Button } from "@components/UI/Button.tsx"; import { Button } from "@components/UI/Button.tsx";
import { import {
Dialog, Dialog,
@@ -19,7 +19,7 @@ export interface RemoveNodeDialogProps {
export const RemoveNodeDialog = ({ export const RemoveNodeDialog = ({
open, open,
onOpenChange, onOpenChange,
}: RemoveNodeDialogProps): JSX.Element => { }: RemoveNodeDialogProps) => {
const { connection, nodes, removeNode } = useDevice(); const { connection, nodes, removeNode } = useDevice();
const { nodeNumToBeRemoved } = useAppStore(); const { nodeNumToBeRemoved } = useAppStore();
+1 -1
View File
@@ -19,7 +19,7 @@ export interface ShutdownDialogProps {
export const ShutdownDialog = ({ export const ShutdownDialog = ({
open, open,
onOpenChange, onOpenChange,
}: ShutdownDialogProps): JSX.Element => { }: ShutdownDialogProps) => {
const { connection } = useDevice(); const { connection } = useDevice();
const [time, setTime] = useState<number>(5); const [time, setTime] = useState<number>(5);
@@ -1,15 +1,15 @@
import { useDevice } from "@app/core/stores/deviceStore"; import { useDevice } from "../../core/stores/deviceStore.ts";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
DialogDescription, DialogDescription,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
} from "@components/UI/Dialog"; } from "../UI/Dialog.tsx";
import type { Protobuf, Types } from "@meshtastic/core"; import type { Protobuf, Types } from "@meshtastic/core";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import type { JSX } from "react";
import { TraceRoute } from "../PageComponents/Messages/TraceRoute"; import { TraceRoute } from "../PageComponents/Messages/TraceRoute.tsx";
export interface TracerouteResponseDialogProps { export interface TracerouteResponseDialogProps {
traceroute: Types.PacketMetadata<Protobuf.Mesh.RouteDiscovery> | undefined; traceroute: Types.PacketMetadata<Protobuf.Mesh.RouteDiscovery> | undefined;
@@ -21,18 +21,16 @@ export const TracerouteResponseDialog = ({
traceroute, traceroute,
open, open,
onOpenChange, onOpenChange,
}: TracerouteResponseDialogProps): JSX.Element => { }: TracerouteResponseDialogProps) => {
const { nodes } = useDevice(); const { nodes } = useDevice();
const route: number[] = traceroute?.data.route ?? []; const route: number[] = traceroute?.data.route ?? [];
const routeBack: number[] = traceroute?.data.routeBack ?? []; const routeBack: number[] = traceroute?.data.routeBack ?? [];
const snrTowards = traceroute?.data.snrTowards ?? []; const snrTowards = traceroute?.data.snrTowards ?? [];
const snrBack = traceroute?.data.snrBack ?? []; const snrBack = traceroute?.data.snrBack ?? [];
const from = nodes.get(traceroute?.from ?? 0); const from = nodes.get(traceroute?.from ?? 0);
const longName = const longName = from?.user?.longName ??
from?.user?.longName ??
(from ? `!${numberToHexUnpadded(from?.num)}` : "Unknown"); (from ? `!${numberToHexUnpadded(from?.num)}` : "Unknown");
const shortName = const shortName = from?.user?.shortName ??
from?.user?.shortName ??
(from ? `${numberToHexUnpadded(from?.num).substring(0, 4)}` : "UNK"); (from ? `${numberToHexUnpadded(from?.num).substring(0, 4)}` : "UNK");
const to = nodes.get(traceroute?.to ?? 0); const to = nodes.get(traceroute?.to ?? 0);
return ( return (
+9 -11
View File
@@ -13,7 +13,8 @@ import {
type SubmitHandler, type SubmitHandler,
useForm, useForm,
} from "react-hook-form"; } from "react-hook-form";
import { Heading } from "../UI/Typography/Heading"; import { Heading } from "@components/UI/Typography/Heading.tsx";
interface DisabledBy<T> { interface DisabledBy<T> {
fieldName: Path<T>; fieldName: Path<T>;
@@ -76,10 +77,11 @@ export function DynamicForm<T extends FieldValues>({
const value = getValues(field.fieldName); const value = getValues(field.fieldName);
if (value === "always") return true; if (value === "always") return true;
if (typeof value === "boolean") return field.invert ? value : !value; if (typeof value === "boolean") return field.invert ? value : !value;
if (typeof value === "number") if (typeof value === "number") {
return field.invert return field.invert
? field.selector !== value ? field.selector !== value
: field.selector === value; : field.selector === value;
}
return false; return false;
}); });
}; };
@@ -87,11 +89,9 @@ export function DynamicForm<T extends FieldValues>({
return ( return (
<form <form
className="space-y-8" className="space-y-8"
{...(submitType === "onSubmit" {...(submitType === "onSubmit" ? { onSubmit: handleSubmit(onSubmit) } : {
? { onSubmit: handleSubmit(onSubmit) } onChange: handleSubmit(onSubmit),
: { })}
onChange: handleSubmit(onSubmit),
})}
> >
{fieldGroups.map((fieldGroup) => ( {fieldGroups.map((fieldGroup) => (
<div key={fieldGroup.label} className="space-y-8 sm:space-y-5"> <div key={fieldGroup.label} className="space-y-8 sm:space-y-5">
@@ -110,10 +110,8 @@ export function DynamicForm<T extends FieldValues>({
label={field.label} label={field.label}
fieldName={field.name} fieldName={field.name}
description={field.description} description={field.description}
valid={ valid={field.validationText === undefined ||
field.validationText === undefined || field.validationText === ""}
field.validationText === ""
}
validationText={field.validationText} validationText={field.validationText}
> >
<DynamicFormField <DynamicFormField
+11 -3
View File
@@ -1,7 +1,7 @@
import { import {
type MultiSelectFieldProps, type MultiSelectFieldProps,
MultiSelectInput, MultiSelectInput,
} from "@app/components/Form/FormMultiSelect"; } from "./FormMultiSelect.tsx";
import { import {
GenericInput, GenericInput,
type InputFieldProps, type InputFieldProps,
@@ -48,11 +48,19 @@ export function DynamicFormField<T extends FieldValues>({
case "toggle": case "toggle":
return ( return (
<ToggleInput field={field} control={control} disabled={disabled} /> <ToggleInput
field={field}
control={control}
disabled={disabled}
/>
); );
case "select": case "select":
return ( return (
<SelectInput field={field} control={control} disabled={disabled} /> <SelectInput
field={field}
control={control}
disabled={disabled}
/>
); );
case "passwordGenerator": case "passwordGenerator":
return ( return (
+9 -11
View File
@@ -40,17 +40,15 @@ export function GenericInput<T extends FieldValues>({
control={control} control={control}
render={({ field: { value, onChange, ...rest } }) => ( render={({ field: { value, onChange, ...rest } }) => (
<Input <Input
type={ type={field.type === "password" && passwordShown
field.type === "password" && passwordShown ? "text" : field.type ? "text"
} : field.type}
action={ action={field.type === "password"
field.type === "password" ? {
? { icon: passwordShown ? EyeOff : Eye,
icon: passwordShown ? EyeOff : Eye, onClick: togglePasswordVisiblity,
onClick: togglePasswordVisiblity, }
} : undefined}
: undefined
}
step={field.properties?.step} step={field.properties?.step}
value={field.type === "number" ? Number.parseFloat(value) : value} value={field.type === "number" ? Number.parseFloat(value) : value}
id={field.name} id={field.name}
+3 -3
View File
@@ -3,7 +3,7 @@ import type {
GenericFormElementProps, GenericFormElementProps,
} from "@components/Form/DynamicForm.tsx"; } from "@components/Form/DynamicForm.tsx";
import type { FieldValues } from "react-hook-form"; import type { FieldValues } from "react-hook-form";
import { MultiSelect, MultiSelectItem } from "../UI/MultiSelect"; import { MultiSelect, MultiSelectItem } from "../UI/MultiSelect.tsx";
export interface MultiSelectFieldProps<T> extends BaseFormBuilderProps<T> { export interface MultiSelectFieldProps<T> extends BaseFormBuilderProps<T> {
type: "multiSelect"; type: "multiSelect";
@@ -28,8 +28,8 @@ export function MultiSelectInput<T extends FieldValues>({
// Make sure to filter out the UNSET value, as it shouldn't be shown in the UI // Make sure to filter out the UNSET value, as it shouldn't be shown in the UI
const optionsEnumValues = enumValue const optionsEnumValues = enumValue
? Object.entries(enumValue) ? Object.entries(enumValue)
.filter((value) => typeof value[1] === "number") .filter((value) => typeof value[1] === "number")
.filter((value) => value[0] !== "UNSET") .filter((value) => value[0] !== "UNSET")
: []; : [];
const formatName = (name: string) => { const formatName = (name: string) => {
+8 -10
View File
@@ -2,10 +2,10 @@ import type {
BaseFormBuilderProps, BaseFormBuilderProps,
GenericFormElementProps, GenericFormElementProps,
} from "@components/Form/DynamicForm.tsx"; } from "@components/Form/DynamicForm.tsx";
import type { ButtonVariant } from "@components/UI/Button"; import type { ButtonVariant } from "../UI/Button.tsx";
import { Generator } from "@components/UI/Generator.tsx"; import { Generator } from "@components/UI/Generator.tsx";
import { Eye, EyeOff } from "lucide-react"; import { Eye, EyeOff } from "lucide-react";
import type { ChangeEventHandler, MouseEventHandler } from "react"; import type { ChangeEventHandler } from "react";
import { useState } from "react"; import { useState } from "react";
import { Controller, type FieldValues } from "react-hook-form"; import { Controller, type FieldValues } from "react-hook-form";
@@ -43,14 +43,12 @@ export function PasswordGenerator<T extends FieldValues>({
<Generator <Generator
type={field.hide && !passwordShown ? "password" : "text"} type={field.hide && !passwordShown ? "password" : "text"}
id={field.id} id={field.id}
action={ action={field.hide
field.hide ? {
? { icon: passwordShown ? EyeOff : Eye,
icon: passwordShown ? EyeOff : Eye, onClick: togglePasswordVisiblity,
onClick: togglePasswordVisiblity, }
} : undefined}
: undefined
}
devicePSKBitCount={field.devicePSKBitCount} devicePSKBitCount={field.devicePSKBitCount}
bits={field.bits} bits={field.bits}
inputChange={field.inputChange} inputChange={field.inputChange}
+9 -7
View File
@@ -36,8 +36,8 @@ export function SelectInput<T extends FieldValues>({
field.properties; field.properties;
const optionsEnumValues = enumValue const optionsEnumValues = enumValue
? Object.entries(enumValue).filter( ? Object.entries(enumValue).filter(
(value) => typeof value[1] === "number", (value) => typeof value[1] === "number",
) )
: []; : [];
return ( return (
<Select <Select
@@ -58,11 +58,13 @@ export function SelectInput<T extends FieldValues>({
<SelectItem key={name} value={value.toString()}> <SelectItem key={name} value={value.toString()}>
{formatEnumName {formatEnumName
? name ? name
.replace(/_/g, " ") .replace(/_/g, " ")
.toLowerCase() .toLowerCase()
.split(" ") .split(" ")
.map((s) => s.charAt(0).toUpperCase() + s.substring(1)) .map((s) =>
.join(" ") s.charAt(0).toUpperCase() + s.substring(1)
)
.join(" ")
: name} : name}
</SelectItem> </SelectItem>
))} ))}
+4 -3
View File
@@ -1,7 +1,7 @@
import { useBackupReminder } from "@app/core/hooks/useKeyBackupReminder"; import { useBackupReminder } from "@core/hooks/useKeyBackupReminder.tsx";
import { useDevice } from "@app/core/stores/deviceStore"; import { useDevice } from "@core/stores/deviceStore.ts";
export const KeyBackupReminder = (): JSX.Element => { export const KeyBackupReminder = () => {
const { setDialogOpen } = useDevice(); const { setDialogOpen } = useDevice();
useBackupReminder({ useBackupReminder({
@@ -15,5 +15,6 @@ export const KeyBackupReminder = (): JSX.Element => {
sameSite: "strict", sameSite: "strict",
}, },
}); });
// deno-lint-ignore jsx-no-useless-fragment
return <></>; return <></>;
}; };
+35 -36
View File
@@ -7,13 +7,13 @@ import { Protobuf } from "@meshtastic/core";
import { fromByteArray, toByteArray } from "base64-js"; import { fromByteArray, toByteArray } from "base64-js";
import cryptoRandomString from "crypto-random-string"; import cryptoRandomString from "crypto-random-string";
import { useState } from "react"; import { useState } from "react";
import { PkiRegenerateDialog } from "../Dialog/PkiRegenerateDialog"; import { PkiRegenerateDialog } from "../Dialog/PkiRegenerateDialog.tsx";
export interface SettingsPanelProps { export interface SettingsPanelProps {
channel: Protobuf.Channel.Channel; channel: Protobuf.Channel.Channel;
} }
export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => { export const Channel = ({ channel }: SettingsPanelProps) => {
const { config, connection, addChannel } = useDevice(); const { config, connection, addChannel } = useDevice();
const { toast } = useToast(); const { toast } = useToast();
@@ -24,8 +24,9 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
channel?.settings?.psk.length ?? 16, channel?.settings?.psk.length ?? 16,
); );
const [validationText, setValidationText] = useState<string>(); const [validationText, setValidationText] = useState<string>();
const [preSharedDialogOpen, setPreSharedDialogOpen] = const [preSharedDialogOpen, setPreSharedDialogOpen] = useState<boolean>(
useState<boolean>(false); false,
);
const onSubmit = (data: ChannelValidation) => { const onSubmit = (data: ChannelValidation) => {
const channel = create(Protobuf.Channel.ChannelSchema, { const channel = create(Protobuf.Channel.ChannelSchema, {
@@ -92,7 +93,7 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
<DynamicForm<ChannelValidation> <DynamicForm<ChannelValidation>
onSubmit={onSubmit} onSubmit={onSubmit}
submitType="onSubmit" submitType="onSubmit"
hasSubmitButton={true} hasSubmitButton
defaultValues={{ defaultValues={{
...channel, ...channel,
...{ ...{
@@ -107,7 +108,7 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
channel?.settings?.moduleSettings?.positionPrecision === 32, channel?.settings?.moduleSettings?.positionPrecision === 32,
positionPrecision: positionPrecision:
channel?.settings?.moduleSettings?.positionPrecision === channel?.settings?.moduleSettings?.positionPrecision ===
undefined undefined
? 10 ? 10
: channel?.settings?.moduleSettings?.positionPrecision, : channel?.settings?.moduleSettings?.positionPrecision,
}, },
@@ -126,10 +127,9 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
description: description:
"Device telemetry is sent over PRIMARY. Only one PRIMARY allowed", "Device telemetry is sent over PRIMARY. Only one PRIMARY allowed",
properties: { properties: {
enumValue: enumValue: channel.index === 0
channel.index === 0 ? { PRIMARY: 1 }
? { PRIMARY: 1 } : { DISABLED: 0, SECONDARY: 2 },
: { DISABLED: 0, SECONDARY: 2 },
}, },
}, },
{ {
@@ -192,32 +192,31 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
description: description:
"If not sharing precise location, position shared on channel will be accurate within this distance", "If not sharing precise location, position shared on channel will be accurate within this distance",
properties: { properties: {
enumValue: enumValue: config.display?.units === 0
config.display?.units === 0 ? {
? { "Within 23 km": 10,
"Within 23 km": 10, "Within 12 km": 11,
"Within 12 km": 11, "Within 5.8 km": 12,
"Within 5.8 km": 12, "Within 2.9 km": 13,
"Within 2.9 km": 13, "Within 1.5 km": 14,
"Within 1.5 km": 14, "Within 700 m": 15,
"Within 700 m": 15, "Within 350 m": 16,
"Within 350 m": 16, "Within 200 m": 17,
"Within 200 m": 17, "Within 90 m": 18,
"Within 90 m": 18, "Within 50 m": 19,
"Within 50 m": 19, }
} : {
: { "Within 15 miles": 10,
"Within 15 miles": 10, "Within 7.3 miles": 11,
"Within 7.3 miles": 11, "Within 3.6 miles": 12,
"Within 3.6 miles": 12, "Within 1.8 miles": 13,
"Within 1.8 miles": 13, "Within 0.9 miles": 14,
"Within 0.9 miles": 14, "Within 0.5 miles": 15,
"Within 0.5 miles": 15, "Within 0.2 miles": 16,
"Within 0.2 miles": 16, "Within 600 feet": 17,
"Within 600 feet": 17, "Within 300 feet": 18,
"Within 300 feet": 18, "Within 150 feet": 19,
"Within 150 feet": 19, },
},
}, },
}, },
], ],
@@ -1,4 +1,4 @@
import { useAppStore } from "@app/core/stores/appStore"; import { useAppStore } from "../../../core/stores/appStore.ts";
import type { BluetoothValidation } from "@app/validation/config/bluetooth.tsx"; import type { BluetoothValidation } from "@app/validation/config/bluetooth.tsx";
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
@@ -113,9 +113,8 @@ export const Bluetooth = () => {
disabledBy: [ disabledBy: [
{ {
fieldName: "mode", fieldName: "mode",
selector: selector: Protobuf.Config.Config_BluetoothConfig_PairingMode
Protobuf.Config.Config_BluetoothConfig_PairingMode .FIXED_PIN,
.FIXED_PIN,
invert: true, invert: true,
}, },
{ {
@@ -4,7 +4,7 @@ import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
export const Device = (): JSX.Element => { export const Device = () => {
const { config, setWorkingConfig } = useDevice(); const { config, setWorkingConfig } = useDevice();
const onSubmit = (data: DeviceValidation) => { const onSubmit = (data: DeviceValidation) => {
@@ -4,7 +4,7 @@ import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
export const Display = (): JSX.Element => { export const Display = () => {
const { config, setWorkingConfig } = useDevice(); const { config, setWorkingConfig } = useDevice();
const onSubmit = (data: DisplayValidation) => { const onSubmit = (data: DisplayValidation) => {
@@ -4,7 +4,7 @@ import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
export const LoRa = (): JSX.Element => { export const LoRa = () => {
const { config, setWorkingConfig } = useDevice(); const { config, setWorkingConfig } = useDevice();
const onSubmit = (data: LoRaValidation) => { const onSubmit = (data: LoRaValidation) => {
@@ -1,7 +1,7 @@
import { import {
type FlagName, type FlagName,
usePositionFlags, usePositionFlags,
} from "@app/core/hooks/usePositionFlags"; } from "../../../core/hooks/usePositionFlags.ts";
import type { PositionValidation } from "@app/validation/config/position.tsx"; import type { PositionValidation } from "@app/validation/config/position.tsx";
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
@@ -4,7 +4,7 @@ import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
export const Power = (): JSX.Element => { export const Power = () => {
const { config, setWorkingConfig } = useDevice(); const { config, setWorkingConfig } = useDevice();
const onSubmit = (data: PowerValidation) => { const onSubmit = (data: PowerValidation) => {
@@ -1,18 +1,18 @@
import { PkiRegenerateDialog } from "@app/components/Dialog/PkiRegenerateDialog"; import { PkiRegenerateDialog } from "@components/Dialog/PkiRegenerateDialog.tsx";
import { DynamicForm } from "@app/components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useAppStore } from "@app/core/stores/appStore"; import { useAppStore } from "@core/stores/appStore.ts";
import { import {
getX25519PrivateKey, getX25519PrivateKey,
getX25519PublicKey, getX25519PublicKey,
} from "@app/core/utils/x25519"; } from "@core/utils/x25519.ts";
import type { SecurityValidation } from "@app/validation/config/security.tsx"; import type { SecurityValidation } from "@app/validation/config/security.ts";
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
import { fromByteArray, toByteArray } from "base64-js"; import { fromByteArray, toByteArray } from "base64-js";
import { Eye, EyeOff } from "lucide-react"; import { Eye, EyeOff } from "lucide-react";
import { useReducer } from "react"; import { useReducer } from "react";
import { securityReducer } from "./securityReducer"; import { securityReducer } from "@components/PageComponents/Config/Security/securityReducer.tsx";
export const Security = () => { export const Security = () => {
const { config, setWorkingConfig, setDialogOpen } = useDevice(); const { config, setWorkingConfig, setDialogOpen } = useDevice();
@@ -58,8 +58,7 @@ export const Security = () => {
if (input.length % 4 !== 0) { if (input.length % 4 !== 0) {
addError( addError(
fieldName, fieldName,
`${ `${fieldName === "privateKey" ? "Private" : "Admin"
fieldName === "privateKey" ? "Private" : "Admin"
} Key is required to be a 256 bit pre-shared key (PSK)`, } Key is required to be a 256 bit pre-shared key (PSK)`,
); );
return; return;
@@ -74,8 +73,7 @@ export const Security = () => {
console.error(e); console.error(e);
addError( addError(
fieldName, fieldName,
`Invalid ${ `Invalid ${fieldName === "privateKey" ? "Private" : "Admin"
fieldName === "privateKey" ? "Private" : "Admin"
} Key format`, } Key format`,
); );
} }
@@ -85,8 +83,6 @@ export const Security = () => {
if (hasErrors()) { if (hasErrors()) {
return; return;
} }
console.log(toByteArray(state.adminKey));
setWorkingConfig( setWorkingConfig(
create(Protobuf.Config.ConfigSchema, { create(Protobuf.Config.ConfigSchema, {
payloadVariant: { payloadVariant: {
@@ -248,7 +244,7 @@ export const Security = () => {
? getErrorMessage("adminKey") ? getErrorMessage("adminKey")
: "", : "",
inputChange: adminKeyInputChangeEvent, inputChange: adminKeyInputChangeEvent,
selectChange: () => {}, selectChange: () => { },
bits: [{ text: "256 bit", value: "32", key: "bit256" }], bits: [{ text: "256 bit", value: "32", key: "bit256" }],
devicePSKBitCount: state.privateKeyBitCount, devicePSKBitCount: state.privateKeyBitCount,
hide: !state.adminKeyVisible, hide: !state.adminKeyVisible,
@@ -308,8 +304,7 @@ export const Security = () => {
<PkiRegenerateDialog <PkiRegenerateDialog
open={state.privateKeyDialogOpen} open={state.privateKeyDialogOpen}
onOpenChange={() => onOpenChange={() =>
dispatch({ type: "SHOW_PRIVATE_KEY_DIALOG", payload: false }) dispatch({ type: "SHOW_PRIVATE_KEY_DIALOG", payload: false })}
}
onSubmit={pkiRegenerate} onSubmit={pkiRegenerate}
/> />
</> </>
@@ -1,4 +1,4 @@
import type { SecurityAction, SecurityState } from "./types"; import type { SecurityAction, SecurityState } from "./types.ts";
export function securityReducer( export function securityReducer(
state: SecurityState, state: SecurityState,
@@ -18,7 +18,7 @@ export type SecurityAction =
| { type: "SET_ADMIN_KEY"; payload: string } | { type: "SET_ADMIN_KEY"; payload: string }
| { type: "SHOW_PRIVATE_KEY_DIALOG"; payload: boolean } | { type: "SHOW_PRIVATE_KEY_DIALOG"; payload: boolean }
| { | {
type: "REGENERATE_PRIV_PUB_KEY"; type: "REGENERATE_PRIV_PUB_KEY";
payload: { privateKey: string; publicKey: string }; payload: { privateKey: string; publicKey: string };
} }
| { type: "REGENERATE_ADMIN_KEY"; payload: { adminKey: string } }; | { type: "REGENERATE_ADMIN_KEY"; payload: { adminKey: string } };
@@ -1,4 +1,4 @@
import type { TabElementProps } from "@app/components/Dialog/NewDeviceDialog"; import type { TabElementProps } from "../../Dialog/NewDeviceDialog.tsx";
import { Button } from "@components/UI/Button.tsx"; import { Button } from "@components/UI/Button.tsx";
import { Mono } from "@components/generic/Mono.tsx"; import { Mono } from "@components/generic/Mono.tsx";
import { useAppStore } from "@core/stores/appStore.ts"; import { useAppStore } from "@core/stores/appStore.ts";
@@ -8,7 +8,7 @@ import { randId } from "@core/utils/randId.ts";
import { BleConnection, Constants } from "@meshtastic/js"; import { BleConnection, Constants } from "@meshtastic/js";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
export const BLE = ({ closeDialog }: TabElementProps): JSX.Element => { export const BLE = ({ closeDialog }: TabElementProps) => {
const [bleDevices, setBleDevices] = useState<BluetoothDevice[]>([]); const [bleDevices, setBleDevices] = useState<BluetoothDevice[]>([]);
const { addDevice } = useDeviceStore(); const { addDevice } = useDeviceStore();
const { setSelectedDevice } = useAppStore(); const { setSelectedDevice } = useAppStore();
+10 -11
View File
@@ -1,4 +1,4 @@
import type { TabElementProps } from "@app/components/Dialog/NewDeviceDialog"; import type { TabElementProps } from "@components/Dialog/NewDeviceDialog.tsx";
import { Button } from "@components/UI/Button.tsx"; import { Button } from "@components/UI/Button.tsx";
import { Input } from "@components/UI/Input.tsx"; import { Input } from "@components/UI/Input.tsx";
import { Label } from "@components/UI/Label.tsx"; import { Label } from "@components/UI/Label.tsx";
@@ -9,23 +9,23 @@ import { subscribeAll } from "@core/subscriptions.ts";
import { randId } from "@core/utils/randId.ts"; import { randId } from "@core/utils/randId.ts";
import { MeshDevice } from "@meshtastic/core"; import { MeshDevice } from "@meshtastic/core";
import { TransportHTTP } from "@meshtastic/transport-http"; import { TransportHTTP } from "@meshtastic/transport-http";
import { type JSX, useState } from "react"; import { useState } from "react";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
export const HTTP = ({ closeDialog }: TabElementProps): JSX.Element => { export const HTTP = ({ closeDialog }: TabElementProps) => {
const [https, setHTTPS] = useState(false); const [https, setHTTPS] = useState(false);
const { addDevice } = useDeviceStore(); const { addDevice } = useDeviceStore();
const { setSelectedDevice } = useAppStore(); const { setSelectedDevice } = useAppStore();
const { register, handleSubmit, control, watch } = useForm<{ const { register, handleSubmit, control } = useForm<{
ip: string; ip: string;
tls: boolean; tls: boolean;
}>({ }>({
defaultValues: { defaultValues: {
ip: ["client.meshtastic.org", "localhost"].includes( ip: ["client.meshtastic.org", "localhost"].includes(
window.location.hostname, globalThis.location.hostname,
) )
? "meshtastic.local" ? "meshtastic.local"
: window.location.host, : globalThis.location.host,
tls: location.protocol === "https:", tls: location.protocol === "https:",
}, },
}); });
@@ -61,16 +61,15 @@ export const HTTP = ({ closeDialog }: TabElementProps): JSX.Element => {
<Controller <Controller
name="tls" name="tls"
control={control} control={control}
render={({ field: { value, onChange, ...rest } }) => ( render={({ field: { ...rest } }) => (
<> <>
<Label>Use HTTPS</Label> <Label>Use HTTPS</Label>
<Switch <Switch
onCheckedChange={(checked) => { onCheckedChange={(checked: boolean) => {
checked ? setHTTPS(true) : setHTTPS(false); checked ? setHTTPS(true) : setHTTPS(false);
}} }}
disabled={ disabled={location.protocol === "https:" ||
location.protocol === "https:" || connectionInProgress connectionInProgress}
}
checked={https} checked={https}
{...rest} {...rest}
/> />
@@ -1,4 +1,4 @@
import type { TabElementProps } from "@app/components/Dialog/NewDeviceDialog"; import type { TabElementProps } from "../../Dialog/NewDeviceDialog.tsx";
import { Button } from "@components/UI/Button.tsx"; import { Button } from "@components/UI/Button.tsx";
import { Mono } from "@components/generic/Mono.tsx"; import { Mono } from "@components/generic/Mono.tsx";
import { useAppStore } from "@core/stores/appStore.ts"; import { useAppStore } from "@core/stores/appStore.ts";
@@ -9,7 +9,7 @@ import { MeshDevice } from "@meshtastic/core";
import { TransportWebSerial } from "@meshtastic/transport-web-serial"; import { TransportWebSerial } from "@meshtastic/transport-web-serial";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
export const Serial = ({ closeDialog }: TabElementProps): JSX.Element => { export const Serial = ({ closeDialog }: TabElementProps) => {
const [serialPorts, setSerialPorts] = useState<SerialPort[]>([]); const [serialPorts, setSerialPorts] = useState<SerialPort[]>([]);
const { addDevice } = useDeviceStore(); const { addDevice } = useDeviceStore();
const { setSelectedDevice } = useAppStore(); const { setSelectedDevice } = useAppStore();
@@ -1,8 +1,8 @@
import { Separator } from "@app/components/UI/Seperator"; import { Separator } from "@components/UI/Seperator.tsx";
import { Heading } from "@app/components/UI/Typography/Heading"; import { Heading } from "@components/UI/Typography/Heading.tsx";
import { Subtle } from "@app/components/UI/Typography/Subtle.tsx"; import { Subtle } from "@components/UI/Typography/Subtle.tsx";
import { formatQuantity } from "@app/core/utils/string"; import { formatQuantity } from "@core/utils/string.ts";
import { Avatar } from "@components/UI/Avatar"; import { Avatar } from "@components/UI/Avatar.tsx";
import { Mono } from "@components/generic/Mono.tsx"; import { Mono } from "@components/generic/Mono.tsx";
import { TimeAgo } from "@components/generic/TimeAgo.tsx"; import { TimeAgo } from "@components/generic/TimeAgo.tsx";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
@@ -34,24 +34,26 @@ export const NodeDetail = ({ node }: NodeDetailProps) => {
<div className="dark:text-slate-900 p-1"> <div className="dark:text-slate-900 p-1">
<div className="flex gap-2"> <div className="flex gap-2">
<div className="flex flex-col items-center gap-2 min-w-6 pt-1"> <div className="flex flex-col items-center gap-2 min-w-6 pt-1">
<Avatar text={node.user?.shortName} /> <Avatar text={node.user?.shortName ?? "UNK"} />
<div> <div>
{node.user?.publicKey && node.user?.publicKey.length > 0 ? ( {node.user?.publicKey && node.user?.publicKey.length > 0
<LockIcon ? (
className="text-green-600" <LockIcon
size={12} className="text-green-600"
strokeWidth={3} size={12}
aria-label="Public Key Enabled" strokeWidth={3}
/> aria-label="Public Key Enabled"
) : ( />
<LockOpenIcon )
className="text-yellow-500" : (
size={12} <LockOpenIcon
strokeWidth={3} className="text-yellow-500"
aria-label="No Public Key" size={12}
/> strokeWidth={3}
)} aria-label="No Public Key"
/>
)}
</div> </div>
<Star <Star
@@ -69,19 +71,16 @@ export const NodeDetail = ({ node }: NodeDetailProps) => {
{!!node.deviceMetrics?.batteryLevel && ( {!!node.deviceMetrics?.batteryLevel && (
<div <div
className="flex items-center gap-1" className="flex items-center gap-1"
title={`${ title={`${node.deviceMetrics?.voltage?.toPrecision(3) ?? "Unknown"
node.deviceMetrics?.voltage?.toPrecision(3) ?? "Unknown" } volts`}
} volts`}
> >
{node.deviceMetrics?.batteryLevel > 100 ? ( {node.deviceMetrics?.batteryLevel > 100
<BatteryChargingIcon size={22} /> ? <BatteryChargingIcon size={22} />
) : node.deviceMetrics?.batteryLevel > 80 ? ( : node.deviceMetrics?.batteryLevel > 80
<BatteryFullIcon size={22} /> ? <BatteryFullIcon size={22} />
) : node.deviceMetrics?.batteryLevel > 20 ? ( : node.deviceMetrics?.batteryLevel > 20
<BatteryMediumIcon size={22} /> ? <BatteryMediumIcon size={22} />
) : ( : <BatteryLowIcon size={22} />}
<BatteryLowIcon size={22} />
)}
<Subtle aria-label="Battery"> <Subtle aria-label="Battery">
{node.deviceMetrics?.batteryLevel > 100 {node.deviceMetrics?.batteryLevel > 100
? "Charging" ? "Charging"
@@ -1,13 +1,9 @@
import { import { type MessageWithState, useDevice } from "@core/stores/deviceStore.ts";
type MessageWithState,
useDevice,
} from "@app/core/stores/deviceStore.ts";
import { Message } from "@components/PageComponents/Messages/Message.tsx"; import { Message } from "@components/PageComponents/Messages/Message.tsx";
import { MessageInput } from "@components/PageComponents/Messages/MessageInput.tsx"; import { MessageInput } from "@components/PageComponents/Messages/MessageInput.tsx";
import type { Types } from "@meshtastic/core"; import type { Types } from "@meshtastic/core";
import { InboxIcon } from "lucide-react"; import { InboxIcon } from "lucide-react";
import { useCallback, useEffect, useRef } from "react"; import { useCallback, useEffect, useRef } from "react";
import type { JSX } from "react";
export interface ChannelChatProps { export interface ChannelChatProps {
messages?: MessageWithState[]; messages?: MessageWithState[];
@@ -26,7 +22,7 @@ export const ChannelChat = ({
messages, messages,
channel, channel,
to, to,
}: ChannelChatProps): JSX.Element => { }: ChannelChatProps) => {
const { nodes } = useDevice(); const { nodes } = useDevice();
const messagesEndRef = useRef<HTMLDivElement>(null); const messagesEndRef = useRef<HTMLDivElement>(null);
const scrollContainerRef = useRef<HTMLDivElement>(null); const scrollContainerRef = useRef<HTMLDivElement>(null);
@@ -34,8 +30,7 @@ export const ChannelChat = ({
const scrollToBottom = useCallback(() => { const scrollToBottom = useCallback(() => {
const scrollContainer = scrollContainerRef.current; const scrollContainer = scrollContainerRef.current;
if (scrollContainer) { if (scrollContainer) {
const isNearBottom = const isNearBottom = scrollContainer.scrollHeight -
scrollContainer.scrollHeight -
scrollContainer.scrollTop - scrollContainer.scrollTop -
scrollContainer.clientHeight < scrollContainer.clientHeight <
100; 100;
@@ -72,9 +67,8 @@ export const ChannelChat = ({
key={message.id} key={message.id}
message={message} message={message}
sender={nodes.get(message.from)} sender={nodes.get(message.from)}
lastMsgSameUser={ lastMsgSameUser={index > 0 &&
index > 0 && messages[index - 1].from === message.from messages[index - 1].from === message.from}
}
/> />
); );
})} })}
@@ -4,13 +4,13 @@ import {
TooltipContent, TooltipContent,
TooltipProvider, TooltipProvider,
TooltipTrigger, TooltipTrigger,
} from "@app/components/UI/Tooltip"; } from "@components/UI/Tooltip.tsx";
import { import {
type MessageWithState, type MessageWithState,
useDeviceStore, useDeviceStore,
} from "@app/core/stores/deviceStore.ts"; } from "@core/stores/deviceStore.ts";
import { cn } from "@app/core/utils/cn"; import { cn } from "@core/utils/cn.ts";
import { Avatar } from "@components/UI/Avatar"; import { Avatar } from "@components/UI/Avatar.tsx";
import type { Protobuf } from "@meshtastic/core"; import type { Protobuf } from "@meshtastic/core";
import { AlertCircle, CheckCircle2, CircleEllipsis } from "lucide-react"; import { AlertCircle, CheckCircle2, CircleEllipsis } from "lucide-react";
import type { LucideIcon } from "lucide-react"; import type { LucideIcon } from "lucide-react";
@@ -44,13 +44,13 @@ const STATUS_TEXT_MAP: Record<MessageState, string> = {
[MESSAGE_STATES.ACK]: "Message delivered", [MESSAGE_STATES.ACK]: "Message delivered",
[MESSAGE_STATES.WAITING]: "Waiting for delivery", [MESSAGE_STATES.WAITING]: "Waiting for delivery",
[MESSAGE_STATES.FAILED]: "Delivery failed", [MESSAGE_STATES.FAILED]: "Delivery failed",
} as const; };
const STATUS_ICON_MAP: Record<MessageState, LucideIcon> = { const STATUS_ICON_MAP: Record<MessageState, LucideIcon> = {
[MESSAGE_STATES.ACK]: CheckCircle2, [MESSAGE_STATES.ACK]: CheckCircle2,
[MESSAGE_STATES.WAITING]: CircleEllipsis, [MESSAGE_STATES.WAITING]: CircleEllipsis,
[MESSAGE_STATES.FAILED]: AlertCircle, [MESSAGE_STATES.FAILED]: AlertCircle,
} as const; };
const getStatusText = (state: MessageState): string => STATUS_TEXT_MAP[state]; const getStatusText = (state: MessageState): string => STATUS_TEXT_MAP[state];
@@ -93,7 +93,6 @@ const StatusIcon = ({ state, className, ...otherProps }: StatusIconProps) => {
const getMessageTextStyles = (state: MessageState) => { const getMessageTextStyles = (state: MessageState) => {
const isAcknowledged = state === MESSAGE_STATES.ACK; const isAcknowledged = state === MESSAGE_STATES.ACK;
const isFailed = state === MESSAGE_STATES.FAILED; const isFailed = state === MESSAGE_STATES.FAILED;
const isWaiting = state === MESSAGE_STATES.WAITING;
return cn( return cn(
"break-words overflow-hidden", "break-words overflow-hidden",
@@ -144,16 +143,18 @@ export const Message = ({ lastMsgSameUser, message, sender }: MessageProps) => {
)} )}
> >
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
{!lastMsgSameUser ? ( {!lastMsgSameUser
<div className="flex place-items-center gap-2 mb-1"> ? (
<Avatar text={messageUser?.shortName} /> <div className="flex place-items-center gap-2 mb-1">
<div className="flex flex-col"> <Avatar text={messageUser?.shortName ?? "UNK"} />
<span className="font-medium text-slate-900 dark:text-white truncate"> <div className="flex flex-col">
{messageUser?.longName} <span className="font-medium text-slate-900 dark:text-white truncate">
</span> {messageUser?.longName}
</span>
</div>
</div> </div>
</div> )
) : null} : null}
</div> </div>
<TimeDisplay date={message.rxTime} /> <TimeDisplay date={message.rxTime} />
<div className="flex place-items-center gap-2 pb-2"> <div className="flex place-items-center gap-2 pb-2">
@@ -1,16 +1,10 @@
import { debounce } from "@app/core/utils/debounce"; import { debounce } from "../../../core/utils/debounce.ts";
import { Button } from "@components/UI/Button.tsx"; import { Button } from "@components/UI/Button.tsx";
import { Input } from "@components/UI/Input.tsx"; import { Input } from "@components/UI/Input.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import type { Types } from "@meshtastic/core"; import type { Types } from "@meshtastic/core";
import { SendIcon } from "lucide-react"; import { SendIcon } from "lucide-react";
import { import { startTransition, useCallback, useMemo, useState } from "react";
type JSX,
startTransition,
useCallback,
useMemo,
useState,
} from "react";
export interface MessageInputProps { export interface MessageInputProps {
to: Types.Destination; to: Types.Destination;
@@ -22,7 +16,7 @@ export const MessageInput = ({
to, to,
channel, channel,
maxBytes, maxBytes,
}: MessageInputProps): JSX.Element => { }: MessageInputProps) => {
const { const {
connection, connection,
setMessageState, setMessageState,
@@ -43,7 +37,7 @@ export const MessageInput = ({
async (message: string) => { async (message: string) => {
await connection await connection
?.sendText(message, to, true, channel) ?.sendText(message, to, true, channel)
.then((id) => .then((id: number) =>
setMessageState( setMessageState(
to === "broadcast" ? "broadcast" : "direct", to === "broadcast" ? "broadcast" : "direct",
channel, channel,
@@ -51,7 +45,7 @@ export const MessageInput = ({
myNodeNum, myNodeNum,
id, id,
"ack", "ack",
), )
) )
.catch((e: Types.PacketError) => .catch((e: Types.PacketError) =>
setMessageState( setMessageState(
@@ -61,7 +55,7 @@ export const MessageInput = ({
myNodeNum, myNodeNum,
e.id, e.id,
e.error, e.error,
), )
); );
}, },
[channel, connection, myNodeNum, setMessageState, to], [channel, connection, myNodeNum, setMessageState, to],
@@ -82,7 +76,7 @@ export const MessageInput = ({
<div className="flex gap-2"> <div className="flex gap-2">
<form <form
className="w-full" className="w-full"
action={async (formData: FormData) => { action={(formData: FormData) => {
// prevent user from sending blank/empty message // prevent user from sending blank/empty message
if (localDraft === "") return; if (localDraft === "") return;
const message = formData.get("messageInput") as string; const message = formData.get("messageInput") as string;
@@ -97,7 +91,7 @@ export const MessageInput = ({
<div className="flex grow gap-2"> <div className="flex grow gap-2">
<span className="w-full"> <span className="w-full">
<Input <Input
autoFocus={true} autoFocus
minLength={1} minLength={1}
name="messageInput" name="messageInput"
placeholder="Enter Message" placeholder="Enter Message"
@@ -1,7 +1,6 @@
import { useDevice } from "@app/core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import type { Protobuf } from "@meshtastic/core"; import type { Protobuf } from "@meshtastic/core";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import type { JSX } from "react";
export interface TraceRouteProps { export interface TraceRouteProps {
from?: Protobuf.Mesh.NodeInfo; from?: Protobuf.Mesh.NodeInfo;
@@ -19,7 +18,7 @@ export const TraceRoute = ({
routeBack, routeBack,
snrTowards, snrTowards,
snrBack, snrBack,
}: TraceRouteProps): JSX.Element => { }: TraceRouteProps) => {
const { nodes } = useDevice(); const { nodes } = useDevice();
return ( return (
@@ -38,23 +37,25 @@ export const TraceRoute = ({
))} ))}
{from?.user?.longName} {from?.user?.longName}
</span> </span>
{routeBack ? ( {routeBack
<span className="ml-4 border-l-2 border-l-background-primary pl-2 text-text-primary"> ? (
<p className="font-semibold">Route back:</p> <span className="ml-4 border-l-2 border-l-background-primary pl-2 text-text-primary">
<p>{from?.user?.longName}</p> <p className="font-semibold">Route back:</p>
<p>↓ {snrBack?.[0] ? snrBack[0] : "??"}dB</p> <p>{from?.user?.longName}</p>
{routeBack.map((hop, i) => ( <p>↓ {snrBack?.[0] ? snrBack[0] : "??"}dB</p>
<span key={nodes.get(hop)?.num}> {routeBack.map((hop, i) => (
<p> <span key={nodes.get(hop)?.num}>
{nodes.get(hop)?.user?.longName ?? <p>
`!${numberToHexUnpadded(hop)}`} {nodes.get(hop)?.user?.longName ??
</p> `!${numberToHexUnpadded(hop)}`}
<p>↓ {snrBack?.[i + 1] ? snrBack[i + 1] : "??"}dB</p> </p>
</span> <p>↓ {snrBack?.[i + 1] ? snrBack[i + 1] : "??"}dB</p>
))} </span>
{to?.user?.longName} ))}
</span> {to?.user?.longName}
) : null} </span>
)
: null}
</div> </div>
); );
}; };
@@ -1,10 +1,10 @@
import { useDevice } from "@app/core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import type { AmbientLightingValidation } from "@app/validation/moduleConfig/ambientLighting.tsx"; import type { AmbientLightingValidation } from "@app/validation/moduleConfig/ambientLighting.tsx";
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
export const AmbientLighting = (): JSX.Element => { export const AmbientLighting = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();
const onSubmit = (data: AmbientLightingValidation) => { const onSubmit = (data: AmbientLightingValidation) => {
@@ -4,7 +4,7 @@ import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
export const Audio = (): JSX.Element => { export const Audio = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();
const onSubmit = (data: AudioValidation) => { const onSubmit = (data: AudioValidation) => {
@@ -4,7 +4,7 @@ import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
export const CannedMessage = (): JSX.Element => { export const CannedMessage = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();
const onSubmit = (data: CannedMessageValidation) => { const onSubmit = (data: CannedMessageValidation) => {
@@ -63,9 +63,8 @@ export const CannedMessage = (): JSX.Element => {
label: "Clockwise event", label: "Clockwise event",
description: "Select input event.", description: "Select input event.",
properties: { properties: {
enumValue: enumValue: Protobuf.ModuleConfig
Protobuf.ModuleConfig .ModuleConfig_CannedMessageConfig_InputEventChar,
.ModuleConfig_CannedMessageConfig_InputEventChar,
}, },
}, },
{ {
@@ -74,9 +73,8 @@ export const CannedMessage = (): JSX.Element => {
label: "Counter Clockwise event", label: "Counter Clockwise event",
description: "Select input event.", description: "Select input event.",
properties: { properties: {
enumValue: enumValue: Protobuf.ModuleConfig
Protobuf.ModuleConfig .ModuleConfig_CannedMessageConfig_InputEventChar,
.ModuleConfig_CannedMessageConfig_InputEventChar,
}, },
}, },
{ {
@@ -85,9 +83,8 @@ export const CannedMessage = (): JSX.Element => {
label: "Press event", label: "Press event",
description: "Select input event", description: "Select input event",
properties: { properties: {
enumValue: enumValue: Protobuf.ModuleConfig
Protobuf.ModuleConfig .ModuleConfig_CannedMessageConfig_InputEventChar,
.ModuleConfig_CannedMessageConfig_InputEventChar,
}, },
}, },
{ {
@@ -1,10 +1,10 @@
import { useDevice } from "@app/core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import type { DetectionSensorValidation } from "@app/validation/moduleConfig/detectionSensor.tsx"; import type { DetectionSensorValidation } from "@app/validation/moduleConfig/detectionSensor.tsx";
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
export const DetectionSensor = (): JSX.Element => { export const DetectionSensor = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();
const onSubmit = (data: DetectionSensorValidation) => { const onSubmit = (data: DetectionSensorValidation) => {
@@ -4,7 +4,7 @@ import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
export const ExternalNotification = (): JSX.Element => { export const ExternalNotification = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();
const onSubmit = (data: ExternalNotificationValidation) => { const onSubmit = (data: ExternalNotificationValidation) => {
@@ -1,10 +1,10 @@
import { useDevice } from "@app/core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import type { MqttValidation } from "@app/validation/moduleConfig/mqtt.tsx"; import type { MqttValidation } from "@app/validation/moduleConfig/mqtt.tsx";
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
export const MQTT = (): JSX.Element => { export const MQTT = () => {
const { config, moduleConfig, setWorkingModuleConfig } = useDevice(); const { config, moduleConfig, setWorkingModuleConfig } = useDevice();
const onSubmit = (data: MqttValidation) => { const onSubmit = (data: MqttValidation) => {
@@ -165,32 +165,31 @@ export const MQTT = (): JSX.Element => {
description: description:
"Position shared will be accurate within this distance", "Position shared will be accurate within this distance",
properties: { properties: {
enumValue: enumValue: config.display?.units === 0
config.display?.units === 0 ? {
? { "Within 23 km": 10,
"Within 23 km": 10, "Within 12 km": 11,
"Within 12 km": 11, "Within 5.8 km": 12,
"Within 5.8 km": 12, "Within 2.9 km": 13,
"Within 2.9 km": 13, "Within 1.5 km": 14,
"Within 1.5 km": 14, "Within 700 m": 15,
"Within 700 m": 15, "Within 350 m": 16,
"Within 350 m": 16, "Within 200 m": 17,
"Within 200 m": 17, "Within 90 m": 18,
"Within 90 m": 18, "Within 50 m": 19,
"Within 50 m": 19, }
} : {
: { "Within 15 miles": 10,
"Within 15 miles": 10, "Within 7.3 miles": 11,
"Within 7.3 miles": 11, "Within 3.6 miles": 12,
"Within 3.6 miles": 12, "Within 1.8 miles": 13,
"Within 1.8 miles": 13, "Within 0.9 miles": 14,
"Within 0.9 miles": 14, "Within 0.5 miles": 15,
"Within 0.5 miles": 15, "Within 0.2 miles": 16,
"Within 0.2 miles": 16, "Within 600 feet": 17,
"Within 600 feet": 17, "Within 300 feet": 18,
"Within 300 feet": 18, "Within 150 feet": 19,
"Within 150 feet": 19, },
},
}, },
disabledBy: [ disabledBy: [
{ {
@@ -1,10 +1,10 @@
import { useDevice } from "@app/core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import type { NeighborInfoValidation } from "@app/validation/moduleConfig/neighborInfo.tsx"; import type { NeighborInfoValidation } from "@app/validation/moduleConfig/neighborInfo.tsx";
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
export const NeighborInfo = (): JSX.Element => { export const NeighborInfo = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();
const onSubmit = (data: NeighborInfoValidation) => { const onSubmit = (data: NeighborInfoValidation) => {
@@ -4,7 +4,7 @@ import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
export const Paxcounter = (): JSX.Element => { export const Paxcounter = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();
const onSubmit = (data: PaxcounterValidation) => { const onSubmit = (data: PaxcounterValidation) => {
@@ -4,7 +4,7 @@ import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
export const RangeTest = (): JSX.Element => { export const RangeTest = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();
const onSubmit = (data: RangeTestValidation) => { const onSubmit = (data: RangeTestValidation) => {
@@ -4,7 +4,7 @@ import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
export const Serial = (): JSX.Element => { export const Serial = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();
const onSubmit = (data: SerialValidation) => { const onSubmit = (data: SerialValidation) => {
@@ -4,7 +4,7 @@ import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
export const StoreForward = (): JSX.Element => { export const StoreForward = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();
const onSubmit = (data: StoreForwardValidation) => { const onSubmit = (data: StoreForwardValidation) => {
@@ -4,7 +4,7 @@ import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
export const Telemetry = (): JSX.Element => { export const Telemetry = () => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();
const onSubmit = (data: TelemetryValidation) => { const onSubmit = (data: TelemetryValidation) => {
+6 -7
View File
@@ -1,9 +1,10 @@
import { cn } from "@app/core/utils/cn.ts"; import { cn } from "@core/utils/cn.ts";
import { AlignLeftIcon, type LucideIcon } from "lucide-react"; import { AlignLeftIcon, type LucideIcon } from "lucide-react";
import Footer from "@components/UI/Footer.tsx";
import { Spinner } from "@components/UI/Spinner.tsx";
import { ErrorBoundary } from "react-error-boundary"; import { ErrorBoundary } from "react-error-boundary";
import { ErrorPage } from "./UI/ErrorPage"; import { ErrorPage } from "@components/UI/ErrorPage.tsx";
import Footer from "./UI/Footer";
import { Spinner } from "./UI/Spinner";
export interface PageLayoutProps { export interface PageLayoutProps {
label: string; label: string;
@@ -46,9 +47,7 @@ export const PageLayout = ({
className="transition-all hover:text-accent" className="transition-all hover:text-accent"
onClick={action.onClick} onClick={action.onClick}
> >
{action?.isLoading ? ( {action?.isLoading ? <Spinner /> : (
<Spinner />
) : (
<action.icon <action.icon
className={action.iconClasses} className={action.iconClasses}
aria-disabled={action.disabled} aria-disabled={action.disabled}
+65 -63
View File
@@ -23,7 +23,7 @@ export interface SidebarProps {
children?: React.ReactNode; children?: React.ReactNode;
} }
export const Sidebar = ({ children }: SidebarProps): JSX.Element => { export const Sidebar = ({ children }: SidebarProps) => {
const { hardware, nodes, metadata } = useDevice(); const { hardware, nodes, metadata } = useDevice();
const myNode = nodes.get(hardware.myNodeNum); const myNode = nodes.get(hardware.myNodeNum);
const myMetadata = metadata.get(0); const myMetadata = metadata.get(0);
@@ -64,69 +64,71 @@ export const Sidebar = ({ children }: SidebarProps): JSX.Element => {
}, },
]; ];
return showSidebar ? ( return showSidebar
<div className="min-w-[280px] max-w-min flex-col overflow-y-auto border-r-[0.5px] bg-background-primary border-slate-300 dark:border-slate-700"> ? (
<div className="flex justify-between px-8 pt-6"> <div className="min-w-[280px] max-w-min flex-col overflow-y-auto border-r-[0.5px] bg-background-primary border-slate-300 dark:border-slate-700">
<div> <div className="flex justify-between px-8 pt-6">
<span className="text-lg font-medium"> <div>
{myNode?.user?.shortName ?? "UNK"} <span className="text-lg font-medium">
</span> {myNode?.user?.shortName ?? "UNK"}
<Subtle>{myNode?.user?.longName ?? "UNK"}</Subtle> </span>
<Subtle>{myNode?.user?.longName ?? "UNK"}</Subtle>
</div>
<button
type="button"
className="transition-all hover:text-accent"
onClick={() => setDialogOpen("deviceName", true)}
>
<EditIcon size={16} />
</button>
<button type="button" onClick={() => setShowSidebar(false)}>
<SidebarCloseIcon size={24} />
</button>
</div> </div>
<button <div className="px-8 pb-6">
type="button" <div className="flex items-center">
className="transition-all hover:text-accent" <BatteryMediumIcon size={24} viewBox="0 0 28 24" />
onClick={() => setDialogOpen("deviceName", true)} <Subtle>
> {myNode?.deviceMetrics?.batteryLevel
<EditIcon size={16} /> ? myNode?.deviceMetrics?.batteryLevel > 100
</button> ? "Charging"
<button type="button" onClick={() => setShowSidebar(false)}> : `${myNode?.deviceMetrics?.batteryLevel}%`
<SidebarCloseIcon size={24} /> : "UNK"}
</button> </Subtle>
</div> </div>
<div className="px-8 pb-6"> <div className="flex items-center">
<div className="flex items-center"> <ZapIcon size={24} viewBox="0 0 36 24" />
<BatteryMediumIcon size={24} viewBox={"0 0 28 24"} /> <Subtle>
<Subtle> {myNode?.deviceMetrics?.voltage?.toPrecision(3) ?? "UNK"} volts
{myNode?.deviceMetrics?.batteryLevel </Subtle>
? myNode?.deviceMetrics?.batteryLevel > 100 </div>
? "Charging" <div className="flex items-center">
: `${myNode?.deviceMetrics?.batteryLevel}%` <CpuIcon size={24} viewBox="0 0 36 24" />
: "UNK"} <Subtle>v{myMetadata?.firmwareVersion ?? "UNK"}</Subtle>
</Subtle> </div>
</div> </div>
<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>
<SidebarSection label="Navigation"> <SidebarSection label="Navigation">
{pages.map((link) => ( {pages.map((link) => (
<SidebarButton <SidebarButton
key={link.name} key={link.name}
label={link.name} label={link.name}
Icon={link.icon} Icon={link.icon}
onClick={() => { onClick={() => {
setActivePage(link.page); setActivePage(link.page);
}} }}
active={link.page === activePage} active={link.page === activePage}
/> />
))} ))}
</SidebarSection> </SidebarSection>
{children} {children}
</div> </div>
) : ( )
<div className="px-1 pt-8 border-r-[0.5px] border-slate-700"> : (
<button type="button" onClick={() => setShowSidebar(true)}> <div className="px-1 pt-8 border-r-[0.5px] border-slate-700">
<SidebarOpenIcon size={24} /> <button type="button" onClick={() => setShowSidebar(true)}>
</button> <SidebarOpenIcon size={24} />
</div> </button>
); </div>
);
}; };
+5 -7
View File
@@ -1,5 +1,5 @@
import { useTheme } from "@app/core/hooks/useTheme"; import { useTheme } from "../core/hooks/useTheme.ts";
import { cn } from "@app/core/utils/cn"; import { cn } from "../core/utils/cn.ts";
import { Monitor, Moon, Sun } from "lucide-react"; import { Monitor, Moon, Sun } from "lucide-react";
type ThemePreference = "light" | "dark" | "system"; type ThemePreference = "light" | "dark" | "system";
@@ -32,11 +32,9 @@ export default function ThemeSwitcher({
className, className,
)} )}
onClick={toggleTheme} onClick={toggleTheme}
aria-label={ aria-label={preference === "system"
preference === "system" ? `System theme (currently ${theme}). Click to change theme.`
? `System theme (currently ${theme}). Click to change theme.` : `Current theme: ${theme}. Click to change theme.`}
: `Current theme: ${theme}. Click to change theme.`
}
> >
{themeIcons[preference]} {themeIcons[preference]}
</button> </button>
+2 -2
View File
@@ -5,8 +5,8 @@ import {
ToastProvider, ToastProvider,
ToastTitle, ToastTitle,
ToastViewport, ToastViewport,
} from "@components/UI/Toast"; } from "./UI/Toast.tsx";
import { useToast } from "@core/hooks/useToast"; import { useToast } from "../core/hooks/useToast.ts";
export function Toaster() { export function Toaster() {
const { toasts } = useToast(); const { toasts } = useToast();
+1 -1
View File
@@ -1,4 +1,4 @@
import { cn } from "@app/core/utils/cn"; import { cn } from "../../core/utils/cn.ts";
import type React from "react"; import type React from "react";
type RGBColor = { type RGBColor = {
+5 -3
View File
@@ -1,4 +1,4 @@
import { type VariantProps, cva } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react"; import * as React from "react";
import { cn } from "@core/utils/cn.ts"; import { cn } from "@core/utils/cn.ts";
@@ -20,7 +20,8 @@ const buttonVariants = cva(
"bg-slate-100 text-slate-700 hover:bg-slate-200 dark:bg-slate-500 dark:text-white dark:hover:bg-slate-400", "bg-slate-100 text-slate-700 hover:bg-slate-200 dark:bg-slate-500 dark:text-white dark:hover:bg-slate-400",
ghost: ghost:
"bg-transparent hover:bg-slate-100 dark:hover:bg-slate-800 dark:text-slate-100 dark:hover:text-slate-100 data-[state=open]:bg-transparent dark:data-[state=open]:bg-transparent", "bg-transparent hover:bg-slate-100 dark:hover:bg-slate-800 dark:text-slate-100 dark:hover:text-slate-100 data-[state=open]:bg-transparent dark:data-[state=open]:bg-transparent",
link: "bg-transparent underline-offset-4 hover:underline text-slate-900 dark:text-slate-100 hover:bg-transparent dark:hover:bg-transparent", link:
"bg-transparent underline-offset-4 hover:underline text-slate-900 dark:text-slate-100 hover:bg-transparent dark:hover:bg-transparent",
}, },
size: { size: {
default: "h-10 py-2 px-4", default: "h-10 py-2 px-4",
@@ -38,7 +39,8 @@ const buttonVariants = cva(
export type ButtonVariant = VariantProps<typeof buttonVariants>["variant"]; export type ButtonVariant = VariantProps<typeof buttonVariants>["variant"];
export interface ButtonProps export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>, extends
React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {} VariantProps<typeof buttonVariants> {}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
+3 -3
View File
@@ -144,11 +144,11 @@ CommandShortcut.displayName = "CommandShortcut";
export { export {
Command, Command,
CommandDialog, CommandDialog,
CommandInput,
CommandList,
CommandEmpty, CommandEmpty,
CommandGroup, CommandGroup,
CommandInput,
CommandItem, CommandItem,
CommandShortcut, CommandList,
CommandSeparator, CommandSeparator,
CommandShortcut,
}; };
+5 -5
View File
@@ -23,7 +23,7 @@ DialogPortal.displayName = DialogPrimitive.Portal.displayName;
const DialogOverlay = React.forwardRef< const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>, React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, children, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay <DialogPrimitive.Overlay
className={cn( className={cn(
"fixed inset-0 z-50 bg-black/50 backdrop-blur-xs transition-opacity animate-in fade-in", "fixed inset-0 z-50 bg-black/50 backdrop-blur-xs transition-opacity animate-in fade-in",
@@ -113,10 +113,10 @@ DialogDescription.displayName = DialogPrimitive.Description.displayName;
export { export {
Dialog, Dialog,
DialogTrigger,
DialogContent, DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription, DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
}; };
+7 -7
View File
@@ -184,18 +184,18 @@ DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
export { export {
DropdownMenu, DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem, DropdownMenuCheckboxItem,
DropdownMenuRadioItem, DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel, DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuShortcut, DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub, DropdownMenuSub,
DropdownMenuSubContent, DropdownMenuSubContent,
DropdownMenuSubTrigger, DropdownMenuSubTrigger,
DropdownMenuRadioGroup, DropdownMenuTrigger,
}; };
+5 -4
View File
@@ -1,8 +1,9 @@
import newGithubIssueUrl from "@app/core/utils/github"; import newGithubIssueUrl from "../../core/utils/github.ts";
import { ExternalLink } from "lucide-react"; import { ExternalLink } from "lucide-react";
import { Heading } from "./Typography/Heading"; import { Heading } from "./Typography/Heading.tsx";
import { Link } from "./Typography/Link"; import { Link } from "./Typography/Link.tsx";
import { P } from "./Typography/P"; import { P } from "./Typography/P.tsx";
export function ErrorPage({ error }: { error: Error }) { export function ErrorPage({ error }: { error: Error }) {
if (!error) { if (!error) {
+28 -26
View File
@@ -1,30 +1,32 @@
import { cn } from "@app/core/utils/cn"; import { cn } from "@core/utils/cn.ts"
import React from "react";
export interface FooterProps extends React.HTMLAttributes<HTMLElement> {} type FooterProps = {
className?: string;
}
const Footer = React.forwardRef<HTMLElement, FooterProps>( const Footer = ({ className, ...props }: FooterProps) => {
({ className, ...props }, ref) => { return (
return ( <footer
<footer className={cn("flex mt-auto justify-center p-2", className)}> className={cn("flex mt-auto justify-center p-2", className)}
<p> {...props}
<a >
href="https://vercel.com/?utm_source=meshtastic&utm_campaign=oss" <p>
className="hover:underline text-link" <a
> href="https://vercel.com/?utm_source=meshtastic&utm_campaign=oss"
Powered by ▲ Vercel className="hover:underline text-link"
</a>{" "} >
| Meshtastic® is a registered trademark of Meshtastic LLC. |{" "} Powered by ▲ Vercel
<a </a>{" "}
href="https://meshtastic.org/docs/legal" | Meshtastic® is a registered trademark of Meshtastic LLC. |{" "}
className="hover:underline text-link" <a
> href="https://meshtastic.org/docs/legal"
Legal Information className="hover:underline text-link"
</a> >
</p> Legal Information
</footer> </a>
); </p>
}, </footer>
); );
}
export default Footer; export default Footer;
+3 -5
View File
@@ -33,7 +33,7 @@ export interface GeneratorProps extends React.BaseHTMLAttributes<HTMLElement> {
disabled?: boolean; disabled?: boolean;
} }
const Generator = React.forwardRef<HTMLInputElement, GeneratorProps>( const Generator =
( (
{ {
type, type,
@@ -53,8 +53,7 @@ const Generator = React.forwardRef<HTMLInputElement, GeneratorProps>(
action, action,
disabled, disabled,
...props ...props
}, }: GeneratorProps
ref,
) => { ) => {
const inputRef = React.useRef<HTMLInputElement>(null); const inputRef = React.useRef<HTMLInputElement>(null);
@@ -116,8 +115,7 @@ const Generator = React.forwardRef<HTMLInputElement, GeneratorProps>(
</div> </div>
</> </>
); );
}, }
);
Generator.displayName = "Button"; Generator.displayName = "Button";
export { Generator }; export { Generator };
+3 -2
View File
@@ -1,7 +1,7 @@
import * as React from "react"; import * as React from "react";
import { cn } from "@core/utils/cn.ts"; import { cn } from "@core/utils/cn.ts";
import { type VariantProps, cva } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import type { LucideIcon } from "lucide-react"; import type { LucideIcon } from "lucide-react";
const inputVariants = cva( const inputVariants = cva(
@@ -20,7 +20,8 @@ const inputVariants = cva(
); );
export interface InputProps export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement>, extends
React.InputHTMLAttributes<HTMLInputElement>,
VariantProps<typeof inputVariants> { VariantProps<typeof inputVariants> {
prefix?: string; prefix?: string;
suffix?: string; suffix?: string;
+10 -10
View File
@@ -216,19 +216,19 @@ MenubarShortcut.displayname = "MenubarShortcut";
export { export {
Menubar, Menubar,
MenubarMenu,
MenubarTrigger,
MenubarContent,
MenubarItem,
MenubarSeparator,
MenubarLabel,
MenubarCheckboxItem, MenubarCheckboxItem,
MenubarContent,
MenubarGroup,
MenubarItem,
MenubarLabel,
MenubarMenu,
MenubarPortal,
MenubarRadioGroup, MenubarRadioGroup,
MenubarRadioItem, MenubarRadioItem,
MenubarPortal, MenubarSeparator,
MenubarShortcut,
MenubarSub,
MenubarSubContent, MenubarSubContent,
MenubarSubTrigger, MenubarSubTrigger,
MenubarGroup, MenubarTrigger,
MenubarSub,
MenubarShortcut,
}; };
-25
View File
@@ -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>
);
};
+3 -4
View File
@@ -1,4 +1,4 @@
import { cn } from "@app/core/utils/cn"; import { cn } from "../../core/utils/cn.ts";
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
import { Check } from "lucide-react"; import { Check } from "lucide-react";
@@ -8,9 +8,8 @@ interface MultiSelectProps {
} }
const MultiSelect = ({ children, className = "" }: MultiSelectProps) => { const MultiSelect = ({ children, className = "" }: MultiSelectProps) => {
return ( return <div className={cn("flex flex-wrap gap-2", className)}>{children}
<div className={cn("flex flex-wrap gap-2", className)}>{children}</div> </div>;
);
}; };
interface MultiSelectItemProps { interface MultiSelectItemProps {
+1 -1
View File
@@ -26,4 +26,4 @@ const PopoverContent = React.forwardRef<
)); ));
PopoverContent.displayName = PopoverPrimitive.Content.displayName; PopoverContent.displayName = PopoverPrimitive.Content.displayName;
export { Popover, PopoverTrigger, PopoverContent }; export { Popover, PopoverContent, PopoverTrigger };
+4 -4
View File
@@ -101,11 +101,11 @@ SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
export { export {
Select, Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent, SelectContent,
SelectLabel, SelectGroup,
SelectItem, SelectItem,
SelectLabel,
SelectSeparator, SelectSeparator,
SelectTrigger,
SelectValue,
}; };
+1 -1
View File
@@ -1,4 +1,4 @@
import { Heading } from "../Typography/Heading"; import { Heading } from "../Typography/Heading.tsx";
export interface SidebarSectionProps { export interface SidebarSectionProps {
label: string; label: string;
+2 -3
View File
@@ -1,12 +1,11 @@
import { Button } from "@components/UI/Button.tsx"; import { Button } from "@components/UI/Button.tsx";
import type { LucideIcon } from "lucide-react"; import type { LucideIcon } from "lucide-react";
import type { JSX } from "react";
export interface SidebarButtonProps { export interface SidebarButtonProps {
label: string; label: string;
active?: boolean; active?: boolean;
Icon?: LucideIcon; Icon?: LucideIcon;
element?: JSX.Element; element?;
onClick?: () => void; onClick?: () => void;
} }
@@ -16,7 +15,7 @@ export const SidebarButton = ({
Icon, Icon,
element, element,
onClick, onClick,
}: SidebarButtonProps): JSX.Element => ( }: SidebarButtonProps) => (
<Button <Button
onClick={onClick} onClick={onClick}
variant={active ? "subtle" : "ghost"} variant={active ? "subtle" : "ghost"}
+1 -1
View File
@@ -1,4 +1,4 @@
import { cn } from "@app/core/utils/cn"; import { cn } from "../../core/utils/cn.ts";
interface SpinnerProps extends React.HTMLAttributes<HTMLDivElement> { interface SpinnerProps extends React.HTMLAttributes<HTMLDivElement> {
size?: "sm" | "md" | "lg"; size?: "sm" | "md" | "lg";
+1 -1
View File
@@ -50,4 +50,4 @@ const TabsContent = React.forwardRef<
)); ));
TabsContent.displayName = TabsPrimitive.Content.displayName; TabsContent.displayName = TabsPrimitive.Content.displayName;
export { Tabs, TabsList, TabsTrigger, TabsContent }; export { Tabs, TabsContent, TabsList, TabsTrigger };
+11 -11
View File
@@ -1,9 +1,9 @@
import * as ToastPrimitives from "@radix-ui/react-toast"; import * as ToastPrimitives from "@radix-ui/react-toast";
import { type VariantProps, cva } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import { X } from "lucide-react"; import { X } from "lucide-react";
import * as React from "react"; import * as React from "react";
import { cn } from "@core/utils/cn"; import { cn } from "../../core/utils/cn.ts";
const ToastProvider = ToastPrimitives.Provider; const ToastProvider = ToastPrimitives.Provider;
@@ -41,8 +41,8 @@ const toastVariants = cva(
const Toast = React.forwardRef< const Toast = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Root>, React.ElementRef<typeof ToastPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> & & React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root>
VariantProps<typeof toastVariants> & VariantProps<typeof toastVariants>
>(({ className, variant, ...props }, ref) => { >(({ className, variant, ...props }, ref) => {
return ( return (
<ToastPrimitives.Root <ToastPrimitives.Root
@@ -116,13 +116,13 @@ type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
type ToastActionElement = React.ReactElement<typeof ToastAction>; type ToastActionElement = React.ReactElement<typeof ToastAction>;
export { export {
type ToastProps,
type ToastActionElement,
ToastProvider,
ToastViewport,
Toast, Toast,
ToastTitle,
ToastDescription,
ToastClose,
ToastAction, ToastAction,
type ToastActionElement,
ToastClose,
ToastDescription,
type ToastProps,
ToastProvider,
ToastTitle,
ToastViewport,
}; };
+2 -2
View File
@@ -29,8 +29,8 @@ TooltipContent.displayName = TooltipPrimitive.Content.displayName;
export { export {
Tooltip, Tooltip,
TooltipTrigger, TooltipArrow,
TooltipContent, TooltipContent,
TooltipProvider, TooltipProvider,
TooltipArrow, TooltipTrigger,
}; };
+1 -1
View File
@@ -2,7 +2,7 @@ export interface BlockquoteProps {
children: React.ReactNode; children: React.ReactNode;
} }
export const BlockQuote = ({ children }: BlockquoteProps): JSX.Element => ( export const BlockQuote = ({ children }: BlockquoteProps) => (
<blockquote className="mt-6 border-l-2 border-slate-300 pl-6 italic text-slate-800 dark:border-slate-600 dark:text-slate-200"> <blockquote className="mt-6 border-l-2 border-slate-300 pl-6 italic text-slate-800 dark:border-slate-600 dark:text-slate-200">
{children} {children}
</blockquote> </blockquote>
+1 -1
View File
@@ -2,7 +2,7 @@ export interface CodeProps {
children: React.ReactNode; children: React.ReactNode;
} }
export const Code = ({ children }: CodeProps): JSX.Element => ( export const Code = ({ children }: CodeProps) => (
<code className="relative rounded-sm bg-slate-100 px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold text-slate-900 dark:bg-slate-800 dark:text-slate-400"> <code className="relative rounded-sm bg-slate-100 px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold text-slate-900 dark:bg-slate-800 dark:text-slate-400">
{children} {children}
</code> </code>
+2 -1
View File
@@ -2,7 +2,8 @@ import type React from "react";
const headingStyles = { const headingStyles = {
h1: "scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl", h1: "scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl",
h2: "scroll-m-20 border-b border-b-slate-200 pb-2 text-3xl font-semibold tracking-tight transition-colors first:mt-0 dark:border-b-slate-700", h2:
"scroll-m-20 border-b border-b-slate-200 pb-2 text-3xl font-semibold tracking-tight transition-colors first:mt-0 dark:border-b-slate-700",
h3: "scroll-m-20 text-2xl font-semibold tracking-tight", h3: "scroll-m-20 text-2xl font-semibold tracking-tight",
h4: "scroll-m-20 text-xl font-semibold tracking-tight", h4: "scroll-m-20 text-xl font-semibold tracking-tight",
h5: "scroll-m-20 text-lg font-medium tracking-tight", h5: "scroll-m-20 text-lg font-medium tracking-tight",
+3 -3
View File
@@ -1,4 +1,4 @@
import { cn } from "@app/core/utils/cn"; import { cn } from "../../../core/utils/cn.ts";
export interface LinkProps { export interface LinkProps {
href: string; href: string;
@@ -6,10 +6,10 @@ export interface LinkProps {
className?: string; className?: string;
} }
export const Link = ({ href, children, className }: LinkProps): JSX.Element => ( export const Link = ({ href, children, className }: LinkProps) => (
<a <a
href={href} href={href}
target={"_blank"} target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className={cn( className={cn(
"font-medium text-slate-900 underline underline-offset-4 dark:text-slate-50", "font-medium text-slate-900 underline underline-offset-4 dark:text-slate-50",
+1 -1
View File
@@ -1,4 +1,4 @@
import { cn } from "@app/core/utils/cn"; import { cn } from "@core/utils/cn.ts";
export interface PProps { export interface PProps {
children: React.ReactNode; children: React.ReactNode;
+2 -2
View File
@@ -1,11 +1,11 @@
import { cn } from "@app/core/utils/cn.ts"; import { cn } from "@core/utils/cn.ts";
export interface SubtleProps { export interface SubtleProps {
className?: string; className?: string;
children: React.ReactNode; children: React.ReactNode;
} }
export const Subtle = ({ className, children }: SubtleProps): JSX.Element => ( export const Subtle = ({ className, children }: SubtleProps) => (
<p className={cn("text-sm text-slate-500 dark:text-slate-400", className)}> <p className={cn("text-sm text-slate-500 dark:text-slate-400", className)}>
{children} {children}
</p> </p>
+1 -1
View File
@@ -1,4 +1,4 @@
export const Blur = (): JSX.Element => { export const Blur = () => {
return ( return (
<div <div
className="fixed inset-0 backdrop-blur-md backdrop-brightness-press" className="fixed inset-0 backdrop-blur-md backdrop-brightness-press"
+1 -1
View File
@@ -2,7 +2,7 @@ export const Mono = ({
children, children,
className, className,
...rest ...rest
}: JSX.IntrinsicElements["span"]): JSX.Element => { }: JSX.IntrinsicElements["span"]) => {
return ( return (
<span <span
className={`font-mono text-sm text-text-secondary ${className ?? ""}`} className={`font-mono text-sm text-text-secondary ${className ?? ""}`}

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