From 3d3a08a23fe11325cafb6a74573eda543b662311 Mon Sep 17 00:00:00 2001 From: Hunter Thornsberry Date: Tue, 17 Sep 2024 13:47:07 -0400 Subject: [PATCH 01/47] replace with select --- .../PageComponents/Config/Position.tsx | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/components/PageComponents/Config/Position.tsx b/src/components/PageComponents/Config/Position.tsx index e8a93c31..ed369d96 100644 --- a/src/components/PageComponents/Config/Position.tsx +++ b/src/components/PageComponents/Config/Position.tsx @@ -78,10 +78,39 @@ export const Position = (): JSX.Element => { description: "GPS module enable pin override", }, { - type: "number", + type: "select", name: "channelPrecision", label: "Channel Precision", - description: "GPS channel precision", + description: + "GPS channel precision", + properties: { + enumValue: + config.display?.units === 0 + ? { + "Within 23 km": 10, + "Within 12 km": 11, + "Within 5.8 km": 12, + "Within 2.9 km": 13, + "Within 1.5 km": 14, + "Within 700 m": 15, + "Within 350 m": 16, + "Within 200 m": 17, + "Within 90 m": 18, + "Within 50 m": 19, + } + : { + "Within 15 miles": 10, + "Within 7.3 miles": 11, + "Within 3.6 miles": 12, + "Within 1.8 miles": 13, + "Within 0.9 miles": 14, + "Within 0.5 miles": 15, + "Within 0.2 miles": 16, + "Within 600 feet": 17, + "Within 300 feet": 18, + "Within 150 feet": 19, + }, + }, }, ], }, From 0e92dd9bea5f16c12b860b012220efd8cbf5e357 Mon Sep 17 00:00:00 2001 From: Hunter Thornsberry Date: Tue, 17 Sep 2024 13:53:36 -0400 Subject: [PATCH 02/47] There is no longer a setting here --- .../PageComponents/Config/Position.tsx | 35 ------------------- src/validation/config/position.ts | 3 -- 2 files changed, 38 deletions(-) diff --git a/src/components/PageComponents/Config/Position.tsx b/src/components/PageComponents/Config/Position.tsx index ed369d96..531750e9 100644 --- a/src/components/PageComponents/Config/Position.tsx +++ b/src/components/PageComponents/Config/Position.tsx @@ -77,41 +77,6 @@ export const Position = (): JSX.Element => { label: "Enable Pin", description: "GPS module enable pin override", }, - { - type: "select", - name: "channelPrecision", - label: "Channel Precision", - description: - "GPS channel precision", - properties: { - enumValue: - config.display?.units === 0 - ? { - "Within 23 km": 10, - "Within 12 km": 11, - "Within 5.8 km": 12, - "Within 2.9 km": 13, - "Within 1.5 km": 14, - "Within 700 m": 15, - "Within 350 m": 16, - "Within 200 m": 17, - "Within 90 m": 18, - "Within 50 m": 19, - } - : { - "Within 15 miles": 10, - "Within 7.3 miles": 11, - "Within 3.6 miles": 12, - "Within 1.8 miles": 13, - "Within 0.9 miles": 14, - "Within 0.5 miles": 15, - "Within 0.2 miles": 16, - "Within 600 feet": 17, - "Within 300 feet": 18, - "Within 150 feet": 19, - }, - }, - }, ], }, { diff --git a/src/validation/config/position.ts b/src/validation/config/position.ts index b02f095d..7eb42c56 100644 --- a/src/validation/config/position.ts +++ b/src/validation/config/position.ts @@ -43,7 +43,4 @@ export class PositionValidation @IsEnum(Protobuf.Config.Config_PositionConfig_GpsMode) gpsMode: Protobuf.Config.Config_PositionConfig_GpsMode; - - @IsArray() - channelPrecision: number[]; } From cecdf9758b1591828794e2498e362c44303943d4 Mon Sep 17 00:00:00 2001 From: Hunter Thornsberry Date: Sun, 22 Sep 2024 18:25:35 -0400 Subject: [PATCH 03/47] remove router-client --- src/components/PageComponents/Config/Device.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/components/PageComponents/Config/Device.tsx b/src/components/PageComponents/Config/Device.tsx index ce2b5ce1..e6df723d 100644 --- a/src/components/PageComponents/Config/Device.tsx +++ b/src/components/PageComponents/Config/Device.tsx @@ -32,7 +32,22 @@ export const Device = (): JSX.Element => { label: "Role", description: "What role the device performs on the mesh", properties: { - enumValue: Protobuf.Config.Config_DeviceConfig_Role, + enumValue: { + Client: Protobuf.Config.Config_DeviceConfig_Role.CLIENT, + "Client Mute": + Protobuf.Config.Config_DeviceConfig_Role.CLIENT_MUTE, + Router: Protobuf.Config.Config_DeviceConfig_Role.ROUTER, + Repeater: Protobuf.Config.Config_DeviceConfig_Role.REPEATER, + Tracker: Protobuf.Config.Config_DeviceConfig_Role.TRACKER, + Sensor: Protobuf.Config.Config_DeviceConfig_Role.SENSOR, + TAK: Protobuf.Config.Config_DeviceConfig_Role.TAK, + "Client Hidden": + Protobuf.Config.Config_DeviceConfig_Role.CLIENT_HIDDEN, + "Lost and Found": + Protobuf.Config.Config_DeviceConfig_Role.LOST_AND_FOUND, + "TAK Tracker": + Protobuf.Config.Config_DeviceConfig_Role.SENSOR, + }, formatEnumName: true, }, }, From 4b532fc7f86305f8793464ef5113244a8fddf38f Mon Sep 17 00:00:00 2001 From: medentem Date: Wed, 2 Oct 2024 15:54:53 -0500 Subject: [PATCH 04/47] added password visibility toggle --- src/components/Form/FormInput.tsx | 13 ++++++++++++- src/components/Form/FormPasswordGenerator.tsx | 14 +++++++++++++- src/components/PageComponents/Channel.tsx | 3 ++- src/components/UI/Generator.tsx | 6 +++--- 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/components/Form/FormInput.tsx b/src/components/Form/FormInput.tsx index 13f77260..27c3828b 100644 --- a/src/components/Form/FormInput.tsx +++ b/src/components/Form/FormInput.tsx @@ -4,7 +4,9 @@ import type { } from "@components/Form/DynamicForm.js"; import { Input } from "@components/UI/Input.js"; import type { LucideIcon } from "lucide-react"; +import { Eye, EyeOff } from "lucide-react"; import type { ChangeEventHandler } from "react"; +import { useState } from "react"; import { Controller, type FieldValues } from "react-hook-form"; export interface InputFieldProps extends BaseFormBuilderProps { @@ -27,13 +29,22 @@ export function GenericInput({ disabled, field, }: GenericFormElementProps>) { + const [passwordShown, setPasswordShown] = useState(false); + const togglePasswordVisiblity = () => { + setPasswordShown(passwordShown ? false : true); + }; + return ( ( { diff --git a/src/components/Form/FormPasswordGenerator.tsx b/src/components/Form/FormPasswordGenerator.tsx index cf05f806..4410097d 100644 --- a/src/components/Form/FormPasswordGenerator.tsx +++ b/src/components/Form/FormPasswordGenerator.tsx @@ -4,6 +4,8 @@ import type { } from "@components/Form/DynamicForm.js"; import { Generator } from "@components/UI/Generator.js"; import type { ChangeEventHandler, MouseEventHandler } from "react"; +import { useState } from "react"; +import { Eye, EyeOff } from "lucide-react"; import { Controller, type FieldValues } from "react-hook-form"; export interface PasswordGeneratorProps extends BaseFormBuilderProps { @@ -21,13 +23,23 @@ export function PasswordGenerator({ field, disabled, }: GenericFormElementProps>) { + + const [passwordShown, setPasswordShown] = useState(false); + const togglePasswordVisiblity = () => { + setPasswordShown(passwordShown ? false : true); + }; + return ( ( { inputChange: inputChangeEvent, selectChange: selectChangeEvent, buttonClick: clickEvent, + hide: true, properties: { - value: pass, + value: pass }, }, { diff --git a/src/components/UI/Generator.tsx b/src/components/UI/Generator.tsx index 7d589be4..f9db3f89 100644 --- a/src/components/UI/Generator.tsx +++ b/src/components/UI/Generator.tsx @@ -12,7 +12,7 @@ import { import type { LucideIcon } from "lucide-react"; export interface GeneratorProps extends React.BaseHTMLAttributes { - hide?: boolean; + type: "text" | "password"; devicePSKBitCount?: number; value: string; variant: "default" | "invalid"; @@ -31,7 +31,7 @@ export interface GeneratorProps extends React.BaseHTMLAttributes { const Generator = React.forwardRef( ( { - hide = true, + type, devicePSKBitCount, variant, value, @@ -68,7 +68,7 @@ const Generator = React.forwardRef( return ( <> Date: Wed, 2 Oct 2024 19:33:09 -0500 Subject: [PATCH 05/47] biome --- src/components/Form/FormInput.tsx | 12 ++++++++---- src/components/Form/FormPasswordGenerator.tsx | 15 +++++++++------ src/components/PageComponents/Channel.tsx | 2 +- src/index.css | 2 +- src/pages/Messages.tsx | 2 +- 5 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/components/Form/FormInput.tsx b/src/components/Form/FormInput.tsx index 27c3828b..ba9fb885 100644 --- a/src/components/Form/FormInput.tsx +++ b/src/components/Form/FormInput.tsx @@ -41,10 +41,14 @@ export function GenericInput({ render={({ field: { value, onChange, ...rest } }) => ( { diff --git a/src/components/Form/FormPasswordGenerator.tsx b/src/components/Form/FormPasswordGenerator.tsx index 4410097d..396d746c 100644 --- a/src/components/Form/FormPasswordGenerator.tsx +++ b/src/components/Form/FormPasswordGenerator.tsx @@ -3,9 +3,9 @@ import type { GenericFormElementProps, } from "@components/Form/DynamicForm.js"; import { Generator } from "@components/UI/Generator.js"; +import { Eye, EyeOff } from "lucide-react"; import type { ChangeEventHandler, MouseEventHandler } from "react"; import { useState } from "react"; -import { Eye, EyeOff } from "lucide-react"; import { Controller, type FieldValues } from "react-hook-form"; export interface PasswordGeneratorProps extends BaseFormBuilderProps { @@ -23,7 +23,6 @@ export function PasswordGenerator({ field, disabled, }: GenericFormElementProps>) { - const [passwordShown, setPasswordShown] = useState(false); const togglePasswordVisiblity = () => { setPasswordShown(passwordShown ? false : true); @@ -36,10 +35,14 @@ export function PasswordGenerator({ render={({ field: { value, ...rest } }) => ( { buttonClick: clickEvent, hide: true, properties: { - value: pass + value: pass, }, }, { diff --git a/src/index.css b/src/index.css index d9508cd3..8a35066d 100644 --- a/src/index.css +++ b/src/index.css @@ -99,4 +99,4 @@ img { -drag: none; -webkit-user-drag: none; -} \ No newline at end of file +} diff --git a/src/pages/Messages.tsx b/src/pages/Messages.tsx index 4e7fbae9..bc624a76 100644 --- a/src/pages/Messages.tsx +++ b/src/pages/Messages.tsx @@ -76,7 +76,7 @@ export const MessagesPage = (): JSX.Element => { chatType === "broadcast" && currentChannel ? getChannelName(currentChannel) : chatType === "direct" && nodes.get(activeChat) - ? nodes.get(activeChat)?.user?.longName ?? nodeHex + ? (nodes.get(activeChat)?.user?.longName ?? nodeHex) : "Loading..." }`} actions={ From d69976454688e382f70e168dfa20d23f1d78149f Mon Sep 17 00:00:00 2001 From: Hunter Thornsberry Date: Wed, 2 Oct 2024 22:07:45 -0400 Subject: [PATCH 06/47] biome --- src/components/Form/FormInput.tsx | 8 +++++--- src/components/Form/FormPasswordGenerator.tsx | 2 +- src/pages/Messages.tsx | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/Form/FormInput.tsx b/src/components/Form/FormInput.tsx index ba9fb885..c4e5001d 100644 --- a/src/components/Form/FormInput.tsx +++ b/src/components/Form/FormInput.tsx @@ -31,7 +31,7 @@ export function GenericInput({ }: GenericFormElementProps>) { const [passwordShown, setPasswordShown] = useState(false); const togglePasswordVisiblity = () => { - setPasswordShown(passwordShown ? false : true); + setPasswordShown(!passwordShown); }; return ( @@ -40,9 +40,11 @@ export function GenericInput({ control={control} render={({ field: { value, onChange, ...rest } }) => ( ({ }: GenericFormElementProps>) { const [passwordShown, setPasswordShown] = useState(false); const togglePasswordVisiblity = () => { - setPasswordShown(passwordShown ? false : true); + setPasswordShown(!passwordShown); }; return ( diff --git a/src/pages/Messages.tsx b/src/pages/Messages.tsx index bc624a76..4e7fbae9 100644 --- a/src/pages/Messages.tsx +++ b/src/pages/Messages.tsx @@ -76,7 +76,7 @@ export const MessagesPage = (): JSX.Element => { chatType === "broadcast" && currentChannel ? getChannelName(currentChannel) : chatType === "direct" && nodes.get(activeChat) - ? (nodes.get(activeChat)?.user?.longName ?? nodeHex) + ? nodes.get(activeChat)?.user?.longName ?? nodeHex : "Loading..." }`} actions={ From 42068ad3d87378b7dd23df7fcdd799b2d5d693ac Mon Sep 17 00:00:00 2001 From: Sacha Weatherstone Date: Sun, 6 Oct 2024 21:24:12 +1000 Subject: [PATCH 07/47] move to RsBuild --- package.json | 14 +- pnpm-lock.yaml | 658 ++++++------------ rsbuild.config.ts | 27 + src/App.tsx | 24 +- src/DeviceWrapper.tsx | 4 +- src/PageRouter.tsx | 12 +- src/components/CommandPalette.tsx | 6 +- src/components/DeviceSelector.tsx | 10 +- src/components/Dialog/DeviceNameDialog.tsx | 10 +- src/components/Dialog/DialogManager.tsx | 14 +- src/components/Dialog/ImportDialog.tsx | 14 +- src/components/Dialog/NewDeviceDialog.tsx | 14 +- src/components/Dialog/PkiRegenerateDialog.tsx | 4 +- src/components/Dialog/QRDialog.tsx | 8 +- src/components/Dialog/RebootDialog.tsx | 8 +- src/components/Dialog/RemoveNodeDialog.tsx | 8 +- src/components/Dialog/ShutdownDialog.tsx | 8 +- src/components/Form/DynamicForm.tsx | 10 +- src/components/Form/DynamicFormField.tsx | 8 +- src/components/Form/FormInput.tsx | 4 +- src/components/Form/FormPasswordGenerator.tsx | 4 +- src/components/Form/FormSelect.tsx | 4 +- src/components/Form/FormToggle.tsx | 4 +- src/components/Form/FormWrapper.tsx | 2 +- src/components/PageComponents/Channel.tsx | 8 +- .../PageComponents/Config/Bluetooth.tsx | 6 +- .../PageComponents/Config/Device.tsx | 6 +- .../PageComponents/Config/Display.tsx | 6 +- src/components/PageComponents/Config/LoRa.tsx | 6 +- .../PageComponents/Config/Network.tsx | 8 +- .../PageComponents/Config/Position.tsx | 6 +- .../PageComponents/Config/Power.tsx | 6 +- .../PageComponents/Config/Security.tsx | 6 +- src/components/PageComponents/Connect/BLE.tsx | 12 +- .../PageComponents/Connect/HTTP.tsx | 16 +- .../PageComponents/Connect/Serial.tsx | 12 +- .../PageComponents/Messages/ChannelChat.tsx | 10 +- .../PageComponents/Messages/Message.tsx | 2 +- .../PageComponents/Messages/MessageInput.tsx | 6 +- .../PageComponents/Messages/TraceRoute.tsx | 2 +- .../ModuleConfig/AmbientLighting.tsx | 6 +- .../PageComponents/ModuleConfig/Audio.tsx | 6 +- .../ModuleConfig/CannedMessage.tsx | 6 +- .../ModuleConfig/DetectionSensor.tsx | 6 +- .../ModuleConfig/ExternalNotification.tsx | 6 +- .../PageComponents/ModuleConfig/MQTT.tsx | 6 +- .../ModuleConfig/NeighborInfo.tsx | 6 +- .../ModuleConfig/Paxcounter.tsx | 6 +- .../PageComponents/ModuleConfig/RangeTest.tsx | 6 +- .../PageComponents/ModuleConfig/Serial.tsx | 6 +- .../ModuleConfig/StoreForward.tsx | 6 +- .../PageComponents/ModuleConfig/Telemetry.tsx | 6 +- src/components/PageLayout.tsx | 2 +- src/components/Sidebar.tsx | 10 +- src/components/Toaster.tsx | 4 +- src/components/UI/Button.tsx | 2 +- src/components/UI/Checkbox.tsx | 2 +- src/components/UI/Command.tsx | 4 +- src/components/UI/Dialog.tsx | 2 +- src/components/UI/DropdownMenu.tsx | 2 +- src/components/UI/Generator.tsx | 6 +- src/components/UI/Input.tsx | 2 +- src/components/UI/Label.tsx | 2 +- src/components/UI/Menubar.tsx | 2 +- src/components/UI/Popover.tsx | 2 +- src/components/UI/ScrollArea.tsx | 2 +- src/components/UI/Select.tsx | 2 +- src/components/UI/Seperator.tsx | 2 +- src/components/UI/Sidebar/SidebarSection.tsx | 2 +- src/components/UI/Sidebar/sidebarButton.tsx | 2 +- src/components/UI/Switch.tsx | 2 +- src/components/UI/Tabs.tsx | 2 +- src/components/UI/Toast.tsx | 2 +- src/components/UI/Tooltip.tsx | 2 +- src/components/UI/Typography/H4.tsx | 2 +- src/components/UI/Typography/Subtle.tsx | 2 +- src/components/generic/ThemeController.tsx | 2 +- src/core/hooks/useToast.ts | 38 +- src/core/subscriptions.ts | 2 +- src/index.tsx | 2 +- src/pages/Channels.tsx | 10 +- src/pages/Config/DeviceConfig.tsx | 20 +- src/pages/Config/ModuleConfig.tsx | 26 +- src/pages/Config/index.tsx | 16 +- src/pages/Dashboard/index.tsx | 12 +- src/pages/Map.tsx | 16 +- src/pages/Messages.tsx | 16 +- src/pages/Nodes.tsx | 12 +- src/validation/rasterSource.ts | 2 +- tsconfig.json | 3 +- 90 files changed, 552 insertions(+), 758 deletions(-) create mode 100644 rsbuild.config.ts diff --git a/package.json b/package.json index e4c7d449..05221d9b 100644 --- a/package.json +++ b/package.json @@ -5,11 +5,12 @@ "description": "Meshtastic web client", "license": "GPL-3.0-only", "scripts": { - "dev": "vite --host", - "build": "tsc && pnpm check && vite build ", + "build": "rsbuild build", "check": "biome check .", "check:fix": "pnpm check --write", - "preview": "vite preview", + "dev": "rsbuild dev --open", + "format": "biome format --write", + "preview": "rsbuild 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/)" }, "repository": { @@ -66,13 +67,14 @@ "devDependencies": { "@biomejs/biome": "^1.8.2", "@buf/meshtastic_protobufs.bufbuild_es": "1.10.0-20240906232734-3da561588c55.1", + "@rsbuild/core": "^1.0.10", + "@rsbuild/plugin-react": "^1.0.3", "@types/chrome": "^0.0.263", "@types/node": "^20.14.9", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@types/w3c-web-serial": "^1.0.6", "@types/web-bluetooth": "^0.0.20", - "@vitejs/plugin-react": "^4.3.1", "autoprefixer": "^10.4.19", "gzipper": "^7.2.0", "postcss": "^8.4.38", @@ -80,8 +82,6 @@ "tailwindcss": "^3.4.4", "tar": "^6.2.1", "tslib": "^2.6.3", - "typescript": "^5.5.2", - "vite": "^5.3.1", - "vite-plugin-environment": "^1.1.3" + "typescript": "^5.5.2" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9e91a4c5..35f67520 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -138,6 +138,12 @@ importers: '@buf/meshtastic_protobufs.bufbuild_es': specifier: 1.10.0-20240906232734-3da561588c55.1 version: 1.10.0-20240906232734-3da561588c55.1(@bufbuild/protobuf@1.10.0) + '@rsbuild/core': + specifier: ^1.0.10 + version: 1.0.10 + '@rsbuild/plugin-react': + specifier: ^1.0.3 + version: 1.0.3(@rsbuild/core@1.0.10) '@types/chrome': specifier: ^0.0.263 version: 0.0.263 @@ -156,9 +162,6 @@ importers: '@types/web-bluetooth': specifier: ^0.0.20 version: 0.0.20 - '@vitejs/plugin-react': - specifier: ^4.3.1 - version: 4.3.1(vite@5.3.1(@types/node@20.14.9)) autoprefixer: specifier: ^10.4.19 version: 10.4.19(postcss@8.4.38) @@ -183,12 +186,6 @@ importers: typescript: specifier: ^5.5.2 version: 5.5.2 - vite: - specifier: ^5.3.1 - version: 5.3.1(@types/node@20.14.9) - vite-plugin-environment: - specifier: ^1.1.3 - version: 1.1.3(vite@5.3.1(@types/node@20.14.9)) packages: @@ -196,117 +193,10 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} - '@ampproject/remapping@2.3.0': - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} - engines: {node: '>=6.0.0'} - - '@babel/code-frame@7.24.7': - resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} - engines: {node: '>=6.9.0'} - - '@babel/compat-data@7.24.7': - resolution: {integrity: sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==} - engines: {node: '>=6.9.0'} - - '@babel/core@7.24.7': - resolution: {integrity: sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.24.7': - resolution: {integrity: sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-compilation-targets@7.24.7': - resolution: {integrity: sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-environment-visitor@7.24.7': - resolution: {integrity: sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-function-name@7.24.7': - resolution: {integrity: sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-hoist-variables@7.24.7': - resolution: {integrity: sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.24.7': - resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.24.7': - resolution: {integrity: sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-plugin-utils@7.24.7': - resolution: {integrity: sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-simple-access@7.24.7': - resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-split-export-declaration@7.24.7': - resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.24.7': - resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.24.7': - resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-option@7.24.7': - resolution: {integrity: sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.24.7': - resolution: {integrity: sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==} - engines: {node: '>=6.9.0'} - - '@babel/highlight@7.24.7': - resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.24.7': - resolution: {integrity: sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/plugin-transform-react-jsx-self@7.24.7': - resolution: {integrity: sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-react-jsx-source@7.24.7': - resolution: {integrity: sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/runtime@7.24.7': resolution: {integrity: sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==} engines: {node: '>=6.9.0'} - '@babel/template@7.24.7': - resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.24.7': - resolution: {integrity: sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.24.7': - resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==} - engines: {node: '>=6.9.0'} - '@biomejs/biome@1.8.2': resolution: {integrity: sha512-XafCzLgs0xbH0bCjYKxQ63ig2V86fZQMq1jiy5pyLToWk9aHxA8GAUxyBtklPHtPYZPGEPOYglQHj4jyfUp+Iw==} engines: {node: '>=14.21.3'} @@ -596,6 +486,18 @@ packages: '@meshtastic/js@2.3.7-5': resolution: {integrity: sha512-77wYoCl83PgRLkvWE8ko0YFm5LbolrfFPqoBkwLd2AFgZOHGsHTlUwA7cj82yhZM3f4mf7yTFxl+8CBawEAXRA==} + '@module-federation/runtime-tools@0.5.1': + resolution: {integrity: sha512-nfBedkoZ3/SWyO0hnmaxuz0R0iGPSikHZOAZ0N/dVSQaIzlffUo35B5nlC2wgWIc0JdMZfkwkjZRrnuuDIJbzg==} + + '@module-federation/runtime@0.5.1': + resolution: {integrity: sha512-xgiMUWwGLWDrvZc9JibuEbXIbhXg6z2oUkemogSvQ4LKvrl/n0kbqP1Blk669mXzyWbqtSp6PpvNdwaE1aN5xQ==} + + '@module-federation/sdk@0.5.1': + resolution: {integrity: sha512-exvchtjNURJJkpqjQ3/opdbfeT2wPKvrbnGnyRkrwW5o3FH1LaST1tkiNviT6OXTexGaVc2DahbdniQHVtQ7pA==} + + '@module-federation/webpack-bundler-runtime@0.5.1': + resolution: {integrity: sha512-mMhRFH0k2VjwHt3Jol9JkUsmI/4XlrAoBG3E0o7HoyoPYv1UFOWyqAflfANcUPgbYpvqmyLzDcO+3IT36LXnrA==} + '@noble/curves@1.5.0': resolution: {integrity: sha512-J5EKamIHnKPyClwVrzmaf5wSdQXgdHcPZIZLu3bwnbeCx8/7NPK5q2ZBWF+5FvYGByjiQQsJYX6jfgB2wDPn3A==} @@ -1343,6 +1245,85 @@ packages: cpu: [x64] os: [win32] + '@rsbuild/core@1.0.10': + resolution: {integrity: sha512-617N8YzDeH5vymeeOyCqK0toO9yt7s2yey49OIvW9jZqBo0IvgbkFNQf34LDLsxVzy+cpf1nGrcYWjPKhVuGfQ==} + engines: {node: '>=16.7.0'} + hasBin: true + + '@rsbuild/plugin-react@1.0.3': + resolution: {integrity: sha512-HVfPiKINmDsIcLLs7YWAYQgzytVZOydBuPOFg5EoJiMHkFVjH0Rg3QViS3Hn6k3INqdc6ylpcYyOHHYItEIkWA==} + peerDependencies: + '@rsbuild/core': 1.x || ^1.0.1-rc.0 + + '@rspack/binding-darwin-arm64@1.0.8': + resolution: {integrity: sha512-1l8/eg3HNz53DHQO3fy5O5QKdYh8hSMZaWGtm3NR5IfdrTm2TaLL9tuR8oL2iHHtd87LEvVKHXdjlcuLV5IPNQ==} + cpu: [arm64] + os: [darwin] + + '@rspack/binding-darwin-x64@1.0.8': + resolution: {integrity: sha512-7BbG8gXVWjtqJegDpsObzM/B90Eig1piEtcahvPdvlC92uZz3/IwtKPpMaywGBrf5RSI3U0nQMSekwz0cO1SOw==} + cpu: [x64] + os: [darwin] + + '@rspack/binding-linux-arm64-gnu@1.0.8': + resolution: {integrity: sha512-QnqCL0wmwYqT/IFx5q0aw7DsIOr8oYUa4+7JI8iiqRf3RuuRJExesVW9VuWr0jS2UvChKgmb8PvRtDy/0tshFw==} + cpu: [arm64] + os: [linux] + + '@rspack/binding-linux-arm64-musl@1.0.8': + resolution: {integrity: sha512-Ns9TsE7zdUjimW5HURRW08BaMyAh16MDh97PPsGEMeRPx9plnRO9aXvuUG6t+0gy4KwlQdeq3BvUsbBpIo5Tow==} + cpu: [arm64] + os: [linux] + + '@rspack/binding-linux-x64-gnu@1.0.8': + resolution: {integrity: sha512-lfqUuKCoyRN/gGeokhX/oNYqB6OpbtgQb57b0QuD8IaiH2a1ee0TtEVvRbyQNEDwht6lW4RTNg0RfMYu52LgXg==} + cpu: [x64] + os: [linux] + + '@rspack/binding-linux-x64-musl@1.0.8': + resolution: {integrity: sha512-MgbHJWV5utVa1/U9skrXClydZ/eZw001++v4B6nb8myU6Ck1D02aMl9ESefb/sSA8TatLLxEXQ2VENG9stnPwQ==} + cpu: [x64] + os: [linux] + + '@rspack/binding-win32-arm64-msvc@1.0.8': + resolution: {integrity: sha512-3NN5VisnSOzhgqX77O/7NvcjPUueg1oIdMKoc5vElJCEu5FEXPqDhwZmr1PpBovaXshAcgExF3j54+20pwdg5g==} + cpu: [arm64] + os: [win32] + + '@rspack/binding-win32-ia32-msvc@1.0.8': + resolution: {integrity: sha512-17VQNC7PSygzsipSVoukDM/SOcVueVNsk9bZiB0Swl20BaqrlBts2Dvlmo+L+ZGsxOYI97WvA/zomMDv860usg==} + cpu: [ia32] + os: [win32] + + '@rspack/binding-win32-x64-msvc@1.0.8': + resolution: {integrity: sha512-Vtjt74Soh09XUsV5Nw0YjZVSk/qtsjtPnzbSZluncSAVUs8l+X1ALcM6n1Jrt3TLTfcqf7a+VIsWOXAMqkCGUg==} + cpu: [x64] + os: [win32] + + '@rspack/binding@1.0.8': + resolution: {integrity: sha512-abRirbrjobcllLAamyeiWxT6Rb0wELUnITynQdqRbSweWm2lvnhm9YBv4BcOjvJBzhJtvRJo5JBtbKXjDTarug==} + + '@rspack/core@1.0.8': + resolution: {integrity: sha512-pbXwXYb4WQwb0l35P5v3l/NpDJXy1WiVE4IcQ/6LxZYU5NyZuqtsK0trR88xIVRZb9qU0JUeCdQq7Xa6Q+c3Xw==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@swc/helpers': '>=0.5.1' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@rspack/lite-tapable@1.0.1': + resolution: {integrity: sha512-VynGOEsVw2s8TAlLf/uESfrgfrq2+rcXB1muPJYBWbsm1Oa6r5qVQhjA5ggM6z/coYPrsVMgovl3Ff7Q7OCp1w==} + engines: {node: '>=16.0.0'} + + '@rspack/plugin-react-refresh@1.0.0': + resolution: {integrity: sha512-WvXkLewW5G0Mlo5H1b251yDh5FFiH4NDAbYlFpvFjcuXX2AchZRf9zdw57BDE/ADyWsJgA8kixN/zZWBTN3iYA==} + peerDependencies: + react-refresh: '>=0.10.0 <1.0.0' + peerDependenciesMeta: + react-refresh: + optional: true + '@stablelib/binary@1.0.1': resolution: {integrity: sha512-ClJWvmL6UBM/wjkvv/7m5VP3GMr9t0osr4yVgLZsLCOz4hGN9gIAFEqnJ0TsSMAN+n840nf2cHZnA5/KFqHC7Q==} @@ -1358,6 +1339,9 @@ packages: '@stablelib/wipe@1.0.1': resolution: {integrity: sha512-WfqfX/eXGiAd3RJe4VU2snh/ZPwtSjLG4ynQ/vYzvghTh7dHFcI1wl+nrkWG6lGhukOxOsUHfv8dUXr58D0ayg==} + '@swc/helpers@0.5.13': + resolution: {integrity: sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==} + '@turf/along@6.5.0': resolution: {integrity: sha512-LLyWQ0AARqJCmMcIEAXF4GEu8usmd4Kbz3qk1Oy5HoRNpZX47+i5exQtmIWKdqJ1MMhW26fCTXgpsEs5zgJ5gw==} @@ -1679,18 +1663,6 @@ packages: '@turf/voronoi@6.5.0': resolution: {integrity: sha512-C/xUsywYX+7h1UyNqnydHXiun4UPjK88VDghtoRypR9cLlb7qozkiLRphQxxsCM0KxyxpVPHBVQXdAL3+Yurow==} - '@types/babel__core@7.20.5': - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - - '@types/babel__generator@7.6.8': - resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} - - '@types/babel__template@7.4.4': - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - - '@types/babel__traverse@7.20.6': - resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} - '@types/chrome@0.0.263': resolution: {integrity: sha512-As0vzv99ov3M6ZR7R6VzhMWFZXkPMrFrCEXXVrMN576Cm70fTkj7Df2CF+qEo170JepX50pd11cX6O4DSAtl2Q==} @@ -1751,12 +1723,6 @@ packages: '@types/web-bluetooth@0.0.20': resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} - '@vitejs/plugin-react@4.3.1': - resolution: {integrity: sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - vite: ^4.2.0 || ^5.0.0 - ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -1765,10 +1731,6 @@ packages: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} engines: {node: '>=12'} - ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -1902,10 +1864,6 @@ packages: caniuse-lite@1.0.30001638: resolution: {integrity: sha512-5SuJUJ7cZnhPpeLHaH0c/HPAnAHZvS6ElWyHK9GSIbVOQABLzowiI2pjmpvZ1WEbkyz46iFd4UXlOHR5SqgfMQ==} - chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} - cheap-ruler@4.0.0: resolution: {integrity: sha512-0BJa8f4t141BYKQyn9NSQt1PguFQXMXwZiA5shfoaBYHAb2fFk2RAX+tiWMoQU+Agtzt3mdt0JtuyshAXqZ+Vw==} @@ -1947,16 +1905,10 @@ packages: react: ^18.0.0 react-dom: ^18.0.0 - color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} - color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} @@ -1980,8 +1932,8 @@ packages: constants-browserify@1.0.0: resolution: {integrity: sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==} - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + core-js@3.38.1: + resolution: {integrity: sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==} core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -2038,15 +1990,6 @@ packages: d3-voronoi@1.1.2: resolution: {integrity: sha512-RhGS1u2vavcO7ay7ZNAPo4xeDh/VYeGof3x5ZLJBQgYhLegxr3s5IykvWmJ94FTU6mcbtp4sloqZ54mP6R4Utw==} - debug@4.3.5: - resolution: {integrity: sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - deep-equal@1.1.2: resolution: {integrity: sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==} engines: {node: '>= 0.4'} @@ -2119,6 +2062,9 @@ packages: end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + error-stack-parser@2.1.4: + resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} + es-define-property@1.0.0: resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} engines: {node: '>= 0.4'} @@ -2139,10 +2085,6 @@ packages: resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} engines: {node: '>=6'} - escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} @@ -2204,10 +2146,6 @@ packages: functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - geojson-equality@0.1.6: resolution: {integrity: sha512-TqG8YbqizP3EfwP5Uw4aLu6pKkg6JQK9uq/XZ1lXQntvTHD1BBKJWhNpJ2M0ax6TuWMP3oyx6Oq7FCIfznrgpQ==} @@ -2260,10 +2198,6 @@ packages: resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==} engines: {node: '>=6'} - globals@11.12.0: - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} - engines: {node: '>=4'} - gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} @@ -2278,10 +2212,6 @@ packages: has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} - has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} - has-property-descriptors@1.0.2: resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} @@ -2315,6 +2245,9 @@ packages: hmac-drbg@1.0.1: resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} + html-entities@2.5.2: + resolution: {integrity: sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==} + https-browserify@1.0.0: resolution: {integrity: sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==} @@ -2487,22 +2420,12 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - jsesc@2.5.2: - resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} - engines: {node: '>=4'} - hasBin: true - json-stringify-pretty-compact@3.0.0: resolution: {integrity: sha512-Rc2suX5meI0S3bfdZuA7JMFBGkJ875ApfVyq2WHELjBiiG22My/l7/8zPpH/CfFVQHuVLd8NLR0nv6vi0BYYKA==} json-stringify-pretty-compact@4.0.0: resolution: {integrity: sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==} - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - kdbush@4.0.2: resolution: {integrity: sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==} @@ -2539,9 +2462,6 @@ packages: resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} engines: {node: 14 || >=16.14} - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - lucide-react@0.363.0: resolution: {integrity: sha512-AlsfPCsXQyQx7wwsIgzcKOL9LwC498LIMAo+c0Es5PkHJa33xwmYAkkSoKoJWWWSYQEStqu58/jT4tL2gi32uQ==} peerDependencies: @@ -2606,9 +2526,6 @@ packages: engines: {node: '>=10'} hasBin: true - ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - murmurhash-js@1.0.0: resolution: {integrity: sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==} @@ -3016,10 +2933,6 @@ packages: scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - serialize-to-js@3.1.2: resolution: {integrity: sha512-owllqNuDDEimQat7EPG0tH7JjO090xKNzUtYz6X+Sk2BXDnOCilDdNLwjWeFywG9xkJul1ULvtUQa9O4pUaY0w==} engines: {node: '>=4.0.0'} @@ -3092,6 +3005,9 @@ packages: resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==} engines: {node: '>=0.10.0'} + stackframe@1.3.4: + resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} + ste-core@3.0.11: resolution: {integrity: sha512-ivkRENMh0mdGoPlZ4xVcEaC8rXQfTEfvonRw5m8VDKV7kgcbZbaNd1TnKl08wXbcLdT7okSc63HNP8cVhy95zg==} engines: {node: '>=4.2.4'} @@ -3143,10 +3059,6 @@ packages: supercluster@8.0.1: resolution: {integrity: sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==} - supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} - supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} @@ -3203,10 +3115,6 @@ packages: tinyqueue@3.0.0: resolution: {integrity: sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==} - to-fast-properties@2.0.0: - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} - engines: {node: '>=4'} - to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -3313,11 +3221,6 @@ packages: resolution: {integrity: sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==} engines: {node: '>= 0.10'} - vite-plugin-environment@1.1.3: - resolution: {integrity: sha512-9LBhB0lx+2lXVBEWxFZC+WO7PKEyE/ykJ7EPWCq95NEcCpblxamTbs5Dm3DLBGzwODpJMEnzQywJU8fw6XGGGA==} - peerDependencies: - vite: '>= 2.7' - vite-plugin-node-polyfills@0.22.0: resolution: {integrity: sha512-F+G3LjiGbG8QpbH9bZ//GSBr9i1InSTkaulfUHFa9jkLqVGORFBoqc2A/Yu5Mmh1kNAbiAeKeK+6aaQUf3x0JA==} peerDependencies: @@ -3396,9 +3299,6 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} @@ -3438,160 +3338,10 @@ snapshots: '@alloc/quick-lru@5.2.0': {} - '@ampproject/remapping@2.3.0': - dependencies: - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 - - '@babel/code-frame@7.24.7': - dependencies: - '@babel/highlight': 7.24.7 - picocolors: 1.0.1 - - '@babel/compat-data@7.24.7': {} - - '@babel/core@7.24.7': - dependencies: - '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.24.7 - '@babel/generator': 7.24.7 - '@babel/helper-compilation-targets': 7.24.7 - '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7) - '@babel/helpers': 7.24.7 - '@babel/parser': 7.24.7 - '@babel/template': 7.24.7 - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - convert-source-map: 2.0.0 - debug: 4.3.5 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/generator@7.24.7': - dependencies: - '@babel/types': 7.24.7 - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 - jsesc: 2.5.2 - - '@babel/helper-compilation-targets@7.24.7': - dependencies: - '@babel/compat-data': 7.24.7 - '@babel/helper-validator-option': 7.24.7 - browserslist: 4.23.1 - lru-cache: 5.1.1 - semver: 6.3.1 - - '@babel/helper-environment-visitor@7.24.7': - dependencies: - '@babel/types': 7.24.7 - - '@babel/helper-function-name@7.24.7': - dependencies: - '@babel/template': 7.24.7 - '@babel/types': 7.24.7 - - '@babel/helper-hoist-variables@7.24.7': - dependencies: - '@babel/types': 7.24.7 - - '@babel/helper-module-imports@7.24.7': - dependencies: - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-module-imports': 7.24.7 - '@babel/helper-simple-access': 7.24.7 - '@babel/helper-split-export-declaration': 7.24.7 - '@babel/helper-validator-identifier': 7.24.7 - transitivePeerDependencies: - - supports-color - - '@babel/helper-plugin-utils@7.24.7': {} - - '@babel/helper-simple-access@7.24.7': - dependencies: - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - transitivePeerDependencies: - - supports-color - - '@babel/helper-split-export-declaration@7.24.7': - dependencies: - '@babel/types': 7.24.7 - - '@babel/helper-string-parser@7.24.7': {} - - '@babel/helper-validator-identifier@7.24.7': {} - - '@babel/helper-validator-option@7.24.7': {} - - '@babel/helpers@7.24.7': - dependencies: - '@babel/template': 7.24.7 - '@babel/types': 7.24.7 - - '@babel/highlight@7.24.7': - dependencies: - '@babel/helper-validator-identifier': 7.24.7 - chalk: 2.4.2 - js-tokens: 4.0.0 - picocolors: 1.0.1 - - '@babel/parser@7.24.7': - dependencies: - '@babel/types': 7.24.7 - - '@babel/plugin-transform-react-jsx-self@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-transform-react-jsx-source@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/runtime@7.24.7': dependencies: regenerator-runtime: 0.14.1 - '@babel/template@7.24.7': - dependencies: - '@babel/code-frame': 7.24.7 - '@babel/parser': 7.24.7 - '@babel/types': 7.24.7 - - '@babel/traverse@7.24.7': - dependencies: - '@babel/code-frame': 7.24.7 - '@babel/generator': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-function-name': 7.24.7 - '@babel/helper-hoist-variables': 7.24.7 - '@babel/helper-split-export-declaration': 7.24.7 - '@babel/parser': 7.24.7 - '@babel/types': 7.24.7 - debug: 4.3.5 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - - '@babel/types@7.24.7': - dependencies: - '@babel/helper-string-parser': 7.24.7 - '@babel/helper-validator-identifier': 7.24.7 - to-fast-properties: 2.0.0 - '@biomejs/biome@1.8.2': optionalDependencies: '@biomejs/cli-darwin-arm64': 1.8.2 @@ -3810,6 +3560,22 @@ snapshots: transitivePeerDependencies: - buffer + '@module-federation/runtime-tools@0.5.1': + dependencies: + '@module-federation/runtime': 0.5.1 + '@module-federation/webpack-bundler-runtime': 0.5.1 + + '@module-federation/runtime@0.5.1': + dependencies: + '@module-federation/sdk': 0.5.1 + + '@module-federation/sdk@0.5.1': {} + + '@module-federation/webpack-bundler-runtime@0.5.1': + dependencies: + '@module-federation/runtime': 0.5.1 + '@module-federation/sdk': 0.5.1 + '@noble/curves@1.5.0': dependencies: '@noble/hashes': 1.4.0 @@ -4542,6 +4308,78 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.18.0': optional: true + '@rsbuild/core@1.0.10': + dependencies: + '@rspack/core': 1.0.8(@swc/helpers@0.5.13) + '@rspack/lite-tapable': 1.0.1 + '@swc/helpers': 0.5.13 + core-js: 3.38.1 + optionalDependencies: + fsevents: 2.3.3 + + '@rsbuild/plugin-react@1.0.3(@rsbuild/core@1.0.10)': + dependencies: + '@rsbuild/core': 1.0.10 + '@rspack/plugin-react-refresh': 1.0.0(react-refresh@0.14.2) + react-refresh: 0.14.2 + + '@rspack/binding-darwin-arm64@1.0.8': + optional: true + + '@rspack/binding-darwin-x64@1.0.8': + optional: true + + '@rspack/binding-linux-arm64-gnu@1.0.8': + optional: true + + '@rspack/binding-linux-arm64-musl@1.0.8': + optional: true + + '@rspack/binding-linux-x64-gnu@1.0.8': + optional: true + + '@rspack/binding-linux-x64-musl@1.0.8': + optional: true + + '@rspack/binding-win32-arm64-msvc@1.0.8': + optional: true + + '@rspack/binding-win32-ia32-msvc@1.0.8': + optional: true + + '@rspack/binding-win32-x64-msvc@1.0.8': + optional: true + + '@rspack/binding@1.0.8': + optionalDependencies: + '@rspack/binding-darwin-arm64': 1.0.8 + '@rspack/binding-darwin-x64': 1.0.8 + '@rspack/binding-linux-arm64-gnu': 1.0.8 + '@rspack/binding-linux-arm64-musl': 1.0.8 + '@rspack/binding-linux-x64-gnu': 1.0.8 + '@rspack/binding-linux-x64-musl': 1.0.8 + '@rspack/binding-win32-arm64-msvc': 1.0.8 + '@rspack/binding-win32-ia32-msvc': 1.0.8 + '@rspack/binding-win32-x64-msvc': 1.0.8 + + '@rspack/core@1.0.8(@swc/helpers@0.5.13)': + dependencies: + '@module-federation/runtime-tools': 0.5.1 + '@rspack/binding': 1.0.8 + '@rspack/lite-tapable': 1.0.1 + caniuse-lite: 1.0.30001638 + optionalDependencies: + '@swc/helpers': 0.5.13 + + '@rspack/lite-tapable@1.0.1': {} + + '@rspack/plugin-react-refresh@1.0.0(react-refresh@0.14.2)': + dependencies: + error-stack-parser: 2.1.4 + html-entities: 2.5.2 + optionalDependencies: + react-refresh: 0.14.2 + '@stablelib/binary@1.0.1': dependencies: '@stablelib/int': 1.0.1 @@ -4558,6 +4396,10 @@ snapshots: '@stablelib/wipe@1.0.1': {} + '@swc/helpers@0.5.13': + dependencies: + tslib: 2.6.3 + '@turf/along@6.5.0': dependencies: '@turf/bearing': 6.5.0 @@ -5374,27 +5216,6 @@ snapshots: '@turf/invariant': 6.5.0 d3-voronoi: 1.1.2 - '@types/babel__core@7.20.5': - dependencies: - '@babel/parser': 7.24.7 - '@babel/types': 7.24.7 - '@types/babel__generator': 7.6.8 - '@types/babel__template': 7.4.4 - '@types/babel__traverse': 7.20.6 - - '@types/babel__generator@7.6.8': - dependencies: - '@babel/types': 7.24.7 - - '@types/babel__template@7.4.4': - dependencies: - '@babel/parser': 7.24.7 - '@babel/types': 7.24.7 - - '@types/babel__traverse@7.20.6': - dependencies: - '@babel/types': 7.24.7 - '@types/chrome@0.0.263': dependencies: '@types/filesystem': 0.0.36 @@ -5457,25 +5278,10 @@ snapshots: '@types/web-bluetooth@0.0.20': {} - '@vitejs/plugin-react@4.3.1(vite@5.3.1(@types/node@20.14.9))': - dependencies: - '@babel/core': 7.24.7 - '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.24.7) - '@types/babel__core': 7.20.5 - react-refresh: 0.14.2 - vite: 5.3.1(@types/node@20.14.9) - transitivePeerDependencies: - - supports-color - ansi-regex@5.0.1: {} ansi-regex@6.0.1: {} - ansi-styles@3.2.1: - dependencies: - color-convert: 1.9.3 - ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 @@ -5639,12 +5445,6 @@ snapshots: caniuse-lite@1.0.30001638: {} - chalk@2.4.2: - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 - cheap-ruler@4.0.0: {} chokidar@3.6.0: @@ -5698,16 +5498,10 @@ snapshots: - '@types/react' - '@types/react-dom' - color-convert@1.9.3: - dependencies: - color-name: 1.1.3 - color-convert@2.0.1: dependencies: color-name: 1.1.4 - color-name@1.1.3: {} - color-name@1.1.4: {} commander@2.20.3: {} @@ -5727,7 +5521,7 @@ snapshots: constants-browserify@1.0.0: {} - convert-source-map@2.0.0: {} + core-js@3.38.1: {} core-util-is@1.0.3: {} @@ -5795,10 +5589,6 @@ snapshots: d3-voronoi@1.1.2: {} - debug@4.3.5: - dependencies: - ms: 2.1.2 - deep-equal@1.1.2: dependencies: is-arguments: 1.1.1 @@ -5899,6 +5689,10 @@ snapshots: dependencies: once: 1.4.0 + error-stack-parser@2.1.4: + dependencies: + stackframe: 1.3.4 + es-define-property@1.0.0: dependencies: get-intrinsic: 1.2.4 @@ -5945,8 +5739,6 @@ snapshots: escalade@3.1.2: {} - escape-string-regexp@1.0.5: {} - estree-walker@2.0.2: {} events@3.3.0: {} @@ -6010,8 +5802,6 @@ snapshots: functions-have-names@1.2.3: {} - gensync@1.0.0-beta.2: {} - geojson-equality@0.1.6: dependencies: deep-equal: 1.1.2 @@ -6069,8 +5859,6 @@ snapshots: kind-of: 6.0.3 which: 1.3.1 - globals@11.12.0: {} - gopd@1.0.1: dependencies: get-intrinsic: 1.2.4 @@ -6087,8 +5875,6 @@ snapshots: has-bigints@1.0.2: {} - has-flag@3.0.0: {} - has-property-descriptors@1.0.2: dependencies: es-define-property: 1.0.0 @@ -6127,6 +5913,8 @@ snapshots: minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 + html-entities@2.5.2: {} + https-browserify@1.0.0: {} ieee754@1.2.1: {} @@ -6275,14 +6063,10 @@ snapshots: js-tokens@4.0.0: {} - jsesc@2.5.2: {} - json-stringify-pretty-compact@3.0.0: {} json-stringify-pretty-compact@4.0.0: {} - json5@2.2.3: {} - kdbush@4.0.2: {} kind-of@6.0.3: {} @@ -6307,10 +6091,6 @@ snapshots: lru-cache@10.2.2: {} - lru-cache@5.1.1: - dependencies: - yallist: 3.1.1 - lucide-react@0.363.0(react@18.3.1): dependencies: react: 18.3.1 @@ -6421,8 +6201,6 @@ snapshots: mkdirp@1.0.4: {} - ms@2.1.2: {} - murmurhash-js@1.0.0: {} mz@2.7.0: @@ -6863,8 +6641,6 @@ snapshots: dependencies: loose-envify: 1.4.0 - semver@6.3.1: {} - serialize-to-js@3.1.2: {} set-function-length@1.2.2: @@ -6944,6 +6720,8 @@ snapshots: dependencies: extend-shallow: 3.0.2 + stackframe@1.3.4: {} + ste-core@3.0.11: {} ste-simple-events@3.0.11: @@ -7010,10 +6788,6 @@ snapshots: dependencies: kdbush: 4.0.2 - supports-color@5.5.0: - dependencies: - has-flag: 3.0.0 - supports-preserve-symlinks-flag@1.0.0: {} tailwind-merge@2.3.0: @@ -7097,8 +6871,6 @@ snapshots: tinyqueue@3.0.0: {} - to-fast-properties@2.0.0: {} - to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -7188,10 +6960,6 @@ snapshots: validator@13.12.0: {} - vite-plugin-environment@1.1.3(vite@5.3.1(@types/node@20.14.9)): - dependencies: - vite: 5.3.1(@types/node@20.14.9) - vite-plugin-node-polyfills@0.22.0(rollup@4.18.0)(vite@5.3.1(@types/node@20.14.9)): dependencies: '@rollup/plugin-inject': 5.0.5(rollup@4.18.0) @@ -7266,8 +7034,6 @@ snapshots: y18n@5.0.8: {} - yallist@3.1.1: {} - yallist@4.0.0: {} yaml@2.4.5: {} diff --git a/rsbuild.config.ts b/rsbuild.config.ts new file mode 100644 index 00000000..b4b29017 --- /dev/null +++ b/rsbuild.config.ts @@ -0,0 +1,27 @@ +import { defineConfig } from "@rsbuild/core"; +import { pluginReact } from "@rsbuild/plugin-react"; +import { execSync } from "node:child_process"; + +let hash = ""; + +try { + hash = execSync("git rev-parse --short HEAD").toString().trim(); +} catch (error) { + hash = "DEV"; +} + +export default defineConfig({ + plugins: [pluginReact()], + source: { + define: { + "process.env.COMMIT_HASH": JSON.stringify(hash), + }, + alias: { + "@app": "./src", + "@pages": "./src/pages", + "@components": "./src/components", + "@core": "./src/core", + "@layouts": "./src/layouts", + }, + }, +}); diff --git a/src/App.tsx b/src/App.tsx index 48a806f4..c936bc9c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,15 +1,15 @@ -import { DeviceWrapper } from "@app/DeviceWrapper.js"; -import { PageRouter } from "@app/PageRouter.js"; -import { CommandPalette } from "@components/CommandPalette.js"; -import { DeviceSelector } from "@components/DeviceSelector.js"; -import { DialogManager } from "@components/Dialog/DialogManager.js"; -import { NewDeviceDialog } from "@components/Dialog/NewDeviceDialog.js"; -import { Toaster } from "@components/Toaster.js"; -import Footer from "@components/UI/Footer.js"; -import { ThemeController } from "@components/generic/ThemeController.js"; -import { useAppStore } from "@core/stores/appStore.js"; -import { useDeviceStore } from "@core/stores/deviceStore.js"; -import { Dashboard } from "@pages/Dashboard/index.js"; +import { DeviceWrapper } from "@app/DeviceWrapper.tsx"; +import { PageRouter } from "@app/PageRouter.tsx"; +import { CommandPalette } from "@components/CommandPalette.tsx"; +import { DeviceSelector } from "@components/DeviceSelector.tsx"; +import { DialogManager } from "@components/Dialog/DialogManager.tsx"; +import { NewDeviceDialog } from "@components/Dialog/NewDeviceDialog.tsx"; +import { Toaster } from "@components/Toaster.tsx"; +import Footer from "@components/UI/Footer.tsx"; +import { ThemeController } from "@components/generic/ThemeController.tsx"; +import { useAppStore } from "@core/stores/appStore.ts"; +import { useDeviceStore } from "@core/stores/deviceStore.ts"; +import { Dashboard } from "@pages/Dashboard/index.tsx"; import { MapProvider } from "react-map-gl"; export const App = (): JSX.Element => { diff --git a/src/DeviceWrapper.tsx b/src/DeviceWrapper.tsx index 9384c303..d2420352 100644 --- a/src/DeviceWrapper.tsx +++ b/src/DeviceWrapper.tsx @@ -1,5 +1,5 @@ -import { DeviceContext } from "@core/stores/deviceStore.js"; -import type { Device } from "@core/stores/deviceStore.js"; +import { DeviceContext } from "@core/stores/deviceStore.ts"; +import type { Device } from "@core/stores/deviceStore.ts"; import type { ReactNode } from "react"; export interface DeviceWrapperProps { diff --git a/src/PageRouter.tsx b/src/PageRouter.tsx index 1fe44dfa..2bec9fdc 100644 --- a/src/PageRouter.tsx +++ b/src/PageRouter.tsx @@ -1,9 +1,9 @@ -import { useDevice } from "@core/stores/deviceStore.js"; -import { ChannelsPage } from "@pages/Channels.js"; -import { ConfigPage } from "@pages/Config/index.js"; -import { MapPage } from "@pages/Map.js"; -import { MessagesPage } from "@pages/Messages.js"; -import { NodesPage } from "@pages/Nodes.js"; +import { useDevice } from "@core/stores/deviceStore.ts"; +import { ChannelsPage } from "@pages/Channels.tsx"; +import { ConfigPage } from "@pages/Config/index.tsx"; +import { MapPage } from "@pages/Map.tsx"; +import { MessagesPage } from "@pages/Messages.tsx"; +import { NodesPage } from "@pages/Nodes.tsx"; export const PageRouter = (): JSX.Element => { const { activePage } = useDevice(); diff --git a/src/components/CommandPalette.tsx b/src/components/CommandPalette.tsx index 2062b740..efcd3954 100644 --- a/src/components/CommandPalette.tsx +++ b/src/components/CommandPalette.tsx @@ -5,9 +5,9 @@ import { CommandInput, CommandItem, CommandList, -} from "@components/UI/Command.js"; -import { useAppStore } from "@core/stores/appStore.js"; -import { useDevice, useDeviceStore } from "@core/stores/deviceStore.js"; +} from "@components/UI/Command.tsx"; +import { useAppStore } from "@core/stores/appStore.ts"; +import { useDevice, useDeviceStore } from "@core/stores/deviceStore.ts"; import { Hashicon } from "@emeraldpay/hashicon-react"; import { useCommandState } from "cmdk"; import { diff --git a/src/components/DeviceSelector.tsx b/src/components/DeviceSelector.tsx index 1465c52b..42be3ecf 100644 --- a/src/components/DeviceSelector.tsx +++ b/src/components/DeviceSelector.tsx @@ -1,8 +1,8 @@ -import { DeviceSelectorButton } from "@components/DeviceSelectorButton.js"; -import { Separator } from "@components/UI/Seperator.js"; -import { Code } from "@components/UI/Typography/Code.js"; -import { useAppStore } from "@core/stores/appStore.js"; -import { useDeviceStore } from "@core/stores/deviceStore.js"; +import { DeviceSelectorButton } from "@components/DeviceSelectorButton.tsx"; +import { Separator } from "@components/UI/Seperator.tsx"; +import { Code } from "@components/UI/Typography/Code.tsx"; +import { useAppStore } from "@core/stores/appStore.ts"; +import { useDeviceStore } from "@core/stores/deviceStore.ts"; import { Hashicon } from "@emeraldpay/hashicon-react"; import { HomeIcon, diff --git a/src/components/Dialog/DeviceNameDialog.tsx b/src/components/Dialog/DeviceNameDialog.tsx index 403f4e8c..3fd6fd2b 100644 --- a/src/components/Dialog/DeviceNameDialog.tsx +++ b/src/components/Dialog/DeviceNameDialog.tsx @@ -1,5 +1,5 @@ -import { useDevice } from "@app/core/stores/deviceStore.js"; -import { Button } from "@components/UI/Button.js"; +import { useDevice } from "@app/core/stores/deviceStore.ts"; +import { Button } from "@components/UI/Button.tsx"; import { Dialog, DialogContent, @@ -7,9 +7,9 @@ import { DialogFooter, DialogHeader, DialogTitle, -} from "@components/UI/Dialog.js"; -import { Input } from "@components/UI/Input.js"; -import { Label } from "@components/UI/Label.js"; +} from "@components/UI/Dialog.tsx"; +import { Input } from "@components/UI/Input.tsx"; +import { Label } from "@components/UI/Label.tsx"; import { Protobuf } from "@meshtastic/js"; import { useForm } from "react-hook-form"; diff --git a/src/components/Dialog/DialogManager.tsx b/src/components/Dialog/DialogManager.tsx index 53e2509b..16e60120 100644 --- a/src/components/Dialog/DialogManager.tsx +++ b/src/components/Dialog/DialogManager.tsx @@ -1,10 +1,10 @@ -import { RemoveNodeDialog } from "@app/components/Dialog/RemoveNodeDialog.js"; -import { DeviceNameDialog } from "@components/Dialog/DeviceNameDialog.js"; -import { ImportDialog } from "@components/Dialog/ImportDialog.js"; -import { QRDialog } from "@components/Dialog/QRDialog.js"; -import { RebootDialog } from "@components/Dialog/RebootDialog.js"; -import { ShutdownDialog } from "@components/Dialog/ShutdownDialog.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import { RemoveNodeDialog } from "@app/components/Dialog/RemoveNodeDialog.tsx"; +import { DeviceNameDialog } from "@components/Dialog/DeviceNameDialog.tsx"; +import { ImportDialog } from "@components/Dialog/ImportDialog.tsx"; +import { QRDialog } from "@components/Dialog/QRDialog.tsx"; +import { RebootDialog } from "@components/Dialog/RebootDialog.tsx"; +import { ShutdownDialog } from "@components/Dialog/ShutdownDialog.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; export const DialogManager = (): JSX.Element => { const { channels, config, dialog, setDialogOpen } = useDevice(); diff --git a/src/components/Dialog/ImportDialog.tsx b/src/components/Dialog/ImportDialog.tsx index 37327096..c331911b 100644 --- a/src/components/Dialog/ImportDialog.tsx +++ b/src/components/Dialog/ImportDialog.tsx @@ -1,5 +1,5 @@ -import { Button } from "@components/UI/Button.js"; -import { Checkbox } from "@components/UI/Checkbox.js"; +import { Button } from "@components/UI/Button.tsx"; +import { Checkbox } from "@components/UI/Checkbox.tsx"; import { Dialog, DialogContent, @@ -7,11 +7,11 @@ import { DialogFooter, DialogHeader, DialogTitle, -} from "@components/UI/Dialog.js"; -import { Input } from "@components/UI/Input.js"; -import { Label } from "@components/UI/Label.js"; -import { Switch } from "@components/UI/Switch.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +} from "@components/UI/Dialog.tsx"; +import { Input } from "@components/UI/Input.tsx"; +import { Label } from "@components/UI/Label.tsx"; +import { Switch } from "@components/UI/Switch.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; import { toByteArray } from "base64-js"; import { useEffect, useState } from "react"; diff --git a/src/components/Dialog/NewDeviceDialog.tsx b/src/components/Dialog/NewDeviceDialog.tsx index d3277884..6062d69c 100644 --- a/src/components/Dialog/NewDeviceDialog.tsx +++ b/src/components/Dialog/NewDeviceDialog.tsx @@ -1,20 +1,20 @@ -import { BLE } from "@components/PageComponents/Connect/BLE.js"; -import { HTTP } from "@components/PageComponents/Connect/HTTP.js"; -import { Serial } from "@components/PageComponents/Connect/Serial.js"; +import { BLE } from "@components/PageComponents/Connect/BLE.tsx"; +import { HTTP } from "@components/PageComponents/Connect/HTTP.tsx"; +import { Serial } from "@components/PageComponents/Connect/Serial.tsx"; import { Dialog, DialogContent, DialogHeader, DialogTitle, -} from "@components/UI/Dialog.js"; +} from "@components/UI/Dialog.tsx"; import { Tabs, TabsContent, TabsList, TabsTrigger, -} from "@components/UI/Tabs.js"; -import { Link } from "@components/UI/Typography/Link.js"; -import { Subtle } from "@components/UI/Typography/Subtle.js"; +} from "@components/UI/Tabs.tsx"; +import { Link } from "@components/UI/Typography/Link.tsx"; +import { Subtle } from "@components/UI/Typography/Subtle.tsx"; export interface TabElementProps { closeDialog: () => void; diff --git a/src/components/Dialog/PkiRegenerateDialog.tsx b/src/components/Dialog/PkiRegenerateDialog.tsx index 3edc221a..818e7207 100644 --- a/src/components/Dialog/PkiRegenerateDialog.tsx +++ b/src/components/Dialog/PkiRegenerateDialog.tsx @@ -1,4 +1,4 @@ -import { Button } from "@components/UI/Button.js"; +import { Button } from "@components/UI/Button.tsx"; import { Dialog, DialogContent, @@ -6,7 +6,7 @@ import { DialogFooter, DialogHeader, DialogTitle, -} from "@components/UI/Dialog.js"; +} from "@components/UI/Dialog.tsx"; export interface PkiRegenerateDialogProps { open: boolean; diff --git a/src/components/Dialog/QRDialog.tsx b/src/components/Dialog/QRDialog.tsx index 9a8853fa..9bd29ed2 100644 --- a/src/components/Dialog/QRDialog.tsx +++ b/src/components/Dialog/QRDialog.tsx @@ -1,4 +1,4 @@ -import { Checkbox } from "@components/UI/Checkbox.js"; +import { Checkbox } from "@components/UI/Checkbox.tsx"; import { Dialog, DialogContent, @@ -6,9 +6,9 @@ import { DialogFooter, DialogHeader, DialogTitle, -} from "@components/UI/Dialog.js"; -import { Input } from "@components/UI/Input.js"; -import { Label } from "@components/UI/Label.js"; +} from "@components/UI/Dialog.tsx"; +import { Input } from "@components/UI/Input.tsx"; +import { Label } from "@components/UI/Label.tsx"; import { Protobuf, type Types } from "@meshtastic/js"; import { fromByteArray } from "base64-js"; import { ClipboardIcon } from "lucide-react"; diff --git a/src/components/Dialog/RebootDialog.tsx b/src/components/Dialog/RebootDialog.tsx index 79ff86bd..3dbcb268 100644 --- a/src/components/Dialog/RebootDialog.tsx +++ b/src/components/Dialog/RebootDialog.tsx @@ -1,13 +1,13 @@ -import { Button } from "@components/UI/Button.js"; +import { Button } from "@components/UI/Button.tsx"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, -} from "@components/UI/Dialog.js"; -import { Input } from "@components/UI/Input.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +} from "@components/UI/Dialog.tsx"; +import { Input } from "@components/UI/Input.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { ClockIcon, RefreshCwIcon } from "lucide-react"; import { useState } from "react"; diff --git a/src/components/Dialog/RemoveNodeDialog.tsx b/src/components/Dialog/RemoveNodeDialog.tsx index b2db5d56..81bfcd04 100644 --- a/src/components/Dialog/RemoveNodeDialog.tsx +++ b/src/components/Dialog/RemoveNodeDialog.tsx @@ -1,6 +1,6 @@ import { useAppStore } from "@app/core/stores/appStore"; -import { useDevice } from "@app/core/stores/deviceStore.js"; -import { Button } from "@components/UI/Button.js"; +import { useDevice } from "@app/core/stores/deviceStore.ts"; +import { Button } from "@components/UI/Button.tsx"; import { Dialog, DialogContent, @@ -8,8 +8,8 @@ import { DialogFooter, DialogHeader, DialogTitle, -} from "@components/UI/Dialog.js"; -import { Label } from "@components/UI/Label.js"; +} from "@components/UI/Dialog.tsx"; +import { Label } from "@components/UI/Label.tsx"; export interface RemoveNodeDialogProps { open: boolean; diff --git a/src/components/Dialog/ShutdownDialog.tsx b/src/components/Dialog/ShutdownDialog.tsx index cf34fee5..deb77770 100644 --- a/src/components/Dialog/ShutdownDialog.tsx +++ b/src/components/Dialog/ShutdownDialog.tsx @@ -1,13 +1,13 @@ -import { Button } from "@components/UI/Button.js"; +import { Button } from "@components/UI/Button.tsx"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, -} from "@components/UI/Dialog.js"; -import { Input } from "@components/UI/Input.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +} from "@components/UI/Dialog.tsx"; +import { Input } from "@components/UI/Input.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { ClockIcon, PowerIcon } from "lucide-react"; import { useState } from "react"; diff --git a/src/components/Form/DynamicForm.tsx b/src/components/Form/DynamicForm.tsx index f957e7f0..c9456b40 100644 --- a/src/components/Form/DynamicForm.tsx +++ b/src/components/Form/DynamicForm.tsx @@ -1,11 +1,11 @@ import { DynamicFormField, type FieldProps, -} from "@components/Form/DynamicFormField.js"; -import { FieldWrapper } from "@components/Form/FormWrapper.js"; -import { Button } from "@components/UI/Button.js"; -import { H4 } from "@components/UI/Typography/H4.js"; -import { Subtle } from "@components/UI/Typography/Subtle.js"; +} from "@components/Form/DynamicFormField.tsx"; +import { FieldWrapper } from "@components/Form/FormWrapper.tsx"; +import { Button } from "@components/UI/Button.tsx"; +import { H4 } from "@components/UI/Typography/H4.tsx"; +import { Subtle } from "@components/UI/Typography/Subtle.tsx"; import { type Control, type DefaultValues, diff --git a/src/components/Form/DynamicFormField.tsx b/src/components/Form/DynamicFormField.tsx index f66b24c1..388836d4 100644 --- a/src/components/Form/DynamicFormField.tsx +++ b/src/components/Form/DynamicFormField.tsx @@ -1,19 +1,19 @@ import { GenericInput, type InputFieldProps, -} from "@components/Form/FormInput.js"; +} from "@components/Form/FormInput.tsx"; import { PasswordGenerator, type PasswordGeneratorProps, -} from "@components/Form/FormPasswordGenerator.js"; +} from "@components/Form/FormPasswordGenerator.tsx"; import { type SelectFieldProps, SelectInput, -} from "@components/Form/FormSelect.js"; +} from "@components/Form/FormSelect.tsx"; import { type ToggleFieldProps, ToggleInput, -} from "@components/Form/FormToggle.js"; +} from "@components/Form/FormToggle.tsx"; import type { Control, FieldValues } from "react-hook-form"; export type FieldProps = diff --git a/src/components/Form/FormInput.tsx b/src/components/Form/FormInput.tsx index c4e5001d..5fef2794 100644 --- a/src/components/Form/FormInput.tsx +++ b/src/components/Form/FormInput.tsx @@ -1,8 +1,8 @@ import type { BaseFormBuilderProps, GenericFormElementProps, -} from "@components/Form/DynamicForm.js"; -import { Input } from "@components/UI/Input.js"; +} from "@components/Form/DynamicForm.tsx"; +import { Input } from "@components/UI/Input.tsx"; import type { LucideIcon } from "lucide-react"; import { Eye, EyeOff } from "lucide-react"; import type { ChangeEventHandler } from "react"; diff --git a/src/components/Form/FormPasswordGenerator.tsx b/src/components/Form/FormPasswordGenerator.tsx index 31bb2f1c..09e97ff5 100644 --- a/src/components/Form/FormPasswordGenerator.tsx +++ b/src/components/Form/FormPasswordGenerator.tsx @@ -1,8 +1,8 @@ import type { BaseFormBuilderProps, GenericFormElementProps, -} from "@components/Form/DynamicForm.js"; -import { Generator } from "@components/UI/Generator.js"; +} from "@components/Form/DynamicForm.tsx"; +import { Generator } from "@components/UI/Generator.tsx"; import { Eye, EyeOff } from "lucide-react"; import type { ChangeEventHandler, MouseEventHandler } from "react"; import { useState } from "react"; diff --git a/src/components/Form/FormSelect.tsx b/src/components/Form/FormSelect.tsx index b0c922d5..a97e390f 100644 --- a/src/components/Form/FormSelect.tsx +++ b/src/components/Form/FormSelect.tsx @@ -1,14 +1,14 @@ import type { BaseFormBuilderProps, GenericFormElementProps, -} from "@components/Form/DynamicForm.js"; +} from "@components/Form/DynamicForm.tsx"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from "@components/UI/Select.js"; +} from "@components/UI/Select.tsx"; import { Controller, type FieldValues } from "react-hook-form"; export interface SelectFieldProps extends BaseFormBuilderProps { diff --git a/src/components/Form/FormToggle.tsx b/src/components/Form/FormToggle.tsx index 7a461147..e79bac76 100644 --- a/src/components/Form/FormToggle.tsx +++ b/src/components/Form/FormToggle.tsx @@ -1,8 +1,8 @@ import type { BaseFormBuilderProps, GenericFormElementProps, -} from "@components/Form/DynamicForm.js"; -import { Switch } from "@components/UI/Switch.js"; +} from "@components/Form/DynamicForm.tsx"; +import { Switch } from "@components/UI/Switch.tsx"; import type { ChangeEvent } from "react"; import { Controller, type FieldValues } from "react-hook-form"; diff --git a/src/components/Form/FormWrapper.tsx b/src/components/Form/FormWrapper.tsx index 87f8aec2..7625946f 100644 --- a/src/components/Form/FormWrapper.tsx +++ b/src/components/Form/FormWrapper.tsx @@ -1,4 +1,4 @@ -import { Label } from "@components/UI/Label.js"; +import { Label } from "@components/UI/Label.tsx"; export interface FieldWrapperProps { label: string; diff --git a/src/components/PageComponents/Channel.tsx b/src/components/PageComponents/Channel.tsx index c89aad57..3655e5f8 100644 --- a/src/components/PageComponents/Channel.tsx +++ b/src/components/PageComponents/Channel.tsx @@ -1,7 +1,7 @@ -import type { ChannelValidation } from "@app/validation/channel.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; -import { useToast } from "@core/hooks/useToast.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { ChannelValidation } from "@app/validation/channel.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; +import { useToast } from "@core/hooks/useToast.ts"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; import { fromByteArray, toByteArray } from "base64-js"; import cryptoRandomString from "crypto-random-string"; diff --git a/src/components/PageComponents/Config/Bluetooth.tsx b/src/components/PageComponents/Config/Bluetooth.tsx index 797a6dff..006fab47 100644 --- a/src/components/PageComponents/Config/Bluetooth.tsx +++ b/src/components/PageComponents/Config/Bluetooth.tsx @@ -1,6 +1,6 @@ -import type { BluetoothValidation } from "@app/validation/config/bluetooth.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { BluetoothValidation } from "@app/validation/config/bluetooth.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; export const Bluetooth = (): JSX.Element => { diff --git a/src/components/PageComponents/Config/Device.tsx b/src/components/PageComponents/Config/Device.tsx index e6df723d..d0e1f029 100644 --- a/src/components/PageComponents/Config/Device.tsx +++ b/src/components/PageComponents/Config/Device.tsx @@ -1,6 +1,6 @@ -import type { DeviceValidation } from "@app/validation/config/device.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { DeviceValidation } from "@app/validation/config/device.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; export const Device = (): JSX.Element => { diff --git a/src/components/PageComponents/Config/Display.tsx b/src/components/PageComponents/Config/Display.tsx index 5d0d0813..5589a8a7 100644 --- a/src/components/PageComponents/Config/Display.tsx +++ b/src/components/PageComponents/Config/Display.tsx @@ -1,6 +1,6 @@ -import type { DisplayValidation } from "@app/validation/config/display.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { DisplayValidation } from "@app/validation/config/display.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; export const Display = (): JSX.Element => { diff --git a/src/components/PageComponents/Config/LoRa.tsx b/src/components/PageComponents/Config/LoRa.tsx index 79e7b810..80665f6c 100644 --- a/src/components/PageComponents/Config/LoRa.tsx +++ b/src/components/PageComponents/Config/LoRa.tsx @@ -1,6 +1,6 @@ -import type { LoRaValidation } from "@app/validation/config/lora.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { LoRaValidation } from "@app/validation/config/lora.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; export const LoRa = (): JSX.Element => { diff --git a/src/components/PageComponents/Config/Network.tsx b/src/components/PageComponents/Config/Network.tsx index 00426104..afda4eb5 100644 --- a/src/components/PageComponents/Config/Network.tsx +++ b/src/components/PageComponents/Config/Network.tsx @@ -1,10 +1,10 @@ -import type { NetworkValidation } from "@app/validation/config/network.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { NetworkValidation } from "@app/validation/config/network.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { convertIntToIpAddress, convertIpAddressToInt, -} from "@core/utils/ip.js"; +} from "@core/utils/ip.ts"; import { Protobuf } from "@meshtastic/js"; export const Network = (): JSX.Element => { diff --git a/src/components/PageComponents/Config/Position.tsx b/src/components/PageComponents/Config/Position.tsx index 531750e9..798da2e9 100644 --- a/src/components/PageComponents/Config/Position.tsx +++ b/src/components/PageComponents/Config/Position.tsx @@ -1,6 +1,6 @@ -import type { PositionValidation } from "@app/validation/config/position.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { PositionValidation } from "@app/validation/config/position.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; export const Position = (): JSX.Element => { diff --git a/src/components/PageComponents/Config/Power.tsx b/src/components/PageComponents/Config/Power.tsx index 0cf685a3..4f52be5f 100644 --- a/src/components/PageComponents/Config/Power.tsx +++ b/src/components/PageComponents/Config/Power.tsx @@ -1,6 +1,6 @@ -import type { PowerValidation } from "@app/validation/config/power.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { PowerValidation } from "@app/validation/config/power.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; export const Power = (): JSX.Element => { diff --git a/src/components/PageComponents/Config/Security.tsx b/src/components/PageComponents/Config/Security.tsx index 9848f491..7baaebda 100644 --- a/src/components/PageComponents/Config/Security.tsx +++ b/src/components/PageComponents/Config/Security.tsx @@ -1,11 +1,11 @@ import { PkiRegenerateDialog } from "@app/components/Dialog/PkiRegenerateDialog"; -import { DynamicForm } from "@app/components/Form/DynamicForm.js"; +import { DynamicForm } from "@app/components/Form/DynamicForm.tsx"; import { getX25519PrivateKey, getX25519PublicKey, } from "@app/core/utils/x25519"; -import type { SecurityValidation } from "@app/validation/config/security.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { SecurityValidation } from "@app/validation/config/security.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; import { fromByteArray, toByteArray } from "base64-js"; import { Eye, EyeOff } from "lucide-react"; diff --git a/src/components/PageComponents/Connect/BLE.tsx b/src/components/PageComponents/Connect/BLE.tsx index 0d78b4e1..82d8dd41 100644 --- a/src/components/PageComponents/Connect/BLE.tsx +++ b/src/components/PageComponents/Connect/BLE.tsx @@ -1,10 +1,10 @@ import type { TabElementProps } from "@app/components/Dialog/NewDeviceDialog"; -import { Button } from "@components/UI/Button.js"; -import { Mono } from "@components/generic/Mono.js"; -import { useAppStore } from "@core/stores/appStore.js"; -import { useDeviceStore } from "@core/stores/deviceStore.js"; -import { subscribeAll } from "@core/subscriptions.js"; -import { randId } from "@core/utils/randId.js"; +import { Button } from "@components/UI/Button.tsx"; +import { Mono } from "@components/generic/Mono.tsx"; +import { useAppStore } from "@core/stores/appStore.ts"; +import { useDeviceStore } from "@core/stores/deviceStore.ts"; +import { subscribeAll } from "@core/subscriptions.ts"; +import { randId } from "@core/utils/randId.ts"; import { BleConnection, Constants } from "@meshtastic/js"; import { useCallback, useEffect, useState } from "react"; diff --git a/src/components/PageComponents/Connect/HTTP.tsx b/src/components/PageComponents/Connect/HTTP.tsx index 1eb7ca5c..de776327 100644 --- a/src/components/PageComponents/Connect/HTTP.tsx +++ b/src/components/PageComponents/Connect/HTTP.tsx @@ -1,12 +1,12 @@ import type { TabElementProps } from "@app/components/Dialog/NewDeviceDialog"; -import { Button } from "@components/UI/Button.js"; -import { Input } from "@components/UI/Input.js"; -import { Label } from "@components/UI/Label.js"; -import { Switch } from "@components/UI/Switch.js"; -import { useAppStore } from "@core/stores/appStore.js"; -import { useDeviceStore } from "@core/stores/deviceStore.js"; -import { subscribeAll } from "@core/subscriptions.js"; -import { randId } from "@core/utils/randId.js"; +import { Button } from "@components/UI/Button.tsx"; +import { Input } from "@components/UI/Input.tsx"; +import { Label } from "@components/UI/Label.tsx"; +import { Switch } from "@components/UI/Switch.tsx"; +import { useAppStore } from "@core/stores/appStore.ts"; +import { useDeviceStore } from "@core/stores/deviceStore.ts"; +import { subscribeAll } from "@core/subscriptions.ts"; +import { randId } from "@core/utils/randId.ts"; import { HttpConnection } from "@meshtastic/js"; import { useState } from "react"; import { Controller, useForm, useWatch } from "react-hook-form"; diff --git a/src/components/PageComponents/Connect/Serial.tsx b/src/components/PageComponents/Connect/Serial.tsx index 68ba70d6..38aab8fb 100644 --- a/src/components/PageComponents/Connect/Serial.tsx +++ b/src/components/PageComponents/Connect/Serial.tsx @@ -1,10 +1,10 @@ import type { TabElementProps } from "@app/components/Dialog/NewDeviceDialog"; -import { Button } from "@components/UI/Button.js"; -import { Mono } from "@components/generic/Mono.js"; -import { useAppStore } from "@core/stores/appStore.js"; -import { useDeviceStore } from "@core/stores/deviceStore.js"; -import { subscribeAll } from "@core/subscriptions.js"; -import { randId } from "@core/utils/randId.js"; +import { Button } from "@components/UI/Button.tsx"; +import { Mono } from "@components/generic/Mono.tsx"; +import { useAppStore } from "@core/stores/appStore.ts"; +import { useDeviceStore } from "@core/stores/deviceStore.ts"; +import { subscribeAll } from "@core/subscriptions.ts"; +import { randId } from "@core/utils/randId.ts"; import { SerialConnection } from "@meshtastic/js"; import { useCallback, useEffect, useState } from "react"; diff --git a/src/components/PageComponents/Messages/ChannelChat.tsx b/src/components/PageComponents/Messages/ChannelChat.tsx index 7507de97..79950bf8 100644 --- a/src/components/PageComponents/Messages/ChannelChat.tsx +++ b/src/components/PageComponents/Messages/ChannelChat.tsx @@ -1,11 +1,11 @@ -import { Subtle } from "@app/components/UI/Typography/Subtle.js"; +import { Subtle } from "@app/components/UI/Typography/Subtle.tsx"; import { type MessageWithState, useDevice, -} from "@app/core/stores/deviceStore.js"; -import { Message } from "@components/PageComponents/Messages/Message.js"; -import { MessageInput } from "@components/PageComponents/Messages/MessageInput.js"; -import { TraceRoute } from "@components/PageComponents/Messages/TraceRoute.js"; +} from "@app/core/stores/deviceStore.ts"; +import { Message } from "@components/PageComponents/Messages/Message.tsx"; +import { MessageInput } from "@components/PageComponents/Messages/MessageInput.tsx"; +import { TraceRoute } from "@components/PageComponents/Messages/TraceRoute.tsx"; import type { Protobuf, Types } from "@meshtastic/js"; import { InboxIcon } from "lucide-react"; diff --git a/src/components/PageComponents/Messages/Message.tsx b/src/components/PageComponents/Messages/Message.tsx index b958b803..d1d35784 100644 --- a/src/components/PageComponents/Messages/Message.tsx +++ b/src/components/PageComponents/Messages/Message.tsx @@ -1,4 +1,4 @@ -import type { MessageWithState } from "@app/core/stores/deviceStore.js"; +import type { MessageWithState } from "@app/core/stores/deviceStore.ts"; import { Hashicon } from "@emeraldpay/hashicon-react"; import type { Protobuf } from "@meshtastic/js"; import { diff --git a/src/components/PageComponents/Messages/MessageInput.tsx b/src/components/PageComponents/Messages/MessageInput.tsx index 42a7fb6a..9a7c865a 100644 --- a/src/components/PageComponents/Messages/MessageInput.tsx +++ b/src/components/PageComponents/Messages/MessageInput.tsx @@ -1,6 +1,6 @@ -import { Button } from "@components/UI/Button.js"; -import { Input } from "@components/UI/Input.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import { Button } from "@components/UI/Button.tsx"; +import { Input } from "@components/UI/Input.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import type { Types } from "@meshtastic/js"; import { SendIcon } from "lucide-react"; diff --git a/src/components/PageComponents/Messages/TraceRoute.tsx b/src/components/PageComponents/Messages/TraceRoute.tsx index e2adf30e..5b768580 100644 --- a/src/components/PageComponents/Messages/TraceRoute.tsx +++ b/src/components/PageComponents/Messages/TraceRoute.tsx @@ -1,4 +1,4 @@ -import { useDevice } from "@app/core/stores/deviceStore.js"; +import { useDevice } from "@app/core/stores/deviceStore.ts"; import type { Protobuf } from "@meshtastic/js"; import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; diff --git a/src/components/PageComponents/ModuleConfig/AmbientLighting.tsx b/src/components/PageComponents/ModuleConfig/AmbientLighting.tsx index 275487a8..978ef633 100644 --- a/src/components/PageComponents/ModuleConfig/AmbientLighting.tsx +++ b/src/components/PageComponents/ModuleConfig/AmbientLighting.tsx @@ -1,6 +1,6 @@ -import { useDevice } from "@app/core/stores/deviceStore.js"; -import type { AmbientLightingValidation } from "@app/validation/moduleConfig/ambientLighting.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; +import { useDevice } from "@app/core/stores/deviceStore.ts"; +import type { AmbientLightingValidation } from "@app/validation/moduleConfig/ambientLighting.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { Protobuf } from "@meshtastic/js"; export const AmbientLighting = (): JSX.Element => { diff --git a/src/components/PageComponents/ModuleConfig/Audio.tsx b/src/components/PageComponents/ModuleConfig/Audio.tsx index 807218fb..c68bd9bf 100644 --- a/src/components/PageComponents/ModuleConfig/Audio.tsx +++ b/src/components/PageComponents/ModuleConfig/Audio.tsx @@ -1,6 +1,6 @@ -import type { AudioValidation } from "@app/validation/moduleConfig/audio.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { AudioValidation } from "@app/validation/moduleConfig/audio.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; export const Audio = (): JSX.Element => { diff --git a/src/components/PageComponents/ModuleConfig/CannedMessage.tsx b/src/components/PageComponents/ModuleConfig/CannedMessage.tsx index 1ab3da31..5fb1046a 100644 --- a/src/components/PageComponents/ModuleConfig/CannedMessage.tsx +++ b/src/components/PageComponents/ModuleConfig/CannedMessage.tsx @@ -1,6 +1,6 @@ -import type { CannedMessageValidation } from "@app/validation/moduleConfig/cannedMessage.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { CannedMessageValidation } from "@app/validation/moduleConfig/cannedMessage.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; export const CannedMessage = (): JSX.Element => { diff --git a/src/components/PageComponents/ModuleConfig/DetectionSensor.tsx b/src/components/PageComponents/ModuleConfig/DetectionSensor.tsx index 8bf49d42..c7ec3c98 100644 --- a/src/components/PageComponents/ModuleConfig/DetectionSensor.tsx +++ b/src/components/PageComponents/ModuleConfig/DetectionSensor.tsx @@ -1,6 +1,6 @@ -import { useDevice } from "@app/core/stores/deviceStore.js"; -import type { DetectionSensorValidation } from "@app/validation/moduleConfig/detectionSensor.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; +import { useDevice } from "@app/core/stores/deviceStore.ts"; +import type { DetectionSensorValidation } from "@app/validation/moduleConfig/detectionSensor.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { Protobuf } from "@meshtastic/js"; export const DetectionSensor = (): JSX.Element => { diff --git a/src/components/PageComponents/ModuleConfig/ExternalNotification.tsx b/src/components/PageComponents/ModuleConfig/ExternalNotification.tsx index 44abc6c1..f6b52662 100644 --- a/src/components/PageComponents/ModuleConfig/ExternalNotification.tsx +++ b/src/components/PageComponents/ModuleConfig/ExternalNotification.tsx @@ -1,6 +1,6 @@ -import type { ExternalNotificationValidation } from "@app/validation/moduleConfig/externalNotification.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { ExternalNotificationValidation } from "@app/validation/moduleConfig/externalNotification.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; export const ExternalNotification = (): JSX.Element => { diff --git a/src/components/PageComponents/ModuleConfig/MQTT.tsx b/src/components/PageComponents/ModuleConfig/MQTT.tsx index 1dc324bb..bb9b1527 100644 --- a/src/components/PageComponents/ModuleConfig/MQTT.tsx +++ b/src/components/PageComponents/ModuleConfig/MQTT.tsx @@ -1,6 +1,6 @@ -import { useDevice } from "@app/core/stores/deviceStore.js"; -import type { MqttValidation } from "@app/validation/moduleConfig/mqtt.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; +import { useDevice } from "@app/core/stores/deviceStore.ts"; +import type { MqttValidation } from "@app/validation/moduleConfig/mqtt.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { Protobuf } from "@meshtastic/js"; export const MQTT = (): JSX.Element => { diff --git a/src/components/PageComponents/ModuleConfig/NeighborInfo.tsx b/src/components/PageComponents/ModuleConfig/NeighborInfo.tsx index 596638cc..9c878911 100644 --- a/src/components/PageComponents/ModuleConfig/NeighborInfo.tsx +++ b/src/components/PageComponents/ModuleConfig/NeighborInfo.tsx @@ -1,6 +1,6 @@ -import { useDevice } from "@app/core/stores/deviceStore.js"; -import type { NeighborInfoValidation } from "@app/validation/moduleConfig/neighborInfo.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; +import { useDevice } from "@app/core/stores/deviceStore.ts"; +import type { NeighborInfoValidation } from "@app/validation/moduleConfig/neighborInfo.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { Protobuf } from "@meshtastic/js"; export const NeighborInfo = (): JSX.Element => { diff --git a/src/components/PageComponents/ModuleConfig/Paxcounter.tsx b/src/components/PageComponents/ModuleConfig/Paxcounter.tsx index 42be1b14..e31a298c 100644 --- a/src/components/PageComponents/ModuleConfig/Paxcounter.tsx +++ b/src/components/PageComponents/ModuleConfig/Paxcounter.tsx @@ -1,6 +1,6 @@ -import type { PaxcounterValidation } from "@app/validation/moduleConfig/paxcounter.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { PaxcounterValidation } from "@app/validation/moduleConfig/paxcounter.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; export const Paxcounter = (): JSX.Element => { diff --git a/src/components/PageComponents/ModuleConfig/RangeTest.tsx b/src/components/PageComponents/ModuleConfig/RangeTest.tsx index 4fb2abb8..a661805d 100644 --- a/src/components/PageComponents/ModuleConfig/RangeTest.tsx +++ b/src/components/PageComponents/ModuleConfig/RangeTest.tsx @@ -1,6 +1,6 @@ -import type { RangeTestValidation } from "@app/validation/moduleConfig/rangeTest.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { RangeTestValidation } from "@app/validation/moduleConfig/rangeTest.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; export const RangeTest = (): JSX.Element => { diff --git a/src/components/PageComponents/ModuleConfig/Serial.tsx b/src/components/PageComponents/ModuleConfig/Serial.tsx index 7fd40571..92db0d9a 100644 --- a/src/components/PageComponents/ModuleConfig/Serial.tsx +++ b/src/components/PageComponents/ModuleConfig/Serial.tsx @@ -1,6 +1,6 @@ -import type { SerialValidation } from "@app/validation/moduleConfig/serial.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { SerialValidation } from "@app/validation/moduleConfig/serial.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; export const Serial = (): JSX.Element => { diff --git a/src/components/PageComponents/ModuleConfig/StoreForward.tsx b/src/components/PageComponents/ModuleConfig/StoreForward.tsx index 994f6051..6fe5c585 100644 --- a/src/components/PageComponents/ModuleConfig/StoreForward.tsx +++ b/src/components/PageComponents/ModuleConfig/StoreForward.tsx @@ -1,6 +1,6 @@ -import type { StoreForwardValidation } from "@app/validation/moduleConfig/storeForward.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { StoreForwardValidation } from "@app/validation/moduleConfig/storeForward.ts"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; export const StoreForward = (): JSX.Element => { diff --git a/src/components/PageComponents/ModuleConfig/Telemetry.tsx b/src/components/PageComponents/ModuleConfig/Telemetry.tsx index 5af2f2b5..24074f48 100644 --- a/src/components/PageComponents/ModuleConfig/Telemetry.tsx +++ b/src/components/PageComponents/ModuleConfig/Telemetry.tsx @@ -1,6 +1,6 @@ -import type { TelemetryValidation } from "@app/validation/moduleConfig/telemetry.js"; -import { DynamicForm } from "@components/Form/DynamicForm.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import type { TelemetryValidation } from "@app/validation/moduleConfig/telemetry.tsx"; +import { DynamicForm } from "@components/Form/DynamicForm.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; export const Telemetry = (): JSX.Element => { diff --git a/src/components/PageLayout.tsx b/src/components/PageLayout.tsx index f6698b97..79495bdc 100644 --- a/src/components/PageLayout.tsx +++ b/src/components/PageLayout.tsx @@ -1,4 +1,4 @@ -import { cn } from "@app/core/utils/cn.js"; +import { cn } from "@app/core/utils/cn.ts"; import { AlignLeftIcon, type LucideIcon } from "lucide-react"; import Footer from "./UI/Footer"; diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 116b6647..05f5ef19 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -1,8 +1,8 @@ -import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.js"; -import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.js"; -import { Subtle } from "@components/UI/Typography/Subtle.js"; -import { useDevice } from "@core/stores/deviceStore.js"; -import type { Page } from "@core/stores/deviceStore.js"; +import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.tsx"; +import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.tsx"; +import { Subtle } from "@components/UI/Typography/Subtle.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; +import type { Page } from "@core/stores/deviceStore.ts"; import { BatteryMediumIcon, CpuIcon, diff --git a/src/components/Toaster.tsx b/src/components/Toaster.tsx index 4aa31845..cfe13a9d 100644 --- a/src/components/Toaster.tsx +++ b/src/components/Toaster.tsx @@ -1,4 +1,4 @@ -import { useToast } from "@core/hooks/useToast.js"; +import { useToast } from "@core/hooks/useToast.ts"; import { Toast, @@ -7,7 +7,7 @@ import { ToastProvider, ToastTitle, ToastViewport, -} from "@components/UI/Toast.js"; +} from "@components/UI/Toast.tsx"; export function Toaster() { const { toasts } = useToast(); diff --git a/src/components/UI/Button.tsx b/src/components/UI/Button.tsx index f903f25e..39418551 100644 --- a/src/components/UI/Button.tsx +++ b/src/components/UI/Button.tsx @@ -1,7 +1,7 @@ import { type VariantProps, cva } from "class-variance-authority"; import * as React from "react"; -import { cn } from "@core/utils/cn.js"; +import { cn } from "@core/utils/cn.ts"; const buttonVariants = cva( "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 dark:hover:bg-slate-800 dark:hover:text-slate-100 disabled:opacity-50 dark:focus:ring-slate-400 disabled:pointer-events-none dark:focus:ring-offset-slate-900 data-[state=open]:bg-slate-100 dark:data-[state=open]:bg-slate-800", diff --git a/src/components/UI/Checkbox.tsx b/src/components/UI/Checkbox.tsx index 38bc7873..7c0eaa9b 100644 --- a/src/components/UI/Checkbox.tsx +++ b/src/components/UI/Checkbox.tsx @@ -2,7 +2,7 @@ import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; import { Check } from "lucide-react"; import * as React from "react"; -import { cn } from "@core/utils/cn.js"; +import { cn } from "@core/utils/cn.ts"; const Checkbox = React.forwardRef< React.ElementRef, diff --git a/src/components/UI/Command.tsx b/src/components/UI/Command.tsx index 00c51b61..63d77d28 100644 --- a/src/components/UI/Command.tsx +++ b/src/components/UI/Command.tsx @@ -3,8 +3,8 @@ import { Command as CommandPrimitive } from "cmdk"; import { Search } from "lucide-react"; import * as React from "react"; -import { Dialog, DialogContent } from "@components/UI/Dialog.js"; -import { cn } from "@core/utils/cn.js"; +import { Dialog, DialogContent } from "@components/UI/Dialog.tsx"; +import { cn } from "@core/utils/cn.ts"; const Command = React.forwardRef< React.ElementRef, diff --git a/src/components/UI/Dialog.tsx b/src/components/UI/Dialog.tsx index 5ab0c342..019b8142 100644 --- a/src/components/UI/Dialog.tsx +++ b/src/components/UI/Dialog.tsx @@ -2,7 +2,7 @@ import * as DialogPrimitive from "@radix-ui/react-dialog"; import { X } from "lucide-react"; import * as React from "react"; -import { cn } from "@core/utils/cn.js"; +import { cn } from "@core/utils/cn.ts"; const Dialog = DialogPrimitive.Root; diff --git a/src/components/UI/DropdownMenu.tsx b/src/components/UI/DropdownMenu.tsx index 02f3eb45..26ccdbe9 100644 --- a/src/components/UI/DropdownMenu.tsx +++ b/src/components/UI/DropdownMenu.tsx @@ -2,7 +2,7 @@ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; import { Check, ChevronRight, Circle } from "lucide-react"; import * as React from "react"; -import { cn } from "@core/utils/cn.js"; +import { cn } from "@core/utils/cn.ts"; const DropdownMenu = DropdownMenuPrimitive.Root; diff --git a/src/components/UI/Generator.tsx b/src/components/UI/Generator.tsx index f9db3f89..cb8fea26 100644 --- a/src/components/UI/Generator.tsx +++ b/src/components/UI/Generator.tsx @@ -1,14 +1,14 @@ import * as React from "react"; -import { Button } from "@components/UI/Button.js"; -import { Input } from "@components/UI/Input.js"; +import { Button } from "@components/UI/Button.tsx"; +import { Input } from "@components/UI/Input.tsx"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from "@components/UI/Select.js"; +} from "@components/UI/Select.tsx"; import type { LucideIcon } from "lucide-react"; export interface GeneratorProps extends React.BaseHTMLAttributes { diff --git a/src/components/UI/Input.tsx b/src/components/UI/Input.tsx index 630a6364..a397cef8 100644 --- a/src/components/UI/Input.tsx +++ b/src/components/UI/Input.tsx @@ -1,6 +1,6 @@ import * as React from "react"; -import { cn } from "@core/utils/cn.js"; +import { cn } from "@core/utils/cn.ts"; import { type VariantProps, cva } from "class-variance-authority"; import type { LucideIcon } from "lucide-react"; diff --git a/src/components/UI/Label.tsx b/src/components/UI/Label.tsx index 9cef3b16..cb78b532 100644 --- a/src/components/UI/Label.tsx +++ b/src/components/UI/Label.tsx @@ -1,7 +1,7 @@ import * as LabelPrimitive from "@radix-ui/react-label"; import * as React from "react"; -import { cn } from "@core/utils/cn.js"; +import { cn } from "@core/utils/cn.ts"; const Label = React.forwardRef< React.ElementRef, diff --git a/src/components/UI/Menubar.tsx b/src/components/UI/Menubar.tsx index 729665b3..bcf4d7ae 100644 --- a/src/components/UI/Menubar.tsx +++ b/src/components/UI/Menubar.tsx @@ -2,7 +2,7 @@ import * as MenubarPrimitive from "@radix-ui/react-menubar"; import { Check, ChevronRight, Circle } from "lucide-react"; import * as React from "react"; -import { cn } from "@core/utils/cn.js"; +import { cn } from "@core/utils/cn.ts"; const MenubarMenu = MenubarPrimitive.Menu; diff --git a/src/components/UI/Popover.tsx b/src/components/UI/Popover.tsx index 95641230..43977f2f 100644 --- a/src/components/UI/Popover.tsx +++ b/src/components/UI/Popover.tsx @@ -1,7 +1,7 @@ import * as PopoverPrimitive from "@radix-ui/react-popover"; import * as React from "react"; -import { cn } from "@core/utils/cn.js"; +import { cn } from "@core/utils/cn.ts"; const Popover = PopoverPrimitive.Root; diff --git a/src/components/UI/ScrollArea.tsx b/src/components/UI/ScrollArea.tsx index 44f69e26..7415b64e 100644 --- a/src/components/UI/ScrollArea.tsx +++ b/src/components/UI/ScrollArea.tsx @@ -1,7 +1,7 @@ import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"; import * as React from "react"; -import { cn } from "@core/utils/cn.js"; +import { cn } from "@core/utils/cn.ts"; const ScrollArea = React.forwardRef< React.ElementRef, diff --git a/src/components/UI/Select.tsx b/src/components/UI/Select.tsx index 59f5a9cd..32a0a8cb 100644 --- a/src/components/UI/Select.tsx +++ b/src/components/UI/Select.tsx @@ -2,7 +2,7 @@ import * as SelectPrimitive from "@radix-ui/react-select"; import { Check, ChevronDown } from "lucide-react"; import * as React from "react"; -import { cn } from "@core/utils/cn.js"; +import { cn } from "@core/utils/cn.ts"; const Select = SelectPrimitive.Root; diff --git a/src/components/UI/Seperator.tsx b/src/components/UI/Seperator.tsx index 6b5a9692..20a5511b 100644 --- a/src/components/UI/Seperator.tsx +++ b/src/components/UI/Seperator.tsx @@ -1,7 +1,7 @@ import * as SeparatorPrimitive from "@radix-ui/react-separator"; import * as React from "react"; -import { cn } from "@core/utils/cn.js"; +import { cn } from "@core/utils/cn.ts"; const Separator = React.forwardRef< React.ElementRef, diff --git a/src/components/UI/Sidebar/SidebarSection.tsx b/src/components/UI/Sidebar/SidebarSection.tsx index ed6cb767..acff2dab 100644 --- a/src/components/UI/Sidebar/SidebarSection.tsx +++ b/src/components/UI/Sidebar/SidebarSection.tsx @@ -1,4 +1,4 @@ -import { H4 } from "@components/UI/Typography/H4.js"; +import { H4 } from "@components/UI/Typography/H4.tsx"; export interface SidebarSectionProps { label: string; diff --git a/src/components/UI/Sidebar/sidebarButton.tsx b/src/components/UI/Sidebar/sidebarButton.tsx index ef8f3ff6..c3f30e79 100644 --- a/src/components/UI/Sidebar/sidebarButton.tsx +++ b/src/components/UI/Sidebar/sidebarButton.tsx @@ -1,4 +1,4 @@ -import { Button } from "@components/UI/Button.js"; +import { Button } from "@components/UI/Button.tsx"; import type { LucideIcon } from "lucide-react"; export interface SidebarButtonProps { diff --git a/src/components/UI/Switch.tsx b/src/components/UI/Switch.tsx index c3e97fdc..c395547d 100644 --- a/src/components/UI/Switch.tsx +++ b/src/components/UI/Switch.tsx @@ -1,7 +1,7 @@ import * as SwitchPrimitives from "@radix-ui/react-switch"; import * as React from "react"; -import { cn } from "@core/utils/cn.js"; +import { cn } from "@core/utils/cn.ts"; const Switch = React.forwardRef< React.ElementRef, diff --git a/src/components/UI/Tabs.tsx b/src/components/UI/Tabs.tsx index f8c83e6d..66f126da 100644 --- a/src/components/UI/Tabs.tsx +++ b/src/components/UI/Tabs.tsx @@ -1,7 +1,7 @@ import * as TabsPrimitive from "@radix-ui/react-tabs"; import * as React from "react"; -import { cn } from "@core/utils/cn.js"; +import { cn } from "@core/utils/cn.ts"; const Tabs = TabsPrimitive.Root; diff --git a/src/components/UI/Toast.tsx b/src/components/UI/Toast.tsx index a3080fbf..ca8dfc90 100644 --- a/src/components/UI/Toast.tsx +++ b/src/components/UI/Toast.tsx @@ -3,7 +3,7 @@ import { type VariantProps, cva } from "class-variance-authority"; import { X } from "lucide-react"; import * as React from "react"; -import { cn } from "@core/utils/cn.js"; +import { cn } from "@core/utils/cn.ts"; const ToastProvider = ToastPrimitives.Provider; diff --git a/src/components/UI/Tooltip.tsx b/src/components/UI/Tooltip.tsx index 9cdc8f83..bde4b344 100644 --- a/src/components/UI/Tooltip.tsx +++ b/src/components/UI/Tooltip.tsx @@ -1,7 +1,7 @@ import * as TooltipPrimitive from "@radix-ui/react-tooltip"; import * as React from "react"; -import { cn } from "@core/utils/cn.js"; +import { cn } from "@core/utils/cn.ts"; const TooltipProvider = TooltipPrimitive.Provider; diff --git a/src/components/UI/Typography/H4.tsx b/src/components/UI/Typography/H4.tsx index e2562d6e..33eeb0af 100644 --- a/src/components/UI/Typography/H4.tsx +++ b/src/components/UI/Typography/H4.tsx @@ -1,4 +1,4 @@ -import { cn } from "@app/core/utils/cn.js"; +import { cn } from "@app/core/utils/cn.ts"; export interface H4Props { className?: string; diff --git a/src/components/UI/Typography/Subtle.tsx b/src/components/UI/Typography/Subtle.tsx index 172e37ec..2772453c 100644 --- a/src/components/UI/Typography/Subtle.tsx +++ b/src/components/UI/Typography/Subtle.tsx @@ -1,4 +1,4 @@ -import { cn } from "@app/core/utils/cn.js"; +import { cn } from "@app/core/utils/cn.ts"; export interface SubtleProps { className?: string; diff --git a/src/components/generic/ThemeController.tsx b/src/components/generic/ThemeController.tsx index c1d89ce0..6dec03ad 100644 --- a/src/components/generic/ThemeController.tsx +++ b/src/components/generic/ThemeController.tsx @@ -1,4 +1,4 @@ -import { useAppStore } from "@core/stores/appStore.js"; +import { useAppStore } from "@core/stores/appStore.ts"; import type { ReactNode } from "react"; export interface ThemeControllerProps { diff --git a/src/core/hooks/useToast.ts b/src/core/hooks/useToast.ts index 90b9a1c5..f64cf45a 100644 --- a/src/core/hooks/useToast.ts +++ b/src/core/hooks/useToast.ts @@ -1,6 +1,6 @@ import { type ReactNode, useSyncExternalStore } from "react"; -import type { ToastActionElement, ToastProps } from "@components/UI/Toast.js"; +import type { ToastActionElement, ToastProps } from "@components/UI/Toast.tsx"; const TOAST_LIMIT = 1; const TOAST_REMOVE_DELAY = 1000000; @@ -30,21 +30,21 @@ type ActionType = typeof actionTypes; type Action = | { - type: ActionType["ADD_TOAST"]; - toast: ToasterToast; - } + type: ActionType["ADD_TOAST"]; + toast: ToasterToast; + } | { - type: ActionType["UPDATE_TOAST"]; - toast: Partial; - } + type: ActionType["UPDATE_TOAST"]; + toast: Partial; + } | { - type: ActionType["DISMISS_TOAST"]; - toastId?: ToasterToast["id"]; - } + type: ActionType["DISMISS_TOAST"]; + toastId?: ToasterToast["id"]; + } | { - type: ActionType["REMOVE_TOAST"]; - toastId?: ToasterToast["id"]; - }; + type: ActionType["REMOVE_TOAST"]; + toastId?: ToasterToast["id"]; + }; interface State { toasts: ToasterToast[]; @@ -80,7 +80,7 @@ export const reducer = (state: State, action: Action): State => { return { ...state, toasts: state.toasts.map((t) => - t.id === action.toast.id ? { ...t, ...action.toast } : t, + t.id === action.toast.id ? { ...t, ...action.toast } : t ), }; @@ -102,10 +102,10 @@ export const reducer = (state: State, action: Action): State => { toasts: state.toasts.map((t) => t.id === toastId || toastId === undefined ? { - ...t, - open: false, - } - : t, + ...t, + open: false, + } + : t ), }; } @@ -190,4 +190,4 @@ function useToast() { }; } -export { useToast, toast }; +export { toast, useToast }; diff --git a/src/core/subscriptions.ts b/src/core/subscriptions.ts index 36bc72d2..afba12b7 100644 --- a/src/core/subscriptions.ts +++ b/src/core/subscriptions.ts @@ -1,4 +1,4 @@ -import type { Device } from "@core/stores/deviceStore.js"; +import type { Device } from "@core/stores/deviceStore.ts"; import { Protobuf, type Types } from "@meshtastic/js"; export const subscribeAll = ( diff --git a/src/index.tsx b/src/index.tsx index bbd5808d..33d79536 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -4,7 +4,7 @@ import "maplibre-gl/dist/maplibre-gl.css"; import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; -import { App } from "@app/App.js"; +import { App } from "@app/App.tsx"; const container = document.getElementById("root") as HTMLElement; const root = createRoot(container); diff --git a/src/pages/Channels.tsx b/src/pages/Channels.tsx index 83eefdbd..3468aa0e 100644 --- a/src/pages/Channels.tsx +++ b/src/pages/Channels.tsx @@ -3,11 +3,11 @@ import { TabsContent, TabsList, TabsTrigger, -} from "@app/components/UI/Tabs.js"; -import { Channel } from "@components/PageComponents/Channel.js"; -import { PageLayout } from "@components/PageLayout.js"; -import { Sidebar } from "@components/Sidebar.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +} from "@app/components/UI/Tabs.tsx"; +import { Channel } from "@components/PageComponents/Channel.tsx"; +import { PageLayout } from "@components/PageLayout.tsx"; +import { Sidebar } from "@components/Sidebar.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Types } from "@meshtastic/js"; import type { Protobuf } from "@meshtastic/js"; import { ImportIcon, QrCodeIcon } from "lucide-react"; diff --git a/src/pages/Config/DeviceConfig.tsx b/src/pages/Config/DeviceConfig.tsx index 45973e60..e870b944 100644 --- a/src/pages/Config/DeviceConfig.tsx +++ b/src/pages/Config/DeviceConfig.tsx @@ -1,18 +1,18 @@ -import { Bluetooth } from "@components/PageComponents/Config/Bluetooth.js"; -import { Device } from "@components/PageComponents/Config/Device.js"; -import { Display } from "@components/PageComponents/Config/Display.js"; -import { LoRa } from "@components/PageComponents/Config/LoRa.js"; -import { Network } from "@components/PageComponents/Config/Network.js"; -import { Position } from "@components/PageComponents/Config/Position.js"; -import { Power } from "@components/PageComponents/Config/Power.js"; -import { Security } from "@components/PageComponents/Config/Security.js"; +import { Bluetooth } from "@components/PageComponents/Config/Bluetooth.tsx"; +import { Device } from "@components/PageComponents/Config/Device.tsx"; +import { Display } from "@components/PageComponents/Config/Display.tsx"; +import { LoRa } from "@components/PageComponents/Config/LoRa.tsx"; +import { Network } from "@components/PageComponents/Config/Network.tsx"; +import { Position } from "@components/PageComponents/Config/Position.tsx"; +import { Power } from "@components/PageComponents/Config/Power.tsx"; +import { Security } from "@components/PageComponents/Config/Security.tsx"; import { Tabs, TabsContent, TabsList, TabsTrigger, -} from "@components/UI/Tabs.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +} from "@components/UI/Tabs.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; export const DeviceConfig = (): JSX.Element => { const { metadata } = useDevice(); diff --git a/src/pages/Config/ModuleConfig.tsx b/src/pages/Config/ModuleConfig.tsx index 276553da..95bd3dd1 100644 --- a/src/pages/Config/ModuleConfig.tsx +++ b/src/pages/Config/ModuleConfig.tsx @@ -1,21 +1,21 @@ -import { AmbientLighting } from "@app/components/PageComponents/ModuleConfig/AmbientLighting.js"; -import { DetectionSensor } from "@app/components/PageComponents/ModuleConfig/DetectionSensor.js"; -import { NeighborInfo } from "@app/components/PageComponents/ModuleConfig/NeighborInfo.js"; -import { Audio } from "@components/PageComponents/ModuleConfig/Audio.js"; -import { CannedMessage } from "@components/PageComponents/ModuleConfig/CannedMessage.js"; -import { ExternalNotification } from "@components/PageComponents/ModuleConfig/ExternalNotification.js"; -import { MQTT } from "@components/PageComponents/ModuleConfig/MQTT.js"; -import { Paxcounter } from "@components/PageComponents/ModuleConfig/Paxcounter.js"; -import { RangeTest } from "@components/PageComponents/ModuleConfig/RangeTest.js"; -import { Serial } from "@components/PageComponents/ModuleConfig/Serial.js"; -import { StoreForward } from "@components/PageComponents/ModuleConfig/StoreForward.js"; -import { Telemetry } from "@components/PageComponents/ModuleConfig/Telemetry.js"; +import { AmbientLighting } from "@app/components/PageComponents/ModuleConfig/AmbientLighting.tsx"; +import { DetectionSensor } from "@app/components/PageComponents/ModuleConfig/DetectionSensor.tsx"; +import { NeighborInfo } from "@app/components/PageComponents/ModuleConfig/NeighborInfo.tsx"; +import { Audio } from "@components/PageComponents/ModuleConfig/Audio.tsx"; +import { CannedMessage } from "@components/PageComponents/ModuleConfig/CannedMessage.tsx"; +import { ExternalNotification } from "@components/PageComponents/ModuleConfig/ExternalNotification.tsx"; +import { MQTT } from "@components/PageComponents/ModuleConfig/MQTT.tsx"; +import { Paxcounter } from "@components/PageComponents/ModuleConfig/Paxcounter.tsx"; +import { RangeTest } from "@components/PageComponents/ModuleConfig/RangeTest.tsx"; +import { Serial } from "@components/PageComponents/ModuleConfig/Serial.tsx"; +import { StoreForward } from "@components/PageComponents/ModuleConfig/StoreForward.tsx"; +import { Telemetry } from "@components/PageComponents/ModuleConfig/Telemetry.tsx"; import { Tabs, TabsContent, TabsList, TabsTrigger, -} from "@components/UI/Tabs.js"; +} from "@components/UI/Tabs.tsx"; export const ModuleConfig = (): JSX.Element => { const tabs = [ diff --git a/src/pages/Config/index.tsx b/src/pages/Config/index.tsx index c568670a..c942940c 100644 --- a/src/pages/Config/index.tsx +++ b/src/pages/Config/index.tsx @@ -1,11 +1,11 @@ -import { useDevice } from "@app/core/stores/deviceStore.js"; -import { PageLayout } from "@components/PageLayout.js"; -import { Sidebar } from "@components/Sidebar.js"; -import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.js"; -import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.js"; -import { useToast } from "@core/hooks/useToast.js"; -import { DeviceConfig } from "@pages/Config/DeviceConfig.js"; -import { ModuleConfig } from "@pages/Config/ModuleConfig.js"; +import { useDevice } from "@app/core/stores/deviceStore.ts"; +import { PageLayout } from "@components/PageLayout.tsx"; +import { Sidebar } from "@components/Sidebar.tsx"; +import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.tsx"; +import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.tsx"; +import { useToast } from "@core/hooks/useToast.ts"; +import { DeviceConfig } from "@pages/Config/DeviceConfig.tsx"; +import { ModuleConfig } from "@pages/Config/ModuleConfig.tsx"; import { BoxesIcon, SaveIcon, SettingsIcon } from "lucide-react"; import { useState } from "react"; diff --git a/src/pages/Dashboard/index.tsx b/src/pages/Dashboard/index.tsx index 3bbbbd9f..ee2c4693 100644 --- a/src/pages/Dashboard/index.tsx +++ b/src/pages/Dashboard/index.tsx @@ -1,9 +1,9 @@ -import { useAppStore } from "@app/core/stores/appStore.js"; -import { useDeviceStore } from "@app/core/stores/deviceStore.js"; -import { Button } from "@components/UI/Button.js"; -import { Separator } from "@components/UI/Seperator.js"; -import { H3 } from "@components/UI/Typography/H3.js"; -import { Subtle } from "@components/UI/Typography/Subtle.js"; +import { useAppStore } from "@app/core/stores/appStore.ts"; +import { useDeviceStore } from "@app/core/stores/deviceStore.ts"; +import { Button } from "@components/UI/Button.tsx"; +import { Separator } from "@components/UI/Seperator.tsx"; +import { H3 } from "@components/UI/Typography/H3.tsx"; +import { Subtle } from "@components/UI/Typography/Subtle.tsx"; import { BluetoothIcon, ListPlusIcon, diff --git a/src/pages/Map.tsx b/src/pages/Map.tsx index 42cc5fcb..68587e44 100644 --- a/src/pages/Map.tsx +++ b/src/pages/Map.tsx @@ -1,11 +1,11 @@ -import { Subtle } from "@app/components/UI/Typography/Subtle.js"; -import { cn } from "@app/core/utils/cn.js"; -import { PageLayout } from "@components/PageLayout.js"; -import { Sidebar } from "@components/Sidebar.js"; -import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.js"; -import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.js"; -import { useAppStore } from "@core/stores/appStore.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import { Subtle } from "@app/components/UI/Typography/Subtle.tsx"; +import { cn } from "@app/core/utils/cn.ts"; +import { PageLayout } from "@components/PageLayout.tsx"; +import { Sidebar } from "@components/Sidebar.tsx"; +import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.tsx"; +import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.tsx"; +import { useAppStore } from "@core/stores/appStore.ts"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Hashicon } from "@emeraldpay/hashicon-react"; import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; import { bbox, lineString } from "@turf/turf"; diff --git a/src/pages/Messages.tsx b/src/pages/Messages.tsx index 4e7fbae9..28a35d2c 100644 --- a/src/pages/Messages.tsx +++ b/src/pages/Messages.tsx @@ -1,14 +1,14 @@ -import { ChannelChat } from "@components/PageComponents/Messages/ChannelChat.js"; -import { PageLayout } from "@components/PageLayout.js"; -import { Sidebar } from "@components/Sidebar.js"; -import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.js"; -import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.js"; -import { useToast } from "@core/hooks/useToast.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import { ChannelChat } from "@components/PageComponents/Messages/ChannelChat.tsx"; +import { PageLayout } from "@components/PageLayout.tsx"; +import { Sidebar } from "@components/Sidebar.tsx"; +import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.tsx"; +import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.tsx"; +import { useToast } from "@core/hooks/useToast.ts"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Hashicon } from "@emeraldpay/hashicon-react"; import { Protobuf, Types } from "@meshtastic/js"; import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; -import { getChannelName } from "@pages/Channels.js"; +import { getChannelName } from "@pages/Channels.tsx"; import { HashIcon, LockIcon, LockOpenIcon, WaypointsIcon } from "lucide-react"; import { useState } from "react"; diff --git a/src/pages/Nodes.tsx b/src/pages/Nodes.tsx index 038d81a8..f66ad1ff 100644 --- a/src/pages/Nodes.tsx +++ b/src/pages/Nodes.tsx @@ -1,11 +1,11 @@ import Footer from "@app/components/UI/Footer"; import { useAppStore } from "@app/core/stores/appStore"; -import { Sidebar } from "@components/Sidebar.js"; -import { Button } from "@components/UI/Button.js"; -import { Mono } from "@components/generic/Mono.js"; -import { Table } from "@components/generic/Table/index.js"; -import { TimeAgo } from "@components/generic/Table/tmp/TimeAgo.js"; -import { useDevice } from "@core/stores/deviceStore.js"; +import { Sidebar } from "@components/Sidebar.tsx"; +import { Button } from "@components/UI/Button.tsx"; +import { Mono } from "@components/generic/Mono.tsx"; +import { Table } from "@components/generic/Table/index.tsx"; +import { TimeAgo } from "@components/generic/Table/tmp/TimeAgo.tsx"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { Hashicon } from "@emeraldpay/hashicon-react"; import { Protobuf } from "@meshtastic/js"; import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; diff --git a/src/validation/rasterSource.ts b/src/validation/rasterSource.ts index 0afeec77..965206d4 100644 --- a/src/validation/rasterSource.ts +++ b/src/validation/rasterSource.ts @@ -1,6 +1,6 @@ import { IsArray, IsBoolean, IsNumber, IsString, IsUrl } from "class-validator"; -import type { RasterSource } from "@core/stores/appStore.js"; +import type { RasterSource } from "@core/stores/appStore.ts"; export class MapValidation { @IsArray() diff --git a/tsconfig.json b/tsconfig.json index af036a6e..4cf9d6f3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -34,6 +34,7 @@ "@types/w3c-web-serial" ], "strictPropertyInitialization": false, - "experimentalDecorators": true + "experimentalDecorators": true, + "allowImportingTsExtensions": true, } } From 2da4a44505bf7f0da9bd6ac1990fe6dccf31a4e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 6 Oct 2024 11:35:24 +0000 Subject: [PATCH 08/47] Bump rollup from 4.18.0 to 4.24.0 Bumps [rollup](https://github.com/rollup/rollup) from 4.18.0 to 4.24.0. - [Release notes](https://github.com/rollup/rollup/releases) - [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md) - [Commits](https://github.com/rollup/rollup/compare/v4.18.0...v4.24.0) --- updated-dependencies: - dependency-name: rollup dependency-type: indirect ... Signed-off-by: dependabot[bot] --- pnpm-lock.yaml | 165 +++++++++++++++++++++++++------------------------ 1 file changed, 85 insertions(+), 80 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 35f67520..ea6d26d8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -127,7 +127,7 @@ importers: version: 3.0.6(react@18.3.1) vite-plugin-node-polyfills: specifier: ^0.22.0 - version: 0.22.0(rollup@4.18.0)(vite@5.3.1(@types/node@20.14.9)) + version: 0.22.0(rollup@4.24.0)(vite@5.3.1(@types/node@20.14.9)) zustand: specifier: 4.5.2 version: 4.5.2(@types/react@18.3.3)(immer@10.1.1)(react@18.3.1) @@ -173,7 +173,7 @@ importers: version: 8.4.38 rollup-plugin-visualizer: specifier: ^5.12.0 - version: 5.12.0(rollup@4.18.0) + version: 5.12.0(rollup@4.24.0) tailwindcss: specifier: ^3.4.4 version: 3.4.4 @@ -1165,83 +1165,83 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.18.0': - resolution: {integrity: sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==} + '@rollup/rollup-android-arm-eabi@4.24.0': + resolution: {integrity: sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.18.0': - resolution: {integrity: sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==} + '@rollup/rollup-android-arm64@4.24.0': + resolution: {integrity: sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.18.0': - resolution: {integrity: sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==} + '@rollup/rollup-darwin-arm64@4.24.0': + resolution: {integrity: sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.18.0': - resolution: {integrity: sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==} + '@rollup/rollup-darwin-x64@4.24.0': + resolution: {integrity: sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==} cpu: [x64] os: [darwin] - '@rollup/rollup-linux-arm-gnueabihf@4.18.0': - resolution: {integrity: sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==} + '@rollup/rollup-linux-arm-gnueabihf@4.24.0': + resolution: {integrity: sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.18.0': - resolution: {integrity: sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==} + '@rollup/rollup-linux-arm-musleabihf@4.24.0': + resolution: {integrity: sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.18.0': - resolution: {integrity: sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==} + '@rollup/rollup-linux-arm64-gnu@4.24.0': + resolution: {integrity: sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.18.0': - resolution: {integrity: sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==} + '@rollup/rollup-linux-arm64-musl@4.24.0': + resolution: {integrity: sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.18.0': - resolution: {integrity: sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==} + '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': + resolution: {integrity: sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.18.0': - resolution: {integrity: sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==} + '@rollup/rollup-linux-riscv64-gnu@4.24.0': + resolution: {integrity: sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.18.0': - resolution: {integrity: sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==} + '@rollup/rollup-linux-s390x-gnu@4.24.0': + resolution: {integrity: sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.18.0': - resolution: {integrity: sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==} + '@rollup/rollup-linux-x64-gnu@4.24.0': + resolution: {integrity: sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.18.0': - resolution: {integrity: sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==} + '@rollup/rollup-linux-x64-musl@4.24.0': + resolution: {integrity: sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.18.0': - resolution: {integrity: sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==} + '@rollup/rollup-win32-arm64-msvc@4.24.0': + resolution: {integrity: sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.18.0': - resolution: {integrity: sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==} + '@rollup/rollup-win32-ia32-msvc@4.24.0': + resolution: {integrity: sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.18.0': - resolution: {integrity: sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==} + '@rollup/rollup-win32-x64-msvc@4.24.0': + resolution: {integrity: sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==} cpu: [x64] os: [win32] @@ -1669,6 +1669,9 @@ packages: '@types/estree@1.0.5': resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/filesystem@0.0.36': resolution: {integrity: sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==} @@ -2913,8 +2916,8 @@ packages: rollup: optional: true - rollup@4.18.0: - resolution: {integrity: sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==} + rollup@4.24.0: + resolution: {integrity: sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -4244,68 +4247,68 @@ snapshots: '@radix-ui/rect@1.1.0': {} - '@rollup/plugin-inject@5.0.5(rollup@4.18.0)': + '@rollup/plugin-inject@5.0.5(rollup@4.24.0)': dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.18.0) + '@rollup/pluginutils': 5.1.0(rollup@4.24.0) estree-walker: 2.0.2 magic-string: 0.30.11 optionalDependencies: - rollup: 4.18.0 + rollup: 4.24.0 - '@rollup/pluginutils@5.1.0(rollup@4.18.0)': + '@rollup/pluginutils@5.1.0(rollup@4.24.0)': dependencies: '@types/estree': 1.0.5 estree-walker: 2.0.2 picomatch: 2.3.1 optionalDependencies: - rollup: 4.18.0 + rollup: 4.24.0 - '@rollup/rollup-android-arm-eabi@4.18.0': + '@rollup/rollup-android-arm-eabi@4.24.0': optional: true - '@rollup/rollup-android-arm64@4.18.0': + '@rollup/rollup-android-arm64@4.24.0': optional: true - '@rollup/rollup-darwin-arm64@4.18.0': + '@rollup/rollup-darwin-arm64@4.24.0': optional: true - '@rollup/rollup-darwin-x64@4.18.0': + '@rollup/rollup-darwin-x64@4.24.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.18.0': + '@rollup/rollup-linux-arm-gnueabihf@4.24.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.18.0': + '@rollup/rollup-linux-arm-musleabihf@4.24.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.18.0': + '@rollup/rollup-linux-arm64-gnu@4.24.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.18.0': + '@rollup/rollup-linux-arm64-musl@4.24.0': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.18.0': + '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.18.0': + '@rollup/rollup-linux-riscv64-gnu@4.24.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.18.0': + '@rollup/rollup-linux-s390x-gnu@4.24.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.18.0': + '@rollup/rollup-linux-x64-gnu@4.24.0': optional: true - '@rollup/rollup-linux-x64-musl@4.18.0': + '@rollup/rollup-linux-x64-musl@4.24.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.18.0': + '@rollup/rollup-win32-arm64-msvc@4.24.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.18.0': + '@rollup/rollup-win32-ia32-msvc@4.24.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.18.0': + '@rollup/rollup-win32-x64-msvc@4.24.0': optional: true '@rsbuild/core@1.0.10': @@ -5223,6 +5226,8 @@ snapshots: '@types/estree@1.0.5': {} + '@types/estree@1.0.6': {} + '@types/filesystem@0.0.36': dependencies: '@types/filewriter': 0.0.33 @@ -6596,35 +6601,35 @@ snapshots: robust-predicates@3.0.2: {} - rollup-plugin-visualizer@5.12.0(rollup@4.18.0): + rollup-plugin-visualizer@5.12.0(rollup@4.24.0): dependencies: open: 8.4.2 picomatch: 2.3.1 source-map: 0.7.4 yargs: 17.7.2 optionalDependencies: - rollup: 4.18.0 + rollup: 4.24.0 - rollup@4.18.0: + rollup@4.24.0: dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.18.0 - '@rollup/rollup-android-arm64': 4.18.0 - '@rollup/rollup-darwin-arm64': 4.18.0 - '@rollup/rollup-darwin-x64': 4.18.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.18.0 - '@rollup/rollup-linux-arm-musleabihf': 4.18.0 - '@rollup/rollup-linux-arm64-gnu': 4.18.0 - '@rollup/rollup-linux-arm64-musl': 4.18.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.18.0 - '@rollup/rollup-linux-riscv64-gnu': 4.18.0 - '@rollup/rollup-linux-s390x-gnu': 4.18.0 - '@rollup/rollup-linux-x64-gnu': 4.18.0 - '@rollup/rollup-linux-x64-musl': 4.18.0 - '@rollup/rollup-win32-arm64-msvc': 4.18.0 - '@rollup/rollup-win32-ia32-msvc': 4.18.0 - '@rollup/rollup-win32-x64-msvc': 4.18.0 + '@rollup/rollup-android-arm-eabi': 4.24.0 + '@rollup/rollup-android-arm64': 4.24.0 + '@rollup/rollup-darwin-arm64': 4.24.0 + '@rollup/rollup-darwin-x64': 4.24.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.24.0 + '@rollup/rollup-linux-arm-musleabihf': 4.24.0 + '@rollup/rollup-linux-arm64-gnu': 4.24.0 + '@rollup/rollup-linux-arm64-musl': 4.24.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.24.0 + '@rollup/rollup-linux-riscv64-gnu': 4.24.0 + '@rollup/rollup-linux-s390x-gnu': 4.24.0 + '@rollup/rollup-linux-x64-gnu': 4.24.0 + '@rollup/rollup-linux-x64-musl': 4.24.0 + '@rollup/rollup-win32-arm64-msvc': 4.24.0 + '@rollup/rollup-win32-ia32-msvc': 4.24.0 + '@rollup/rollup-win32-x64-msvc': 4.24.0 fsevents: 2.3.3 run-parallel@1.2.0: @@ -6960,9 +6965,9 @@ snapshots: validator@13.12.0: {} - vite-plugin-node-polyfills@0.22.0(rollup@4.18.0)(vite@5.3.1(@types/node@20.14.9)): + vite-plugin-node-polyfills@0.22.0(rollup@4.24.0)(vite@5.3.1(@types/node@20.14.9)): dependencies: - '@rollup/plugin-inject': 5.0.5(rollup@4.18.0) + '@rollup/plugin-inject': 5.0.5(rollup@4.24.0) node-stdlib-browser: 1.2.0 vite: 5.3.1(@types/node@20.14.9) transitivePeerDependencies: @@ -6972,7 +6977,7 @@ snapshots: dependencies: esbuild: 0.21.5 postcss: 8.4.38 - rollup: 4.18.0 + rollup: 4.24.0 optionalDependencies: '@types/node': 20.14.9 fsevents: 2.3.3 From 1e26eed861f6cdf70f0b6ea7990fd2861699ea92 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 6 Oct 2024 11:35:29 +0000 Subject: [PATCH 09/47] Bump micromatch from 4.0.7 to 4.0.8 Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.7 to 4.0.8. - [Release notes](https://github.com/micromatch/micromatch/releases) - [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/micromatch/compare/4.0.7...4.0.8) --- updated-dependencies: - dependency-name: micromatch dependency-type: indirect ... Signed-off-by: dependabot[bot] --- pnpm-lock.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 35f67520..a06968c3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2484,8 +2484,8 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - micromatch@4.0.7: - resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} miller-rabin@4.0.1: @@ -5763,7 +5763,7 @@ snapshots: '@nodelib/fs.walk': 1.2.8 glob-parent: 5.1.2 merge2: 1.4.1 - micromatch: 4.0.7 + micromatch: 4.0.8 fastq@1.17.1: dependencies: @@ -6166,7 +6166,7 @@ snapshots: merge2@1.4.1: {} - micromatch@4.0.7: + micromatch@4.0.8: dependencies: braces: 3.0.3 picomatch: 2.3.1 @@ -6810,7 +6810,7 @@ snapshots: is-glob: 4.0.3 jiti: 1.21.6 lilconfig: 2.1.0 - micromatch: 4.0.7 + micromatch: 4.0.8 normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.0.1 From f2760a941bd2ed1815ab5ded7d0bd559ea2d6f19 Mon Sep 17 00:00:00 2001 From: Jan Grewe Date: Mon, 14 Oct 2024 16:38:30 +0200 Subject: [PATCH 10/47] Update Containerfile to use plain Nginx Only add required files to container Fix HTML title --- .dockerignore | 2 ++ Containerfile | 9 +++++++-- README.md | 6 +++--- rsbuild.config.ts | 3 +++ 4 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..c61e351e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +dist/build.tar +dist/output diff --git a/Containerfile b/Containerfile index 261f7346..db568ca4 100644 --- a/Containerfile +++ b/Containerfile @@ -1,5 +1,10 @@ -FROM registry.access.redhat.com/ubi9/nginx-122:1-45 +FROM nginx:1.27.2-alpine + +RUN rm -r /usr/share/nginx/html \ + && mkdir /usr/share/nginx/html + +WORKDIR /usr/share/nginx/html ADD dist . -CMD nginx -g "daemon off;" \ No newline at end of file +CMD nginx -g "daemon off;" diff --git a/README.md b/README.md index ba64a5b5..ec46a087 100644 --- a/README.md +++ b/README.md @@ -20,14 +20,14 @@ Official [Meshtastic](https://meshtastic.org) web interface, that can be hosted ## Self-host The client can be self hosted using the precompiled container images with an OCI compatible runtime such as [Docker](https://www.docker.com/) or [Podman](https://podman.io/). -The base image used is [UBI9 Nginx 1.22](https://catalog.redhat.com/software/containers/ubi9/nginx-122/63f7653b9b0ca19f84f7e9a1) +The base image used is [Nginx 1.27](https://hub.docker.com/_/nginx) ```bash # With Docker -docker run -d -p 8080:8080 -p 8443:8443 --restart always --name Meshtastic-Web ghcr.io/meshtastic/web +docker run -d -p 8080:80 --restart always --name Meshtastic-Web ghcr.io/meshtastic/web #With Podman -podman run -d -p 8080:8080 -p 8443:8443 --restart always --name Meshtastic-Web ghcr.io/meshtastic/web +podman run -d -p 8080:80 --restart always --name Meshtastic-Web ghcr.io/meshtastic/web ``` ## Development & Building diff --git a/rsbuild.config.ts b/rsbuild.config.ts index b4b29017..9d47879b 100644 --- a/rsbuild.config.ts +++ b/rsbuild.config.ts @@ -24,4 +24,7 @@ export default defineConfig({ "@layouts": "./src/layouts", }, }, + html: { + title: 'Meshtastic Web', + }, }); From f53d53ea20d66f690f1ddf739e31fd5adc423d31 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Oct 2024 02:19:04 +0000 Subject: [PATCH 11/47] Bump elliptic from 6.5.7 to 6.6.0 Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.7 to 6.6.0. - [Commits](https://github.com/indutny/elliptic/compare/v6.5.7...v6.6.0) --- updated-dependencies: - dependency-name: elliptic dependency-type: indirect ... Signed-off-by: dependabot[bot] --- pnpm-lock.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 35f67520..c1e58180 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2050,8 +2050,8 @@ packages: electron-to-chromium@1.4.812: resolution: {integrity: sha512-7L8fC2Ey/b6SePDFKR2zHAy4mbdp1/38Yk5TsARO66W3hC5KEaeKMMHoxwtuH+jcu2AYLSn9QX04i95t6Fl1Hg==} - elliptic@6.5.7: - resolution: {integrity: sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==} + elliptic@6.6.0: + resolution: {integrity: sha512-dpwoQcLc/2WLQvJvLRHKZ+f9FgOdjnq11rurqwekGQygGPsYSK29OMMD2WalatiqQ+XGFDglTNixpPfI+lpaAA==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -5395,7 +5395,7 @@ snapshots: browserify-rsa: 4.1.0 create-hash: 1.2.0 create-hmac: 1.1.7 - elliptic: 6.5.7 + elliptic: 6.6.0 hash-base: 3.0.4 inherits: 2.0.4 parse-asn1: 5.1.7 @@ -5530,7 +5530,7 @@ snapshots: create-ecdh@4.0.4: dependencies: bn.js: 4.12.0 - elliptic: 6.5.7 + elliptic: 6.6.0 create-hash@1.2.0: dependencies: @@ -5671,7 +5671,7 @@ snapshots: electron-to-chromium@1.4.812: {} - elliptic@6.5.7: + elliptic@6.6.0: dependencies: bn.js: 4.12.0 brorand: 1.1.0 From 79c5638e10e45b4e7d04d8bfa96e99ad4a86e5df Mon Sep 17 00:00:00 2001 From: Felix Moessbauer Date: Fri, 1 Nov 2024 12:55:54 +0100 Subject: [PATCH 12/47] document scalar clamping of curve25519 keys The "scalars" are just random bytes. To make them secure curve25519 keys, they need to be clamped according to rfc7748 section 5. As this is not obvious, we need to add a reference to the RFC. No functional change. Closes: #324 --- src/core/utils/x25519.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/utils/x25519.ts b/src/core/utils/x25519.ts index 2729d80a..7e69f339 100644 --- a/src/core/utils/x25519.ts +++ b/src/core/utils/x25519.ts @@ -3,6 +3,8 @@ import { x25519 } from "@noble/curves/ed25519"; export function getX25519PrivateKey(): Uint8Array { const key = x25519.utils.randomPrivateKey(); + // scalar clamping for curve25519, according to + // https://www.rfc-editor.org/rfc/rfc7748#section-5 key[0] &= 248; key[31] &= 127; key[31] |= 64; From 2cf7655562f9b60993daa29ef2d512f55287d675 Mon Sep 17 00:00:00 2001 From: Tom <116762865+Nestpebble@users.noreply.github.com> Date: Sat, 2 Nov 2024 01:37:28 +0000 Subject: [PATCH 13/47] Update Security.tsx --- src/components/PageComponents/Config/Security.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PageComponents/Config/Security.tsx b/src/components/PageComponents/Config/Security.tsx index 7baaebda..ced7f1f7 100644 --- a/src/components/PageComponents/Config/Security.tsx +++ b/src/components/PageComponents/Config/Security.tsx @@ -187,7 +187,7 @@ export const Security = (): JSX.Element => { name: "isManaged", label: "Managed", description: - 'If true, device is considered to be "managed" by a mesh administrator via admin messages', + 'If true, device configuration options are only able to be changed remotely by a mesh administrator via admin messages. Do not enable this option unless a suitable Remote Admin node has been setup, and the public key stored in the field below.', }, { type: "text", From 4f9fb9976dbdb454a1bd50d1839c69d3c808c410 Mon Sep 17 00:00:00 2001 From: rcarteraz Date: Sat, 9 Nov 2024 10:37:06 -0700 Subject: [PATCH 14/47] fix title --- rsbuild.config.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rsbuild.config.ts b/rsbuild.config.ts index b4b29017..3191db5c 100644 --- a/rsbuild.config.ts +++ b/rsbuild.config.ts @@ -24,4 +24,7 @@ export default defineConfig({ "@layouts": "./src/layouts", }, }, + html: { + title: "Meshtastic Web", + }, }); From 94cca88e2eccc9e06d1d6a76b64679f7190aede9 Mon Sep 17 00:00:00 2001 From: Hunter275 Date: Sat, 9 Nov 2024 21:10:55 -0500 Subject: [PATCH 15/47] If the batteryLevel is over 100, its charging --- src/components/Sidebar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 05f5ef19..48c496e5 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -80,7 +80,7 @@ export const Sidebar = ({ children }: SidebarProps): JSX.Element => {
- {myNode?.deviceMetrics?.batteryLevel ?? "UNK"}% + {myNode?.deviceMetrics?.batteryLevel ? myNode?.deviceMetrics?.batteryLevel > 100 ? "Charging" : myNode?.deviceMetrics?.batteryLevel + "%" : "UNK"}
From 2bd80bb5b47337978411ef92c61e15cb5d68a3a3 Mon Sep 17 00:00:00 2001 From: Hunter275 Date: Sat, 9 Nov 2024 22:39:57 -0500 Subject: [PATCH 16/47] Add pin validation --- .../PageComponents/Config/Bluetooth.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/components/PageComponents/Config/Bluetooth.tsx b/src/components/PageComponents/Config/Bluetooth.tsx index 006fab47..b01d3c69 100644 --- a/src/components/PageComponents/Config/Bluetooth.tsx +++ b/src/components/PageComponents/Config/Bluetooth.tsx @@ -2,9 +2,24 @@ import type { BluetoothValidation } from "@app/validation/config/bluetooth.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { useDevice } from "@core/stores/deviceStore.ts"; import { Protobuf } from "@meshtastic/js"; +import { useState } from "react"; export const Bluetooth = (): JSX.Element => { const { config, setWorkingConfig } = useDevice(); + const [bluetoothValidationText, setBluetoothValidationText] = useState(); + + const bluetoothPinChangeEvent = ( + e: React.ChangeEvent, + ) => { + if (e.target.value[0] == "0") + { + setBluetoothValidationText("Bluetooth Pin cannot start with 0."); + } + else + { + setBluetoothValidationText(""); + } + } const onSubmit = (data: BluetoothValidation) => { setWorkingConfig( @@ -52,6 +67,8 @@ export const Bluetooth = (): JSX.Element => { name: "fixedPin", label: "Pin", description: "Pin to use when pairing", + validationText: bluetoothValidationText, + inputChange: bluetoothPinChangeEvent, disabledBy: [ { fieldName: "mode", From 9aafa681e8f239878c8bf7476bbf54e138c5b43c Mon Sep 17 00:00:00 2001 From: hectorthemonk Date: Sun, 10 Nov 2024 21:34:33 +1300 Subject: [PATCH 17/47] Add ledHeartbeatDisabled to Device Settings in Radio Config `led_heartbeat_disabled` in https://buf.build/meshtastic/protobufs/docs/main:meshtastic#meshtastic.Config.DeviceConfig --- src/components/PageComponents/Config/Device.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/PageComponents/Config/Device.tsx b/src/components/PageComponents/Config/Device.tsx index d0e1f029..9ef11904 100644 --- a/src/components/PageComponents/Config/Device.tsx +++ b/src/components/PageComponents/Config/Device.tsx @@ -94,6 +94,12 @@ export const Device = (): JSX.Element => { label: "Disable Triple Click", description: "Disable triple click", }, + { + type: "toggle", + name: "ledHeartbeatDisabled", + label: "LED Heartbeat Disabled", + description: "Disable default blinking LED", + }, ], }, ]} From 6fc183ff15f3037e93b1fffcdfa5c93e302b3d33 Mon Sep 17 00:00:00 2001 From: Tom <116762865+Nestpebble@users.noreply.github.com> Date: Sun, 10 Nov 2024 12:58:54 +0000 Subject: [PATCH 18/47] Update Security.tsx --- src/components/PageComponents/Config/Security.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PageComponents/Config/Security.tsx b/src/components/PageComponents/Config/Security.tsx index ced7f1f7..cf675751 100644 --- a/src/components/PageComponents/Config/Security.tsx +++ b/src/components/PageComponents/Config/Security.tsx @@ -187,7 +187,7 @@ export const Security = (): JSX.Element => { name: "isManaged", label: "Managed", description: - 'If true, device configuration options are only able to be changed remotely by a mesh administrator via admin messages. Do not enable this option unless a suitable Remote Admin node has been setup, and the public key stored in the field below.', + 'If true, device configuration options are only able to be changed remotely by a Remote Admin node via admin messages. Do not enable this option unless a suitable Remote Admin node has been setup, and the public key stored in the field below.', }, { type: "text", From 2f9af111c89ddab93739ecef1ce69d2851d6810b Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Tue, 12 Nov 2024 10:29:27 +0800 Subject: [PATCH 19/47] Update vite to 5.3.6 Fixes CVE-2024-45812 --- pnpm-lock.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ad597abd..080b010d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -127,7 +127,7 @@ importers: version: 3.0.6(react@18.3.1) vite-plugin-node-polyfills: specifier: ^0.22.0 - version: 0.22.0(rollup@4.24.0)(vite@5.3.1(@types/node@20.14.9)) + version: 0.22.0(rollup@4.24.0)(vite@5.3.6(@types/node@20.14.9)) zustand: specifier: 4.5.2 version: 4.5.2(@types/react@18.3.3)(immer@10.1.1)(react@18.3.1) @@ -3229,7 +3229,7 @@ packages: peerDependencies: vite: ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 - vite@5.3.1: + vite@5.3.6: resolution: {integrity: sha512-XBmSKRLXLxiaPYamLv3/hnP/KXDai1NDexN0FpkTaZXTfycHvkRHoenpgl/fvuK/kPbB6xAgoyiryAhQNxYmAQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -6965,7 +6965,7 @@ snapshots: validator@13.12.0: {} - vite-plugin-node-polyfills@0.22.0(rollup@4.24.0)(vite@5.3.1(@types/node@20.14.9)): + vite-plugin-node-polyfills@0.22.0(rollup@4.24.0)(vite@5.3.6(@types/node@20.14.9)): dependencies: '@rollup/plugin-inject': 5.0.5(rollup@4.24.0) node-stdlib-browser: 1.2.0 @@ -6973,7 +6973,7 @@ snapshots: transitivePeerDependencies: - rollup - vite@5.3.1(@types/node@20.14.9): + vite@5.3.6(@types/node@20.14.9): dependencies: esbuild: 0.21.5 postcss: 8.4.38 From 6df0b287ef4cb95f565eeb31476a6ea2dbae64b4 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Tue, 12 Nov 2024 10:29:27 +0800 Subject: [PATCH 20/47] Update vite to 5.3.6 Fixes CVE-2024-45812 --- pnpm-lock.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 080b010d..b5696415 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6969,7 +6969,7 @@ snapshots: dependencies: '@rollup/plugin-inject': 5.0.5(rollup@4.24.0) node-stdlib-browser: 1.2.0 - vite: 5.3.1(@types/node@20.14.9) + vite: 5.3.6(@types/node@20.14.9) transitivePeerDependencies: - rollup From 0fc4211c136278de446cfa2753a38e0d528a33ee Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Tue, 12 Nov 2024 10:29:27 +0800 Subject: [PATCH 21/47] Update vite to 5.3.6 Fixes CVE-2024-45812 --- pnpm-lock.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b5696415..31e2c695 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3230,7 +3230,7 @@ packages: vite: ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 vite@5.3.6: - resolution: {integrity: sha512-XBmSKRLXLxiaPYamLv3/hnP/KXDai1NDexN0FpkTaZXTfycHvkRHoenpgl/fvuK/kPbB6xAgoyiryAhQNxYmAQ==} + resolution: {integrity: sha512-es78AlrylO8mTVBygC0gTC0FENv0C6T496vvd33ydbjF/mIi9q3XQ9A3NWo5qLGFKywvz10J26813OkLvcQleA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: From 3fa73894edb7499aafa8d28f742e93d518602d79 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Nov 2024 15:01:05 +0000 Subject: [PATCH 22/47] Bump cross-spawn from 7.0.3 to 7.0.6 Bumps [cross-spawn](https://github.com/moxystudio/node-cross-spawn) from 7.0.3 to 7.0.6. - [Changelog](https://github.com/moxystudio/node-cross-spawn/blob/master/CHANGELOG.md) - [Commits](https://github.com/moxystudio/node-cross-spawn/compare/v7.0.3...v7.0.6) --- updated-dependencies: - dependency-name: cross-spawn dependency-type: indirect ... Signed-off-by: dependabot[bot] --- pnpm-lock.yaml | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 31e2c695..bfc3743d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1962,8 +1962,8 @@ packages: create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} crypto-browserify@3.12.0: @@ -2643,6 +2643,9 @@ packages: picocolors@1.0.1: resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -2710,6 +2713,10 @@ packages: resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} engines: {node: ^10 || ^12 || >=14} + postcss@8.4.49: + resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} + engines: {node: ^10 || ^12 || >=14} + potpack@2.0.0: resolution: {integrity: sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw==} @@ -2997,6 +3004,10 @@ packages: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + source-map@0.7.4: resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} engines: {node: '>= 8'} @@ -5556,7 +5567,7 @@ snapshots: create-require@1.1.1: {} - cross-spawn@7.0.3: + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 @@ -5791,7 +5802,7 @@ snapshots: foreground-child@3.2.1: dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 signal-exit: 4.1.0 fraction.js@4.3.7: {} @@ -6341,6 +6352,8 @@ snapshots: picocolors@1.0.1: {} + picocolors@1.1.1: {} + picomatch@2.3.1: {} pify@2.3.0: {} @@ -6397,6 +6410,12 @@ snapshots: picocolors: 1.0.1 source-map-js: 1.2.0 + postcss@8.4.49: + dependencies: + nanoid: 3.3.7 + picocolors: 1.1.1 + source-map-js: 1.2.1 + potpack@2.0.0: {} process-nextick-args@2.0.1: {} @@ -6717,6 +6736,8 @@ snapshots: source-map-js@1.2.0: {} + source-map-js@1.2.1: {} + source-map@0.7.4: {} splaytree@3.1.2: {} @@ -6976,7 +6997,7 @@ snapshots: vite@5.3.6(@types/node@20.14.9): dependencies: esbuild: 0.21.5 - postcss: 8.4.38 + postcss: 8.4.49 rollup: 4.24.0 optionalDependencies: '@types/node': 20.14.9 From fed6b2a6da48418fbc04f924fa16b5c061194ce2 Mon Sep 17 00:00:00 2001 From: Kyle Wistrand Date: Tue, 26 Nov 2024 18:45:59 -0800 Subject: [PATCH 23/47] feat: Add Node detail popup in Map view --- .../PageComponents/Map/NodeDetail.tsx | 171 ++++++++++++++++++ src/components/UI/Typography/H5.tsx | 14 ++ src/pages/Map.tsx | 20 +- 3 files changed, 203 insertions(+), 2 deletions(-) create mode 100644 src/components/PageComponents/Map/NodeDetail.tsx create mode 100644 src/components/UI/Typography/H5.tsx diff --git a/src/components/PageComponents/Map/NodeDetail.tsx b/src/components/PageComponents/Map/NodeDetail.tsx new file mode 100644 index 00000000..cc01b8db --- /dev/null +++ b/src/components/PageComponents/Map/NodeDetail.tsx @@ -0,0 +1,171 @@ +import { Mono } from "@components/generic/Mono.tsx"; +import { H5 } from "@app/components/UI/Typography/H5.tsx"; +import { Subtle } from "@app/components/UI/Typography/Subtle.tsx"; +import { Separator } from "@app/components/UI/Seperator"; +import { TimeAgo } from "@components/generic/Table/tmp/TimeAgo.tsx"; +import { Hashicon } from "@emeraldpay/hashicon-react"; +import { Protobuf } from "@meshtastic/js"; +import type { Protobuf as ProtobufType } from "@meshtastic/js"; +import { + BatteryChargingIcon, + BatteryFullIcon, + BatteryLowIcon, + BatteryMediumIcon, + Dot, + LockIcon, + LockOpenIcon, + MountainSnow, + Star, +} from "lucide-react"; +import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; + +export interface NodeDetailProps { + node: ProtobufType.Mesh.NodeInfo; +} + +export const NodeDetail = ({ node }: NodeDetailProps): JSX.Element => { + const name = node.user?.longName || `!${numberToHexUnpadded(node.num)}`; + const hardwareType = Protobuf.Mesh.HardwareModel[ + node.user?.hwModel ?? 0 + ].replaceAll("_", " "); + + return ( + <> +
+
+ + +
+ {node.user?.publicKey && node.user?.publicKey.length > 0 ? ( + + ) : ( + + )} +
+ + +
+ +
+
{name}
+ + {hardwareType !== "UNSET" && {hardwareType}} + + {!!node.deviceMetrics?.batteryLevel && ( +
+ {node.deviceMetrics?.batteryLevel > 100 ? ( + + ) : node.deviceMetrics?.batteryLevel > 80 ? ( + + ) : node.deviceMetrics?.batteryLevel > 20 ? ( + + ) : ( + + )} + + {node.deviceMetrics?.batteryLevel > 100 + ? "Charging" + : node.deviceMetrics?.batteryLevel + "%"} + +
+ )} + +
+ {node.user?.shortName &&
"{node.user?.shortName}"
} + {node.user?.id &&
{node.user?.id}
} +
+ +
+
+ {node.lastHeard > 0 && ( +
+ Heard +
+ )} +
+ {node.viaMqtt && ( +
+ MQTT +
+ )} +
+
+
+ + + +
+
+
+ {isNaN(node.hopsAway) ? "?" : node.hopsAway} +
+
{node.hopsAway === 1 ? "Hop" : "Hops"}
+
+ {node.position?.altitude && ( +
+ +
{node.position?.altitude} ft
+
+ )} +
+ +
+ {!!node.deviceMetrics?.channelUtilization && ( +
+
Channel Util
+ + {node.deviceMetrics?.channelUtilization.toPrecision(3)}% + +
+ )} + {!!node.deviceMetrics?.airUtilTx && ( +
+
Airtime Util
+ {node.deviceMetrics?.airUtilTx.toPrecision(3)}% +
+ )} +
+ + {node.snr !== 0 && ( +
+
SNR
+ + {node.snr}db + + {Math.min(Math.max((node.snr + 10) * 5, 0), 100)}% + + {(node.snr + 10) * 5}raw + +
+ )} + + ); +}; diff --git a/src/components/UI/Typography/H5.tsx b/src/components/UI/Typography/H5.tsx new file mode 100644 index 00000000..5bd1dfb1 --- /dev/null +++ b/src/components/UI/Typography/H5.tsx @@ -0,0 +1,14 @@ +import { cn } from "@app/core/utils/cn.ts"; + +export interface H5Props { + className?: string; + children: React.ReactNode; +} + +export const H5 = ({ className, children }: H5Props): JSX.Element => ( +
+ {children} +
+); diff --git a/src/pages/Map.tsx b/src/pages/Map.tsx index 68587e44..2d764b83 100644 --- a/src/pages/Map.tsx +++ b/src/pages/Map.tsx @@ -1,4 +1,5 @@ import { Subtle } from "@app/components/UI/Typography/Subtle.tsx"; +import { NodeDetail } from "@app/components/PageComponents/Map/NodeDetail"; import { cn } from "@app/core/utils/cn.ts"; import { PageLayout } from "@components/PageLayout.tsx"; import { Sidebar } from "@components/Sidebar.tsx"; @@ -16,8 +17,9 @@ import { ZoomOutIcon, } from "lucide-react"; import { useCallback, useEffect, useState } from "react"; -import { Marker, useMap } from "react-map-gl"; +import { Marker, useMap, Popup } from "react-map-gl"; import MapGl from "react-map-gl/maplibre"; +import { Protobuf } from "@meshtastic/js"; export const MapPage = (): JSX.Element => { const { nodes, waypoints } = useDevice(); @@ -25,6 +27,8 @@ export const MapPage = (): JSX.Element => { const { default: map } = useMap(); const [zoom, setZoom] = useState(0); + const [selectedNode, setSelectedNode] = + useState(null); const allNodes = Array.from(nodes.values()); @@ -160,7 +164,7 @@ export const MapPage = (): JSX.Element => { ))} */} {allNodes.map((node) => { - if (node.position?.latitudeI) { + if (node.position?.latitudeI && node.num !== selectedNode?.num) { return ( { style={{ filter: darkMode ? "invert(1)" : "" }} anchor="bottom" onClick={() => { + setSelectedNode(node); map?.easeTo({ zoom: 12, center: [ @@ -189,6 +194,17 @@ export const MapPage = (): JSX.Element => { ); } })} + {selectedNode?.position && ( + setSelectedNode(null)} + > + + + )} From 56874851543a86f0ae39b367f9865261799f60a2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 14 Dec 2024 09:37:41 +0000 Subject: [PATCH 24/47] Bump nanoid from 3.3.7 to 3.3.8 Bumps [nanoid](https://github.com/ai/nanoid) from 3.3.7 to 3.3.8. - [Release notes](https://github.com/ai/nanoid/releases) - [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md) - [Commits](https://github.com/ai/nanoid/compare/3.3.7...3.3.8) --- updated-dependencies: - dependency-name: nanoid dependency-type: indirect ... Signed-off-by: dependabot[bot] --- pnpm-lock.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bfc3743d..b9edbfd5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2535,8 +2535,8 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - nanoid@3.3.7: - resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -6225,7 +6225,7 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 - nanoid@3.3.7: {} + nanoid@3.3.8: {} node-releases@2.0.14: {} @@ -6406,13 +6406,13 @@ snapshots: postcss@8.4.38: dependencies: - nanoid: 3.3.7 + nanoid: 3.3.8 picocolors: 1.0.1 source-map-js: 1.2.0 postcss@8.4.49: dependencies: - nanoid: 3.3.7 + nanoid: 3.3.8 picocolors: 1.1.1 source-map-js: 1.2.1 From 856556c12b067cf6b67e5e943f1e9ec10db0becb Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Sat, 28 Dec 2024 20:21:40 -0500 Subject: [PATCH 25/47] feat: Add AttributionControl to Map to comply with OSM license requirements --- src/pages/Map.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pages/Map.tsx b/src/pages/Map.tsx index 68587e44..1deeee64 100644 --- a/src/pages/Map.tsx +++ b/src/pages/Map.tsx @@ -16,7 +16,7 @@ import { ZoomOutIcon, } from "lucide-react"; import { useCallback, useEffect, useState } from "react"; -import { Marker, useMap } from "react-map-gl"; +import { AttributionControl, Marker, useMap } from "react-map-gl"; import MapGl from "react-map-gl/maplibre"; export const MapPage = (): JSX.Element => { @@ -126,6 +126,7 @@ export const MapPage = (): JSX.Element => { // }} // @ts-ignore + attributionControl={false} renderWorldCopies={false} maxPitch={0} @@ -142,6 +143,9 @@ export const MapPage = (): JSX.Element => { longitude: 0, }} > + {waypoints.map((wp) => ( Date: Fri, 3 Jan 2025 12:05:29 -0500 Subject: [PATCH 26/47] feat: Add pki backup dialog, refactor Channels pre-shared key to support regenerate dialog --- src/components/Dialog/DialogManager.tsx | 7 + src/components/Dialog/PKIBackupDialog.tsx | 104 +++++++++ src/components/Form/FormPasswordGenerator.tsx | 17 +- src/components/PageComponents/Channel.tsx | 216 ++++++++++-------- .../PageComponents/Config/Security.tsx | 29 ++- src/components/UI/Button.tsx | 4 +- src/components/UI/Generator.tsx | 37 +-- src/core/stores/deviceStore.ts | 5 +- 8 files changed, 289 insertions(+), 130 deletions(-) create mode 100644 src/components/Dialog/PKIBackupDialog.tsx diff --git a/src/components/Dialog/DialogManager.tsx b/src/components/Dialog/DialogManager.tsx index 16e60120..762bd5a7 100644 --- a/src/components/Dialog/DialogManager.tsx +++ b/src/components/Dialog/DialogManager.tsx @@ -5,6 +5,7 @@ import { QRDialog } from "@components/Dialog/QRDialog.tsx"; import { RebootDialog } from "@components/Dialog/RebootDialog.tsx"; import { ShutdownDialog } from "@components/Dialog/ShutdownDialog.tsx"; import { useDevice } from "@core/stores/deviceStore.ts"; +import { PkiBackupDialog } from "./PKIBackupDialog"; export const DialogManager = (): JSX.Element => { const { channels, config, dialog, setDialogOpen } = useDevice(); @@ -49,6 +50,12 @@ export const DialogManager = (): JSX.Element => { setDialogOpen("nodeRemoval", open); }} /> + { + setDialogOpen("pkiBackup", open); + }} + /> ); }; diff --git a/src/components/Dialog/PKIBackupDialog.tsx b/src/components/Dialog/PKIBackupDialog.tsx new file mode 100644 index 00000000..3737236a --- /dev/null +++ b/src/components/Dialog/PKIBackupDialog.tsx @@ -0,0 +1,104 @@ +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@components/UI/Dialog.tsx"; +import { Button } from "@components/UI/Button"; +import { DownloadIcon, PrinterIcon } from "lucide-react"; +import React from "react"; +import { useDevice } from "@app/core/stores/deviceStore"; +import { fromByteArray } from "base64-js"; + +export interface PkiBackupDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export const PkiBackupDialog = ({ + open, + onOpenChange, +}: PkiBackupDialogProps) => { + const { config } = useDevice(); + const privateKeyData = config.security?.privateKey + + // If the private data doesn't exist return null + if (!privateKeyData) { + return null + } + + const getPrivateKey = React.useMemo(() => fromByteArray(config.security?.privateKey ?? new Uint8Array(0)), [config.security?.privateKey]); + + const renderPrintWindow = React.useCallback(() => { + const printWindow = window.open("", "_blank"); + if (printWindow) { + printWindow.document.write(` + + + Your Private Key + + + +

Your Private Key

+

${getPrivateKey}

+ + + `); + printWindow.document.close(); + printWindow.print(); + } + }, [getPrivateKey]); + + const createDownloadKeyFile = React.useCallback(() => { + const blob = new Blob([getPrivateKey], { type: "text/plain" }); + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + link.download = "meshtastic_private_key.txt"; + link.style.display = "none"; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + }, [getPrivateKey]); + + + return ( + + + + Backup Key + + Its important to backup your private key and store your backup securely! + + + If you lose your private key, you will need to reset your device. + + + + + + + + + ); +}; diff --git a/src/components/Form/FormPasswordGenerator.tsx b/src/components/Form/FormPasswordGenerator.tsx index 09e97ff5..784086fb 100644 --- a/src/components/Form/FormPasswordGenerator.tsx +++ b/src/components/Form/FormPasswordGenerator.tsx @@ -7,6 +7,7 @@ import { Eye, EyeOff } from "lucide-react"; import type { ChangeEventHandler, MouseEventHandler } from "react"; import { useState } from "react"; import { Controller, type FieldValues } from "react-hook-form"; +import type { ButtonVariant } from "@components/UI/Button"; export interface PasswordGeneratorProps extends BaseFormBuilderProps { type: "passwordGenerator"; @@ -15,7 +16,12 @@ export interface PasswordGeneratorProps extends BaseFormBuilderProps { devicePSKBitCount: number; inputChange: ChangeEventHandler; selectChange: (event: string) => void; - buttonClick: MouseEventHandler; + actionButtons: { + text: string; + onClick: React.MouseEventHandler; + variant: ButtonVariant; + className?: string; + }[]; } export function PasswordGenerator({ @@ -38,19 +44,18 @@ export function PasswordGenerator({ action={ field.hide ? { - icon: passwordShown ? EyeOff : Eye, - onClick: togglePasswordVisiblity, - } + icon: passwordShown ? EyeOff : Eye, + onClick: togglePasswordVisiblity, + } : undefined } devicePSKBitCount={field.devicePSKBitCount} bits={field.bits} inputChange={field.inputChange} selectChange={field.selectChange} - buttonClick={field.buttonClick} value={value} variant={field.validationText ? "invalid" : "default"} - buttonText="Generate" + actionButtons={field.actionButtons} {...field.properties} {...rest} disabled={disabled} diff --git a/src/components/PageComponents/Channel.tsx b/src/components/PageComponents/Channel.tsx index 3655e5f8..7b581d72 100644 --- a/src/components/PageComponents/Channel.tsx +++ b/src/components/PageComponents/Channel.tsx @@ -6,6 +6,7 @@ import { Protobuf } from "@meshtastic/js"; import { fromByteArray, toByteArray } from "base64-js"; import cryptoRandomString from "crypto-random-string"; import { useState } from "react"; +import { PkiRegenerateDialog } from "../Dialog/PkiRegenerateDialog"; export interface SettingsPanelProps { channel: Protobuf.Channel.Channel; @@ -22,6 +23,7 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => { channel?.settings?.psk.length ?? 16, ); const [validationText, setValidationText] = useState(); + const [preSharedDialogOpen, setPreSharedDialogOpen] = useState(false); const onSubmit = (data: ChannelValidation) => { const channel = new Protobuf.Channel.Channel({ @@ -46,7 +48,7 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => { }); }; - const clickEvent = () => { + const preSharedKeyRegenerate = () => { setPass( btoa( cryptoRandomString({ @@ -56,6 +58,11 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => { ), ); setValidationText(undefined); + setPreSharedDialogOpen(false); + }; + + const preSharedClickEvent = () => { + setPreSharedDialogOpen(true); }; const validatePass = (input: string, count: number) => { @@ -79,104 +86,105 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => { }; return ( - - onSubmit={onSubmit} - submitType="onSubmit" - hasSubmitButton={true} - defaultValues={{ - ...channel, - ...{ - settings: { - ...channel?.settings, - psk: pass, - positionEnabled: - channel?.settings?.moduleSettings?.positionPrecision !== + <> + + onSubmit={onSubmit} + submitType="onSubmit" + hasSubmitButton={true} + defaultValues={{ + ...channel, + ...{ + settings: { + ...channel?.settings, + psk: pass, + positionEnabled: + channel?.settings?.moduleSettings?.positionPrecision !== undefined && - channel?.settings?.moduleSettings?.positionPrecision > 0, - preciseLocation: - channel?.settings?.moduleSettings?.positionPrecision === 32, - positionPrecision: - channel?.settings?.moduleSettings?.positionPrecision === undefined - ? 10 - : channel?.settings?.moduleSettings?.positionPrecision, + channel?.settings?.moduleSettings?.positionPrecision > 0, + preciseLocation: + channel?.settings?.moduleSettings?.positionPrecision === 32, + positionPrecision: + channel?.settings?.moduleSettings?.positionPrecision === undefined + ? 10 + : channel?.settings?.moduleSettings?.positionPrecision, + }, }, - }, - }} - fieldGroups={[ - { - label: "Channel Settings", - description: "Crypto, MQTT & misc settings", - fields: [ - { - type: "select", - name: "role", - label: "Role", - disabled: channel.index === 0, - description: - "Device telemetry is sent over PRIMARY. Only one PRIMARY allowed", - properties: { - enumValue: - channel.index === 0 - ? { PRIMARY: 1 } - : { DISABLED: 0, SECONDARY: 2 }, + }} + fieldGroups={[ + { + label: "Channel Settings", + description: "Crypto, MQTT & misc settings", + fields: [ + { + type: "select", + name: "role", + label: "Role", + disabled: channel.index === 0, + description: + "Device telemetry is sent over PRIMARY. Only one PRIMARY allowed", + properties: { + enumValue: + channel.index === 0 + ? { PRIMARY: 1 } + : { DISABLED: 0, SECONDARY: 2 }, + }, }, - }, - { - type: "passwordGenerator", - name: "settings.psk", - label: "pre-Shared Key", - description: "256, 128, or 8 bit PSKs allowed", - validationText: validationText, - devicePSKBitCount: bitCount ?? 0, - inputChange: inputChangeEvent, - selectChange: selectChangeEvent, - buttonClick: clickEvent, - hide: true, - properties: { - value: pass, + { + type: "passwordGenerator", + name: "settings.psk", + label: "Pre-Shared Key", + description: "256, 128, or 8 bit PSKs allowed", + validationText: validationText, + devicePSKBitCount: bitCount ?? 0, + inputChange: inputChangeEvent, + selectChange: selectChangeEvent, + actionButtons: [{ text: 'Generate', variant: 'success', onClick: preSharedClickEvent }], + hide: true, + properties: { + value: pass, + }, }, - }, - { - type: "text", - name: "settings.name", - label: "Name", - description: - "A unique name for the channel <12 bytes, leave blank for default", - }, - { - type: "toggle", - name: "settings.uplinkEnabled", - label: "Uplink Enabled", - description: "Send messages from the local mesh to MQTT", - }, - { - type: "toggle", - name: "settings.downlinkEnabled", - label: "Downlink Enabled", - description: "Send messages from MQTT to the local mesh", - }, - { - type: "toggle", - name: "settings.positionEnabled", - label: "Allow Position Requests", - description: "Send position to channel", - }, - { - type: "toggle", - name: "settings.preciseLocation", - label: "Precise Location", - description: "Send precise location to channel", - }, - { - type: "select", - name: "settings.positionPrecision", - label: "Approximate Location", - description: - "If not sharing precise location, position shared on channel will be accurate within this distance", - properties: { - enumValue: - config.display?.units === 0 - ? { + { + type: "text", + name: "settings.name", + label: "Name", + description: + "A unique name for the channel <12 bytes, leave blank for default", + }, + { + type: "toggle", + name: "settings.uplinkEnabled", + label: "Uplink Enabled", + description: "Send messages from the local mesh to MQTT", + }, + { + type: "toggle", + name: "settings.downlinkEnabled", + label: "Downlink Enabled", + description: "Send messages from MQTT to the local mesh", + }, + { + type: "toggle", + name: "settings.positionEnabled", + label: "Allow Position Requests", + description: "Send position to channel", + }, + { + type: "toggle", + name: "settings.preciseLocation", + label: "Precise Location", + description: "Send precise location to channel", + }, + { + type: "select", + name: "settings.positionPrecision", + label: "Approximate Location", + description: + "If not sharing precise location, position shared on channel will be accurate within this distance", + properties: { + enumValue: + config.display?.units === 0 + ? { "Within 23 km": 10, "Within 12 km": 11, "Within 5.8 km": 12, @@ -188,7 +196,7 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => { "Within 90 m": 18, "Within 50 m": 19, } - : { + : { "Within 15 miles": 10, "Within 7.3 miles": 11, "Within 3.6 miles": 12, @@ -200,11 +208,17 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => { "Within 300 feet": 18, "Within 150 feet": 19, }, + }, }, - }, - ], - }, - ]} - /> + ], + }, + ]} + /> + setPreSharedDialogOpen(false)} + onSubmit={() => preSharedKeyRegenerate()} + /> + ); }; diff --git a/src/components/PageComponents/Config/Security.tsx b/src/components/PageComponents/Config/Security.tsx index cf675751..d6e76cc9 100644 --- a/src/components/PageComponents/Config/Security.tsx +++ b/src/components/PageComponents/Config/Security.tsx @@ -12,7 +12,7 @@ import { Eye, EyeOff } from "lucide-react"; import { useState } from "react"; export const Security = (): JSX.Element => { - const { config, nodes, hardware, setWorkingConfig } = useDevice(); + const { config, nodes, hardware, setWorkingConfig, setDialogOpen } = useDevice(); const [privateKey, setPrivateKey] = useState( fromByteArray(config.security?.privateKey ?? new Uint8Array(0)), @@ -31,7 +31,7 @@ export const Security = (): JSX.Element => { ); const [adminKeyValidationText, setAdminKeyValidationText] = useState(); - const [dialogOpen, setDialogOpen] = useState(false); + const [privateKeyDialogOpen, setPrivateKeyDialogOpen] = useState(false); const onSubmit = (data: SecurityValidation) => { if (privateKeyValidationText || adminKeyValidationText) return; @@ -71,9 +71,13 @@ export const Security = (): JSX.Element => { }; const privateKeyClickEvent = () => { - setDialogOpen(true); + setPrivateKeyDialogOpen(true); }; + const pkiBackupClickEvent = () => { + setDialogOpen("pkiBackup", true); + } + const pkiRegenerate = () => { const privateKey = getX25519PrivateKey(); const publicKey = getX25519PublicKey(privateKey); @@ -86,7 +90,7 @@ export const Security = (): JSX.Element => { setPrivateKeyValidationText, ); - setDialogOpen(false); + setPrivateKeyDialogOpen(false); }; const privateKeyInputChangeEvent = ( @@ -149,7 +153,18 @@ export const Security = (): JSX.Element => { inputChange: privateKeyInputChangeEvent, selectChange: privateKeySelectChangeEvent, hide: !privateKeyVisible, - buttonClick: privateKeyClickEvent, + actionButtons: [ + { + text: "Generate", + onClick: privateKeyClickEvent, + variant: "success", + }, + { + text: "Backup Key", + onClick: pkiBackupClickEvent, + variant: "subtle", + }, + ], properties: { value: privateKey, action: { @@ -228,8 +243,8 @@ export const Security = (): JSX.Element => { ]} /> setDialogOpen(false)} + open={privateKeyDialogOpen} + onOpenChange={() => setPrivateKeyDialogOpen(false)} onSubmit={() => pkiRegenerate()} /> diff --git a/src/components/UI/Button.tsx b/src/components/UI/Button.tsx index 39418551..e1b2d704 100644 --- a/src/components/UI/Button.tsx +++ b/src/components/UI/Button.tsx @@ -35,9 +35,11 @@ const buttonVariants = cva( }, ); +export type ButtonVariant = VariantProps["variant"]; + export interface ButtonProps extends React.ButtonHTMLAttributes, - VariantProps {} + VariantProps { } const Button = React.forwardRef( ({ className, variant, size, ...props }, ref) => { diff --git a/src/components/UI/Generator.tsx b/src/components/UI/Generator.tsx index cb8fea26..c983b8ef 100644 --- a/src/components/UI/Generator.tsx +++ b/src/components/UI/Generator.tsx @@ -1,6 +1,6 @@ import * as React from "react"; -import { Button } from "@components/UI/Button.tsx"; +import { Button, type ButtonVariant } from "@components/UI/Button.tsx"; import { Input } from "@components/UI/Input.tsx"; import { Select, @@ -16,11 +16,15 @@ export interface GeneratorProps extends React.BaseHTMLAttributes { devicePSKBitCount?: number; value: string; variant: "default" | "invalid"; - buttonText?: string; + actionButtons: { + text: string; + onClick: React.MouseEventHandler; + variant: ButtonVariant; + className?: string; + }[]; bits?: { text: string; value: string; key: string }[]; selectChange: (event: string) => void; inputChange: (event: React.ChangeEvent) => void; - buttonClick: React.MouseEventHandler; action?: { icon: LucideIcon; onClick: () => void; @@ -35,7 +39,7 @@ const Generator = React.forwardRef( devicePSKBitCount, variant, value, - buttonText, + actionButtons, bits = [ { text: "256 bit", value: "32", key: "bit256" }, { text: "128 bit", value: "16", key: "bit128" }, @@ -43,7 +47,6 @@ const Generator = React.forwardRef( ], selectChange, inputChange, - buttonClick, action, disabled, ...props @@ -93,15 +96,21 @@ const Generator = React.forwardRef( ))} - +
+ {actionButtons?.map(({ text, onClick, variant, className }) => ( + + ))} +
); }, diff --git a/src/core/stores/deviceStore.ts b/src/core/stores/deviceStore.ts index a716a85b..bd407611 100644 --- a/src/core/stores/deviceStore.ts +++ b/src/core/stores/deviceStore.ts @@ -25,7 +25,8 @@ export type DialogVariant = | "shutdown" | "reboot" | "deviceName" - | "nodeRemoval"; + | "nodeRemoval" + | "pkiBackup"; export interface Device { id: number; @@ -60,6 +61,7 @@ export interface Device { reboot: boolean; deviceName: boolean; nodeRemoval: boolean; + pkiBackup: boolean; }; setStatus: (status: Types.DeviceStatusEnum) => void; @@ -142,6 +144,7 @@ export const useDeviceStore = create((set, get) => ({ reboot: false, deviceName: false, nodeRemoval: false, + pkiBackup: false, }, pendingSettingsChanges: false, messageDraft: "", From 7cd03c6a527140912a6dfc05815feaf9fa16a687 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Fri, 3 Jan 2025 21:40:56 -0500 Subject: [PATCH 27/47] feat: Add key backup reminder. Refactor Toast component to handle dark mode better --- package.json | 2 + pnpm-lock.yaml | 214 +++++++++++++--------- src/App.tsx | 4 +- src/components/Dialog/DialogManager.tsx | 2 +- src/components/Dialog/PKIBackupDialog.tsx | 13 +- src/components/KeyBackupReminder.tsx | 21 +++ src/components/Toaster.tsx | 17 +- src/components/UI/Toast.tsx | 70 +++---- src/core/hooks/useCookie.ts | 35 ++++ src/core/hooks/useKeyBackupReminder.tsx | 85 +++++++++ 10 files changed, 326 insertions(+), 137 deletions(-) create mode 100644 src/components/KeyBackupReminder.tsx create mode 100644 src/core/hooks/useCookie.ts create mode 100644 src/core/hooks/useKeyBackupReminder.tsx diff --git a/package.json b/package.json index 05221d9b..84a49432 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "cmdk": "^1.0.0", "crypto-random-string": "^5.0.0", "immer": "^10.1.1", + "js-cookie": "^3.0.5", "lucide-react": "^0.363.0", "mapbox-gl": "^3.6.0", "maplibre-gl": "4.1.2", @@ -70,6 +71,7 @@ "@rsbuild/core": "^1.0.10", "@rsbuild/plugin-react": "^1.0.3", "@types/chrome": "^0.0.263", + "@types/js-cookie": "^3.0.6", "@types/node": "^20.14.9", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b9edbfd5..79d6d5ff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -89,6 +89,9 @@ importers: immer: specifier: ^10.1.1 version: 10.1.1 + js-cookie: + specifier: ^3.0.5 + version: 3.0.5 lucide-react: specifier: ^0.363.0 version: 0.363.0(react@18.3.1) @@ -127,7 +130,7 @@ importers: version: 3.0.6(react@18.3.1) vite-plugin-node-polyfills: specifier: ^0.22.0 - version: 0.22.0(rollup@4.24.0)(vite@5.3.6(@types/node@20.14.9)) + version: 0.22.0(rollup@4.29.1)(vite@5.3.6(@types/node@20.14.9)) zustand: specifier: 4.5.2 version: 4.5.2(@types/react@18.3.3)(immer@10.1.1)(react@18.3.1) @@ -147,6 +150,9 @@ importers: '@types/chrome': specifier: ^0.0.263 version: 0.0.263 + '@types/js-cookie': + specifier: ^3.0.6 + version: 3.0.6 '@types/node': specifier: ^20.14.9 version: 20.14.9 @@ -173,7 +179,7 @@ importers: version: 8.4.38 rollup-plugin-visualizer: specifier: ^5.12.0 - version: 5.12.0(rollup@4.24.0) + version: 5.12.0(rollup@4.29.1) tailwindcss: specifier: ^3.4.4 version: 3.4.4 @@ -1165,83 +1171,98 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.24.0': - resolution: {integrity: sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==} + '@rollup/rollup-android-arm-eabi@4.29.1': + resolution: {integrity: sha512-ssKhA8RNltTZLpG6/QNkCSge+7mBQGUqJRisZ2MDQcEGaK93QESEgWK2iOpIDZ7k9zPVkG5AS3ksvD5ZWxmItw==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.24.0': - resolution: {integrity: sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==} + '@rollup/rollup-android-arm64@4.29.1': + resolution: {integrity: sha512-CaRfrV0cd+NIIcVVN/jx+hVLN+VRqnuzLRmfmlzpOzB87ajixsN/+9L5xNmkaUUvEbI5BmIKS+XTwXsHEb65Ew==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.24.0': - resolution: {integrity: sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==} + '@rollup/rollup-darwin-arm64@4.29.1': + resolution: {integrity: sha512-2ORr7T31Y0Mnk6qNuwtyNmy14MunTAMx06VAPI6/Ju52W10zk1i7i5U3vlDRWjhOI5quBcrvhkCHyF76bI7kEw==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.24.0': - resolution: {integrity: sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==} + '@rollup/rollup-darwin-x64@4.29.1': + resolution: {integrity: sha512-j/Ej1oanzPjmN0tirRd5K2/nncAhS9W6ICzgxV+9Y5ZsP0hiGhHJXZ2JQ53iSSjj8m6cRY6oB1GMzNn2EUt6Ng==} cpu: [x64] os: [darwin] - '@rollup/rollup-linux-arm-gnueabihf@4.24.0': - resolution: {integrity: sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==} + '@rollup/rollup-freebsd-arm64@4.29.1': + resolution: {integrity: sha512-91C//G6Dm/cv724tpt7nTyP+JdN12iqeXGFM1SqnljCmi5yTXriH7B1r8AD9dAZByHpKAumqP1Qy2vVNIdLZqw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.29.1': + resolution: {integrity: sha512-hEioiEQ9Dec2nIRoeHUP6hr1PSkXzQaCUyqBDQ9I9ik4gCXQZjJMIVzoNLBRGet+hIUb3CISMh9KXuCcWVW/8w==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.29.1': + resolution: {integrity: sha512-Py5vFd5HWYN9zxBv3WMrLAXY3yYJ6Q/aVERoeUFwiDGiMOWsMs7FokXihSOaT/PMWUty/Pj60XDQndK3eAfE6A==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.24.0': - resolution: {integrity: sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==} + '@rollup/rollup-linux-arm-musleabihf@4.29.1': + resolution: {integrity: sha512-RiWpGgbayf7LUcuSNIbahr0ys2YnEERD4gYdISA06wa0i8RALrnzflh9Wxii7zQJEB2/Eh74dX4y/sHKLWp5uQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.24.0': - resolution: {integrity: sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==} + '@rollup/rollup-linux-arm64-gnu@4.29.1': + resolution: {integrity: sha512-Z80O+taYxTQITWMjm/YqNoe9d10OX6kDh8X5/rFCMuPqsKsSyDilvfg+vd3iXIqtfmp+cnfL1UrYirkaF8SBZA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.24.0': - resolution: {integrity: sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==} + '@rollup/rollup-linux-arm64-musl@4.29.1': + resolution: {integrity: sha512-fOHRtF9gahwJk3QVp01a/GqS4hBEZCV1oKglVVq13kcK3NeVlS4BwIFzOHDbmKzt3i0OuHG4zfRP0YoG5OF/rA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': - resolution: {integrity: sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==} + '@rollup/rollup-linux-loongarch64-gnu@4.29.1': + resolution: {integrity: sha512-5a7q3tnlbcg0OodyxcAdrrCxFi0DgXJSoOuidFUzHZ2GixZXQs6Tc3CHmlvqKAmOs5eRde+JJxeIf9DonkmYkw==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.29.1': + resolution: {integrity: sha512-9b4Mg5Yfz6mRnlSPIdROcfw1BU22FQxmfjlp/CShWwO3LilKQuMISMTtAu/bxmmrE6A902W2cZJuzx8+gJ8e9w==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.24.0': - resolution: {integrity: sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==} + '@rollup/rollup-linux-riscv64-gnu@4.29.1': + resolution: {integrity: sha512-G5pn0NChlbRM8OJWpJFMX4/i8OEU538uiSv0P6roZcbpe/WfhEO+AT8SHVKfp8qhDQzaz7Q+1/ixMy7hBRidnQ==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.24.0': - resolution: {integrity: sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==} + '@rollup/rollup-linux-s390x-gnu@4.29.1': + resolution: {integrity: sha512-WM9lIkNdkhVwiArmLxFXpWndFGuOka4oJOZh8EP3Vb8q5lzdSCBuhjavJsw68Q9AKDGeOOIHYzYm4ZFvmWez5g==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.24.0': - resolution: {integrity: sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==} + '@rollup/rollup-linux-x64-gnu@4.29.1': + resolution: {integrity: sha512-87xYCwb0cPGZFoGiErT1eDcssByaLX4fc0z2nRM6eMtV9njAfEE6OW3UniAoDhX4Iq5xQVpE6qO9aJbCFumKYQ==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.24.0': - resolution: {integrity: sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==} + '@rollup/rollup-linux-x64-musl@4.29.1': + resolution: {integrity: sha512-xufkSNppNOdVRCEC4WKvlR1FBDyqCSCpQeMMgv9ZyXqqtKBfkw1yfGMTUTs9Qsl6WQbJnsGboWCp7pJGkeMhKA==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.24.0': - resolution: {integrity: sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==} + '@rollup/rollup-win32-arm64-msvc@4.29.1': + resolution: {integrity: sha512-F2OiJ42m77lSkizZQLuC+jiZ2cgueWQL5YC9tjo3AgaEw+KJmVxHGSyQfDUoYR9cci0lAywv2Clmckzulcq6ig==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.24.0': - resolution: {integrity: sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==} + '@rollup/rollup-win32-ia32-msvc@4.29.1': + resolution: {integrity: sha512-rYRe5S0FcjlOBZQHgbTKNrqxCBUmgDJem/VQTCcTnA2KCabYSWQDrytOzX7avb79cAAweNmMUb/Zw18RNd4mng==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.24.0': - resolution: {integrity: sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==} + '@rollup/rollup-win32-x64-msvc@4.29.1': + resolution: {integrity: sha512-+10CMg9vt1MoHj6x1pxyjPSMjHTIlqs8/tBztXvPAx24SKs9jwVnKqHJumlH/IzhaPUaj3T6T6wfZr8okdXaIg==} cpu: [x64] os: [win32] @@ -1690,6 +1711,9 @@ packages: '@types/har-format@1.2.15': resolution: {integrity: sha512-RpQH4rXLuvTXKR0zqHq3go0RVXYv/YVqv4TnPH95VbwUxZdQlK1EtcMvQvMpDngHbt13Csh9Z4qT9AbkiQH5BA==} + '@types/js-cookie@3.0.6': + resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==} + '@types/mapbox-gl@3.1.0': resolution: {integrity: sha512-hI6cQDjw1bkJw7MC/eHMqq5TWUamLwsujnUUeiIX2KDRjxRNSYMjnHz07+LATz9I9XIsKumOtUz4gRYnZOJ/FA==} @@ -1864,8 +1888,8 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - caniuse-lite@1.0.30001638: - resolution: {integrity: sha512-5SuJUJ7cZnhPpeLHaH0c/HPAnAHZvS6ElWyHK9GSIbVOQABLzowiI2pjmpvZ1WEbkyz46iFd4UXlOHR5SqgfMQ==} + caniuse-lite@1.0.30001690: + resolution: {integrity: sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==} cheap-ruler@4.0.0: resolution: {integrity: sha512-0BJa8f4t141BYKQyn9NSQt1PguFQXMXwZiA5shfoaBYHAb2fFk2RAX+tiWMoQU+Agtzt3mdt0JtuyshAXqZ+Vw==} @@ -2417,6 +2441,10 @@ packages: resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} hasBin: true + js-cookie@3.0.5: + resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} + engines: {node: '>=14'} + js-sha3@0.8.0: resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} @@ -2923,8 +2951,8 @@ packages: rollup: optional: true - rollup@4.24.0: - resolution: {integrity: sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==} + rollup@4.29.1: + resolution: {integrity: sha512-RaJ45M/kmJUzSWDs1Nnd5DdV4eerC98idtUOVr6FfKcgxqvjwHmxc5upLF9qZU9EpsVzzhleFahrT3shLuJzIw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -4258,68 +4286,77 @@ snapshots: '@radix-ui/rect@1.1.0': {} - '@rollup/plugin-inject@5.0.5(rollup@4.24.0)': + '@rollup/plugin-inject@5.0.5(rollup@4.29.1)': dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.24.0) + '@rollup/pluginutils': 5.1.0(rollup@4.29.1) estree-walker: 2.0.2 magic-string: 0.30.11 optionalDependencies: - rollup: 4.24.0 + rollup: 4.29.1 - '@rollup/pluginutils@5.1.0(rollup@4.24.0)': + '@rollup/pluginutils@5.1.0(rollup@4.29.1)': dependencies: '@types/estree': 1.0.5 estree-walker: 2.0.2 picomatch: 2.3.1 optionalDependencies: - rollup: 4.24.0 + rollup: 4.29.1 + + '@rollup/rollup-android-arm-eabi@4.29.1': + optional: true + + '@rollup/rollup-android-arm64@4.29.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.29.1': + optional: true - '@rollup/rollup-android-arm-eabi@4.24.0': + '@rollup/rollup-darwin-x64@4.29.1': optional: true - '@rollup/rollup-android-arm64@4.24.0': + '@rollup/rollup-freebsd-arm64@4.29.1': optional: true - '@rollup/rollup-darwin-arm64@4.24.0': + '@rollup/rollup-freebsd-x64@4.29.1': optional: true - '@rollup/rollup-darwin-x64@4.24.0': + '@rollup/rollup-linux-arm-gnueabihf@4.29.1': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.24.0': + '@rollup/rollup-linux-arm-musleabihf@4.29.1': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.24.0': + '@rollup/rollup-linux-arm64-gnu@4.29.1': optional: true - '@rollup/rollup-linux-arm64-gnu@4.24.0': + '@rollup/rollup-linux-arm64-musl@4.29.1': optional: true - '@rollup/rollup-linux-arm64-musl@4.24.0': + '@rollup/rollup-linux-loongarch64-gnu@4.29.1': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': + '@rollup/rollup-linux-powerpc64le-gnu@4.29.1': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.24.0': + '@rollup/rollup-linux-riscv64-gnu@4.29.1': optional: true - '@rollup/rollup-linux-s390x-gnu@4.24.0': + '@rollup/rollup-linux-s390x-gnu@4.29.1': optional: true - '@rollup/rollup-linux-x64-gnu@4.24.0': + '@rollup/rollup-linux-x64-gnu@4.29.1': optional: true - '@rollup/rollup-linux-x64-musl@4.24.0': + '@rollup/rollup-linux-x64-musl@4.29.1': optional: true - '@rollup/rollup-win32-arm64-msvc@4.24.0': + '@rollup/rollup-win32-arm64-msvc@4.29.1': optional: true - '@rollup/rollup-win32-ia32-msvc@4.24.0': + '@rollup/rollup-win32-ia32-msvc@4.29.1': optional: true - '@rollup/rollup-win32-x64-msvc@4.24.0': + '@rollup/rollup-win32-x64-msvc@4.29.1': optional: true '@rsbuild/core@1.0.10': @@ -4381,7 +4418,7 @@ snapshots: '@module-federation/runtime-tools': 0.5.1 '@rspack/binding': 1.0.8 '@rspack/lite-tapable': 1.0.1 - caniuse-lite: 1.0.30001638 + caniuse-lite: 1.0.30001690 optionalDependencies: '@swc/helpers': 0.5.13 @@ -5255,6 +5292,8 @@ snapshots: '@types/har-format@1.2.15': {} + '@types/js-cookie@3.0.6': {} + '@types/mapbox-gl@3.1.0': dependencies: '@types/geojson': 7946.0.14 @@ -5343,7 +5382,7 @@ snapshots: autoprefixer@10.4.19(postcss@8.4.38): dependencies: browserslist: 4.23.1 - caniuse-lite: 1.0.30001638 + caniuse-lite: 1.0.30001690 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.0.1 @@ -5424,7 +5463,7 @@ snapshots: browserslist@4.23.1: dependencies: - caniuse-lite: 1.0.30001638 + caniuse-lite: 1.0.30001690 electron-to-chromium: 1.4.812 node-releases: 2.0.14 update-browserslist-db: 1.0.16(browserslist@4.23.1) @@ -5459,7 +5498,7 @@ snapshots: camelcase-css@2.0.1: {} - caniuse-lite@1.0.30001638: {} + caniuse-lite@1.0.30001690: {} cheap-ruler@4.0.0: {} @@ -6075,6 +6114,8 @@ snapshots: jiti@1.21.6: {} + js-cookie@3.0.5: {} + js-sha3@0.8.0: {} js-tokens@4.0.0: {} @@ -6620,35 +6661,38 @@ snapshots: robust-predicates@3.0.2: {} - rollup-plugin-visualizer@5.12.0(rollup@4.24.0): + rollup-plugin-visualizer@5.12.0(rollup@4.29.1): dependencies: open: 8.4.2 picomatch: 2.3.1 source-map: 0.7.4 yargs: 17.7.2 optionalDependencies: - rollup: 4.24.0 + rollup: 4.29.1 - rollup@4.24.0: + rollup@4.29.1: dependencies: '@types/estree': 1.0.6 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.24.0 - '@rollup/rollup-android-arm64': 4.24.0 - '@rollup/rollup-darwin-arm64': 4.24.0 - '@rollup/rollup-darwin-x64': 4.24.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.24.0 - '@rollup/rollup-linux-arm-musleabihf': 4.24.0 - '@rollup/rollup-linux-arm64-gnu': 4.24.0 - '@rollup/rollup-linux-arm64-musl': 4.24.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.24.0 - '@rollup/rollup-linux-riscv64-gnu': 4.24.0 - '@rollup/rollup-linux-s390x-gnu': 4.24.0 - '@rollup/rollup-linux-x64-gnu': 4.24.0 - '@rollup/rollup-linux-x64-musl': 4.24.0 - '@rollup/rollup-win32-arm64-msvc': 4.24.0 - '@rollup/rollup-win32-ia32-msvc': 4.24.0 - '@rollup/rollup-win32-x64-msvc': 4.24.0 + '@rollup/rollup-android-arm-eabi': 4.29.1 + '@rollup/rollup-android-arm64': 4.29.1 + '@rollup/rollup-darwin-arm64': 4.29.1 + '@rollup/rollup-darwin-x64': 4.29.1 + '@rollup/rollup-freebsd-arm64': 4.29.1 + '@rollup/rollup-freebsd-x64': 4.29.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.29.1 + '@rollup/rollup-linux-arm-musleabihf': 4.29.1 + '@rollup/rollup-linux-arm64-gnu': 4.29.1 + '@rollup/rollup-linux-arm64-musl': 4.29.1 + '@rollup/rollup-linux-loongarch64-gnu': 4.29.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.29.1 + '@rollup/rollup-linux-riscv64-gnu': 4.29.1 + '@rollup/rollup-linux-s390x-gnu': 4.29.1 + '@rollup/rollup-linux-x64-gnu': 4.29.1 + '@rollup/rollup-linux-x64-musl': 4.29.1 + '@rollup/rollup-win32-arm64-msvc': 4.29.1 + '@rollup/rollup-win32-ia32-msvc': 4.29.1 + '@rollup/rollup-win32-x64-msvc': 4.29.1 fsevents: 2.3.3 run-parallel@1.2.0: @@ -6986,9 +7030,9 @@ snapshots: validator@13.12.0: {} - vite-plugin-node-polyfills@0.22.0(rollup@4.24.0)(vite@5.3.6(@types/node@20.14.9)): + vite-plugin-node-polyfills@0.22.0(rollup@4.29.1)(vite@5.3.6(@types/node@20.14.9)): dependencies: - '@rollup/plugin-inject': 5.0.5(rollup@4.24.0) + '@rollup/plugin-inject': 5.0.5(rollup@4.29.1) node-stdlib-browser: 1.2.0 vite: 5.3.6(@types/node@20.14.9) transitivePeerDependencies: @@ -6998,7 +7042,7 @@ snapshots: dependencies: esbuild: 0.21.5 postcss: 8.4.49 - rollup: 4.24.0 + rollup: 4.29.1 optionalDependencies: '@types/node': 20.14.9 fsevents: 2.3.3 diff --git a/src/App.tsx b/src/App.tsx index c936bc9c..8c15dc97 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,7 +2,7 @@ import { DeviceWrapper } from "@app/DeviceWrapper.tsx"; import { PageRouter } from "@app/PageRouter.tsx"; import { CommandPalette } from "@components/CommandPalette.tsx"; import { DeviceSelector } from "@components/DeviceSelector.tsx"; -import { DialogManager } from "@components/Dialog/DialogManager.tsx"; +import { DialogManager } from "@components/Dialog/DialogManager"; import { NewDeviceDialog } from "@components/Dialog/NewDeviceDialog.tsx"; import { Toaster } from "@components/Toaster.tsx"; import Footer from "@components/UI/Footer.tsx"; @@ -11,6 +11,7 @@ import { useAppStore } from "@core/stores/appStore.ts"; import { useDeviceStore } from "@core/stores/deviceStore.ts"; import { Dashboard } from "@pages/Dashboard/index.tsx"; import { MapProvider } from "react-map-gl"; +import { KeyBackupReminder } from "@components/KeyBackupReminder"; export const App = (): JSX.Element => { const { getDevice } = useDeviceStore(); @@ -37,6 +38,7 @@ export const App = (): JSX.Element => { {device ? (
+
diff --git a/src/components/Dialog/DialogManager.tsx b/src/components/Dialog/DialogManager.tsx index 762bd5a7..afebdf5d 100644 --- a/src/components/Dialog/DialogManager.tsx +++ b/src/components/Dialog/DialogManager.tsx @@ -5,7 +5,7 @@ import { QRDialog } from "@components/Dialog/QRDialog.tsx"; import { RebootDialog } from "@components/Dialog/RebootDialog.tsx"; import { ShutdownDialog } from "@components/Dialog/ShutdownDialog.tsx"; import { useDevice } from "@core/stores/deviceStore.ts"; -import { PkiBackupDialog } from "./PKIBackupDialog"; +import { PkiBackupDialog } from "@components/Dialog/PKIBackupDialog"; export const DialogManager = (): JSX.Element => { const { channels, config, dialog, setDialogOpen } = useDevice(); diff --git a/src/components/Dialog/PKIBackupDialog.tsx b/src/components/Dialog/PKIBackupDialog.tsx index 3737236a..4b482bf7 100644 --- a/src/components/Dialog/PKIBackupDialog.tsx +++ b/src/components/Dialog/PKIBackupDialog.tsx @@ -21,7 +21,7 @@ export const PkiBackupDialog = ({ open, onOpenChange, }: PkiBackupDialogProps) => { - const { config } = useDevice(); + const { config, setDialogOpen } = useDevice(); const privateKeyData = config.security?.privateKey // If the private data doesn't exist return null @@ -31,6 +31,10 @@ export const PkiBackupDialog = ({ const getPrivateKey = React.useMemo(() => fromByteArray(config.security?.privateKey ?? new Uint8Array(0)), [config.security?.privateKey]); + const closeDialog = React.useCallback(() => { + setDialogOpen("pkiBackup", false) + }, [setDialogOpen]) + const renderPrintWindow = React.useCallback(() => { const printWindow = window.open("", "_blank"); if (printWindow) { @@ -52,8 +56,10 @@ export const PkiBackupDialog = ({ `); printWindow.document.close(); printWindow.print(); + closeDialog() + } - }, [getPrivateKey]); + }, [getPrivateKey, closeDialog]); const createDownloadKeyFile = React.useCallback(() => { const blob = new Blob([getPrivateKey], { type: "text/plain" }); @@ -65,8 +71,9 @@ export const PkiBackupDialog = ({ document.body.appendChild(link); link.click(); document.body.removeChild(link); + closeDialog() URL.revokeObjectURL(url); - }, [getPrivateKey]); + }, [getPrivateKey, closeDialog]); return ( diff --git a/src/components/KeyBackupReminder.tsx b/src/components/KeyBackupReminder.tsx new file mode 100644 index 00000000..45819bd7 --- /dev/null +++ b/src/components/KeyBackupReminder.tsx @@ -0,0 +1,21 @@ +import { useBackupReminder } from "@app/core/hooks/useKeyBackupReminder"; +import { useDevice } from "@app/core/stores/deviceStore"; + +export const KeyBackupReminder = (): JSX.Element => { + const { setDialogOpen } = useDevice(); + + useBackupReminder({ + suppressDays: 7, + message: "We recommend backing up your key data regularly. Would you like to back up now?", + onAccept: () => setDialogOpen("pkiBackup", true), + cookieOptions: { + secure: true, + sameSite: 'strict' + } + }); + + return ( + <> + + ); +}; diff --git a/src/components/Toaster.tsx b/src/components/Toaster.tsx index cfe13a9d..cf322758 100644 --- a/src/components/Toaster.tsx +++ b/src/components/Toaster.tsx @@ -1,5 +1,3 @@ -import { useToast } from "@core/hooks/useToast.ts"; - import { Toast, ToastClose, @@ -7,7 +5,8 @@ import { ToastProvider, ToastTitle, ToastViewport, -} from "@components/UI/Toast.tsx"; +} from "@components/UI/Toast"; +import { useToast } from "@core/hooks/useToast"; export function Toaster() { const { toasts } = useToast(); @@ -15,16 +14,10 @@ export function Toaster() { return ( {toasts.map(({ id, title, description, action, ...props }) => ( - +
- {title && ( - {title} - )} - {description && ( - - {description} - - )} + {title && {title}} + {description && {description}}
{action} diff --git a/src/components/UI/Toast.tsx b/src/components/UI/Toast.tsx index ca8dfc90..d40b294a 100644 --- a/src/components/UI/Toast.tsx +++ b/src/components/UI/Toast.tsx @@ -1,11 +1,11 @@ -import * as ToastPrimitives from "@radix-ui/react-toast"; -import { type VariantProps, cva } from "class-variance-authority"; -import { X } from "lucide-react"; -import * as React from "react"; +import * as React from "react" +import * as ToastPrimitives from "@radix-ui/react-toast" +import { cva, type VariantProps } from "class-variance-authority" +import { X } from 'lucide-react' -import { cn } from "@core/utils/cn.ts"; +import { cn } from "@core/utils/cn" -const ToastProvider = ToastPrimitives.Provider; +const ToastProvider = ToastPrimitives.Provider const ToastViewport = React.forwardRef< React.ElementRef, @@ -14,35 +14,34 @@ const ToastViewport = React.forwardRef< -)); -ToastViewport.displayName = ToastPrimitives.Viewport.displayName; +)) +ToastViewport.displayName = ToastPrimitives.Viewport.displayName const toastVariants = cva( - "data-[swipe=move]:transition-none grow-1 group relative pointer-events-auto flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full mt-4 data-[state=closed]:slide-out-to-right-full dark:border-slate-700 last:mt-0 sm:last:mt-4", + "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", { variants: { variant: { - default: - "bg-white border-slate-200 dark:bg-slate-800 dark:border-slate-700", + default: "border bg-background text-foreground dark:bg-slate-700 dark:border-slate-600 dark:text-slate-50", destructive: - "group destructive bg-red-600 text-white border-red-600 dark:border-red-600", + "group destructive bg-red-600 text-white dark:border-red-900 dark:bg-red-900 dark:text-red-50" }, }, defaultVariants: { variant: "default", }, - }, -); + } +) const Toast = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & - VariantProps + VariantProps >(({ className, variant, ...props }, ref) => { return ( - ); -}); -Toast.displayName = ToastPrimitives.Root.displayName; + ) +}) +Toast.displayName = ToastPrimitives.Root.displayName const ToastAction = React.forwardRef< React.ElementRef, @@ -61,13 +60,13 @@ const ToastAction = React.forwardRef< -)); -ToastAction.displayName = ToastPrimitives.Action.displayName; +)) +ToastAction.displayName = ToastPrimitives.Action.displayName const ToastClose = React.forwardRef< React.ElementRef, @@ -76,16 +75,16 @@ const ToastClose = React.forwardRef< -)); -ToastClose.displayName = ToastPrimitives.Close.displayName; +)) +ToastClose.displayName = ToastPrimitives.Close.displayName const ToastTitle = React.forwardRef< React.ElementRef, @@ -96,8 +95,8 @@ const ToastTitle = React.forwardRef< className={cn("text-sm font-semibold", className)} {...props} /> -)); -ToastTitle.displayName = ToastPrimitives.Title.displayName; +)) +ToastTitle.displayName = ToastPrimitives.Title.displayName const ToastDescription = React.forwardRef< React.ElementRef, @@ -108,12 +107,12 @@ const ToastDescription = React.forwardRef< className={cn("text-sm opacity-90", className)} {...props} /> -)); -ToastDescription.displayName = ToastPrimitives.Description.displayName; +)) +ToastDescription.displayName = ToastPrimitives.Description.displayName -type ToastProps = React.ComponentPropsWithoutRef; +type ToastProps = React.ComponentPropsWithoutRef -type ToastActionElement = React.ReactElement; +type ToastActionElement = React.ReactElement export { type ToastProps, @@ -125,4 +124,5 @@ export { ToastDescription, ToastClose, ToastAction, -}; +} + diff --git a/src/core/hooks/useCookie.ts b/src/core/hooks/useCookie.ts new file mode 100644 index 00000000..6a1eb311 --- /dev/null +++ b/src/core/hooks/useCookie.ts @@ -0,0 +1,35 @@ +import React from "react"; +import Cookies, { type CookieAttributes } from "js-cookie"; + +type Cookie = [ + T | undefined, + (value: T, options?: CookieAttributes) => void, + () => void, +]; + +const useCookie = ( + cookieName: string, + initialValue?: T, +): Cookie => { + const [cookieValue, setCookieValue] = React.useState(() => { + const cookie = Cookies.get(cookieName); + return cookie ? (JSON.parse(cookie) as T) : initialValue; + }); + + const setCookie = React.useCallback( + (value: T, options?: CookieAttributes) => { + Cookies.set(cookieName, JSON.stringify(value), options); + setCookieValue(value); + }, + [cookieName], + ); + + const removeCookie = React.useCallback(() => { + Cookies.remove(cookieName); + setCookieValue(undefined); + }, [cookieName]); + + return [cookieValue, setCookie, removeCookie]; +}; + +export default useCookie; diff --git a/src/core/hooks/useKeyBackupReminder.tsx b/src/core/hooks/useKeyBackupReminder.tsx new file mode 100644 index 00000000..8ce9e1d8 --- /dev/null +++ b/src/core/hooks/useKeyBackupReminder.tsx @@ -0,0 +1,85 @@ +import { useEffect, useCallback } from 'react'; +import { useToast } from './useToast'; +import useCookie from './useCookie'; +import type { CookieAttributes } from 'js-cookie'; +import { Button } from '@app/components/UI/Button'; + +interface UseBackupReminderOptions { + suppressDays?: number; + message?: string; + onAccept?: () => void | Promise; + cookieName?: string; + cookieOptions?: CookieAttributes; +} + +interface ReminderState { + suppressed: boolean; + lastShown: string; +} + +export function useBackupReminder({ + suppressDays = 365, + message = "It's time to back up your key data. Would you like to do this now?", + onAccept = () => { }, + cookieName = "backup_reminder_state", + cookieOptions = {}, +}: UseBackupReminderOptions = {}) { + const { toast } = useToast(); + + const [reminderState, setReminderState, resetReminderState] = useCookie(cookieName); + + const suppressReminder = useCallback(() => { + const expiryDate = new Date(); + expiryDate.setDate(expiryDate.getDate() + suppressDays); + + setReminderState( + { + suppressed: true, + lastShown: new Date().toISOString(), + }, + { + ...cookieOptions, + expires: expiryDate, + } + ); + + }, [setReminderState, suppressDays, cookieOptions]); + + useEffect(() => { + if (!reminderState) { + const { dismiss: dimissToast } = toast({ + title: "Backup Reminder", + description: message, + action: ( +
+ + +
+ ), + }); + } + }, [reminderState]); + + return { + resetReminder: resetReminderState + }; +} \ No newline at end of file From 1c8476df534c6a9f2d2395895c56ec5523992409 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Mon, 6 Jan 2025 11:43:36 -0500 Subject: [PATCH 28/47] refactor: consolidate browser feature detection into typed hook. Update connect dialog messaging to describe requirement for https when conneecting --- src/components/Dialog/NewDeviceDialog.tsx | 73 +++++++++++++------- src/core/hooks/useBrowserFeatureDetection.ts | 32 +++++++++ 2 files changed, 80 insertions(+), 25 deletions(-) create mode 100644 src/core/hooks/useBrowserFeatureDetection.ts diff --git a/src/components/Dialog/NewDeviceDialog.tsx b/src/components/Dialog/NewDeviceDialog.tsx index 6062d69c..92d4c75a 100644 --- a/src/components/Dialog/NewDeviceDialog.tsx +++ b/src/components/Dialog/NewDeviceDialog.tsx @@ -1,3 +1,4 @@ +import { useBrowserFeatureDetection } from "@app/core/hooks/useBrowserFeatureDetection"; import { BLE } from "@components/PageComponents/Connect/BLE.tsx"; import { HTTP } from "@components/PageComponents/Connect/HTTP.tsx"; import { Serial } from "@components/PageComponents/Connect/Serial.tsx"; @@ -28,30 +29,8 @@ export interface TabManifest { disabledLink?: string; } -const tabs: TabManifest[] = [ - { - label: "HTTP", - element: HTTP, - disabled: false, - disabledMessage: "Unsuported connection method", - }, - { - label: "Bluetooth", - element: BLE, - disabled: !navigator.bluetooth, - disabledMessage: - "Web Bluetooth is currently only supported by Chromium-based browsers", - disabledLink: - "https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API#browser_compatibility", - }, - { - label: "Serial", - element: Serial, - disabled: !navigator.serial, - disabledMessage: - "WebSerial is currently only supported by Chromium based browsers: https://developer.mozilla.org/en-US/docs/Web/API/Web_Bluetooth_API#browser_compatibility", - }, -]; + + export interface NewDeviceProps { open: boolean; onOpenChange: (open: boolean) => void; @@ -61,6 +40,36 @@ export const NewDeviceDialog = ({ open, onOpenChange, }: NewDeviceProps): JSX.Element => { + const { hasRequiredFeatures, isSecureContext, missingFeatures } = useBrowserFeatureDetection(); + console.log(missingFeatures); + + const tabs: TabManifest[] = [ + { + label: "HTTP", + element: HTTP, + disabled: false, + disabledMessage: "Unsuported connection method", + }, + { + label: "Bluetooth", + element: BLE, + disabled: missingFeatures.includes("Web Bluetooth"), + disabledMessage: + "Web Bluetooth is currently only supported by Chromium-based browsers", + disabledLink: + "https://developer.mozilla.org/en-US/docs/Web/API/Web_Bluetooth_API#browser_compatibility" + }, + { + label: "Serial", + element: Serial, + disabled: missingFeatures.includes("Web Serial"), + disabledMessage: + "Web Serial is currently only supported by Chromium based browsers", + disabledLink: "https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API#browser_compatibility", + }, + ]; + + return ( @@ -92,7 +101,21 @@ export const NewDeviceDialog = ({ ))} - {(!navigator.bluetooth || !navigator.serial) && ( + {!isSecureContext && ( + <> + + Web Bluetooth and Web Serial require using a HTTPS connection or to localhost. + + + Read more:  + + Secure Contexts + + + + )} + + {!hasRequiredFeatures && ( <> Web Bluetooth and Web Serial are currently only supported by diff --git a/src/core/hooks/useBrowserFeatureDetection.ts b/src/core/hooks/useBrowserFeatureDetection.ts new file mode 100644 index 00000000..df103b2c --- /dev/null +++ b/src/core/hooks/useBrowserFeatureDetection.ts @@ -0,0 +1,32 @@ +type Feature = 'Web Bluetooth' | 'Web Serial'; +type FeatureKey = 'bluetooth' | 'serial'; + +interface BrowserFeatureDetection { + hasRequiredFeatures: boolean; + missingFeatures: Feature[]; + isSecureContext: boolean; +} + +const featureLabels: Record = { + bluetooth: 'Web Bluetooth', + serial: 'Web Serial' +}; + +export function useBrowserFeatureDetection(): BrowserFeatureDetection { + const { bluetooth, serial } = navigator; + const isSecureContext = window.location.protocol === 'https:' || + window.location.hostname === 'localhost'; + + const features = { + bluetooth, + serial + }; + + return { + hasRequiredFeatures: Object.values(features).every(Boolean), + missingFeatures: Object.entries(features) + .filter(([_, supported]) => !supported) + .map(([feature]) => featureLabels[feature as FeatureKey]), + isSecureContext + }; +} \ No newline at end of file From 63be65a4871e9e391a4d6653e684e612b9c4e211 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Mon, 6 Jan 2025 13:45:45 -0500 Subject: [PATCH 29/47] feat: added empty value to pre-shared key size. --- src/components/PageComponents/Channel.tsx | 50 +++++++++++------------ src/components/UI/Generator.tsx | 1 + 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/src/components/PageComponents/Channel.tsx b/src/components/PageComponents/Channel.tsx index 3655e5f8..e281b242 100644 --- a/src/components/PageComponents/Channel.tsx +++ b/src/components/PageComponents/Channel.tsx @@ -91,7 +91,7 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => { psk: pass, positionEnabled: channel?.settings?.moduleSettings?.positionPrecision !== - undefined && + undefined && channel?.settings?.moduleSettings?.positionPrecision > 0, preciseLocation: channel?.settings?.moduleSettings?.positionPrecision === 32, @@ -124,8 +124,8 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => { { type: "passwordGenerator", name: "settings.psk", - label: "pre-Shared Key", - description: "256, 128, or 8 bit PSKs allowed", + label: "Pre-Shared Key", + description: "Supported PSK lengths: 256-bit, 128-bit, 8-bit, Empty (0-bit)", validationText: validationText, devicePSKBitCount: bitCount ?? 0, inputChange: inputChangeEvent, @@ -177,29 +177,29 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => { enumValue: config.display?.units === 0 ? { - "Within 23 km": 10, - "Within 12 km": 11, - "Within 5.8 km": 12, - "Within 2.9 km": 13, - "Within 1.5 km": 14, - "Within 700 m": 15, - "Within 350 m": 16, - "Within 200 m": 17, - "Within 90 m": 18, - "Within 50 m": 19, - } + "Within 23 km": 10, + "Within 12 km": 11, + "Within 5.8 km": 12, + "Within 2.9 km": 13, + "Within 1.5 km": 14, + "Within 700 m": 15, + "Within 350 m": 16, + "Within 200 m": 17, + "Within 90 m": 18, + "Within 50 m": 19, + } : { - "Within 15 miles": 10, - "Within 7.3 miles": 11, - "Within 3.6 miles": 12, - "Within 1.8 miles": 13, - "Within 0.9 miles": 14, - "Within 0.5 miles": 15, - "Within 0.2 miles": 16, - "Within 600 feet": 17, - "Within 300 feet": 18, - "Within 150 feet": 19, - }, + "Within 15 miles": 10, + "Within 7.3 miles": 11, + "Within 3.6 miles": 12, + "Within 1.8 miles": 13, + "Within 0.9 miles": 14, + "Within 0.5 miles": 15, + "Within 0.2 miles": 16, + "Within 600 feet": 17, + "Within 300 feet": 18, + "Within 150 feet": 19, + }, }, }, ], diff --git a/src/components/UI/Generator.tsx b/src/components/UI/Generator.tsx index cb8fea26..5e29ef49 100644 --- a/src/components/UI/Generator.tsx +++ b/src/components/UI/Generator.tsx @@ -40,6 +40,7 @@ const Generator = React.forwardRef( { text: "256 bit", value: "32", key: "bit256" }, { text: "128 bit", value: "16", key: "bit128" }, { text: "8 bit", value: "1", key: "bit8" }, + { text: "Empty", value: "0", key: "empty" }, ], selectChange, inputChange, From cbabcd4782c3097ce1113008dca8eed4ebc34ce2 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Mon, 6 Jan 2025 16:00:13 -0500 Subject: [PATCH 30/47] feat: added delay to toast appearing to avoid conflicting with messages/node loading. --- src/core/hooks/useKeyBackupReminder.tsx | 62 +++++++++++++------------ 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/src/core/hooks/useKeyBackupReminder.tsx b/src/core/hooks/useKeyBackupReminder.tsx index 8ce9e1d8..9af1ec70 100644 --- a/src/core/hooks/useKeyBackupReminder.tsx +++ b/src/core/hooks/useKeyBackupReminder.tsx @@ -17,6 +17,8 @@ interface ReminderState { lastShown: string; } +const TOAST_DELAY = 10000; + export function useBackupReminder({ suppressDays = 365, message = "It's time to back up your key data. Would you like to do this now?", @@ -47,35 +49,37 @@ export function useBackupReminder({ useEffect(() => { if (!reminderState) { - const { dismiss: dimissToast } = toast({ - title: "Backup Reminder", - description: message, - action: ( -
- - -
- ), - }); + setTimeout(() => { + const { dismiss: dimissToast } = toast({ + title: "Backup Reminder", + description: message, + action: ( +
+ + +
+ ), + }); + }, TOAST_DELAY); } }, [reminderState]); From 74e33d09b1133cd7b4f2e4f96f2e4ee33c9d5780 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Fri, 3 Jan 2025 12:05:29 -0500 Subject: [PATCH 31/47] feat: Add pki backup dialog, refactor Channels pre-shared key to support regenerate dialog --- src/components/Dialog/DialogManager.tsx | 7 + src/components/Dialog/PKIBackupDialog.tsx | 104 +++++++ src/components/Form/FormPasswordGenerator.tsx | 17 +- src/components/PageComponents/Channel.tsx | 262 +++++++++--------- .../PageComponents/Config/Security.tsx | 29 +- src/components/UI/Button.tsx | 4 +- src/components/UI/Generator.tsx | 37 ++- src/core/stores/deviceStore.ts | 5 +- 8 files changed, 312 insertions(+), 153 deletions(-) create mode 100644 src/components/Dialog/PKIBackupDialog.tsx diff --git a/src/components/Dialog/DialogManager.tsx b/src/components/Dialog/DialogManager.tsx index 16e60120..762bd5a7 100644 --- a/src/components/Dialog/DialogManager.tsx +++ b/src/components/Dialog/DialogManager.tsx @@ -5,6 +5,7 @@ import { QRDialog } from "@components/Dialog/QRDialog.tsx"; import { RebootDialog } from "@components/Dialog/RebootDialog.tsx"; import { ShutdownDialog } from "@components/Dialog/ShutdownDialog.tsx"; import { useDevice } from "@core/stores/deviceStore.ts"; +import { PkiBackupDialog } from "./PKIBackupDialog"; export const DialogManager = (): JSX.Element => { const { channels, config, dialog, setDialogOpen } = useDevice(); @@ -49,6 +50,12 @@ export const DialogManager = (): JSX.Element => { setDialogOpen("nodeRemoval", open); }} /> + { + setDialogOpen("pkiBackup", open); + }} + /> ); }; diff --git a/src/components/Dialog/PKIBackupDialog.tsx b/src/components/Dialog/PKIBackupDialog.tsx new file mode 100644 index 00000000..3737236a --- /dev/null +++ b/src/components/Dialog/PKIBackupDialog.tsx @@ -0,0 +1,104 @@ +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@components/UI/Dialog.tsx"; +import { Button } from "@components/UI/Button"; +import { DownloadIcon, PrinterIcon } from "lucide-react"; +import React from "react"; +import { useDevice } from "@app/core/stores/deviceStore"; +import { fromByteArray } from "base64-js"; + +export interface PkiBackupDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export const PkiBackupDialog = ({ + open, + onOpenChange, +}: PkiBackupDialogProps) => { + const { config } = useDevice(); + const privateKeyData = config.security?.privateKey + + // If the private data doesn't exist return null + if (!privateKeyData) { + return null + } + + const getPrivateKey = React.useMemo(() => fromByteArray(config.security?.privateKey ?? new Uint8Array(0)), [config.security?.privateKey]); + + const renderPrintWindow = React.useCallback(() => { + const printWindow = window.open("", "_blank"); + if (printWindow) { + printWindow.document.write(` + + + Your Private Key + + + +

Your Private Key

+

${getPrivateKey}

+ + + `); + printWindow.document.close(); + printWindow.print(); + } + }, [getPrivateKey]); + + const createDownloadKeyFile = React.useCallback(() => { + const blob = new Blob([getPrivateKey], { type: "text/plain" }); + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + link.download = "meshtastic_private_key.txt"; + link.style.display = "none"; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + }, [getPrivateKey]); + + + return ( + + + + Backup Key + + Its important to backup your private key and store your backup securely! + + + If you lose your private key, you will need to reset your device. + + + + + + + + + ); +}; diff --git a/src/components/Form/FormPasswordGenerator.tsx b/src/components/Form/FormPasswordGenerator.tsx index 09e97ff5..784086fb 100644 --- a/src/components/Form/FormPasswordGenerator.tsx +++ b/src/components/Form/FormPasswordGenerator.tsx @@ -7,6 +7,7 @@ import { Eye, EyeOff } from "lucide-react"; import type { ChangeEventHandler, MouseEventHandler } from "react"; import { useState } from "react"; import { Controller, type FieldValues } from "react-hook-form"; +import type { ButtonVariant } from "@components/UI/Button"; export interface PasswordGeneratorProps extends BaseFormBuilderProps { type: "passwordGenerator"; @@ -15,7 +16,12 @@ export interface PasswordGeneratorProps extends BaseFormBuilderProps { devicePSKBitCount: number; inputChange: ChangeEventHandler; selectChange: (event: string) => void; - buttonClick: MouseEventHandler; + actionButtons: { + text: string; + onClick: React.MouseEventHandler; + variant: ButtonVariant; + className?: string; + }[]; } export function PasswordGenerator({ @@ -38,19 +44,18 @@ export function PasswordGenerator({ action={ field.hide ? { - icon: passwordShown ? EyeOff : Eye, - onClick: togglePasswordVisiblity, - } + icon: passwordShown ? EyeOff : Eye, + onClick: togglePasswordVisiblity, + } : undefined } devicePSKBitCount={field.devicePSKBitCount} bits={field.bits} inputChange={field.inputChange} selectChange={field.selectChange} - buttonClick={field.buttonClick} value={value} variant={field.validationText ? "invalid" : "default"} - buttonText="Generate" + actionButtons={field.actionButtons} {...field.properties} {...rest} disabled={disabled} diff --git a/src/components/PageComponents/Channel.tsx b/src/components/PageComponents/Channel.tsx index e281b242..ef316c91 100644 --- a/src/components/PageComponents/Channel.tsx +++ b/src/components/PageComponents/Channel.tsx @@ -6,6 +6,7 @@ import { Protobuf } from "@meshtastic/js"; import { fromByteArray, toByteArray } from "base64-js"; import cryptoRandomString from "crypto-random-string"; import { useState } from "react"; +import { PkiRegenerateDialog } from "../Dialog/PkiRegenerateDialog"; export interface SettingsPanelProps { channel: Protobuf.Channel.Channel; @@ -22,6 +23,7 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => { channel?.settings?.psk.length ?? 16, ); const [validationText, setValidationText] = useState(); + const [preSharedDialogOpen, setPreSharedDialogOpen] = useState(false); const onSubmit = (data: ChannelValidation) => { const channel = new Protobuf.Channel.Channel({ @@ -46,7 +48,7 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => { }); }; - const clickEvent = () => { + const preSharedKeyRegenerate = () => { setPass( btoa( cryptoRandomString({ @@ -56,6 +58,11 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => { ), ); setValidationText(undefined); + setPreSharedDialogOpen(false); + }; + + const preSharedClickEvent = () => { + setPreSharedDialogOpen(true); }; const validatePass = (input: string, count: number) => { @@ -79,132 +86,139 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => { }; return ( - - onSubmit={onSubmit} - submitType="onSubmit" - hasSubmitButton={true} - defaultValues={{ - ...channel, - ...{ - settings: { - ...channel?.settings, - psk: pass, - positionEnabled: - channel?.settings?.moduleSettings?.positionPrecision !== - undefined && - channel?.settings?.moduleSettings?.positionPrecision > 0, - preciseLocation: - channel?.settings?.moduleSettings?.positionPrecision === 32, - positionPrecision: - channel?.settings?.moduleSettings?.positionPrecision === undefined - ? 10 - : channel?.settings?.moduleSettings?.positionPrecision, + <> + + onSubmit={onSubmit} + submitType="onSubmit" + hasSubmitButton={true} + defaultValues={{ + ...channel, + ...{ + settings: { + ...channel?.settings, + psk: pass, + positionEnabled: + channel?.settings?.moduleSettings?.positionPrecision !== + undefined && + channel?.settings?.moduleSettings?.positionPrecision > 0, + preciseLocation: + channel?.settings?.moduleSettings?.positionPrecision === 32, + positionPrecision: + channel?.settings?.moduleSettings?.positionPrecision === undefined + ? 10 + : channel?.settings?.moduleSettings?.positionPrecision, + }, }, - }, - }} - fieldGroups={[ - { - label: "Channel Settings", - description: "Crypto, MQTT & misc settings", - fields: [ - { - type: "select", - name: "role", - label: "Role", - disabled: channel.index === 0, - description: - "Device telemetry is sent over PRIMARY. Only one PRIMARY allowed", - properties: { - enumValue: - channel.index === 0 - ? { PRIMARY: 1 } - : { DISABLED: 0, SECONDARY: 2 }, + }} + fieldGroups={[ + { + label: "Channel Settings", + description: "Crypto, MQTT & misc settings", + fields: [ + { + type: "select", + name: "role", + label: "Role", + disabled: channel.index === 0, + description: + "Device telemetry is sent over PRIMARY. Only one PRIMARY allowed", + properties: { + enumValue: + channel.index === 0 + ? { PRIMARY: 1 } + : { DISABLED: 0, SECONDARY: 2 }, + }, }, - }, - { - type: "passwordGenerator", - name: "settings.psk", - label: "Pre-Shared Key", - description: "Supported PSK lengths: 256-bit, 128-bit, 8-bit, Empty (0-bit)", - validationText: validationText, - devicePSKBitCount: bitCount ?? 0, - inputChange: inputChangeEvent, - selectChange: selectChangeEvent, - buttonClick: clickEvent, - hide: true, - properties: { - value: pass, + { + type: "passwordGenerator", + name: "settings.psk", + label: "Pre-Shared Key", + description: "Supported PSK lengths: 256-bit, 128-bit, 8-bit, Empty (0-bit)", + validationText: validationText, + devicePSKBitCount: bitCount ?? 0, + inputChange: inputChangeEvent, + selectChange: selectChangeEvent, + actionButtons: [{ text: 'Generate', variant: 'success', onClick: preSharedClickEvent }], + hide: true, + properties: { + value: pass, + }, }, - }, - { - type: "text", - name: "settings.name", - label: "Name", - description: - "A unique name for the channel <12 bytes, leave blank for default", - }, - { - type: "toggle", - name: "settings.uplinkEnabled", - label: "Uplink Enabled", - description: "Send messages from the local mesh to MQTT", - }, - { - type: "toggle", - name: "settings.downlinkEnabled", - label: "Downlink Enabled", - description: "Send messages from MQTT to the local mesh", - }, - { - type: "toggle", - name: "settings.positionEnabled", - label: "Allow Position Requests", - description: "Send position to channel", - }, - { - type: "toggle", - name: "settings.preciseLocation", - label: "Precise Location", - description: "Send precise location to channel", - }, - { - type: "select", - name: "settings.positionPrecision", - label: "Approximate Location", - description: - "If not sharing precise location, position shared on channel will be accurate within this distance", - properties: { - enumValue: - config.display?.units === 0 - ? { - "Within 23 km": 10, - "Within 12 km": 11, - "Within 5.8 km": 12, - "Within 2.9 km": 13, - "Within 1.5 km": 14, - "Within 700 m": 15, - "Within 350 m": 16, - "Within 200 m": 17, - "Within 90 m": 18, - "Within 50 m": 19, - } - : { - "Within 15 miles": 10, - "Within 7.3 miles": 11, - "Within 3.6 miles": 12, - "Within 1.8 miles": 13, - "Within 0.9 miles": 14, - "Within 0.5 miles": 15, - "Within 0.2 miles": 16, - "Within 600 feet": 17, - "Within 300 feet": 18, - "Within 150 feet": 19, - }, + { + type: "text", + name: "settings.name", + label: "Name", + description: + "A unique name for the channel <12 bytes, leave blank for default", }, - }, - ], - }, - ]} - /> + { + type: "toggle", + name: "settings.uplinkEnabled", + label: "Uplink Enabled", + description: "Send messages from the local mesh to MQTT", + }, + { + type: "toggle", + name: "settings.downlinkEnabled", + label: "Downlink Enabled", + description: "Send messages from MQTT to the local mesh", + }, + { + type: "toggle", + name: "settings.positionEnabled", + label: "Allow Position Requests", + description: "Send position to channel", + }, + { + type: "toggle", + name: "settings.preciseLocation", + label: "Precise Location", + description: "Send precise location to channel", + }, + { + type: "select", + name: "settings.positionPrecision", + label: "Approximate Location", + description: + "If not sharing precise location, position shared on channel will be accurate within this distance", + properties: { + enumValue: + config.display?.units === 0 + ? { + "Within 23 km": 10, + "Within 12 km": 11, + "Within 5.8 km": 12, + "Within 2.9 km": 13, + "Within 1.5 km": 14, + "Within 700 m": 15, + "Within 350 m": 16, + "Within 200 m": 17, + "Within 90 m": 18, + "Within 50 m": 19, + } + : { + "Within 15 miles": 10, + "Within 7.3 miles": 11, + "Within 3.6 miles": 12, + "Within 1.8 miles": 13, + "Within 0.9 miles": 14, + "Within 0.5 miles": 15, + "Within 0.2 miles": 16, + "Within 600 feet": 17, + "Within 300 feet": 18, + "Within 150 feet": 19, + }, + }, + }, + ], + }, + ]} + /> + setPreSharedDialogOpen(false)} + onSubmit={() => preSharedKeyRegenerate()} + /> + ); }; diff --git a/src/components/PageComponents/Config/Security.tsx b/src/components/PageComponents/Config/Security.tsx index cf675751..d6e76cc9 100644 --- a/src/components/PageComponents/Config/Security.tsx +++ b/src/components/PageComponents/Config/Security.tsx @@ -12,7 +12,7 @@ import { Eye, EyeOff } from "lucide-react"; import { useState } from "react"; export const Security = (): JSX.Element => { - const { config, nodes, hardware, setWorkingConfig } = useDevice(); + const { config, nodes, hardware, setWorkingConfig, setDialogOpen } = useDevice(); const [privateKey, setPrivateKey] = useState( fromByteArray(config.security?.privateKey ?? new Uint8Array(0)), @@ -31,7 +31,7 @@ export const Security = (): JSX.Element => { ); const [adminKeyValidationText, setAdminKeyValidationText] = useState(); - const [dialogOpen, setDialogOpen] = useState(false); + const [privateKeyDialogOpen, setPrivateKeyDialogOpen] = useState(false); const onSubmit = (data: SecurityValidation) => { if (privateKeyValidationText || adminKeyValidationText) return; @@ -71,9 +71,13 @@ export const Security = (): JSX.Element => { }; const privateKeyClickEvent = () => { - setDialogOpen(true); + setPrivateKeyDialogOpen(true); }; + const pkiBackupClickEvent = () => { + setDialogOpen("pkiBackup", true); + } + const pkiRegenerate = () => { const privateKey = getX25519PrivateKey(); const publicKey = getX25519PublicKey(privateKey); @@ -86,7 +90,7 @@ export const Security = (): JSX.Element => { setPrivateKeyValidationText, ); - setDialogOpen(false); + setPrivateKeyDialogOpen(false); }; const privateKeyInputChangeEvent = ( @@ -149,7 +153,18 @@ export const Security = (): JSX.Element => { inputChange: privateKeyInputChangeEvent, selectChange: privateKeySelectChangeEvent, hide: !privateKeyVisible, - buttonClick: privateKeyClickEvent, + actionButtons: [ + { + text: "Generate", + onClick: privateKeyClickEvent, + variant: "success", + }, + { + text: "Backup Key", + onClick: pkiBackupClickEvent, + variant: "subtle", + }, + ], properties: { value: privateKey, action: { @@ -228,8 +243,8 @@ export const Security = (): JSX.Element => { ]} /> setDialogOpen(false)} + open={privateKeyDialogOpen} + onOpenChange={() => setPrivateKeyDialogOpen(false)} onSubmit={() => pkiRegenerate()} /> diff --git a/src/components/UI/Button.tsx b/src/components/UI/Button.tsx index 39418551..e1b2d704 100644 --- a/src/components/UI/Button.tsx +++ b/src/components/UI/Button.tsx @@ -35,9 +35,11 @@ const buttonVariants = cva( }, ); +export type ButtonVariant = VariantProps["variant"]; + export interface ButtonProps extends React.ButtonHTMLAttributes, - VariantProps {} + VariantProps { } const Button = React.forwardRef( ({ className, variant, size, ...props }, ref) => { diff --git a/src/components/UI/Generator.tsx b/src/components/UI/Generator.tsx index 5e29ef49..b365d7c9 100644 --- a/src/components/UI/Generator.tsx +++ b/src/components/UI/Generator.tsx @@ -1,6 +1,6 @@ import * as React from "react"; -import { Button } from "@components/UI/Button.tsx"; +import { Button, type ButtonVariant } from "@components/UI/Button.tsx"; import { Input } from "@components/UI/Input.tsx"; import { Select, @@ -16,11 +16,15 @@ export interface GeneratorProps extends React.BaseHTMLAttributes { devicePSKBitCount?: number; value: string; variant: "default" | "invalid"; - buttonText?: string; + actionButtons: { + text: string; + onClick: React.MouseEventHandler; + variant: ButtonVariant; + className?: string; + }[]; bits?: { text: string; value: string; key: string }[]; selectChange: (event: string) => void; inputChange: (event: React.ChangeEvent) => void; - buttonClick: React.MouseEventHandler; action?: { icon: LucideIcon; onClick: () => void; @@ -35,7 +39,7 @@ const Generator = React.forwardRef( devicePSKBitCount, variant, value, - buttonText, + actionButtons, bits = [ { text: "256 bit", value: "32", key: "bit256" }, { text: "128 bit", value: "16", key: "bit128" }, @@ -44,7 +48,6 @@ const Generator = React.forwardRef( ], selectChange, inputChange, - buttonClick, action, disabled, ...props @@ -94,15 +97,21 @@ const Generator = React.forwardRef( ))} - +
+ {actionButtons?.map(({ text, onClick, variant, className }) => ( + + ))} +
); }, diff --git a/src/core/stores/deviceStore.ts b/src/core/stores/deviceStore.ts index a716a85b..bd407611 100644 --- a/src/core/stores/deviceStore.ts +++ b/src/core/stores/deviceStore.ts @@ -25,7 +25,8 @@ export type DialogVariant = | "shutdown" | "reboot" | "deviceName" - | "nodeRemoval"; + | "nodeRemoval" + | "pkiBackup"; export interface Device { id: number; @@ -60,6 +61,7 @@ export interface Device { reboot: boolean; deviceName: boolean; nodeRemoval: boolean; + pkiBackup: boolean; }; setStatus: (status: Types.DeviceStatusEnum) => void; @@ -142,6 +144,7 @@ export const useDeviceStore = create((set, get) => ({ reboot: false, deviceName: false, nodeRemoval: false, + pkiBackup: false, }, pendingSettingsChanges: false, messageDraft: "", From f3a3741216b32c87fb2f762f6b300f883d4f528b Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Fri, 3 Jan 2025 21:40:56 -0500 Subject: [PATCH 32/47] feat: Add key backup reminder. Refactor Toast component to handle dark mode better --- package.json | 2 + pnpm-lock.yaml | 214 +++++++++++++--------- src/App.tsx | 4 +- src/components/Dialog/DialogManager.tsx | 2 +- src/components/Dialog/PKIBackupDialog.tsx | 13 +- src/components/KeyBackupReminder.tsx | 21 +++ src/components/Toaster.tsx | 17 +- src/components/UI/Toast.tsx | 70 +++---- src/core/hooks/useCookie.ts | 35 ++++ src/core/hooks/useKeyBackupReminder.tsx | 85 +++++++++ 10 files changed, 326 insertions(+), 137 deletions(-) create mode 100644 src/components/KeyBackupReminder.tsx create mode 100644 src/core/hooks/useCookie.ts create mode 100644 src/core/hooks/useKeyBackupReminder.tsx diff --git a/package.json b/package.json index 05221d9b..84a49432 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "cmdk": "^1.0.0", "crypto-random-string": "^5.0.0", "immer": "^10.1.1", + "js-cookie": "^3.0.5", "lucide-react": "^0.363.0", "mapbox-gl": "^3.6.0", "maplibre-gl": "4.1.2", @@ -70,6 +71,7 @@ "@rsbuild/core": "^1.0.10", "@rsbuild/plugin-react": "^1.0.3", "@types/chrome": "^0.0.263", + "@types/js-cookie": "^3.0.6", "@types/node": "^20.14.9", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b9edbfd5..79d6d5ff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -89,6 +89,9 @@ importers: immer: specifier: ^10.1.1 version: 10.1.1 + js-cookie: + specifier: ^3.0.5 + version: 3.0.5 lucide-react: specifier: ^0.363.0 version: 0.363.0(react@18.3.1) @@ -127,7 +130,7 @@ importers: version: 3.0.6(react@18.3.1) vite-plugin-node-polyfills: specifier: ^0.22.0 - version: 0.22.0(rollup@4.24.0)(vite@5.3.6(@types/node@20.14.9)) + version: 0.22.0(rollup@4.29.1)(vite@5.3.6(@types/node@20.14.9)) zustand: specifier: 4.5.2 version: 4.5.2(@types/react@18.3.3)(immer@10.1.1)(react@18.3.1) @@ -147,6 +150,9 @@ importers: '@types/chrome': specifier: ^0.0.263 version: 0.0.263 + '@types/js-cookie': + specifier: ^3.0.6 + version: 3.0.6 '@types/node': specifier: ^20.14.9 version: 20.14.9 @@ -173,7 +179,7 @@ importers: version: 8.4.38 rollup-plugin-visualizer: specifier: ^5.12.0 - version: 5.12.0(rollup@4.24.0) + version: 5.12.0(rollup@4.29.1) tailwindcss: specifier: ^3.4.4 version: 3.4.4 @@ -1165,83 +1171,98 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.24.0': - resolution: {integrity: sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==} + '@rollup/rollup-android-arm-eabi@4.29.1': + resolution: {integrity: sha512-ssKhA8RNltTZLpG6/QNkCSge+7mBQGUqJRisZ2MDQcEGaK93QESEgWK2iOpIDZ7k9zPVkG5AS3ksvD5ZWxmItw==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.24.0': - resolution: {integrity: sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==} + '@rollup/rollup-android-arm64@4.29.1': + resolution: {integrity: sha512-CaRfrV0cd+NIIcVVN/jx+hVLN+VRqnuzLRmfmlzpOzB87ajixsN/+9L5xNmkaUUvEbI5BmIKS+XTwXsHEb65Ew==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.24.0': - resolution: {integrity: sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==} + '@rollup/rollup-darwin-arm64@4.29.1': + resolution: {integrity: sha512-2ORr7T31Y0Mnk6qNuwtyNmy14MunTAMx06VAPI6/Ju52W10zk1i7i5U3vlDRWjhOI5quBcrvhkCHyF76bI7kEw==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.24.0': - resolution: {integrity: sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==} + '@rollup/rollup-darwin-x64@4.29.1': + resolution: {integrity: sha512-j/Ej1oanzPjmN0tirRd5K2/nncAhS9W6ICzgxV+9Y5ZsP0hiGhHJXZ2JQ53iSSjj8m6cRY6oB1GMzNn2EUt6Ng==} cpu: [x64] os: [darwin] - '@rollup/rollup-linux-arm-gnueabihf@4.24.0': - resolution: {integrity: sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==} + '@rollup/rollup-freebsd-arm64@4.29.1': + resolution: {integrity: sha512-91C//G6Dm/cv724tpt7nTyP+JdN12iqeXGFM1SqnljCmi5yTXriH7B1r8AD9dAZByHpKAumqP1Qy2vVNIdLZqw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.29.1': + resolution: {integrity: sha512-hEioiEQ9Dec2nIRoeHUP6hr1PSkXzQaCUyqBDQ9I9ik4gCXQZjJMIVzoNLBRGet+hIUb3CISMh9KXuCcWVW/8w==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.29.1': + resolution: {integrity: sha512-Py5vFd5HWYN9zxBv3WMrLAXY3yYJ6Q/aVERoeUFwiDGiMOWsMs7FokXihSOaT/PMWUty/Pj60XDQndK3eAfE6A==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.24.0': - resolution: {integrity: sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==} + '@rollup/rollup-linux-arm-musleabihf@4.29.1': + resolution: {integrity: sha512-RiWpGgbayf7LUcuSNIbahr0ys2YnEERD4gYdISA06wa0i8RALrnzflh9Wxii7zQJEB2/Eh74dX4y/sHKLWp5uQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.24.0': - resolution: {integrity: sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==} + '@rollup/rollup-linux-arm64-gnu@4.29.1': + resolution: {integrity: sha512-Z80O+taYxTQITWMjm/YqNoe9d10OX6kDh8X5/rFCMuPqsKsSyDilvfg+vd3iXIqtfmp+cnfL1UrYirkaF8SBZA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.24.0': - resolution: {integrity: sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==} + '@rollup/rollup-linux-arm64-musl@4.29.1': + resolution: {integrity: sha512-fOHRtF9gahwJk3QVp01a/GqS4hBEZCV1oKglVVq13kcK3NeVlS4BwIFzOHDbmKzt3i0OuHG4zfRP0YoG5OF/rA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': - resolution: {integrity: sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==} + '@rollup/rollup-linux-loongarch64-gnu@4.29.1': + resolution: {integrity: sha512-5a7q3tnlbcg0OodyxcAdrrCxFi0DgXJSoOuidFUzHZ2GixZXQs6Tc3CHmlvqKAmOs5eRde+JJxeIf9DonkmYkw==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.29.1': + resolution: {integrity: sha512-9b4Mg5Yfz6mRnlSPIdROcfw1BU22FQxmfjlp/CShWwO3LilKQuMISMTtAu/bxmmrE6A902W2cZJuzx8+gJ8e9w==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.24.0': - resolution: {integrity: sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==} + '@rollup/rollup-linux-riscv64-gnu@4.29.1': + resolution: {integrity: sha512-G5pn0NChlbRM8OJWpJFMX4/i8OEU538uiSv0P6roZcbpe/WfhEO+AT8SHVKfp8qhDQzaz7Q+1/ixMy7hBRidnQ==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.24.0': - resolution: {integrity: sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==} + '@rollup/rollup-linux-s390x-gnu@4.29.1': + resolution: {integrity: sha512-WM9lIkNdkhVwiArmLxFXpWndFGuOka4oJOZh8EP3Vb8q5lzdSCBuhjavJsw68Q9AKDGeOOIHYzYm4ZFvmWez5g==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.24.0': - resolution: {integrity: sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==} + '@rollup/rollup-linux-x64-gnu@4.29.1': + resolution: {integrity: sha512-87xYCwb0cPGZFoGiErT1eDcssByaLX4fc0z2nRM6eMtV9njAfEE6OW3UniAoDhX4Iq5xQVpE6qO9aJbCFumKYQ==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.24.0': - resolution: {integrity: sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==} + '@rollup/rollup-linux-x64-musl@4.29.1': + resolution: {integrity: sha512-xufkSNppNOdVRCEC4WKvlR1FBDyqCSCpQeMMgv9ZyXqqtKBfkw1yfGMTUTs9Qsl6WQbJnsGboWCp7pJGkeMhKA==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.24.0': - resolution: {integrity: sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==} + '@rollup/rollup-win32-arm64-msvc@4.29.1': + resolution: {integrity: sha512-F2OiJ42m77lSkizZQLuC+jiZ2cgueWQL5YC9tjo3AgaEw+KJmVxHGSyQfDUoYR9cci0lAywv2Clmckzulcq6ig==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.24.0': - resolution: {integrity: sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==} + '@rollup/rollup-win32-ia32-msvc@4.29.1': + resolution: {integrity: sha512-rYRe5S0FcjlOBZQHgbTKNrqxCBUmgDJem/VQTCcTnA2KCabYSWQDrytOzX7avb79cAAweNmMUb/Zw18RNd4mng==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.24.0': - resolution: {integrity: sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==} + '@rollup/rollup-win32-x64-msvc@4.29.1': + resolution: {integrity: sha512-+10CMg9vt1MoHj6x1pxyjPSMjHTIlqs8/tBztXvPAx24SKs9jwVnKqHJumlH/IzhaPUaj3T6T6wfZr8okdXaIg==} cpu: [x64] os: [win32] @@ -1690,6 +1711,9 @@ packages: '@types/har-format@1.2.15': resolution: {integrity: sha512-RpQH4rXLuvTXKR0zqHq3go0RVXYv/YVqv4TnPH95VbwUxZdQlK1EtcMvQvMpDngHbt13Csh9Z4qT9AbkiQH5BA==} + '@types/js-cookie@3.0.6': + resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==} + '@types/mapbox-gl@3.1.0': resolution: {integrity: sha512-hI6cQDjw1bkJw7MC/eHMqq5TWUamLwsujnUUeiIX2KDRjxRNSYMjnHz07+LATz9I9XIsKumOtUz4gRYnZOJ/FA==} @@ -1864,8 +1888,8 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - caniuse-lite@1.0.30001638: - resolution: {integrity: sha512-5SuJUJ7cZnhPpeLHaH0c/HPAnAHZvS6ElWyHK9GSIbVOQABLzowiI2pjmpvZ1WEbkyz46iFd4UXlOHR5SqgfMQ==} + caniuse-lite@1.0.30001690: + resolution: {integrity: sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==} cheap-ruler@4.0.0: resolution: {integrity: sha512-0BJa8f4t141BYKQyn9NSQt1PguFQXMXwZiA5shfoaBYHAb2fFk2RAX+tiWMoQU+Agtzt3mdt0JtuyshAXqZ+Vw==} @@ -2417,6 +2441,10 @@ packages: resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} hasBin: true + js-cookie@3.0.5: + resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} + engines: {node: '>=14'} + js-sha3@0.8.0: resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} @@ -2923,8 +2951,8 @@ packages: rollup: optional: true - rollup@4.24.0: - resolution: {integrity: sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==} + rollup@4.29.1: + resolution: {integrity: sha512-RaJ45M/kmJUzSWDs1Nnd5DdV4eerC98idtUOVr6FfKcgxqvjwHmxc5upLF9qZU9EpsVzzhleFahrT3shLuJzIw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -4258,68 +4286,77 @@ snapshots: '@radix-ui/rect@1.1.0': {} - '@rollup/plugin-inject@5.0.5(rollup@4.24.0)': + '@rollup/plugin-inject@5.0.5(rollup@4.29.1)': dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.24.0) + '@rollup/pluginutils': 5.1.0(rollup@4.29.1) estree-walker: 2.0.2 magic-string: 0.30.11 optionalDependencies: - rollup: 4.24.0 + rollup: 4.29.1 - '@rollup/pluginutils@5.1.0(rollup@4.24.0)': + '@rollup/pluginutils@5.1.0(rollup@4.29.1)': dependencies: '@types/estree': 1.0.5 estree-walker: 2.0.2 picomatch: 2.3.1 optionalDependencies: - rollup: 4.24.0 + rollup: 4.29.1 + + '@rollup/rollup-android-arm-eabi@4.29.1': + optional: true + + '@rollup/rollup-android-arm64@4.29.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.29.1': + optional: true - '@rollup/rollup-android-arm-eabi@4.24.0': + '@rollup/rollup-darwin-x64@4.29.1': optional: true - '@rollup/rollup-android-arm64@4.24.0': + '@rollup/rollup-freebsd-arm64@4.29.1': optional: true - '@rollup/rollup-darwin-arm64@4.24.0': + '@rollup/rollup-freebsd-x64@4.29.1': optional: true - '@rollup/rollup-darwin-x64@4.24.0': + '@rollup/rollup-linux-arm-gnueabihf@4.29.1': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.24.0': + '@rollup/rollup-linux-arm-musleabihf@4.29.1': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.24.0': + '@rollup/rollup-linux-arm64-gnu@4.29.1': optional: true - '@rollup/rollup-linux-arm64-gnu@4.24.0': + '@rollup/rollup-linux-arm64-musl@4.29.1': optional: true - '@rollup/rollup-linux-arm64-musl@4.24.0': + '@rollup/rollup-linux-loongarch64-gnu@4.29.1': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': + '@rollup/rollup-linux-powerpc64le-gnu@4.29.1': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.24.0': + '@rollup/rollup-linux-riscv64-gnu@4.29.1': optional: true - '@rollup/rollup-linux-s390x-gnu@4.24.0': + '@rollup/rollup-linux-s390x-gnu@4.29.1': optional: true - '@rollup/rollup-linux-x64-gnu@4.24.0': + '@rollup/rollup-linux-x64-gnu@4.29.1': optional: true - '@rollup/rollup-linux-x64-musl@4.24.0': + '@rollup/rollup-linux-x64-musl@4.29.1': optional: true - '@rollup/rollup-win32-arm64-msvc@4.24.0': + '@rollup/rollup-win32-arm64-msvc@4.29.1': optional: true - '@rollup/rollup-win32-ia32-msvc@4.24.0': + '@rollup/rollup-win32-ia32-msvc@4.29.1': optional: true - '@rollup/rollup-win32-x64-msvc@4.24.0': + '@rollup/rollup-win32-x64-msvc@4.29.1': optional: true '@rsbuild/core@1.0.10': @@ -4381,7 +4418,7 @@ snapshots: '@module-federation/runtime-tools': 0.5.1 '@rspack/binding': 1.0.8 '@rspack/lite-tapable': 1.0.1 - caniuse-lite: 1.0.30001638 + caniuse-lite: 1.0.30001690 optionalDependencies: '@swc/helpers': 0.5.13 @@ -5255,6 +5292,8 @@ snapshots: '@types/har-format@1.2.15': {} + '@types/js-cookie@3.0.6': {} + '@types/mapbox-gl@3.1.0': dependencies: '@types/geojson': 7946.0.14 @@ -5343,7 +5382,7 @@ snapshots: autoprefixer@10.4.19(postcss@8.4.38): dependencies: browserslist: 4.23.1 - caniuse-lite: 1.0.30001638 + caniuse-lite: 1.0.30001690 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.0.1 @@ -5424,7 +5463,7 @@ snapshots: browserslist@4.23.1: dependencies: - caniuse-lite: 1.0.30001638 + caniuse-lite: 1.0.30001690 electron-to-chromium: 1.4.812 node-releases: 2.0.14 update-browserslist-db: 1.0.16(browserslist@4.23.1) @@ -5459,7 +5498,7 @@ snapshots: camelcase-css@2.0.1: {} - caniuse-lite@1.0.30001638: {} + caniuse-lite@1.0.30001690: {} cheap-ruler@4.0.0: {} @@ -6075,6 +6114,8 @@ snapshots: jiti@1.21.6: {} + js-cookie@3.0.5: {} + js-sha3@0.8.0: {} js-tokens@4.0.0: {} @@ -6620,35 +6661,38 @@ snapshots: robust-predicates@3.0.2: {} - rollup-plugin-visualizer@5.12.0(rollup@4.24.0): + rollup-plugin-visualizer@5.12.0(rollup@4.29.1): dependencies: open: 8.4.2 picomatch: 2.3.1 source-map: 0.7.4 yargs: 17.7.2 optionalDependencies: - rollup: 4.24.0 + rollup: 4.29.1 - rollup@4.24.0: + rollup@4.29.1: dependencies: '@types/estree': 1.0.6 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.24.0 - '@rollup/rollup-android-arm64': 4.24.0 - '@rollup/rollup-darwin-arm64': 4.24.0 - '@rollup/rollup-darwin-x64': 4.24.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.24.0 - '@rollup/rollup-linux-arm-musleabihf': 4.24.0 - '@rollup/rollup-linux-arm64-gnu': 4.24.0 - '@rollup/rollup-linux-arm64-musl': 4.24.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.24.0 - '@rollup/rollup-linux-riscv64-gnu': 4.24.0 - '@rollup/rollup-linux-s390x-gnu': 4.24.0 - '@rollup/rollup-linux-x64-gnu': 4.24.0 - '@rollup/rollup-linux-x64-musl': 4.24.0 - '@rollup/rollup-win32-arm64-msvc': 4.24.0 - '@rollup/rollup-win32-ia32-msvc': 4.24.0 - '@rollup/rollup-win32-x64-msvc': 4.24.0 + '@rollup/rollup-android-arm-eabi': 4.29.1 + '@rollup/rollup-android-arm64': 4.29.1 + '@rollup/rollup-darwin-arm64': 4.29.1 + '@rollup/rollup-darwin-x64': 4.29.1 + '@rollup/rollup-freebsd-arm64': 4.29.1 + '@rollup/rollup-freebsd-x64': 4.29.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.29.1 + '@rollup/rollup-linux-arm-musleabihf': 4.29.1 + '@rollup/rollup-linux-arm64-gnu': 4.29.1 + '@rollup/rollup-linux-arm64-musl': 4.29.1 + '@rollup/rollup-linux-loongarch64-gnu': 4.29.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.29.1 + '@rollup/rollup-linux-riscv64-gnu': 4.29.1 + '@rollup/rollup-linux-s390x-gnu': 4.29.1 + '@rollup/rollup-linux-x64-gnu': 4.29.1 + '@rollup/rollup-linux-x64-musl': 4.29.1 + '@rollup/rollup-win32-arm64-msvc': 4.29.1 + '@rollup/rollup-win32-ia32-msvc': 4.29.1 + '@rollup/rollup-win32-x64-msvc': 4.29.1 fsevents: 2.3.3 run-parallel@1.2.0: @@ -6986,9 +7030,9 @@ snapshots: validator@13.12.0: {} - vite-plugin-node-polyfills@0.22.0(rollup@4.24.0)(vite@5.3.6(@types/node@20.14.9)): + vite-plugin-node-polyfills@0.22.0(rollup@4.29.1)(vite@5.3.6(@types/node@20.14.9)): dependencies: - '@rollup/plugin-inject': 5.0.5(rollup@4.24.0) + '@rollup/plugin-inject': 5.0.5(rollup@4.29.1) node-stdlib-browser: 1.2.0 vite: 5.3.6(@types/node@20.14.9) transitivePeerDependencies: @@ -6998,7 +7042,7 @@ snapshots: dependencies: esbuild: 0.21.5 postcss: 8.4.49 - rollup: 4.24.0 + rollup: 4.29.1 optionalDependencies: '@types/node': 20.14.9 fsevents: 2.3.3 diff --git a/src/App.tsx b/src/App.tsx index c936bc9c..8c15dc97 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,7 +2,7 @@ import { DeviceWrapper } from "@app/DeviceWrapper.tsx"; import { PageRouter } from "@app/PageRouter.tsx"; import { CommandPalette } from "@components/CommandPalette.tsx"; import { DeviceSelector } from "@components/DeviceSelector.tsx"; -import { DialogManager } from "@components/Dialog/DialogManager.tsx"; +import { DialogManager } from "@components/Dialog/DialogManager"; import { NewDeviceDialog } from "@components/Dialog/NewDeviceDialog.tsx"; import { Toaster } from "@components/Toaster.tsx"; import Footer from "@components/UI/Footer.tsx"; @@ -11,6 +11,7 @@ import { useAppStore } from "@core/stores/appStore.ts"; import { useDeviceStore } from "@core/stores/deviceStore.ts"; import { Dashboard } from "@pages/Dashboard/index.tsx"; import { MapProvider } from "react-map-gl"; +import { KeyBackupReminder } from "@components/KeyBackupReminder"; export const App = (): JSX.Element => { const { getDevice } = useDeviceStore(); @@ -37,6 +38,7 @@ export const App = (): JSX.Element => { {device ? (
+
diff --git a/src/components/Dialog/DialogManager.tsx b/src/components/Dialog/DialogManager.tsx index 762bd5a7..afebdf5d 100644 --- a/src/components/Dialog/DialogManager.tsx +++ b/src/components/Dialog/DialogManager.tsx @@ -5,7 +5,7 @@ import { QRDialog } from "@components/Dialog/QRDialog.tsx"; import { RebootDialog } from "@components/Dialog/RebootDialog.tsx"; import { ShutdownDialog } from "@components/Dialog/ShutdownDialog.tsx"; import { useDevice } from "@core/stores/deviceStore.ts"; -import { PkiBackupDialog } from "./PKIBackupDialog"; +import { PkiBackupDialog } from "@components/Dialog/PKIBackupDialog"; export const DialogManager = (): JSX.Element => { const { channels, config, dialog, setDialogOpen } = useDevice(); diff --git a/src/components/Dialog/PKIBackupDialog.tsx b/src/components/Dialog/PKIBackupDialog.tsx index 3737236a..4b482bf7 100644 --- a/src/components/Dialog/PKIBackupDialog.tsx +++ b/src/components/Dialog/PKIBackupDialog.tsx @@ -21,7 +21,7 @@ export const PkiBackupDialog = ({ open, onOpenChange, }: PkiBackupDialogProps) => { - const { config } = useDevice(); + const { config, setDialogOpen } = useDevice(); const privateKeyData = config.security?.privateKey // If the private data doesn't exist return null @@ -31,6 +31,10 @@ export const PkiBackupDialog = ({ const getPrivateKey = React.useMemo(() => fromByteArray(config.security?.privateKey ?? new Uint8Array(0)), [config.security?.privateKey]); + const closeDialog = React.useCallback(() => { + setDialogOpen("pkiBackup", false) + }, [setDialogOpen]) + const renderPrintWindow = React.useCallback(() => { const printWindow = window.open("", "_blank"); if (printWindow) { @@ -52,8 +56,10 @@ export const PkiBackupDialog = ({ `); printWindow.document.close(); printWindow.print(); + closeDialog() + } - }, [getPrivateKey]); + }, [getPrivateKey, closeDialog]); const createDownloadKeyFile = React.useCallback(() => { const blob = new Blob([getPrivateKey], { type: "text/plain" }); @@ -65,8 +71,9 @@ export const PkiBackupDialog = ({ document.body.appendChild(link); link.click(); document.body.removeChild(link); + closeDialog() URL.revokeObjectURL(url); - }, [getPrivateKey]); + }, [getPrivateKey, closeDialog]); return ( diff --git a/src/components/KeyBackupReminder.tsx b/src/components/KeyBackupReminder.tsx new file mode 100644 index 00000000..45819bd7 --- /dev/null +++ b/src/components/KeyBackupReminder.tsx @@ -0,0 +1,21 @@ +import { useBackupReminder } from "@app/core/hooks/useKeyBackupReminder"; +import { useDevice } from "@app/core/stores/deviceStore"; + +export const KeyBackupReminder = (): JSX.Element => { + const { setDialogOpen } = useDevice(); + + useBackupReminder({ + suppressDays: 7, + message: "We recommend backing up your key data regularly. Would you like to back up now?", + onAccept: () => setDialogOpen("pkiBackup", true), + cookieOptions: { + secure: true, + sameSite: 'strict' + } + }); + + return ( + <> + + ); +}; diff --git a/src/components/Toaster.tsx b/src/components/Toaster.tsx index cfe13a9d..cf322758 100644 --- a/src/components/Toaster.tsx +++ b/src/components/Toaster.tsx @@ -1,5 +1,3 @@ -import { useToast } from "@core/hooks/useToast.ts"; - import { Toast, ToastClose, @@ -7,7 +5,8 @@ import { ToastProvider, ToastTitle, ToastViewport, -} from "@components/UI/Toast.tsx"; +} from "@components/UI/Toast"; +import { useToast } from "@core/hooks/useToast"; export function Toaster() { const { toasts } = useToast(); @@ -15,16 +14,10 @@ export function Toaster() { return ( {toasts.map(({ id, title, description, action, ...props }) => ( - +
- {title && ( - {title} - )} - {description && ( - - {description} - - )} + {title && {title}} + {description && {description}}
{action} diff --git a/src/components/UI/Toast.tsx b/src/components/UI/Toast.tsx index ca8dfc90..d40b294a 100644 --- a/src/components/UI/Toast.tsx +++ b/src/components/UI/Toast.tsx @@ -1,11 +1,11 @@ -import * as ToastPrimitives from "@radix-ui/react-toast"; -import { type VariantProps, cva } from "class-variance-authority"; -import { X } from "lucide-react"; -import * as React from "react"; +import * as React from "react" +import * as ToastPrimitives from "@radix-ui/react-toast" +import { cva, type VariantProps } from "class-variance-authority" +import { X } from 'lucide-react' -import { cn } from "@core/utils/cn.ts"; +import { cn } from "@core/utils/cn" -const ToastProvider = ToastPrimitives.Provider; +const ToastProvider = ToastPrimitives.Provider const ToastViewport = React.forwardRef< React.ElementRef, @@ -14,35 +14,34 @@ const ToastViewport = React.forwardRef< -)); -ToastViewport.displayName = ToastPrimitives.Viewport.displayName; +)) +ToastViewport.displayName = ToastPrimitives.Viewport.displayName const toastVariants = cva( - "data-[swipe=move]:transition-none grow-1 group relative pointer-events-auto flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full mt-4 data-[state=closed]:slide-out-to-right-full dark:border-slate-700 last:mt-0 sm:last:mt-4", + "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", { variants: { variant: { - default: - "bg-white border-slate-200 dark:bg-slate-800 dark:border-slate-700", + default: "border bg-background text-foreground dark:bg-slate-700 dark:border-slate-600 dark:text-slate-50", destructive: - "group destructive bg-red-600 text-white border-red-600 dark:border-red-600", + "group destructive bg-red-600 text-white dark:border-red-900 dark:bg-red-900 dark:text-red-50" }, }, defaultVariants: { variant: "default", }, - }, -); + } +) const Toast = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & - VariantProps + VariantProps >(({ className, variant, ...props }, ref) => { return ( - ); -}); -Toast.displayName = ToastPrimitives.Root.displayName; + ) +}) +Toast.displayName = ToastPrimitives.Root.displayName const ToastAction = React.forwardRef< React.ElementRef, @@ -61,13 +60,13 @@ const ToastAction = React.forwardRef< -)); -ToastAction.displayName = ToastPrimitives.Action.displayName; +)) +ToastAction.displayName = ToastPrimitives.Action.displayName const ToastClose = React.forwardRef< React.ElementRef, @@ -76,16 +75,16 @@ const ToastClose = React.forwardRef< -)); -ToastClose.displayName = ToastPrimitives.Close.displayName; +)) +ToastClose.displayName = ToastPrimitives.Close.displayName const ToastTitle = React.forwardRef< React.ElementRef, @@ -96,8 +95,8 @@ const ToastTitle = React.forwardRef< className={cn("text-sm font-semibold", className)} {...props} /> -)); -ToastTitle.displayName = ToastPrimitives.Title.displayName; +)) +ToastTitle.displayName = ToastPrimitives.Title.displayName const ToastDescription = React.forwardRef< React.ElementRef, @@ -108,12 +107,12 @@ const ToastDescription = React.forwardRef< className={cn("text-sm opacity-90", className)} {...props} /> -)); -ToastDescription.displayName = ToastPrimitives.Description.displayName; +)) +ToastDescription.displayName = ToastPrimitives.Description.displayName -type ToastProps = React.ComponentPropsWithoutRef; +type ToastProps = React.ComponentPropsWithoutRef -type ToastActionElement = React.ReactElement; +type ToastActionElement = React.ReactElement export { type ToastProps, @@ -125,4 +124,5 @@ export { ToastDescription, ToastClose, ToastAction, -}; +} + diff --git a/src/core/hooks/useCookie.ts b/src/core/hooks/useCookie.ts new file mode 100644 index 00000000..6a1eb311 --- /dev/null +++ b/src/core/hooks/useCookie.ts @@ -0,0 +1,35 @@ +import React from "react"; +import Cookies, { type CookieAttributes } from "js-cookie"; + +type Cookie = [ + T | undefined, + (value: T, options?: CookieAttributes) => void, + () => void, +]; + +const useCookie = ( + cookieName: string, + initialValue?: T, +): Cookie => { + const [cookieValue, setCookieValue] = React.useState(() => { + const cookie = Cookies.get(cookieName); + return cookie ? (JSON.parse(cookie) as T) : initialValue; + }); + + const setCookie = React.useCallback( + (value: T, options?: CookieAttributes) => { + Cookies.set(cookieName, JSON.stringify(value), options); + setCookieValue(value); + }, + [cookieName], + ); + + const removeCookie = React.useCallback(() => { + Cookies.remove(cookieName); + setCookieValue(undefined); + }, [cookieName]); + + return [cookieValue, setCookie, removeCookie]; +}; + +export default useCookie; diff --git a/src/core/hooks/useKeyBackupReminder.tsx b/src/core/hooks/useKeyBackupReminder.tsx new file mode 100644 index 00000000..8ce9e1d8 --- /dev/null +++ b/src/core/hooks/useKeyBackupReminder.tsx @@ -0,0 +1,85 @@ +import { useEffect, useCallback } from 'react'; +import { useToast } from './useToast'; +import useCookie from './useCookie'; +import type { CookieAttributes } from 'js-cookie'; +import { Button } from '@app/components/UI/Button'; + +interface UseBackupReminderOptions { + suppressDays?: number; + message?: string; + onAccept?: () => void | Promise; + cookieName?: string; + cookieOptions?: CookieAttributes; +} + +interface ReminderState { + suppressed: boolean; + lastShown: string; +} + +export function useBackupReminder({ + suppressDays = 365, + message = "It's time to back up your key data. Would you like to do this now?", + onAccept = () => { }, + cookieName = "backup_reminder_state", + cookieOptions = {}, +}: UseBackupReminderOptions = {}) { + const { toast } = useToast(); + + const [reminderState, setReminderState, resetReminderState] = useCookie(cookieName); + + const suppressReminder = useCallback(() => { + const expiryDate = new Date(); + expiryDate.setDate(expiryDate.getDate() + suppressDays); + + setReminderState( + { + suppressed: true, + lastShown: new Date().toISOString(), + }, + { + ...cookieOptions, + expires: expiryDate, + } + ); + + }, [setReminderState, suppressDays, cookieOptions]); + + useEffect(() => { + if (!reminderState) { + const { dismiss: dimissToast } = toast({ + title: "Backup Reminder", + description: message, + action: ( +
+ + +
+ ), + }); + } + }, [reminderState]); + + return { + resetReminder: resetReminderState + }; +} \ No newline at end of file From 0e78d0bd5061cc8d1fa6d6a4f25edf59770bcbe9 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Tue, 7 Jan 2025 15:09:00 -0500 Subject: [PATCH 33/47] feat: added delay to toast appearing to avoid conflicting with messages/node loading. --- src/core/hooks/useKeyBackupReminder.tsx | 62 +++++++++++++------------ 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/src/core/hooks/useKeyBackupReminder.tsx b/src/core/hooks/useKeyBackupReminder.tsx index 8ce9e1d8..9af1ec70 100644 --- a/src/core/hooks/useKeyBackupReminder.tsx +++ b/src/core/hooks/useKeyBackupReminder.tsx @@ -17,6 +17,8 @@ interface ReminderState { lastShown: string; } +const TOAST_DELAY = 10000; + export function useBackupReminder({ suppressDays = 365, message = "It's time to back up your key data. Would you like to do this now?", @@ -47,35 +49,37 @@ export function useBackupReminder({ useEffect(() => { if (!reminderState) { - const { dismiss: dimissToast } = toast({ - title: "Backup Reminder", - description: message, - action: ( -
- - -
- ), - }); + setTimeout(() => { + const { dismiss: dimissToast } = toast({ + title: "Backup Reminder", + description: message, + action: ( +
+ + +
+ ), + }); + }, TOAST_DELAY); } }, [reminderState]); From b50edb276207300358d323d88ceefafb74c007f4 Mon Sep 17 00:00:00 2001 From: Kyle Wistrand Date: Sat, 11 Jan 2025 20:31:33 -0800 Subject: [PATCH 34/47] fix: Node map detail dark mode --- src/components/PageComponents/Map/NodeDetail.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/PageComponents/Map/NodeDetail.tsx b/src/components/PageComponents/Map/NodeDetail.tsx index cc01b8db..d1cbd8a9 100644 --- a/src/components/PageComponents/Map/NodeDetail.tsx +++ b/src/components/PageComponents/Map/NodeDetail.tsx @@ -30,7 +30,7 @@ export const NodeDetail = ({ node }: NodeDetailProps): JSX.Element => { ].replaceAll("_", " "); return ( - <> +
@@ -166,6 +166,6 @@ export const NodeDetail = ({ node }: NodeDetailProps): JSX.Element => {
)} - +
); }; From c2cdc92ae9baed8650cd5ec8f84c32a885c19b4b Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 12 Jan 2025 18:56:22 +0100 Subject: [PATCH 35/47] feat: Add sidebar close button --- src/components/Sidebar.tsx | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 48c496e5..dba5b3fa 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -12,9 +12,12 @@ import { MapIcon, MessageSquareIcon, SettingsIcon, + SidebarCloseIcon, + SidebarOpenIcon, UsersIcon, ZapIcon, } from "lucide-react"; +import { useState } from "react"; export interface SidebarProps { children?: React.ReactNode; @@ -25,6 +28,7 @@ export const Sidebar = ({ children }: SidebarProps): JSX.Element => { const myNode = nodes.get(hardware.myNodeNum); const myMetadata = metadata.get(0); const { activePage, setActivePage, setDialogOpen } = useDevice(); + const [showSidebar, setShowSidebar] = useState(true); interface NavLink { name: string; @@ -60,7 +64,7 @@ export const Sidebar = ({ children }: SidebarProps): JSX.Element => { }, ]; - return ( + return showSidebar ? (
@@ -76,6 +80,12 @@ export const Sidebar = ({ children }: SidebarProps): JSX.Element => { > +
@@ -109,5 +119,14 @@ export const Sidebar = ({ children }: SidebarProps): JSX.Element => { {children}
+ ) : ( +
+ +
); }; From 2d041ab6d0c778306f4f2493c076361783939486 Mon Sep 17 00:00:00 2001 From: PhotoNomad0 Date: Mon, 13 Jan 2025 06:37:18 -0500 Subject: [PATCH 36/47] Added date to messages displayed --- src/components/PageComponents/Messages/Message.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/PageComponents/Messages/Message.tsx b/src/components/PageComponents/Messages/Message.tsx index d1d35784..22ed52b2 100644 --- a/src/components/PageComponents/Messages/Message.tsx +++ b/src/components/PageComponents/Messages/Message.tsx @@ -44,6 +44,9 @@ export const Message = ({ {sender?.user?.longName ?? "UNK"} + + {message.rxTime.toLocaleDateString()} + {message.rxTime.toLocaleTimeString(undefined, { hour: "2-digit", From 7884991ac6e1d36252dd2575c3abc69977113925 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Tue, 14 Jan 2025 21:51:06 -0500 Subject: [PATCH 37/47] Refactor based on code review. Improved typing of useCookie hook. Added duration/delay to control length of toast. --- src/components/Dialog/PKIBackupDialog.tsx | 85 +++++++---- src/components/KeyBackupReminder.tsx | 16 +-- src/components/Toaster.tsx | 12 +- src/core/hooks/useCookie.ts | 55 +++++--- src/core/hooks/useKeyBackupReminder.tsx | 165 +++++++++++++--------- src/core/hooks/useToast.ts | 27 ++-- 6 files changed, 219 insertions(+), 141 deletions(-) diff --git a/src/components/Dialog/PKIBackupDialog.tsx b/src/components/Dialog/PKIBackupDialog.tsx index 4b482bf7..9b6c18d8 100644 --- a/src/components/Dialog/PKIBackupDialog.tsx +++ b/src/components/Dialog/PKIBackupDialog.tsx @@ -1,3 +1,5 @@ +import { useDevice } from "@app/core/stores/deviceStore"; +import { Button } from "@components/UI/Button"; import { Dialog, DialogContent, @@ -6,11 +8,9 @@ import { DialogHeader, DialogTitle, } from "@components/UI/Dialog.tsx"; -import { Button } from "@components/UI/Button"; +import { fromByteArray } from "base64-js"; import { DownloadIcon, PrinterIcon } from "lucide-react"; import React from "react"; -import { useDevice } from "@app/core/stores/deviceStore"; -import { fromByteArray } from "base64-js"; export interface PkiBackupDialogProps { open: boolean; @@ -22,26 +22,30 @@ export const PkiBackupDialog = ({ onOpenChange, }: PkiBackupDialogProps) => { const { config, setDialogOpen } = useDevice(); - const privateKeyData = config.security?.privateKey - - // If the private data doesn't exist return null - if (!privateKeyData) { - return null - } + const privateKey = config.security?.privateKey; + const publicKey = config.security?.publicKey; - const getPrivateKey = React.useMemo(() => fromByteArray(config.security?.privateKey ?? new Uint8Array(0)), [config.security?.privateKey]); + const decodeKeyData = React.useCallback( + (key: Uint8Array) => { + if (!key) return ""; + return fromByteArray(key ?? new Uint8Array(0)); + }, + [], + ); const closeDialog = React.useCallback(() => { - setDialogOpen("pkiBackup", false) - }, [setDialogOpen]) + setDialogOpen("pkiBackup", false); + }, [setDialogOpen]); const renderPrintWindow = React.useCallback(() => { + if (!privateKey || !publicKey) return; + const printWindow = window.open("", "_blank"); if (printWindow) { printWindow.document.write(` - Your Private Key + === MESHTASTIC KEYS === -

Your Private Key

-

${getPrivateKey}

+

=== MESHTASTIC KEYS ===

+
+

Public Key:

+

${decodeKeyData(publicKey)}

+

Private Key:

+

${decodeKeyData(privateKey)}

+
+

=== END OF KEYS ===

`); printWindow.document.close(); printWindow.print(); - closeDialog() - + closeDialog(); } - }, [getPrivateKey, closeDialog]); + }, [decodeKeyData, privateKey, publicKey, closeDialog]); const createDownloadKeyFile = React.useCallback(() => { - const blob = new Blob([getPrivateKey], { type: "text/plain" }); + if (!privateKey || !publicKey) return; + + const decodedPrivateKey = decodeKeyData(privateKey); + const decodedPublicKey = decodeKeyData(publicKey); + + const formattedContent = [ + "=== MESHTASTIC KEYS ===\n\n", + "Private Key:\n", + decodedPrivateKey, + "\n\nPublic Key:\n", + decodedPublicKey, + "\n\n=== END OF KEYS ===", + ].join(""); + + const blob = new Blob([formattedContent], { type: "text/plain" }); const url = URL.createObjectURL(blob); + const link = document.createElement("a"); link.href = url; - link.download = "meshtastic_private_key.txt"; + link.download = "meshtastic_keys.txt"; link.style.display = "none"; document.body.appendChild(link); link.click(); document.body.removeChild(link); - closeDialog() + closeDialog(); URL.revokeObjectURL(url); - }, [getPrivateKey, closeDialog]); - + }, [decodeKeyData, privateKey, publicKey, closeDialog]); return ( - Backup Key + Backup Keys - Its important to backup your private key and store your backup securely! + Its important to backup your public and private keys and store your + backup securely! - If you lose your private key, you will need to reset your device. + + If you lose your keys, you will need to reset your device. + - diff --git a/src/components/KeyBackupReminder.tsx b/src/components/KeyBackupReminder.tsx index 45819bd7..7463d85f 100644 --- a/src/components/KeyBackupReminder.tsx +++ b/src/components/KeyBackupReminder.tsx @@ -5,17 +5,15 @@ export const KeyBackupReminder = (): JSX.Element => { const { setDialogOpen } = useDevice(); useBackupReminder({ - suppressDays: 7, - message: "We recommend backing up your key data regularly. Would you like to back up now?", + reminderInDays: 7, + message: + "We recommend backing up your key data regularly. Would you like to back up now?", onAccept: () => setDialogOpen("pkiBackup", true), + enabled: true, cookieOptions: { secure: true, - sameSite: 'strict' - } + sameSite: "strict", + }, }); - - return ( - <> - - ); + return <>; }; diff --git a/src/components/Toaster.tsx b/src/components/Toaster.tsx index cf322758..f5531bba 100644 --- a/src/components/Toaster.tsx +++ b/src/components/Toaster.tsx @@ -13,8 +13,14 @@ export function Toaster() { return ( - {toasts.map(({ id, title, description, action, ...props }) => ( - + {toasts.map(({ id, title, description, action, duration, ...props }) => ( +
{title && {title}} {description && {description}} @@ -26,4 +32,4 @@ export function Toaster() { ); -} +} \ No newline at end of file diff --git a/src/core/hooks/useCookie.ts b/src/core/hooks/useCookie.ts index 6a1eb311..df3d9d82 100644 --- a/src/core/hooks/useCookie.ts +++ b/src/core/hooks/useCookie.ts @@ -1,35 +1,52 @@ -import React from "react"; import Cookies, { type CookieAttributes } from "js-cookie"; +import { useCallback, useState } from "react"; -type Cookie = [ - T | undefined, - (value: T, options?: CookieAttributes) => void, - () => void, -]; +interface CookieHookResult { + value: T | undefined; + setCookie: (value: T, options?: CookieAttributes) => void; + removeCookie: () => void; +} -const useCookie = ( +function useCookie( cookieName: string, initialValue?: T, -): Cookie => { - const [cookieValue, setCookieValue] = React.useState(() => { - const cookie = Cookies.get(cookieName); - return cookie ? (JSON.parse(cookie) as T) : initialValue; +): CookieHookResult { + const [cookieValue, setCookieValue] = useState(() => { + try { + const cookie = Cookies.get(cookieName); + return cookie ? (JSON.parse(cookie) as T) : initialValue; + } catch (error) { + console.error(`Error parsing cookie ${cookieName}:`, error); + return initialValue; + } }); - const setCookie = React.useCallback( + const setCookie = useCallback( (value: T, options?: CookieAttributes) => { - Cookies.set(cookieName, JSON.stringify(value), options); - setCookieValue(value); + try { + Cookies.set(cookieName, JSON.stringify(value), options); + setCookieValue(value); + } catch (error) { + console.error(`Error setting cookie ${cookieName}:`, error); + } }, [cookieName], ); - const removeCookie = React.useCallback(() => { - Cookies.remove(cookieName); - setCookieValue(undefined); + const removeCookie = useCallback(() => { + try { + Cookies.remove(cookieName); + setCookieValue(undefined); + } catch (error) { + console.error(`Error removing cookie ${cookieName}:`, error); + } }, [cookieName]); - return [cookieValue, setCookie, removeCookie]; -}; + return { + value: cookieValue, + setCookie, + removeCookie, + }; +} export default useCookie; diff --git a/src/core/hooks/useKeyBackupReminder.tsx b/src/core/hooks/useKeyBackupReminder.tsx index 9af1ec70..ee65d161 100644 --- a/src/core/hooks/useKeyBackupReminder.tsx +++ b/src/core/hooks/useKeyBackupReminder.tsx @@ -1,14 +1,14 @@ -import { useEffect, useCallback } from 'react'; -import { useToast } from './useToast'; -import useCookie from './useCookie'; -import type { CookieAttributes } from 'js-cookie'; -import { Button } from '@app/components/UI/Button'; +import { Button } from "@app/components/UI/Button"; +import type { CookieAttributes } from "js-cookie"; +import { useCallback, useEffect, useRef } from "react"; +import useCookie from "./useCookie"; +import { useToast } from "./useToast"; interface UseBackupReminderOptions { - suppressDays?: number; - message?: string; + reminderInDays?: number; + message: string; onAccept?: () => void | Promise; - cookieName?: string; + enabled: boolean; cookieOptions?: CookieAttributes; } @@ -17,73 +17,104 @@ interface ReminderState { lastShown: string; } -const TOAST_DELAY = 10000; +const TOAST_APPEAR_DELAY = 10_000 // 10 seconds; +const TOAST_DURATION = 30_000 // 30 seconds;: + +// remind user in 1 year to backup keys again, if they accept the reminder; +const ON_ACCEPT_REMINDER_DAYS = 365 + +function isReminderExpired(lastShown: string): boolean { + const lastShownDate = new Date(lastShown); + const now = new Date(); + const daysSinceLastShown = + (now.getTime() - lastShownDate.getTime()) / (1000 * 60 * 60 * 24); + return daysSinceLastShown >= 7; +} export function useBackupReminder({ - suppressDays = 365, - message = "It's time to back up your key data. Would you like to do this now?", + reminderInDays = 7, + enabled, + message, onAccept = () => { }, - cookieName = "backup_reminder_state", - cookieOptions = {}, -}: UseBackupReminderOptions = {}) { + cookieOptions, +}: UseBackupReminderOptions) { const { toast } = useToast(); + const toastShownRef = useRef(false); + const { value: reminderCookie, setCookie } = + useCookie("key_backup_reminder"); + + const suppressReminder = useCallback( + (days: number) => { + const expiryDate = new Date(); + expiryDate.setDate(expiryDate.getDate() + days); + + setCookie( + { + suppressed: true, + lastShown: new Date().toISOString(), + }, + { ...cookieOptions, expires: expiryDate }, + ); + }, + [setCookie, cookieOptions], + ); + + useEffect(() => { + if (!enabled || toastShownRef.current) return; - const [reminderState, setReminderState, resetReminderState] = useCookie(cookieName); + const shouldShowReminder = + !reminderCookie?.suppressed || + isReminderExpired(reminderCookie.lastShown); + if (!shouldShowReminder) return; - const suppressReminder = useCallback(() => { - const expiryDate = new Date(); - expiryDate.setDate(expiryDate.getDate() + suppressDays); + toastShownRef.current = true; - setReminderState( + const { dismiss } = toast( { - suppressed: true, - lastShown: new Date().toISOString(), + title: "Backup Reminder", + duration: TOAST_DURATION, + delay: TOAST_APPEAR_DELAY, + description: message, + action: ( +
+ + +
+ ), }, - { - ...cookieOptions, - expires: expiryDate, - } ); - }, [setReminderState, suppressDays, cookieOptions]); - - useEffect(() => { - if (!reminderState) { - setTimeout(() => { - const { dismiss: dimissToast } = toast({ - title: "Backup Reminder", - description: message, - action: ( -
- - -
- ), - }); - }, TOAST_DELAY); - } - }, [reminderState]); - - return { - resetReminder: resetReminderState - }; -} \ No newline at end of file + return () => { + if (!toastShownRef.current) { + dismiss(); + } + }; + }, [ + enabled, + message, + onAccept, + reminderInDays, + suppressReminder, + toast, + reminderCookie, + ]); +} diff --git a/src/core/hooks/useToast.ts b/src/core/hooks/useToast.ts index f64cf45a..c913d223 100644 --- a/src/core/hooks/useToast.ts +++ b/src/core/hooks/useToast.ts @@ -10,6 +10,7 @@ type ToasterToast = ToastProps & { title?: ReactNode; description?: ReactNode; action?: ToastActionElement; + delay?: number; }; const actionTypes = { @@ -137,7 +138,7 @@ function dispatch(action: Action) { type Toast = Omit; -function toast({ ...props }: Toast) { +function toast({ delay = 0, ...props }: Toast) { const id = genId(); const update = (props: ToasterToast) => @@ -147,17 +148,19 @@ function toast({ ...props }: Toast) { }); const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }); - dispatch({ - type: "ADD_TOAST", - toast: { - ...props, - id, - open: true, - onOpenChange: (open) => { - if (!open) dismiss(); + setTimeout(() => { + dispatch({ + type: "ADD_TOAST", + toast: { + ...props, + id, + open: true, + onOpenChange: (open) => { + if (!open) dismiss(); + }, }, - }, - }); + }); + }, delay); return { id: id, @@ -190,4 +193,4 @@ function useToast() { }; } -export { toast, useToast }; +export { toast, useToast }; \ No newline at end of file From 225d6055d4c39cf258332058b6c4007854269695 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Wed, 15 Jan 2025 20:11:46 -0500 Subject: [PATCH 38/47] Improved error messaging based on feature missing from browser --- src/components/Dialog/NewDeviceDialog.tsx | 138 ++++++++++++------- src/components/UI/Typography/Link.tsx | 7 +- src/core/hooks/useBrowserFeatureDetection.ts | 49 ++++--- 3 files changed, 117 insertions(+), 77 deletions(-) diff --git a/src/components/Dialog/NewDeviceDialog.tsx b/src/components/Dialog/NewDeviceDialog.tsx index 92d4c75a..e03668d7 100644 --- a/src/components/Dialog/NewDeviceDialog.tsx +++ b/src/components/Dialog/NewDeviceDialog.tsx @@ -1,4 +1,7 @@ -import { useBrowserFeatureDetection } from "@app/core/hooks/useBrowserFeatureDetection"; +import { + type BrowserFeature, + useBrowserFeatureDetection, +} from "@app/core/hooks/useBrowserFeatureDetection"; import { BLE } from "@components/PageComponents/Connect/BLE.tsx"; import { HTTP } from "@components/PageComponents/Connect/HTTP.tsx"; import { Serial } from "@components/PageComponents/Connect/Serial.tsx"; @@ -8,14 +11,16 @@ import { DialogHeader, DialogTitle, } from "@components/UI/Dialog.tsx"; +import { AlertCircle, } from "lucide-react"; import { Tabs, TabsContent, TabsList, TabsTrigger, } from "@components/UI/Tabs.tsx"; -import { Link } from "@components/UI/Typography/Link.tsx"; import { Subtle } from "@components/UI/Typography/Subtle.tsx"; +import { Link } from "../UI/Typography/Link"; +import { Fragment } from "react/jsx-runtime"; export interface TabElementProps { closeDialog: () => void; @@ -29,19 +34,88 @@ export interface TabManifest { disabledLink?: string; } - - export interface NewDeviceProps { open: boolean; onOpenChange: (open: boolean) => void; } +interface FeatureErrorProps { + missingFeatures: BrowserFeature[]; +} + +const links: { [key: string]: string } = { + "Web Bluetooth": + "https://developer.mozilla.org/en-US/docs/Web/API/Web_Bluetooth_API#browser_compatibility", + "Web Serial": + "https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API#browser_compatibility", + "Secure Context": + "https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts", +}; + +const listFormatter = new Intl.ListFormat('en', { + style: 'long', + type: 'conjunction' +}); + +const ErrorMessage = ({ missingFeatures }: FeatureErrorProps) => { + if (missingFeatures.length === 0) return null; + + const browserFeatures = missingFeatures.filter(feature => feature !== "Secure Context"); + const needsSecureContext = missingFeatures.includes("Secure Context"); + + const formatFeatureList = (features: string[]) => { + const parts = listFormatter.formatToParts(features); + return parts.map((part) => { + if (part.type === 'element') { + return ( + + {part.value} + + ); + } + return {part.value}; + }); + }; + + return ( + +
+ +
+

+ {browserFeatures.length > 0 && ( + <> + This application requires {formatFeatureList(browserFeatures)}. + Please use a Chromium-based browser like Chrome or Edge. + + )} + {needsSecureContext && ( + <> + {browserFeatures.length > 0 && " Additionally, it"} + {browserFeatures.length === 0 && "This application"} requires a{" "} + + secure context + + . Please connect using HTTPS or localhost. + + )} +

+
+
+
+ ); +}; + export const NewDeviceDialog = ({ open, onOpenChange, }: NewDeviceProps): JSX.Element => { - const { hasRequiredFeatures, isSecureContext, missingFeatures } = useBrowserFeatureDetection(); - console.log(missingFeatures); + const { unsupported } = useBrowserFeatureDetection(); const tabs: TabManifest[] = [ { @@ -53,23 +127,21 @@ export const NewDeviceDialog = ({ { label: "Bluetooth", element: BLE, - disabled: missingFeatures.includes("Web Bluetooth"), - disabledMessage: - "Web Bluetooth is currently only supported by Chromium-based browsers", - disabledLink: - "https://developer.mozilla.org/en-US/docs/Web/API/Web_Bluetooth_API#browser_compatibility" + disabled: + unsupported.includes("Web Bluetooth") || + unsupported.includes("Secure Context"), + disabledMessage: "", }, { label: "Serial", element: Serial, - disabled: missingFeatures.includes("Web Serial"), - disabledMessage: - "Web Serial is currently only supported by Chromium based browsers", - disabledLink: "https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API#browser_compatibility", + disabled: + unsupported.includes("Web Serial") || + unsupported.includes("Secure Context"), + disabledMessage: "", }, ]; - return ( @@ -99,40 +171,8 @@ export const NewDeviceDialog = ({ )} ))} + - - {!isSecureContext && ( - <> - - Web Bluetooth and Web Serial require using a HTTPS connection or to localhost. - - - Read more:  - - Secure Contexts - - - - )} - - {!hasRequiredFeatures && ( - <> - - Web Bluetooth and Web Serial are currently only supported by - Chromium-based browsers. - - - Read more:  - - Web Bluetooth - -   - - Web Serial - - - - )} ); diff --git a/src/components/UI/Typography/Link.tsx b/src/components/UI/Typography/Link.tsx index af1a90ac..efcfbdf6 100644 --- a/src/components/UI/Typography/Link.tsx +++ b/src/components/UI/Typography/Link.tsx @@ -1,14 +1,17 @@ +import { cn } from "@app/core/utils/cn"; + export interface LinkProps { href: string; children: React.ReactNode; + className?: string; } -export const Link = ({ href, children }: LinkProps): JSX.Element => ( +export const Link = ({ href, children, className }: LinkProps): JSX.Element => ( {children} diff --git a/src/core/hooks/useBrowserFeatureDetection.ts b/src/core/hooks/useBrowserFeatureDetection.ts index df103b2c..83a90a7a 100644 --- a/src/core/hooks/useBrowserFeatureDetection.ts +++ b/src/core/hooks/useBrowserFeatureDetection.ts @@ -1,32 +1,29 @@ -type Feature = 'Web Bluetooth' | 'Web Serial'; -type FeatureKey = 'bluetooth' | 'serial'; +import { useMemo } from 'react'; -interface BrowserFeatureDetection { - hasRequiredFeatures: boolean; - missingFeatures: Feature[]; - isSecureContext: boolean; -} +export type BrowserFeature = 'Web Bluetooth' | 'Web Serial' | 'Secure Context'; -const featureLabels: Record = { - bluetooth: 'Web Bluetooth', - serial: 'Web Serial' -}; +interface BrowserSupport { + supported: BrowserFeature[]; + unsupported: BrowserFeature[]; +} -export function useBrowserFeatureDetection(): BrowserFeatureDetection { - const { bluetooth, serial } = navigator; - const isSecureContext = window.location.protocol === 'https:' || - window.location.hostname === 'localhost'; +export function useBrowserFeatureDetection(): BrowserSupport { + const support = useMemo(() => { + const features: [BrowserFeature, boolean][] = [ + ['Web Bluetooth', !!navigator.bluetooth], + ['Web Serial', !!navigator.serial], + ['Secure Context', window.location.protocol === 'https:' || window.location.hostname === 'localhost'] + ]; - const features = { - bluetooth, - serial - }; + return features.reduce( + (acc, [feature, isSupported]) => { + const list = isSupported ? acc.supported : acc.unsupported; + list.push(feature); + return acc; + }, + { supported: [], unsupported: [] } + ); + }, []); - return { - hasRequiredFeatures: Object.values(features).every(Boolean), - missingFeatures: Object.entries(features) - .filter(([_, supported]) => !supported) - .map(([feature]) => featureLabels[feature as FeatureKey]), - isSecureContext - }; + return support; } \ No newline at end of file From be6acc5ef2d96031e2bbd1b2aa15259049c1d6cc Mon Sep 17 00:00:00 2001 From: Hunter275 Date: Sun, 19 Jan 2025 00:50:23 -0500 Subject: [PATCH 39/47] add right border --- src/components/Sidebar.tsx | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index dba5b3fa..d4ada2bd 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -80,17 +80,20 @@ export const Sidebar = ({ children }: SidebarProps): JSX.Element => { > -
- {myNode?.deviceMetrics?.batteryLevel ? myNode?.deviceMetrics?.batteryLevel > 100 ? "Charging" : myNode?.deviceMetrics?.batteryLevel + "%" : "UNK"} + + {myNode?.deviceMetrics?.batteryLevel + ? myNode?.deviceMetrics?.batteryLevel > 100 + ? "Charging" + : myNode?.deviceMetrics?.batteryLevel + "%" + : "UNK"} +
@@ -120,13 +123,10 @@ export const Sidebar = ({ children }: SidebarProps): JSX.Element => { {children}
) : ( -
- +
); }; From ebb32f0893eb7093bcd2655ebd88dc599e3dc31f Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Sun, 19 Jan 2025 22:42:38 -0500 Subject: [PATCH 40/47] fix: changed position of error message, disabled buttons when error is showing --- src/components/Dialog/NewDeviceDialog.tsx | 26 ++++++------------- .../PageComponents/Connect/Serial.tsx | 11 ++++---- src/core/hooks/useBrowserFeatureDetection.ts | 4 +-- 3 files changed, 15 insertions(+), 26 deletions(-) diff --git a/src/components/Dialog/NewDeviceDialog.tsx b/src/components/Dialog/NewDeviceDialog.tsx index e03668d7..e3330cbf 100644 --- a/src/components/Dialog/NewDeviceDialog.tsx +++ b/src/components/Dialog/NewDeviceDialog.tsx @@ -11,7 +11,7 @@ import { DialogHeader, DialogTitle, } from "@components/UI/Dialog.tsx"; -import { AlertCircle, } from "lucide-react"; +import { AlertCircle, InfoIcon, } from "lucide-react"; import { Tabs, TabsContent, @@ -29,9 +29,7 @@ export interface TabElementProps { export interface TabManifest { label: string; element: React.FC; - disabled: boolean; - disabledMessage: string; - disabledLink?: string; + isDisabled: boolean; } export interface NewDeviceProps { @@ -121,24 +119,21 @@ export const NewDeviceDialog = ({ { label: "HTTP", element: HTTP, - disabled: false, - disabledMessage: "Unsuported connection method", + isDisabled: false, }, { label: "Bluetooth", element: BLE, - disabled: + isDisabled: unsupported.includes("Web Bluetooth") || unsupported.includes("Secure Context"), - disabledMessage: "", }, { label: "Serial", element: Serial, - disabled: + isDisabled: unsupported.includes("Web Serial") || unsupported.includes("Secure Context"), - disabledMessage: "", }, ]; @@ -154,7 +149,6 @@ export const NewDeviceDialog = ({ {tab.label} @@ -162,16 +156,12 @@ export const NewDeviceDialog = ({ {tabs.map((tab) => ( - {tab.disabled ? ( -

- {tab.disabledMessage} -

- ) : ( +
+ {tab.isDisabled ? : null} onOpenChange(false)} /> - )} +
))} -
diff --git a/src/components/PageComponents/Connect/Serial.tsx b/src/components/PageComponents/Connect/Serial.tsx index 38aab8fb..53c623cb 100644 --- a/src/components/PageComponents/Connect/Serial.tsx +++ b/src/components/PageComponents/Connect/Serial.tsx @@ -14,13 +14,13 @@ export const Serial = ({ closeDialog }: TabElementProps): JSX.Element => { const { setSelectedDevice } = useAppStore(); const updateSerialPortList = useCallback(async () => { - setSerialPorts(await navigator.serial.getPorts()); + setSerialPorts(await navigator?.serial.getPorts()); }, []); - navigator.serial.addEventListener("connect", () => { + navigator?.serial?.addEventListener("connect", () => { updateSerialPortList(); }); - navigator.serial.addEventListener("disconnect", () => { + navigator?.serial?.addEventListener("disconnect", () => { updateSerialPortList(); }); useEffect(() => { @@ -58,9 +58,8 @@ export const Serial = ({ closeDialog }: TabElementProps): JSX.Element => { await onConnect(port); }} > - {`# ${index} - ${usbVendorId ?? "UNK"} - ${ - usbProductId ?? "UNK" - }`} + {`# ${index} - ${usbVendorId ?? "UNK"} - ${usbProductId ?? "UNK" + }`} ); })} diff --git a/src/core/hooks/useBrowserFeatureDetection.ts b/src/core/hooks/useBrowserFeatureDetection.ts index 83a90a7a..a31b7b5a 100644 --- a/src/core/hooks/useBrowserFeatureDetection.ts +++ b/src/core/hooks/useBrowserFeatureDetection.ts @@ -10,8 +10,8 @@ interface BrowserSupport { export function useBrowserFeatureDetection(): BrowserSupport { const support = useMemo(() => { const features: [BrowserFeature, boolean][] = [ - ['Web Bluetooth', !!navigator.bluetooth], - ['Web Serial', !!navigator.serial], + ['Web Bluetooth', !!navigator?.bluetooth], + ['Web Serial', !!navigator?.serial], ['Secure Context', window.location.protocol === 'https:' || window.location.hostname === 'localhost'] ]; From 42ccd953c09ee6bf9707309042a6565395411f14 Mon Sep 17 00:00:00 2001 From: Hunter275 Date: Mon, 20 Jan 2025 20:20:50 -0500 Subject: [PATCH 41/47] remove version spec --- .github/workflows/ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd73ce31..2591421c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,8 +16,6 @@ jobs: - name: Checkout uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 - with: - version: latest - name: Install Dependencies run: pnpm install From 8d35b57d1983029a0e971721823cd0200e4899a8 Mon Sep 17 00:00:00 2001 From: Tilen Komel Date: Thu, 23 Jan 2025 12:59:57 +0100 Subject: [PATCH 42/47] Add packageManager in package.json --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 84a49432..2946c813 100644 --- a/package.json +++ b/package.json @@ -85,5 +85,6 @@ "tar": "^6.2.1", "tslib": "^2.6.3", "typescript": "^5.5.2" - } + }, + "packageManager": "pnpm@9.15.4" } From ea648ca88712e0d688c4a93402b774b0c2fca117 Mon Sep 17 00:00:00 2001 From: Tilen Komel Date: Thu, 23 Jan 2025 13:30:16 +0100 Subject: [PATCH 43/47] Just for joke without actions/checkout@v4 --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2591421c..7f568b6c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,6 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 - name: Install Dependencies From 2967a74480e88f4c5fe5b7172aeb9ec993b203c2 Mon Sep 17 00:00:00 2001 From: Tilen Komel Date: Thu, 23 Jan 2025 13:33:13 +0100 Subject: [PATCH 44/47] Blank version --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7f568b6c..975a3e9b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v4 + with: + version: - name: Install Dependencies run: pnpm install From c16b3a3ce0286603d4e16187f05a3f729b1f0946 Mon Sep 17 00:00:00 2001 From: Tilen Komel Date: Thu, 23 Jan 2025 13:35:12 +0100 Subject: [PATCH 45/47] Add version --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 975a3e9b..9b467af3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: - name: Checkout uses: pnpm/action-setup@v4 with: - version: + version: 9.15.4 - name: Install Dependencies run: pnpm install From c8cd5b0eaa5be59d32622fa726fc0c74638d5700 Mon Sep 17 00:00:00 2001 From: Tilen Komel Date: Thu, 23 Jan 2025 13:49:44 +0100 Subject: [PATCH 46/47] Define package_json_file: package.json in CI --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b467af3..3d1fac56 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: - name: Checkout uses: pnpm/action-setup@v4 with: - version: 9.15.4 + package_json_file: package.json - name: Install Dependencies run: pnpm install From 9493649a69729ed0abea6db950bfc5b2755d0ae8 Mon Sep 17 00:00:00 2001 From: Tilen Komel Date: Thu, 23 Jan 2025 14:02:21 +0100 Subject: [PATCH 47/47] This works now --- .github/workflows/ci.yml | 7 ++++--- .github/workflows/pr.yml | 2 -- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3d1fac56..76f226cc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,10 +13,11 @@ jobs: build-and-package: runs-on: ubuntu-latest steps: - - name: Checkout + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup pnpm uses: pnpm/action-setup@v4 - with: - package_json_file: package.json - name: Install Dependencies run: pnpm install diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 870ca6fe..0e3f324a 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -11,8 +11,6 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@v4 - with: - version: latest - name: Install Dependencies run: pnpm install