diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index f6bbf6ba..c92d312c 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,13 +1,12 @@ { - "name": "meshtastic-web", - "image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm", - "features": { - "ghcr.io/r3dpoint/devcontainer-features/tailwindcss-standalone-cli:1": { - "version": "latest" - }, - "ghcr.io/devcontainers-extra/features/pnpm:2": { - "version": "latest" - } - } + "name": "meshtastic-web", + "image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm", + "features": { + "ghcr.io/r3dpoint/devcontainer-features/tailwindcss-standalone-cli:1": { + "version": "latest" + }, + "ghcr.io/devcontainers-extra/features/pnpm:2": { + "version": "latest" + } + } } - diff --git a/.github/workflows/release-protobufs.yml b/.github/workflows/release-protobufs.yml index ac60296c..6bb40c21 100644 --- a/.github/workflows/release-protobufs.yml +++ b/.github/workflows/release-protobufs.yml @@ -16,7 +16,7 @@ on: permissions: write-all env: - PROTOBUF_DIR: ./packages/protobufs + PROTOBUF_DIR: ./packages/protobufs jobs: codegen: @@ -70,7 +70,7 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@v6 - + - name: Install workspace dependencies run: pnpm install --frozen-lockfile diff --git a/.github/workflows/update-stable-from-master.yml b/.github/workflows/update-stable-from-master.yml index 44c0682a..cfa4968d 100644 --- a/.github/workflows/update-stable-from-master.yml +++ b/.github/workflows/update-stable-from-master.yml @@ -20,7 +20,7 @@ jobs: uses: actions/checkout@v6 with: fetch-depth: 0 - fetch-tags: true # IMPORTANT: we need tags to resolve the release commit + fetch-tags: true # IMPORTANT: we need tags to resolve the release commit - name: Configure Git author run: | diff --git a/.oxfmtrc.json b/.oxfmtrc.json index b5fa1fc7..32547c59 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -1,5 +1,5 @@ { - "$schema": "./node_modules/oxfmt/configuration_schema.json", + "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxfmt/configuration_schema.json", "useTabs": false, "tabWidth": 2, "printWidth": 80, diff --git a/.vscode/settings.json b/.vscode/settings.json index 2253a76e..9f4f9317 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,5 +2,5 @@ "editor.formatOnSave": true, "search.exclude": { "**/i18n/locales/*-*/**": true - }, -} \ No newline at end of file + } +} diff --git a/apps/web/index.html b/apps/web/index.html index 30ad400c..4d5b33b6 100644 --- a/apps/web/index.html +++ b/apps/web/index.html @@ -1,4 +1,4 @@ - + @@ -11,11 +11,7 @@ - + diff --git a/apps/web/package.json b/apps/web/package.json index 506b318c..7f46f261 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,17 +1,17 @@ { "name": "meshtastic-web", "version": "2.6.6-0", - "type": "module", "description": "Meshtastic web client", + "homepage": "https://meshtastic.org", + "bugs": { + "url": "https://github.com/meshtastic/web/issues" + }, "license": "GPL-3.0-only", "repository": { "type": "git", "url": "git+https://github.com/meshtastic/web.git" }, - "bugs": { - "url": "https://github.com/meshtastic/web/issues" - }, - "homepage": "https://meshtastic.org", + "type": "module", "scripts": { "preinstall": "npx only-allow pnpm", "setup:certs": "mkcert localhost 127.0.0.1 ::1", @@ -41,7 +41,6 @@ "@meshtastic/transport-web-serial": "workspace:*", "@noble/curves": "^1.9.2", "@preact/signals-core": "^1.8.0", - "better-result": "^2.9.2", "@radix-ui/react-accordion": "^1.2.13", "@radix-ui/react-alert-dialog": "^1.1.16", "@radix-ui/react-checkbox": "^1.3.4", @@ -69,6 +68,7 @@ "@types/node": "^25.9.3", "@types/web-bluetooth": "^0.0.21", "base64-js": "^1.5.1", + "better-result": "^2.9.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", diff --git a/apps/web/public/site.webmanifest b/apps/web/public/site.webmanifest index 7f923721..dc9509b9 100644 --- a/apps/web/public/site.webmanifest +++ b/apps/web/public/site.webmanifest @@ -1,16 +1,16 @@ { - "name": "Meshtastic", - "short_name": "Web Client", - "start_url": ".", - "description": "Meshtastic Web App", - "icons": [ - { - "src": "/logo.svg", - "sizes": "any", - "type": "image/svg+xml" - } - ], - "theme_color": "#67ea94", - "background_color": "#67ea94", - "display": "standalone" + "name": "Meshtastic", + "short_name": "Web Client", + "start_url": ".", + "description": "Meshtastic Web App", + "icons": [ + { + "src": "/logo.svg", + "sizes": "any", + "type": "image/svg+xml" + } + ], + "theme_color": "#67ea94", + "background_color": "#67ea94", + "display": "standalone" } diff --git a/apps/web/src/DeviceWrapper.tsx b/apps/web/src/DeviceWrapper.tsx index 05ae6c57..d9ee3def 100644 --- a/apps/web/src/DeviceWrapper.tsx +++ b/apps/web/src/DeviceWrapper.tsx @@ -8,6 +8,8 @@ export interface DeviceWrapperProps { export const DeviceWrapper = ({ children, deviceId }: DeviceWrapperProps) => { return ( - {children} + + {children} + ); }; diff --git a/apps/web/src/__mocks__/components/UI/Checkbox.tsx b/apps/web/src/__mocks__/components/UI/Checkbox.tsx index 62150e99..a194124d 100644 --- a/apps/web/src/__mocks__/components/UI/Checkbox.tsx +++ b/apps/web/src/__mocks__/components/UI/Checkbox.tsx @@ -1,7 +1,21 @@ import { vi } from "vitest"; vi.mock("@components/UI/Checkbox.tsx", () => ({ - Checkbox: ({ id, checked, onChange }: { id: string; checked: boolean; onChange: () => void }) => ( - + Checkbox: ({ + id, + checked, + onChange, + }: { + id: string; + checked: boolean; + onChange: () => void; + }) => ( + ), })); diff --git a/apps/web/src/components/BatteryStatus.tsx b/apps/web/src/components/BatteryStatus.tsx index 71b64c62..9f541ce6 100644 --- a/apps/web/src/components/BatteryStatus.tsx +++ b/apps/web/src/components/BatteryStatus.tsx @@ -1,4 +1,9 @@ -import { BatteryFullIcon, BatteryLowIcon, BatteryMediumIcon, PlugZapIcon } from "lucide-react"; +import { + BatteryFullIcon, + BatteryLowIcon, + BatteryMediumIcon, + PlugZapIcon, +} from "lucide-react"; import type React from "react"; import { useTranslation } from "react-i18next"; import type { DeviceMetrics } from "./types.ts"; @@ -38,7 +43,10 @@ export const getBatteryStatus = (level: number): BatteryStatusKey => { const BatteryStatus: React.FC = ({ deviceMetrics }) => { const { t } = useTranslation(); - if (deviceMetrics?.batteryLevel === undefined || deviceMetrics?.batteryLevel === null) { + if ( + deviceMetrics?.batteryLevel === undefined || + deviceMetrics?.batteryLevel === null + ) { return null; } diff --git a/apps/web/src/components/CommandPalette/index.tsx b/apps/web/src/components/CommandPalette/index.tsx index 5911bcef..75590d02 100644 --- a/apps/web/src/components/CommandPalette/index.tsx +++ b/apps/web/src/components/CommandPalette/index.tsx @@ -58,8 +58,12 @@ export interface SubItem { } export const CommandPalette = () => { - const { commandPaletteOpen, setCommandPaletteOpen, setConnectDialogOpen, setSelectedDevice } = - useAppStore(); + const { + commandPaletteOpen, + setCommandPaletteOpen, + setConnectDialogOpen, + setSelectedDevice, + } = useAppStore(); const { getDevices } = useDeviceStore(); const { setDialogOpen, connection } = useDevice(); const allNodes = useNodesAsProto(); @@ -116,7 +120,9 @@ export const CommandPalette = () => { label: t("manage.command.switchNode"), icon: ArrowLeftRightIcon, subItems: getDevices().map((device) => ({ - label: getNode(device.hardware.myNodeNum)?.user?.longName ?? t("unknown.shortName"), + label: + getNode(device.hardware.myNodeNum)?.user?.longName ?? + t("unknown.shortName"), icon: , action() { setSelectedDevice(device.id); @@ -259,7 +265,10 @@ export const CommandPalette = () => { }, [setCommandPaletteOpen]); return ( - + {t("emptyState")} diff --git a/apps/web/src/components/ConnectingOverlay.tsx b/apps/web/src/components/ConnectingOverlay.tsx index 417e1da8..4c6defa1 100644 --- a/apps/web/src/components/ConnectingOverlay.tsx +++ b/apps/web/src/components/ConnectingOverlay.tsx @@ -64,12 +64,21 @@ export const ConnectingOverlay = (): ReactElement | null => { const counters = progress.phase === "configuring" || progress.phase === "configured" ? progress.received - : { config: 0, modules: 0, channels: 0, nodes: 0, myInfo: false, metadata: false }; + : { + config: 0, + modules: 0, + channels: 0, + nodes: 0, + myInfo: false, + metadata: false, + }; const isConnecting = active.status === "connecting"; const phaseLabel = isConnecting ? t("overlay.phase.connecting", { defaultValue: "Opening transport…" }) - : t("overlay.phase.configuring", { defaultValue: "Streaming configuration…" }); + : t("overlay.phase.configuring", { + defaultValue: "Streaming configuration…", + }); return ( @@ -133,7 +142,11 @@ export const ConnectingOverlay = (): ReactElement | null => { "Taking longer than usual. The device may be in CLI / bootloader mode, or the firmware isn't responding to config requests.", })}

- diff --git a/apps/web/src/components/DeviceInfoPanel.tsx b/apps/web/src/components/DeviceInfoPanel.tsx index 3df92d74..ca517981 100644 --- a/apps/web/src/components/DeviceInfoPanel.tsx +++ b/apps/web/src/components/DeviceInfoPanel.tsx @@ -107,7 +107,9 @@ export const DeviceInfoPanel = ({ label: t("batteryVoltage.title"), icon: ZapIcon, value: - voltage !== undefined ? `${voltage?.toPrecision(3)} V` : t("unknown.notAvailable", "N/A"), + voltage !== undefined + ? `${voltage?.toPrecision(3)} V` + : t("unknown.notAvailable", "N/A"), }, { id: "firmware", @@ -205,7 +207,9 @@ export const DeviceInfoPanel = ({ )} - {!isCollapsed &&
} + {!isCollapsed && ( +
+ )}
- {!isCollapsed &&
} + {!isCollapsed && ( +
+ )}
{ const Icon = buttonItem.icon; if (buttonItem.render) { - return {buttonItem.render()}; + return ( + {buttonItem.render()} + ); } return ( -
{display}
+
+ {display} +
- {helper ?

{helper}

: null} + {helper ? ( +

{helper}

+ ) : null}
); } @@ -238,8 +267,10 @@ export default function AddConnectionDialog({ const { unsupported } = useBrowserFeatureDetection(); const { t } = useTranslation(); - const bluetoothSupported = typeof navigator !== "undefined" && "bluetooth" in navigator; - const serialSupported = typeof navigator !== "undefined" && "serial" in navigator; + const bluetoothSupported = + typeof navigator !== "undefined" && "bluetooth" in navigator; + const serialSupported = + typeof navigator !== "undefined" && "serial" in navigator; const isURLHTTPS = isHTTPS; const reset = useCallback(() => { @@ -268,7 +299,9 @@ export default function AddConnectionDialog({ if (!bluetoothSupported) { toast({ title: t("addConnection.bluetoothConnection.notSupported.title"), - description: t("addConnection.bluetoothConnection.notSupported.description"), + description: t( + "addConnection.bluetoothConnection.notSupported.description", + ), }); return; } @@ -303,7 +336,9 @@ export default function AddConnectionDialog({ if (!serialSupported) { toast({ title: t("addConnection.serialConnection.notSupported.title"), - description: t("addConnection.serialConnection.notSupported.description"), + description: t( + "addConnection.serialConnection.notSupported.description", + ), }); return; } @@ -335,7 +370,9 @@ export default function AddConnectionDialog({ } toast({ title: t("addConnection.serialConnection.portSelected.title"), - description: t("addConnection.serialConnection.portSelected.description"), + description: t( + "addConnection.serialConnection.portSelected.description", + ), }); } catch (err) { makeToastErrorHandler("Serial")(err); @@ -360,7 +397,9 @@ export default function AddConnectionDialog({ dispatch({ type: "SET_TEST_STATUS", payload: "failure" }); toast({ title: t("addConnection.httpConnection.connectionTest.failure.title"), - description: t("addConnection.httpConnection.connectionTest.failure.description"), + description: t( + "addConnection.httpConnection.connectionTest.failure.description", + ), }); } }, [state.protocol, state.url, toast, t]); @@ -371,7 +410,9 @@ export default function AddConnectionDialog({ placeholder: t("addConnection.httpConnection.namePlaceholder"), children: () => (
- + {state.testStatus === "testing" ? ( <> - {t("addConnection.httpConnection.connectionTest.button.loading")} + {t( + "addConnection.httpConnection.connectionTest.button.loading", + )} ) : ( <> - {t("addConnection.httpConnection.connectionTest.button.label")} + {t( + "addConnection.httpConnection.connectionTest.button.label", + )} )} @@ -432,7 +477,9 @@ export default function AddConnectionDialog({ {state.testStatus === "failure" && (
- {t("addConnection.httpConnection.connectionTest.notReachable")} + {t( + "addConnection.httpConnection.connectionTest.notReachable", + )}
)}
@@ -442,8 +489,8 @@ export default function AddConnectionDialog({
), validate: () => - urlOrIpv4Schema.safeParse(`${state.protocol}://${state.url}`).success === true && - state.testStatus === "success", + urlOrIpv4Schema.safeParse(`${state.protocol}://${state.url}`) + .success === true && state.testStatus === "success", build: () => ({ type: "http", name: state.name.trim(), @@ -456,8 +503,12 @@ export default function AddConnectionDialog({ <> - + ), validate: () => state.name.trim().length > 0 && !!state.btSelected, @@ -489,8 +543,12 @@ export default function AddConnectionDialog({ <> ), validate: () => - state.name.trim().length > 0 && (!!state.serialSelected || !serialSupported), + state.name.trim().length > 0 && + (!!state.serialSelected || !serialSupported), build: () => ({ type: "serial", name: state.name.trim(), @@ -537,7 +598,8 @@ export default function AddConnectionDialog({ const canCreate = useMemo(() => currentPane.validate(), [currentPane]); const submit = - (fn: (p: NewConnection, device?: BluetoothDevice) => Promise) => async () => { + (fn: (p: NewConnection, device?: BluetoothDevice) => Promise) => + async () => { if (!canCreate) { return; } @@ -546,7 +608,8 @@ export default function AddConnectionDialog({ if (!payload) { return; } - const btDevice = state.tab === "bluetooth" ? state.btSelected?.device : undefined; + const btDevice = + state.tab === "bluetooth" ? state.btSelected?.device : undefined; await fn(payload, btDevice); }; @@ -562,7 +625,9 @@ export default function AddConnectionDialog({ > dispatch({ type: "SET_TAB", payload: v as TabKey })} + onValueChange={(v) => + dispatch({ type: "SET_TAB", payload: v as TabKey }) + } > {TAB_META.map(({ key, label, Icon }) => ( @@ -582,14 +647,20 @@ export default function AddConnectionDialog({ dispatch({ type: "SET_NAME", payload: evt.target.value })} + onChange={(evt) => + dispatch({ type: "SET_NAME", payload: evt.target.value }) + } placeholder={currentPane.placeholder} />
{PANES[key].children()}
-
diff --git a/apps/web/src/components/Dialog/AddConnectionDialog/validation.ts b/apps/web/src/components/Dialog/AddConnectionDialog/validation.ts index a92848c1..e9d278b4 100644 --- a/apps/web/src/components/Dialog/AddConnectionDialog/validation.ts +++ b/apps/web/src/components/Dialog/AddConnectionDialog/validation.ts @@ -28,13 +28,18 @@ export const urlOrIpv4Schema = z } // IPv4 pattern - const ipv4Regex = /^(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$/; + const ipv4Regex = + /^(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$/; // Domain pattern (e.g. example.com, meshtastic.local) const domainRegex = /^(?!-)(?:[a-zA-Z0-9-]{1,63}\.)+[a-zA-Z]{2,}$/; // Local domain (e.g. meshtastic.local) const localDomainRegex = /^(?!-)[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.local$/; - return ipv4Regex.test(host) || domainRegex.test(host) || localDomainRegex.test(host); + return ( + ipv4Regex.test(host) || + domainRegex.test(host) || + localDomainRegex.test(host) + ); }, "Must be a valid IPv4 address or domain name with optional port (10-65535)") .transform((val) => { return /^https?:\/\//i.test(val) ? val : `http://${val}`; diff --git a/apps/web/src/components/Dialog/ClearAllStoresDialog/ClearAllStoresDialog.test.tsx b/apps/web/src/components/Dialog/ClearAllStoresDialog/ClearAllStoresDialog.test.tsx index bf05c329..50e6af3d 100644 --- a/apps/web/src/components/Dialog/ClearAllStoresDialog/ClearAllStoresDialog.test.tsx +++ b/apps/web/src/components/Dialog/ClearAllStoresDialog/ClearAllStoresDialog.test.tsx @@ -47,7 +47,9 @@ describe("ClearAllStoresDialog", () => { it("calls clearAllStores and navigates to '/' when confirm is clicked", () => { render(); - fireEvent.click(screen.getByRole("button", { name: "Clear all local storage" })); + fireEvent.click( + screen.getByRole("button", { name: "Clear all local storage" }), + ); expect(mockClearAllStores).toHaveBeenCalledTimes(1); expect(assignedHref).toBe("/"); // forced reload target diff --git a/apps/web/src/components/Dialog/ClearAllStoresDialog/ClearAllStoresDialog.tsx b/apps/web/src/components/Dialog/ClearAllStoresDialog/ClearAllStoresDialog.tsx index 78d049dd..731e0e8c 100644 --- a/apps/web/src/components/Dialog/ClearAllStoresDialog/ClearAllStoresDialog.tsx +++ b/apps/web/src/components/Dialog/ClearAllStoresDialog/ClearAllStoresDialog.tsx @@ -7,7 +7,10 @@ export interface ClearAllStoresDialogProps { onOpenChange: (open: boolean) => void; } -export const ClearAllStoresDialog = ({ open, onOpenChange }: ClearAllStoresDialogProps) => { +export const ClearAllStoresDialog = ({ + open, + onOpenChange, +}: ClearAllStoresDialogProps) => { const { t } = useTranslation("dialog"); const handleClearAllStores = () => { diff --git a/apps/web/src/components/Dialog/ClientNotificationDialog/ClientNotificationDialog.tsx b/apps/web/src/components/Dialog/ClientNotificationDialog/ClientNotificationDialog.tsx index 0b64100c..20896a02 100644 --- a/apps/web/src/components/Dialog/ClientNotificationDialog/ClientNotificationDialog.tsx +++ b/apps/web/src/components/Dialog/ClientNotificationDialog/ClientNotificationDialog.tsx @@ -21,9 +21,13 @@ export interface ClientNotificationDialogProps { onOpenChange: (open: boolean) => void; } -export const ClientNotificationDialog = ({ open, onOpenChange }: ClientNotificationDialogProps) => { +export const ClientNotificationDialog = ({ + open, + onOpenChange, +}: ClientNotificationDialogProps) => { const { t } = useTranslation("dialog"); - const { connection, getClientNotification, removeClientNotification } = useDevice(); + const { connection, getClientNotification, removeClientNotification } = + useDevice(); const [securityNumber, setSecurityNumber] = useState(""); const notification = getClientNotification(0); @@ -42,9 +46,11 @@ export const ClientNotificationDialog = ({ open, onOpenChange }: ClientNotificat nonce: bigint, secNum?: number, ) => { - connection?.sendKeyVerification(messageType, 0, nonce, secNum).catch((error) => { - console.error("Failed to send key verification message:", error); - }); + connection + ?.sendKeyVerification(messageType, 0, nonce, secNum) + .catch((error) => { + console.error("Failed to send key verification message:", error); + }); dismiss(); }; @@ -62,7 +68,9 @@ export const ClientNotificationDialog = ({ open, onOpenChange }: ClientNotificat {t("keyVerification.inform.title")} - {t("keyVerification.inform.description", { name: value.remoteLongname })} + {t("keyVerification.inform.description", { + name: value.remoteLongname, + })}

@@ -84,7 +92,9 @@ export const ClientNotificationDialog = ({ open, onOpenChange }: ClientNotificat {t("keyVerification.request.title")} - {t("keyVerification.request.description", { name: value.remoteLongname })} + {t("keyVerification.request.description", { + name: value.remoteLongname, + })} - respond(MessageType.PROVIDE_SECURITY_NUMBER, value.nonce, Number(securityNumber)) + respond( + MessageType.PROVIDE_SECURITY_NUMBER, + value.nonce, + Number(securityNumber), + ) } > {t("keyVerification.request.submit")} @@ -119,7 +133,9 @@ export const ClientNotificationDialog = ({ open, onOpenChange }: ClientNotificat {t("keyVerification.final.title")} - {t("keyVerification.final.description", { name: value.remoteLongname })} + {t("keyVerification.final.description", { + name: value.remoteLongname, + })}

@@ -132,7 +148,10 @@ export const ClientNotificationDialog = ({ open, onOpenChange }: ClientNotificat > {t("keyVerification.final.reject")} - @@ -145,7 +164,10 @@ export const ClientNotificationDialog = ({ open, onOpenChange }: ClientNotificat {t("clientNotification.title")} - {t([`clientNotification.${notification?.message}`, notification?.message ?? ""])} + {t([ + `clientNotification.${notification?.message}`, + notification?.message ?? "", + ])} ); diff --git a/apps/web/src/components/Dialog/DeleteMessagesDialog/DeleteMessagesDialog.test.tsx b/apps/web/src/components/Dialog/DeleteMessagesDialog/DeleteMessagesDialog.test.tsx index 0d2545e1..e323cfea 100644 --- a/apps/web/src/components/Dialog/DeleteMessagesDialog/DeleteMessagesDialog.test.tsx +++ b/apps/web/src/components/Dialog/DeleteMessagesDialog/DeleteMessagesDialog.test.tsx @@ -38,13 +38,19 @@ describe("DeleteMessagesDialog", () => { it("renders the dialog when open is true", () => { render(); expect(screen.getByText("Clear All Messages")).toBeInTheDocument(); - expect(screen.getByText(/This action will clear all message history./)).toBeInTheDocument(); + expect( + screen.getByText(/This action will clear all message history./), + ).toBeInTheDocument(); expect(screen.getByRole("button", { name: "Dismiss" })).toBeInTheDocument(); - expect(screen.getByRole("button", { name: "Clear Messages" })).toBeInTheDocument(); + expect( + screen.getByRole("button", { name: "Clear Messages" }), + ).toBeInTheDocument(); }); it("does not render the dialog when open is false", () => { - render(); + render( + , + ); expect(screen.queryByText("Clear All Messages")).toBeNull(); }); diff --git a/apps/web/src/components/Dialog/DeleteMessagesDialog/DeleteMessagesDialog.tsx b/apps/web/src/components/Dialog/DeleteMessagesDialog/DeleteMessagesDialog.tsx index 64a63b45..081d2244 100644 --- a/apps/web/src/components/Dialog/DeleteMessagesDialog/DeleteMessagesDialog.tsx +++ b/apps/web/src/components/Dialog/DeleteMessagesDialog/DeleteMessagesDialog.tsx @@ -8,7 +8,10 @@ export interface DeleteMessagesDialogProps { onOpenChange: (open: boolean) => void; } -export const DeleteMessagesDialog = ({ open, onOpenChange }: DeleteMessagesDialogProps) => { +export const DeleteMessagesDialog = ({ + open, + onOpenChange, +}: DeleteMessagesDialogProps) => { const { t } = useTranslation("dialog"); const meshClient = useActiveClient(); diff --git a/apps/web/src/components/Dialog/FactoryResetConfigDialog/FactoryResetConfigDialog.test.tsx b/apps/web/src/components/Dialog/FactoryResetConfigDialog/FactoryResetConfigDialog.test.tsx index 678f2a5a..ec205e93 100644 --- a/apps/web/src/components/Dialog/FactoryResetConfigDialog/FactoryResetConfigDialog.test.tsx +++ b/apps/web/src/components/Dialog/FactoryResetConfigDialog/FactoryResetConfigDialog.test.tsx @@ -33,7 +33,9 @@ describe("FactoryResetConfigDialog", () => { it("calls factoryResetConfig and then closes the dialog on confirm", async () => { render(); - fireEvent.click(screen.getByRole("button", { name: "Factory Reset Config" })); + fireEvent.click( + screen.getByRole("button", { name: "Factory Reset Config" }), + ); expect(mockFactoryReset).toHaveBeenCalledTimes(1); diff --git a/apps/web/src/components/Dialog/FactoryResetConfigDialog/FactoryResetConfigDialog.tsx b/apps/web/src/components/Dialog/FactoryResetConfigDialog/FactoryResetConfigDialog.tsx index 21d8061d..b350447d 100644 --- a/apps/web/src/components/Dialog/FactoryResetConfigDialog/FactoryResetConfigDialog.tsx +++ b/apps/web/src/components/Dialog/FactoryResetConfigDialog/FactoryResetConfigDialog.tsx @@ -8,7 +8,10 @@ export interface FactoryResetConfigDialogProps { onOpenChange: (open: boolean) => void; } -export const FactoryResetConfigDialog = ({ open, onOpenChange }: FactoryResetConfigDialogProps) => { +export const FactoryResetConfigDialog = ({ + open, + onOpenChange, +}: FactoryResetConfigDialogProps) => { const { t } = useTranslation("dialog"); const { connection } = useDevice(); diff --git a/apps/web/src/components/Dialog/FactoryResetDeviceDialog/FactoryResetDeviceDialog.test.tsx b/apps/web/src/components/Dialog/FactoryResetDeviceDialog/FactoryResetDeviceDialog.test.tsx index 41d82570..7337c9b9 100644 --- a/apps/web/src/components/Dialog/FactoryResetDeviceDialog/FactoryResetDeviceDialog.test.tsx +++ b/apps/web/src/components/Dialog/FactoryResetDeviceDialog/FactoryResetDeviceDialog.test.tsx @@ -47,7 +47,9 @@ describe("FactoryResetDeviceDialog", () => { ); render(); - fireEvent.click(screen.getByRole("button", { name: "Factory Reset Device" })); + fireEvent.click( + screen.getByRole("button", { name: "Factory Reset Device" }), + ); expect(mockFactoryResetDevice).toHaveBeenCalledTimes(1); diff --git a/apps/web/src/components/Dialog/FactoryResetDeviceDialog/FactoryResetDeviceDialog.tsx b/apps/web/src/components/Dialog/FactoryResetDeviceDialog/FactoryResetDeviceDialog.tsx index 42c90fca..e89850d5 100644 --- a/apps/web/src/components/Dialog/FactoryResetDeviceDialog/FactoryResetDeviceDialog.tsx +++ b/apps/web/src/components/Dialog/FactoryResetDeviceDialog/FactoryResetDeviceDialog.tsx @@ -8,7 +8,10 @@ export interface FactoryResetDeviceDialogProps { onOpenChange: (open: boolean) => void; } -export const FactoryResetDeviceDialog = ({ open, onOpenChange }: FactoryResetDeviceDialogProps) => { +export const FactoryResetDeviceDialog = ({ + open, + onOpenChange, +}: FactoryResetDeviceDialogProps) => { const { t } = useTranslation("dialog"); const { connection, id } = useDevice(); diff --git a/apps/web/src/components/Dialog/ImportDialog.tsx b/apps/web/src/components/Dialog/ImportDialog.tsx index ea33a7ea..1354aa8c 100644 --- a/apps/web/src/components/Dialog/ImportDialog.tsx +++ b/apps/web/src/components/Dialog/ImportDialog.tsx @@ -48,7 +48,8 @@ export const ImportDialog = ({ open, onOpenChange }: ImportDialogProps) => { try { const channelsUrl = new URL(importDialogInput); if ( - (channelsUrl.hostname !== "meshtastic.org" && channelsUrl.pathname !== "/e/") || + (channelsUrl.hostname !== "meshtastic.org" && + channelsUrl.pathname !== "/e/") || !channelsUrl.hash ) { throw t("import.error.invalidUrl"); @@ -56,7 +57,11 @@ export const ImportDialog = ({ open, onOpenChange }: ImportDialogProps) => { const encodedChannelConfig = channelsUrl.hash.substring(1); const paddedString = encodedChannelConfig - .padEnd(encodedChannelConfig.length + ((4 - (encodedChannelConfig.length % 4)) % 4), "=") + .padEnd( + encodedChannelConfig.length + + ((4 - (encodedChannelConfig.length % 4)) % 4), + "=", + ) .replace(/-/g, "+") .replace(/_/g, "/"); @@ -79,22 +84,24 @@ export const ImportDialog = ({ open, onOpenChange }: ImportDialogProps) => { const apply = () => { if (!editor) return; - channelSet?.settings.forEach((ch: Protobuf.Channel.ChannelSettings, index: number) => { - if (importIndex[index] === -1) { - return; - } + channelSet?.settings.forEach( + (ch: Protobuf.Channel.ChannelSettings, index: number) => { + if (importIndex[index] === -1) { + return; + } - const payload = create(Protobuf.Channel.ChannelSchema, { - index: importIndex[index], - role: - importIndex[index] === 0 - ? Protobuf.Channel.Channel_Role.PRIMARY - : Protobuf.Channel.Channel_Role.SECONDARY, - settings: ch, - }); + const payload = create(Protobuf.Channel.ChannelSchema, { + index: importIndex[index], + role: + importIndex[index] === 0 + ? Protobuf.Channel.Channel_Role.PRIMARY + : Protobuf.Channel.Channel_Role.SECONDARY, + settings: ch, + }); - editor.setChannel(payload); - }); + editor.setChannel(payload); + }, + ); if (channelSet?.loraConfig && updateConfig) { const payload = { @@ -126,14 +133,23 @@ export const ImportDialog = ({ open, onOpenChange }: ImportDialogProps) => { {t("import.title")} - , br:
}} /> + , br:
}} + />

{ setImportDialogInput(e.target.value); }} @@ -162,7 +178,10 @@ export const ImportDialog = ({ open, onOpenChange }: ImportDialogProps) => { {t("import.channelSlot")}
{channelSet?.settings.map((channel, index) => ( -
+
diff --git a/apps/web/src/components/Dialog/LocationResponseDialog.tsx b/apps/web/src/components/Dialog/LocationResponseDialog.tsx index 25d3d399..08fe5c5f 100644 --- a/apps/web/src/components/Dialog/LocationResponseDialog.tsx +++ b/apps/web/src/components/Dialog/LocationResponseDialog.tsx @@ -25,10 +25,13 @@ export const LocationResponseDialog = ({ const { t } = useTranslation("dialog"); const from = useNodeAsProto(location?.from ?? 0); const longName = - from?.user?.longName ?? (from ? `!${numberToHexUnpadded(from?.num)}` : t("unknown.shortName")); + from?.user?.longName ?? + (from ? `!${numberToHexUnpadded(from?.num)}` : t("unknown.shortName")); const shortName = from?.user?.shortName ?? - (from ? `${numberToHexUnpadded(from?.num).substring(0, 4)}` : t("unknown.shortName")); + (from + ? `${numberToHexUnpadded(from?.num).substring(0, 4)}` + : t("unknown.shortName")); const position = location?.data; @@ -65,18 +68,23 @@ export const LocationResponseDialog = ({ rel="noreferrer" > {" "} - {position.latitudeI ?? 0 / 1e7}, {position.longitudeI ?? 0 / 1e7} + {position.latitudeI ?? 0 / 1e7},{" "} + {position.longitudeI ?? 0 / 1e7}

{t("locationResponse.altitude")} {position.altitude} - {(position.altitude ?? 0) < 1 ? t("unit.meter.one") : t("unit.meter.plural")} + {(position.altitude ?? 0) < 1 + ? t("unit.meter.one") + : t("unit.meter.plural")}

) : ( // Optional: Show a message if coordinates are not available -

{t("locationResponse.noCoordinates")}

+

+ {t("locationResponse.noCoordinates")} +

)} diff --git a/apps/web/src/components/Dialog/ManagedModeDialog.tsx b/apps/web/src/components/Dialog/ManagedModeDialog.tsx index da94e46c..275ccfa5 100644 --- a/apps/web/src/components/Dialog/ManagedModeDialog.tsx +++ b/apps/web/src/components/Dialog/ManagedModeDialog.tsx @@ -18,7 +18,11 @@ export interface ManagedModeDialogProps { onSubmit: () => void; } -export const ManagedModeDialog = ({ open, onOpenChange, onSubmit }: ManagedModeDialogProps) => { +export const ManagedModeDialog = ({ + open, + onOpenChange, + onSubmit, +}: ManagedModeDialogProps) => { const { t } = useTranslation("dialog"); const [confirmState, setConfirmState] = useState(false); @@ -43,7 +47,9 @@ export const ManagedModeDialog = ({ open, onOpenChange, onSubmit }: ManagedModeD onChange={() => setConfirmState(!confirmState)} name="confirmUnderstanding" > -

{t("managedMode.confirmUnderstanding")}

+

+ {t("managedMode.confirmUnderstanding")} +

diff --git a/apps/web/src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx b/apps/web/src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx index 5f4bd70f..b0d87f55 100644 --- a/apps/web/src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx +++ b/apps/web/src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx @@ -52,7 +52,10 @@ export interface NodeDetailsDialogProps { onOpenChange: (open: boolean) => void; } -export const NodeDetailsDialog = ({ open, onOpenChange }: NodeDetailsDialogProps) => { +export const NodeDetailsDialog = ({ + open, + onOpenChange, +}: NodeDetailsDialogProps) => { const { t } = useTranslation("dialog"); const { setDialogOpen, connection } = useDevice(); const navigate = useNavigate(); @@ -62,8 +65,12 @@ export const NodeDetailsDialog = ({ open, onOpenChange }: NodeDetailsDialogProps const node = useNodeAsProto(nodeNumDetails); - const [isFavoriteState, setIsFavoriteState] = useState(node?.isFavorite ?? false); - const [isIgnoredState, setIsIgnoredState] = useState(node?.isIgnored ?? false); + const [isFavoriteState, setIsFavoriteState] = useState( + node?.isFavorite ?? false, + ); + const [isIgnoredState, setIsIgnoredState] = useState( + node?.isIgnored ?? false, + ); useEffect(() => { if (!node) { @@ -178,7 +185,8 @@ export const NodeDetailsDialog = ({ open, onOpenChange }: NodeDetailsDialogProps key: "batteryLevel", label: t("nodeDetails.batteryLevel"), value: node.deviceMetrics?.batteryLevel, - format: (val: number) => (val === 101 ? t("batteryStatus.pluggedIn") : `${val.toFixed(2)}%`), + format: (val: number) => + val === 101 ? t("batteryStatus.pluggedIn") : `${val.toFixed(2)}%`, }, { key: "voltage", @@ -211,21 +219,35 @@ export const NodeDetailsDialog = ({ open, onOpenChange }: NodeDetailsDialogProps
- - {node.user?.publicKey && node.user.publicKey.length > 0 && ( - @@ -248,7 +270,9 @@ export const NodeDetailsDialog = ({ open, onOpenChange }: NodeDetailsDialogProps - {isIgnoredState ? t("nodeDetails.unignoreNode") : t("nodeDetails.ignoreNode")} + {isIgnoredState + ? t("nodeDetails.unignoreNode") + : t("nodeDetails.ignoreNode")} @@ -278,7 +302,9 @@ export const NodeDetailsDialog = ({ open, onOpenChange }: NodeDetailsDialogProps
-

{t("nodeDetails.details")}

+

+ {t("nodeDetails.details")} +

@@ -292,10 +318,9 @@ export const NodeDetailsDialog = ({ open, onOpenChange }: NodeDetailsDialogProps @@ -314,35 +339,44 @@ export const NodeDetailsDialog = ({ open, onOpenChange }: NodeDetailsDialogProps - +
{t("nodeDetails.role")} - {Protobuf.Config.Config_DeviceConfig_Role[node.user?.role ?? 0]?.replace( - /_/g, - " ", - )} + {Protobuf.Config.Config_DeviceConfig_Role[ + node.user?.role ?? 0 + ]?.replace(/_/g, " ")}
{t("nodeDetails.hardware")} {( - Protobuf.Mesh.HardwareModel[node.user?.hwModel ?? 0] ?? - t("unknown.shortName") + Protobuf.Mesh.HardwareModel[ + node.user?.hwModel ?? 0 + ] ?? t("unknown.shortName") ).replace(/_/g, " ")}
{t("nodeDetails.messageable")}{node.user?.isUnmessagable ? t("no") : t("yes")} + {node.user?.isUnmessagable ? t("no") : t("yes")} +
-

{t("nodeDetails.security")}

+

+ {t("nodeDetails.security")} +

{t("nodeDetails.publicKey")}
-                          {node.user?.publicKey && node.user?.publicKey.length > 0
+                          {node.user?.publicKey &&
+                          node.user?.publicKey.length > 0
                             ? fromByteArray(node.user.publicKey)
                             : t("unknown.longName")}
                         
@@ -361,7 +395,9 @@ export const NodeDetailsDialog = ({ open, onOpenChange }: NodeDetailsDialogProps
-

{t("nodeDetails.position")}

+

+ {t("nodeDetails.position")} +

{node.position ? ( @@ -378,7 +414,8 @@ export const NodeDetailsDialog = ({ open, onOpenChange }: NodeDetailsDialogProps target="_blank" rel="noreferrer" > - {node.position.latitudeI / 1e7}, {node.position.longitudeI / 1e7} + {node.position.latitudeI / 1e7},{" "} + {node.position.longitudeI / 1e7} @@ -397,7 +434,11 @@ export const NodeDetailsDialog = ({ open, onOpenChange }: NodeDetailsDialogProps ) : (

{t("unknown.longName")}

)} - @@ -422,7 +463,9 @@ export const NodeDetailsDialog = ({ open, onOpenChange }: NodeDetailsDialogProps )} @@ -441,7 +484,9 @@ export const NodeDetailsDialog = ({ open, onOpenChange }: NodeDetailsDialogProps

-
{JSON.stringify(node, null, 2)}
+
+                      {JSON.stringify(node, null, 2)}
+                    
diff --git a/apps/web/src/components/Dialog/PKIBackupDialog.tsx b/apps/web/src/components/Dialog/PKIBackupDialog.tsx index 4d7fa01c..0223bc1e 100644 --- a/apps/web/src/components/Dialog/PKIBackupDialog.tsx +++ b/apps/web/src/components/Dialog/PKIBackupDialog.tsx @@ -20,19 +20,25 @@ export interface PkiBackupDialogProps { onOpenChange: (open: boolean) => void; } -export const PkiBackupDialog = ({ open, onOpenChange }: PkiBackupDialogProps) => { +export const PkiBackupDialog = ({ + open, + onOpenChange, +}: PkiBackupDialogProps) => { const { t } = useTranslation("dialog"); const { config, setDialogOpen } = useDevice(); const myNode = useMyNodeAsProto(); const privateKey = config.security?.privateKey; const publicKey = config.security?.publicKey; - const decodeKeyData = React.useCallback((key: Uint8Array) => { - if (!key) { - return ""; - } - return fromByteArray(key ?? new Uint8Array(0)); - }, []); + const decodeKeyData = React.useCallback( + (key: Uint8Array) => { + if (!key) { + return ""; + } + return fromByteArray(key ?? new Uint8Array(0)); + }, + [], + ); const closeDialog = React.useCallback(() => { setDialogOpen("pkiBackup", false); @@ -128,7 +134,9 @@ export const PkiBackupDialog = ({ open, onOpenChange }: PkiBackupDialogProps) => {t("pkiBackup.title")} {t("pkiBackup.secureBackup")} - {t("pkiBackup.loseKeysWarning")} + + {t("pkiBackup.loseKeysWarning")} + diff --git a/apps/web/src/components/Dialog/PkiRegenerateDialog.tsx b/apps/web/src/components/Dialog/PkiRegenerateDialog.tsx index 7caada0e..8a07969d 100644 --- a/apps/web/src/components/Dialog/PkiRegenerateDialog.tsx +++ b/apps/web/src/components/Dialog/PkiRegenerateDialog.tsx @@ -46,7 +46,11 @@ export const PkiRegenerateDialog = ({ {dialogText.description} - diff --git a/apps/web/src/components/Dialog/QRDialog.tsx b/apps/web/src/components/Dialog/QRDialog.tsx index f6d87be2..7220604b 100644 --- a/apps/web/src/components/Dialog/QRDialog.tsx +++ b/apps/web/src/components/Dialog/QRDialog.tsx @@ -44,12 +44,16 @@ export const QRDialog = ({ open, onOpenChange, loraConfig }: QRDialogProps) => { settings: channelsToEncode, }), ); - const base64 = fromByteArray(toBinary(Protobuf.AppOnly.ChannelSetSchema, encoded)) + const base64 = fromByteArray( + toBinary(Protobuf.AppOnly.ChannelSetSchema, encoded), + ) .replace(/=/g, "") .replace(/\+/g, "-") .replace(/\//g, "_"); - setQrCodeUrl(`https://meshtastic.org/e/${qrCodeAdd ? "?add=true" : ""}#${base64}`); + setQrCodeUrl( + `https://meshtastic.org/e/${qrCodeAdd ? "?add=true" : ""}#${base64}`, + ); }, [allChannels, selectedChannels, qrCodeAdd, loraConfig]); return ( @@ -80,9 +84,14 @@ export const QRDialog = ({ open, onOpenChange, loraConfig }: QRDialogProps) => { checked={selectedChannels.includes(channel.index)} onChange={() => { if (selectedChannels.includes(channel.index)) { - setSelectedChannels(selectedChannels.filter((c) => c !== channel.index)); + setSelectedChannels( + selectedChannels.filter((c) => c !== channel.index), + ); } else { - setSelectedChannels([...selectedChannels, channel.index]); + setSelectedChannels([ + ...selectedChannels, + channel.index, + ]); } }} /> diff --git a/apps/web/src/components/Dialog/RebootDialog.test.tsx b/apps/web/src/components/Dialog/RebootDialog.test.tsx index c90a7e82..edf4afce 100644 --- a/apps/web/src/components/Dialog/RebootDialog.test.tsx +++ b/apps/web/src/components/Dialog/RebootDialog.test.tsx @@ -1,5 +1,10 @@ import { act, fireEvent, render, screen } from "@testing-library/react"; -import type { ButtonHTMLAttributes, ClassAttributes, InputHTMLAttributes, ReactNode } from "react"; +import type { + ButtonHTMLAttributes, + ClassAttributes, + InputHTMLAttributes, + ReactNode, +} from "react"; import type { JSX } from "react/jsx-runtime"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { RebootDialog } from "./RebootDialog.tsx"; @@ -49,10 +54,16 @@ vi.mock("@components/UI/Input.tsx", async () => { vi.mock("@components/UI/Dialog.tsx", () => { return { Dialog: ({ children }: { children: ReactNode }) =>
{children}
, - DialogContent: ({ children }: { children: ReactNode }) =>
{children}
, - DialogHeader: ({ children }: { children: ReactNode }) =>
{children}
, + DialogContent: ({ children }: { children: ReactNode }) => ( +
{children}
+ ), + DialogHeader: ({ children }: { children: ReactNode }) => ( +
{children}
+ ), DialogTitle: ({ children }: { children: ReactNode }) =>

{children}

, - DialogDescription: ({ children }: { children: ReactNode }) =>

{children}

, + DialogDescription: ({ children }: { children: ReactNode }) => ( +

{children}

+ ), DialogClose: () => null, }; }); @@ -70,8 +81,12 @@ describe("RebootDialog", () => { it("renders dialog with default input value", () => { render( {}} />); expect(screen.getByPlaceholderText(/enter delay/i)).toHaveValue(5); - expect(screen.getByRole("heading", { name: /reboot device/i, level: 1 })).toBeInTheDocument(); - expect(screen.getByRole("button", { name: /reboot now/i })).toBeInTheDocument(); + expect( + screen.getByRole("heading", { name: /reboot device/i, level: 1 }), + ).toBeInTheDocument(); + expect( + screen.getByRole("button", { name: /reboot now/i }), + ).toBeInTheDocument(); }); it("calls correct reboot function based on OTA checkbox state", () => { @@ -184,6 +199,8 @@ describe("RebootDialog", () => { fireEvent.click(screen.getByRole("button", { name: /cancel/i })); }); expect(rebootMock).toHaveBeenCalledWith(-1); - expect(screen.queryByText(/reboot has been scheduled/i)).not.toBeInTheDocument(); + expect( + screen.queryByText(/reboot has been scheduled/i), + ).not.toBeInTheDocument(); }); }); diff --git a/apps/web/src/components/Dialog/RebootDialog.tsx b/apps/web/src/components/Dialog/RebootDialog.tsx index 70ae7015..b6e4ed6f 100644 --- a/apps/web/src/components/Dialog/RebootDialog.tsx +++ b/apps/web/src/components/Dialog/RebootDialog.tsx @@ -96,7 +96,11 @@ export const RebootDialog = ({ open, onOpenChange }: RebootDialogProps) => { {!isScheduled ? ( <> - setIsOTA(checked)} className="px-2"> + setIsOTA(checked)} + className="px-2" + > {t("reboot.ota")}
@@ -133,7 +137,9 @@ export const RebootDialog = ({ open, onOpenChange }: RebootDialogProps) => { ) : (
- +
-
diff --git a/apps/web/src/components/Dialog/RemoveNodeDialog.tsx b/apps/web/src/components/Dialog/RemoveNodeDialog.tsx index 8aaafc61..08d947ee 100644 --- a/apps/web/src/components/Dialog/RemoveNodeDialog.tsx +++ b/apps/web/src/components/Dialog/RemoveNodeDialog.tsx @@ -10,7 +10,10 @@ export interface RemoveNodeDialogProps { onOpenChange: (open: boolean) => void; } -export const RemoveNodeDialog = ({ open, onOpenChange }: RemoveNodeDialogProps) => { +export const RemoveNodeDialog = ({ + open, + onOpenChange, +}: RemoveNodeDialogProps) => { const { t } = useTranslation("dialog"); const meshClient = useActiveClient(); const { nodeNumToBeRemoved } = useAppStore(); diff --git a/apps/web/src/components/Dialog/ResetNodeDbDialog/ResetNodeDbDialog.test.tsx b/apps/web/src/components/Dialog/ResetNodeDbDialog/ResetNodeDbDialog.test.tsx index 29397c0f..3f75bac2 100644 --- a/apps/web/src/components/Dialog/ResetNodeDbDialog/ResetNodeDbDialog.test.tsx +++ b/apps/web/src/components/Dialog/ResetNodeDbDialog/ResetNodeDbDialog.test.tsx @@ -25,7 +25,9 @@ describe("ResetNodeDbDialog", () => { }); it("calls reset({ keepMyNode: true }) then clears chat after resolve", async () => { - let resolveReset: ((value: { status: "ok"; value: number }) => void) | undefined; + let resolveReset: + | ((value: { status: "ok"; value: number }) => void) + | undefined; mockResetNodes.mockImplementation( () => new Promise((resolve) => { @@ -35,7 +37,9 @@ describe("ResetNodeDbDialog", () => { mockClearAll.mockResolvedValue(undefined); render(); - fireEvent.click(screen.getByRole("button", { name: "Reset Node Database" })); + fireEvent.click( + screen.getByRole("button", { name: "Reset Node Database" }), + ); expect(mockResetNodes).toHaveBeenCalledWith({ keepMyNode: true }); diff --git a/apps/web/src/components/Dialog/ResetNodeDbDialog/ResetNodeDbDialog.tsx b/apps/web/src/components/Dialog/ResetNodeDbDialog/ResetNodeDbDialog.tsx index 7263ebfd..d3ca6e0c 100644 --- a/apps/web/src/components/Dialog/ResetNodeDbDialog/ResetNodeDbDialog.tsx +++ b/apps/web/src/components/Dialog/ResetNodeDbDialog/ResetNodeDbDialog.tsx @@ -8,7 +8,10 @@ export interface ResetNodeDbDialogProps { onOpenChange: (open: boolean) => void; } -export const ResetNodeDbDialog = ({ open, onOpenChange }: ResetNodeDbDialogProps) => { +export const ResetNodeDbDialog = ({ + open, + onOpenChange, +}: ResetNodeDbDialogProps) => { const { t } = useTranslation("dialog"); const meshClient = useActiveClient(); diff --git a/apps/web/src/components/Dialog/TracerouteResponseDialog.tsx b/apps/web/src/components/Dialog/TracerouteResponseDialog.tsx index 0d4b4350..27fba291 100644 --- a/apps/web/src/components/Dialog/TracerouteResponseDialog.tsx +++ b/apps/web/src/components/Dialog/TracerouteResponseDialog.tsx @@ -31,10 +31,13 @@ export const TracerouteResponseDialog = ({ const snrBack = (traceroute?.data.snrBack ?? []).map((snr) => snr / 4); const from = useNodeAsProto(traceroute?.to ?? 0); // The origin of the traceroute = the "to" node of the mesh packet const fromLongName = - from?.user?.longName ?? (from ? `!${numberToHexUnpadded(from?.num)}` : t("unknown.shortName")); + from?.user?.longName ?? + (from ? `!${numberToHexUnpadded(from?.num)}` : t("unknown.shortName")); const fromShortName = from?.user?.shortName ?? - (from ? `${numberToHexUnpadded(from?.num).substring(0, 4)}` : t("unknown.shortName")); + (from + ? `${numberToHexUnpadded(from?.num).substring(0, 4)}` + : t("unknown.shortName")); const toUser = useNodeAsProto(traceroute?.from ?? 0); // The destination of the traceroute = the "from" node of the mesh packet diff --git a/apps/web/src/components/Dialog/UnsafeRolesDialog/UnsafeRolesDialog.tsx b/apps/web/src/components/Dialog/UnsafeRolesDialog/UnsafeRolesDialog.tsx index 6d537022..b3978c19 100644 --- a/apps/web/src/components/Dialog/UnsafeRolesDialog/UnsafeRolesDialog.tsx +++ b/apps/web/src/components/Dialog/UnsafeRolesDialog/UnsafeRolesDialog.tsx @@ -20,12 +20,16 @@ export interface RouterRoleDialogProps { onOpenChange: (open: boolean) => void; } -export const UnsafeRolesDialog = ({ open, onOpenChange }: RouterRoleDialogProps) => { +export const UnsafeRolesDialog = ({ + open, + onOpenChange, +}: RouterRoleDialogProps) => { const { t } = useTranslation("dialog"); const [confirmState, setConfirmState] = useState(false); const { setDialogOpen } = useDevice(); - const deviceRoleLink = "https://meshtastic.org/docs/configuration/radio/device/"; + const deviceRoleLink = + "https://meshtastic.org/docs/configuration/radio/device/"; const choosingTheRightDeviceRoleLink = "https://meshtastic.org/blog/choosing-the-right-device-role/"; @@ -59,11 +63,17 @@ export const UnsafeRolesDialog = ({ open, onOpenChange }: RouterRoleDialogProps) onChange={() => setConfirmState(!confirmState)} name="confirmUnderstanding" > - {t("unsafeRoles.confirmUnderstanding")} + + {t("unsafeRoles.confirmUnderstanding")} +
- @@ -85,9 +103,16 @@ export const Channels = ({ onFormInit }: ConfigProps) => { {allChannels.map((channel) => ( - + }> - + ))} diff --git a/apps/web/src/components/PageComponents/Connections/ConnectionStatusBadge.tsx b/apps/web/src/components/PageComponents/Connections/ConnectionStatusBadge.tsx index 221335e4..cc42a258 100644 --- a/apps/web/src/components/PageComponents/Connections/ConnectionStatusBadge.tsx +++ b/apps/web/src/components/PageComponents/Connections/ConnectionStatusBadge.tsx @@ -1,7 +1,11 @@ import { Button } from "@app/components/UI/Button"; import type { Connection } from "@app/core/stores/deviceStore/types"; -export function ConnectionStatusBadge({ status }: { status: Connection["status"] }) { +export function ConnectionStatusBadge({ + status, +}: { + status: Connection["status"]; +}) { let color = ""; let displayStatus = status; @@ -26,8 +30,13 @@ export function ConnectionStatusBadge({ status }: { status: Connection["status"] } return ( ); } diff --git a/apps/web/src/components/PageComponents/Map/Layers/HeatmapLayer.tsx b/apps/web/src/components/PageComponents/Map/Layers/HeatmapLayer.tsx index ce5d0f2c..9df005f3 100644 --- a/apps/web/src/components/PageComponents/Map/Layers/HeatmapLayer.tsx +++ b/apps/web/src/components/PageComponents/Map/Layers/HeatmapLayer.tsx @@ -14,7 +14,12 @@ export interface HeatmapLayerProps { isVisible: boolean; } -export const HeatmapLayer = ({ id, filteredNodes, mode, isVisible }: HeatmapLayerProps) => { +export const HeatmapLayer = ({ + id, + filteredNodes, + mode, + isVisible, +}: HeatmapLayerProps) => { if (!isVisible) return null; const data: FeatureCollection = useMemo(() => { const features: Feature[] = filteredNodes @@ -43,7 +48,9 @@ export const HeatmapLayer = ({ id, filteredNodes, mode, isVisible }: HeatmapLaye const paintProps: HeatmapLayerSpecification["paint"] = useMemo( () => ({ "heatmap-weight": - mode === "density" ? 1 : ["interpolate", ["linear"], ["get", "snr"], -20, 0, 10, 1], + mode === "density" + ? 1 + : ["interpolate", ["linear"], ["get", "snr"], -20, 0, 10, 1], "heatmap-intensity": ["interpolate", ["linear"], ["zoom"], 0, 1, 15, 3], // Color ramp for heatmap. Domain is 0 (low) to 1 (high). // Begin color ramp at 0-stop with a 0-transparancy color @@ -65,7 +72,17 @@ export const HeatmapLayer = ({ id, filteredNodes, mode, isVisible }: HeatmapLaye 1, "rgb(178,24,43)", ], - "heatmap-radius": ["interpolate", ["linear"], ["zoom"], 0, 2, 9, 20, 15, 30], + "heatmap-radius": [ + "interpolate", + ["linear"], + ["zoom"], + 0, + 2, + 9, + 20, + 15, + 30, + ], // Opacity 0.7 to be visible but not blocking "heatmap-opacity": 0.7, }), diff --git a/apps/web/src/components/PageComponents/Map/Layers/NodesLayer.tsx b/apps/web/src/components/PageComponents/Map/Layers/NodesLayer.tsx index e60f1712..3a16b674 100644 --- a/apps/web/src/components/PageComponents/Map/Layers/NodesLayer.tsx +++ b/apps/web/src/components/PageComponents/Map/Layers/NodesLayer.tsx @@ -45,14 +45,18 @@ export const NodesLayer = ({ const errors = useNodeErrors(); const errorSet = useMemo(() => new Set(errors.map((e) => e.node)), [errors]); - const hasNodeError = useCallback((num: number) => errorSet.has(num), [errorSet]); + const hasNodeError = useCallback( + (num: number) => errorSet.has(num), + [errorSet], + ); const { focusLngLat } = useMapFitting(mapRef); const selectedNode = useMemo( () => popupState?.type !== "node" ? undefined - : (filteredNodes.find((node) => node.num === popupState.num) ?? undefined), + : (filteredNodes.find((node) => node.num === popupState.num) ?? + undefined), [popupState, filteredNodes], ); @@ -79,7 +83,9 @@ export const NodesLayer = ({ const isExpanded = expandedCluster === key; // Precompute pixel offsets for expanded state - const expandedOffsets = isExpanded ? fanOutOffsetsPx(nodes.length, key) : undefined; + const expandedOffsets = isExpanded + ? fanOutOffsetsPx(nodes.length, key) + : undefined; // Always render all node markers in the cluster for (const [i, node] of nodes.entries()) { diff --git a/apps/web/src/components/PageComponents/Map/Layers/PrecisionLayer.tsx b/apps/web/src/components/PageComponents/Map/Layers/PrecisionLayer.tsx index f6e4f6fc..c40dbc0a 100644 --- a/apps/web/src/components/PageComponents/Map/Layers/PrecisionLayer.tsx +++ b/apps/web/src/components/PageComponents/Map/Layers/PrecisionLayer.tsx @@ -36,7 +36,10 @@ export function generatePrecisionCircles( >(); for (const node of filteredNodes) { - if (node.position?.precisionBits === undefined || node.position.precisionBits === 0) { + if ( + node.position?.precisionBits === undefined || + node.position.precisionBits === 0 + ) { continue; } const [lng, lat] = toLngLat(node.position); @@ -60,7 +63,9 @@ export function generatePrecisionCircles( } } - const items = Array.from(unique.values()).sort((a, b) => a.radiusM - b.radiusM); + const items = Array.from(unique.values()).sort( + (a, b) => a.radiusM - b.radiusM, + ); const features: Feature[] = items.map( ({ lng, lat, radiusM, r, g, b, a }) => { @@ -91,7 +96,13 @@ export const SourcePrecisionCircles = ({ type="fill" layout={{ visibility: isVisible ? "visible" : "none" }} paint={{ - "fill-color": ["rgba", ["get", "r"], ["get", "g"], ["get", "b"], ["get", "a"]], + "fill-color": [ + "rgba", + ["get", "r"], + ["get", "g"], + ["get", "b"], + ["get", "a"], + ], }} /> a }; -function arcSegment(aLngLat: LngLat, bLngLat: LngLat, curved: boolean): LngLat[] | undefined { +function arcSegment( + aLngLat: LngLat, + bLngLat: LngLat, + curved: boolean, +): LngLat[] | undefined { if (!curved && distanceMeters(aLngLat, bLngLat) < MIN_LEN) { // Straight line return [aLngLat, bLngLat]; @@ -176,7 +180,9 @@ function pushIfFeature( } } -function generateNeighborLines(neighborInfos: NeighborInfos[]): FeatureCollection { +function generateNeighborLines( + neighborInfos: NeighborInfos[], +): FeatureCollection { // Collect positions for all referenced nodes, discard pairs with missing positions const idToLngLat = new Map(); const ensure = (node?: Protobuf.Mesh.NodeInfo | NeighborPlus) => { @@ -240,7 +246,12 @@ function generateNeighborLines(neighborInfos: NeighborInfos[]): FeatureCollectio return { type: "FeatureCollection", features }; } -export const SNRTooltip = ({ pos, snr, from, to }: Partial = {}) => { +export const SNRTooltip = ({ + pos, + snr, + from, + to, +}: Partial = {}) => { const { t } = useTranslation(); if (!pos) { @@ -290,7 +301,9 @@ export const SNRLayer = ({ neighborInfo: { ...neighborInfo, neighbors: neighborInfo.neighbors.map((n) => { - const node = filteredNodes.find((node) => node.num === n.nodeId); + const node = filteredNodes.find( + (node) => node.num === n.nodeId, + ); return { ...n, num: node?.num, position: node?.position }; }), }, @@ -312,7 +325,10 @@ export const SNRLayer = ({ })) : []; - const featureCollection = generateNeighborLines([...remotePairs, ...directPairs]); + const featureCollection = generateNeighborLines([ + ...remotePairs, + ...directPairs, + ]); return ( diff --git a/apps/web/src/components/PageComponents/Map/Layers/WaypointLayer.tsx b/apps/web/src/components/PageComponents/Map/Layers/WaypointLayer.tsx index e0eef266..429bb1ef 100644 --- a/apps/web/src/components/PageComponents/Map/Layers/WaypointLayer.tsx +++ b/apps/web/src/components/PageComponents/Map/Layers/WaypointLayer.tsx @@ -82,7 +82,11 @@ export const WaypointLayer = ({ offset={[0, 25]} onClose={() => setPopupState(undefined)} > - {}} /> + {}} + /> , ); } diff --git a/apps/web/src/components/PageComponents/Map/Markers/StackBadge.tsx b/apps/web/src/components/PageComponents/Map/Markers/StackBadge.tsx index 405ad1ec..8ddc0faa 100644 --- a/apps/web/src/components/PageComponents/Map/Markers/StackBadge.tsx +++ b/apps/web/src/components/PageComponents/Map/Markers/StackBadge.tsx @@ -8,7 +8,13 @@ type StackBadgeProps = { onClick: (e: { originalEvent: MouseEvent }) => void; }; -export const StackBadge = ({ lng, lat, count, isVisible = true, onClick }: StackBadgeProps) => { +export const StackBadge = ({ + lng, + lat, + count, + isVisible = true, + onClick, +}: StackBadgeProps) => { return ( { : rawHardwareType.replaceAll("_", " ") : `${hwModel}`; // SNR is in dB; derive a 0–100% quality heuristic (-10 dB → 0%, +10 dB → 100%). - const snrQuality = Math.min(Math.max(Math.round((node.snr + 10) * 5), 0), 100); + const snrQuality = Math.min( + Math.max(Math.round((node.snr + 10) * 5), 0), + 100, + ); const snrTone = - snrQuality >= 67 ? "text-green-600" : snrQuality >= 34 ? "text-yellow-600" : "text-red-600"; + snrQuality >= 67 + ? "text-green-600" + : snrQuality >= 34 + ? "text-yellow-600" + : "text-red-600"; function handleDirectMessage() { navigate({ to: `/messages/direct/${node.num}` }); } @@ -101,7 +115,9 @@ export const NodeDetail = ({ node }: NodeDetailProps) => { fill={node.isFavorite ? "black" : "none"} size={15} aria-label={ - node.isFavorite ? t("nodeDetail.favorite.label") : t("nodeDetail.notFavorite.label") + node.isFavorite + ? t("nodeDetail.favorite.label") + : t("nodeDetail.notFavorite.label") } /> @@ -122,12 +138,15 @@ export const NodeDetail = ({ node }: NodeDetailProps) => {
{node.lastHeard > 0 && (
- {t("nodeDetail.status.heard")} + {t("nodeDetail.status.heard")}{" "} +
)}
@@ -145,9 +164,13 @@ export const NodeDetail = ({ node }: NodeDetailProps) => {
- {Number.isNaN(node.hopsAway) ? t("unit.hopsAway.unknown") : node.hopsAway} + {Number.isNaN(node.hopsAway) + ? t("unit.hopsAway.unknown") + : node.hopsAway} +
+
+ {node.hopsAway === 1 ? t("unit.hop.one") : t("unit.hop.plural")}
-
{node.hopsAway === 1 ? t("unit.hop.one") : t("unit.hop.plural")}
{node.position?.altitude && (
@@ -170,13 +193,17 @@ export const NodeDetail = ({ node }: NodeDetailProps) => { {!!node.deviceMetrics?.channelUtilization && (
{t("channelUtilization.short")}
- {node.deviceMetrics?.channelUtilization.toPrecision(3)}% + + {node.deviceMetrics?.channelUtilization.toPrecision(3)}% +
)} {!!node.deviceMetrics?.airUtilTx && (
{t("airtimeUtilization.short")}
- {node.deviceMetrics?.airUtilTx.toPrecision(3)}% + + {node.deviceMetrics?.airUtilTx.toPrecision(3)}% +
)}
diff --git a/apps/web/src/components/PageComponents/Map/Popups/WaypointDetail.tsx b/apps/web/src/components/PageComponents/Map/Popups/WaypointDetail.tsx index a99f9f6b..b938d39f 100644 --- a/apps/web/src/components/PageComponents/Map/Popups/WaypointDetail.tsx +++ b/apps/web/src/components/PageComponents/Map/Popups/WaypointDetail.tsx @@ -2,7 +2,12 @@ import { TimeAgo } from "@components/generic/TimeAgo"; import { Separator } from "@components/UI/Separator.tsx"; import { useNodeAsProto } from "@core/hooks/useNodesAsProto.ts"; import type { WaypointWithMetadata } from "@core/stores"; -import { bearingDegrees, distanceMeters, hasPos, toLngLat } from "@core/utils/geo"; +import { + bearingDegrees, + distanceMeters, + hasPos, + toLngLat, +} from "@core/utils/geo"; import type { Protobuf } from "@meshtastic/sdk"; import { ClockFadingIcon, @@ -83,10 +88,14 @@ export const WaypointDetail = ({ waypoint, myNode }: WaypointDetailProps) => {
- {t("waypointDetail.createdDate")} + + {t("waypointDetail.createdDate")} +
-
@@ -100,7 +109,9 @@ export const WaypointDetail = ({ waypoint, myNode }: WaypointDetailProps) => { {t("waypointDetail.updated")}
-
@@ -132,7 +143,9 @@ export const WaypointDetail = ({ waypoint, myNode }: WaypointDetailProps) => {
{Math.round(distance)}{" "} - {distance === 1 ? t("unit.meter.one") : t("unit.meter.plural")} + {distance === 1 + ? t("unit.meter.one") + : t("unit.meter.plural")}
diff --git a/apps/web/src/components/PageComponents/Map/Tools/MapLayerTool.tsx b/apps/web/src/components/PageComponents/Map/Tools/MapLayerTool.tsx index dd550dd4..3b9b1cef 100644 --- a/apps/web/src/components/PageComponents/Map/Tools/MapLayerTool.tsx +++ b/apps/web/src/components/PageComponents/Map/Tools/MapLayerTool.tsx @@ -1,6 +1,10 @@ import type { HeatmapMode } from "@components/PageComponents/Map/Layers/HeatmapLayer.tsx"; import { Checkbox } from "@components/UI/Checkbox/index.tsx"; -import { Popover, PopoverContent, PopoverTrigger } from "@components/UI/Popover.tsx"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@components/UI/Popover.tsx"; import { cn } from "@core/utils/cn.ts"; import { LayersIcon } from "lucide-react"; import { type ReactNode, useMemo } from "react"; @@ -40,7 +44,12 @@ interface CheckboxProps { className?: string; } -const CheckboxItem = ({ label, checked, onChange, className }: CheckboxProps) => { +const CheckboxItem = ({ + label, + checked, + onChange, + className, +}: CheckboxProps) => { return ( handleCheckboxChange(key as keyof VisibilityState)} + onChange={() => + handleCheckboxChange(key as keyof VisibilityState) + } /> {key === "heatmap" && visibilityState.heatmap && (
@@ -151,7 +162,9 @@ export function MapLayerTool({ checked={heatmapMode === "density"} onChange={() => setHeatmapMode("density")} /> - {t("layerTool.density")} + + {t("layerTool.density")} +
{t("nodeDetails.uptime")} - +