From 939b42e6384580d76c910034179be1d8f42191e8 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Mon, 9 Mar 2026 21:58:37 -0400 Subject: [PATCH 1/9] fix: resolve lint warnings/errors and apply formatting Fix 10 oxlint issues (2 errors, 8 warnings): - Remove unused catch parameters in Security.tsx and ImportDialog.tsx - Remove stray expression in Generator.tsx - Add eslint-disable for debounced useCallback in FilterControl.tsx - Remove unnecessary deps (resolveDB, store) in bindStoreToDevice.ts - Prefix unused variant param in AppSidebar.tsx - Memoize tabs arrays in DeviceConfig, RadioConfig, ModuleConfig - Fix channels type in RadioConfig TabItem Also applies oxfmt formatting across all files. --- .husky/pre-commit | 1 + .vscode/settings.json | 6 +- package.json | 30 +-- packages/core/src/meshDevice.ts | 166 ++++------------ packages/core/src/utils/eventSystem.ts | 107 +++++------ packages/core/src/utils/queue.ts | 19 +- .../core/src/utils/transform/decodePacket.ts | 46 ++--- .../core/src/utils/transform/fromDevice.ts | 106 +++++------ packages/core/src/utils/transform/toDevice.ts | 24 +-- packages/core/src/utils/xmodem.ts | 6 +- packages/transport-deno/scripts/build_npm.ts | 3 +- packages/transport-deno/src/transport.ts | 4 +- packages/transport-http/src/transport.test.ts | 34 +--- packages/transport-http/src/transport.ts | 28 +-- .../src/transport.test.ts | 54 ++---- .../transport-node-serial/src/transport.ts | 22 +-- packages/transport-node/src/transport.test.ts | 46 ++--- packages/transport-node/src/transport.ts | 39 +--- .../src/transport.test.ts | 54 ++---- .../transport-web-bluetooth/src/transport.ts | 51 ++--- .../src/transport.test.ts | 25 +-- .../transport-web-serial/src/transport.ts | 32 +--- packages/ui/src/components/theme-provider.tsx | 7 +- packages/ui/src/components/ui/badge.tsx | 15 +- packages/ui/src/components/ui/button.tsx | 6 +- packages/ui/src/components/ui/collapsible.tsx | 18 +- .../ui/src/components/ui/dropdown-menu.tsx | 49 +---- packages/ui/src/components/ui/sheet.tsx | 17 +- packages/ui/src/components/ui/sidebar.tsx | 50 +---- packages/ui/src/components/ui/tooltip.tsx | 8 +- packages/ui/src/hooks/use-mobile.ts | 4 +- .../src/lib/components/Sidebar/AppSidebar.tsx | 50 ++--- .../ui/src/lib/components/theme-toggle.tsx | 12 +- packages/web/.oxfmtrc.json | 13 ++ packages/web/.oxlintrc.json | 26 +++ packages/web/package.json | 12 +- packages/web/src/App.tsx | 3 +- packages/web/src/DeviceWrapper.tsx | 4 +- .../src/__mocks__/components/UI/Checkbox.tsx | 18 +- packages/web/src/components/BatteryStatus.tsx | 12 +- .../src/components/CommandPalette/index.tsx | 24 +-- .../web/src/components/DeviceInfoPanel.tsx | 22 +-- .../AddConnectionDialog.tsx | 177 ++++++------------ .../Dialog/AddConnectionDialog/validation.ts | 9 +- .../ClearAllStoresDialog.test.tsx | 4 +- .../ClearAllStoresDialog.tsx | 5 +- .../ClientNotificationDialog.tsx | 5 +- .../DeleteMessagesDialog.test.tsx | 12 +- .../DeleteMessagesDialog.tsx | 5 +- .../FactoryResetConfigDialog.test.tsx | 4 +- .../FactoryResetConfigDialog.tsx | 5 +- .../FactoryResetDeviceDialog.test.tsx | 4 +- .../FactoryResetDeviceDialog.tsx | 12 +- .../src/components/Dialog/ImportDialog.tsx | 89 +++------ .../Dialog/LocationResponseDialog.tsx | 18 +- .../components/Dialog/ManagedModeDialog.tsx | 10 +- .../src/components/Dialog/NewDeviceDialog.tsx | 19 +- .../NodeDetailsDialog/NodeDetailsDialog.tsx | 89 +++------ .../src/components/Dialog/PKIBackupDialog.tsx | 24 +-- .../components/Dialog/PkiRegenerateDialog.tsx | 6 +- .../web/src/components/Dialog/QRDialog.tsx | 24 +-- .../components/Dialog/RebootDialog.test.tsx | 31 +-- .../src/components/Dialog/RebootDialog.tsx | 10 +- .../RefreshKeysDialog.test.tsx | 10 +- .../RefreshKeysDialog/RefreshKeysDialog.tsx | 31 +-- .../components/Dialog/RemoveNodeDialog.tsx | 5 +- .../ResetNodeDbDialog.test.tsx | 4 +- .../ResetNodeDbDialog/ResetNodeDbDialog.tsx | 5 +- .../Dialog/TracerouteResponseDialog.tsx | 7 +- .../UnsafeRolesDialog/UnsafeRolesDialog.tsx | 18 +- .../useUnsafeRolesDialog.test.tsx | 66 ++----- .../UnsafeRolesDialog/useUnsafeRolesDialog.ts | 6 +- .../web/src/components/Dialog/useDialog.ts | 9 +- .../web/src/components/Form/DynamicForm.tsx | 25 +-- .../src/components/Form/DynamicFormField.tsx | 29 +-- .../web/src/components/Form/FormInput.tsx | 23 +-- .../src/components/Form/FormMultiSelect.tsx | 25 ++- .../web/src/components/Form/FormSelect.tsx | 13 +- .../web/src/components/Form/FormToggle.tsx | 8 +- .../web/src/components/Form/FormWrapper.tsx | 4 +- .../src/components/Form/createZodResolver.ts | 14 +- .../web/src/components/LanguageSwitcher.tsx | 17 +- packages/web/src/components/Map.tsx | 4 +- .../PageComponents/Channels/Channel.tsx | 23 +-- .../PageComponents/Channels/Channels.tsx | 31 +-- .../Connections/ConnectionStatusBadge.tsx | 15 +- .../Map/Layers/HeatmapLayer.tsx | 22 +-- .../PageComponents/Map/Layers/NodesLayer.tsx | 7 +- .../Map/Layers/PrecisionLayer.tsx | 17 +- .../PageComponents/Map/Layers/SNRLayer.tsx | 26 +-- .../Map/Layers/WaypointLayer.tsx | 6 +- .../PageComponents/Map/Markers/StackBadge.tsx | 8 +- .../PageComponents/Map/Popups/NodeDetail.tsx | 36 +--- .../Map/Popups/WaypointDetail.tsx | 26 +-- .../PageComponents/Map/Tools/MapLayerTool.tsx | 21 +-- .../PageComponents/Messages/ChannelChat.tsx | 8 +- .../Messages/MessageActionsMenu.tsx | 5 +- .../Messages/MessageInput.test.tsx | 34 +--- .../PageComponents/Messages/MessageInput.tsx | 4 +- .../PageComponents/Messages/MessageItem.tsx | 29 +-- .../Messages/TraceRoute.test.tsx | 9 +- .../ModuleConfig/AmbientLighting.tsx | 12 +- .../PageComponents/ModuleConfig/Audio.tsx | 22 +-- .../ModuleConfig/CannedMessage.tsx | 30 +-- .../ModuleConfig/DetectionSensor.tsx | 24 +-- .../ModuleConfig/ExternalNotification.tsx | 24 +-- .../PageComponents/ModuleConfig/MQTT.tsx | 112 +++-------- .../ModuleConfig/NeighborInfo.tsx | 14 +- .../ModuleConfig/Paxcounter.tsx | 14 +- .../PageComponents/ModuleConfig/RangeTest.tsx | 14 +- .../PageComponents/ModuleConfig/Serial.tsx | 20 +- .../ModuleConfig/StoreForward.tsx | 14 +- .../PageComponents/ModuleConfig/Telemetry.tsx | 22 +-- .../PageComponents/Settings/Bluetooth.tsx | 5 +- .../PageComponents/Settings/Device/index.tsx | 13 +- .../PageComponents/Settings/Display.tsx | 14 +- .../PageComponents/Settings/LoRa.tsx | 10 +- .../PageComponents/Settings/Network/index.tsx | 58 ++---- .../PageComponents/Settings/Position.tsx | 57 ++---- .../PageComponents/Settings/Power.tsx | 10 +- .../Settings/Security/Security.tsx | 42 ++--- .../PageComponents/Settings/User.tsx | 14 +- packages/web/src/components/PageLayout.tsx | 14 +- packages/web/src/components/Sidebar.tsx | 23 +-- packages/web/src/components/ThemeSwitcher.tsx | 6 +- packages/web/src/components/Toaster.tsx | 7 +- packages/web/src/components/UI/Accordion.tsx | 5 +- .../web/src/components/UI/AlertDialog.tsx | 48 +---- packages/web/src/components/UI/Badge.tsx | 7 +- packages/web/src/components/UI/Button.tsx | 9 +- packages/web/src/components/UI/Card.tsx | 105 ++++------- .../components/UI/Checkbox/Checkbox.test.tsx | 13 +- .../web/src/components/UI/Checkbox/index.tsx | 6 +- packages/web/src/components/UI/Command.tsx | 19 +- packages/web/src/components/UI/Dialog.tsx | 33 +--- .../web/src/components/UI/DropdownMenu.tsx | 22 +-- packages/web/src/components/UI/ErrorPage.tsx | 25 +-- packages/web/src/components/UI/Footer.tsx | 19 +- packages/web/src/components/UI/Generator.tsx | 1 - packages/web/src/components/UI/Input.tsx | 23 +-- packages/web/src/components/UI/Menubar.tsx | 48 ++--- .../web/src/components/UI/MultiSelect.tsx | 4 +- packages/web/src/components/UI/ScrollArea.tsx | 6 +- packages/web/src/components/UI/Select.tsx | 4 +- packages/web/src/components/UI/Separator.tsx | 31 ++- .../components/UI/Sidebar/SidebarButton.tsx | 8 +- .../components/UI/Sidebar/SidebarSection.tsx | 6 +- packages/web/src/components/UI/Skeleton.tsx | 10 +- packages/web/src/components/UI/Slider.tsx | 15 +- packages/web/src/components/UI/Spinner.tsx | 5 +- packages/web/src/components/UI/Tabs.tsx | 5 +- packages/web/src/components/UI/Toast.tsx | 9 +- .../web/src/components/UI/ToggleGroup.tsx | 5 +- packages/web/src/components/UI/Tooltip.tsx | 9 +- .../web/src/components/UI/Typography/Link.tsx | 10 +- .../src/components/UI/Typography/Subtle.tsx | 4 +- packages/web/src/components/generic/Blur.tsx | 5 +- .../generic/Filter/FilterComponents.tsx | 22 +-- .../generic/Filter/FilterControl.tsx | 40 +--- .../generic/Filter/useFilterNode.test.ts | 16 +- .../generic/Filter/useFilterNode.ts | 52 ++--- packages/web/src/components/generic/Mono.tsx | 5 +- .../components/generic/Table/index.test.tsx | 10 +- .../src/components/generic/Table/index.tsx | 3 +- .../web/src/core/dto/NodeNumToNodeInfoDTO.ts | 4 +- .../web/src/core/dto/PacketToMessageDTO.ts | 6 +- .../core/hooks/useBrowserFeatureDetection.ts | 3 +- packages/web/src/core/hooks/useCookie.ts | 5 +- .../web/src/core/hooks/useCopyToClipboard.ts | 4 +- .../web/src/core/hooks/useDeviceContext.ts | 8 +- .../web/src/core/hooks/useFeatureFlags.ts | 6 +- .../src/core/hooks/useKeyBackupReminder.tsx | 6 +- packages/web/src/core/hooks/useLRUList.ts | 14 +- packages/web/src/core/hooks/useLang.ts | 7 +- .../web/src/core/hooks/useLocalStorage.ts | 43 +---- packages/web/src/core/hooks/useNewNodeNum.ts | 10 +- .../hooks/usePasswordVisibilityToggle.test.ts | 12 +- packages/web/src/core/hooks/usePinnedItems.ts | 9 +- .../web/src/core/hooks/usePositionFlags.ts | 13 +- packages/web/src/core/hooks/useTheme.ts | 4 +- packages/web/src/core/hooks/useToast.ts | 4 +- .../web/src/core/hooks/useWaitForConfig.ts | 11 +- .../web/src/core/services/featureFlags.ts | 5 +- .../src/core/stores/appStore/appStore.test.ts | 54 +++--- .../web/src/core/stores/appStore/index.ts | 14 +- .../core/stores/deviceStore/changeRegistry.ts | 14 +- .../stores/deviceStore/deviceStore.test.ts | 66 ++----- .../web/src/core/stores/deviceStore/index.ts | 122 +++--------- .../src/core/stores/deviceStore/selectors.ts | 16 +- .../web/src/core/stores/deviceStore/types.ts | 5 +- packages/web/src/core/stores/index.ts | 13 +- .../web/src/core/stores/messageStore/index.ts | 52 ++--- .../stores/messageStore/messageStore.test.ts | 130 ++++--------- .../web/src/core/stores/messageStore/types.ts | 4 +- .../web/src/core/stores/nodeDBStore/index.ts | 57 ++---- .../stores/nodeDBStore/nodeDBStore.test.tsx | 16 +- .../core/stores/nodeDBStore/nodeValidation.ts | 31 +-- .../web/src/core/stores/nodeDBStore/types.ts | 5 +- .../src/core/stores/sidebarStore/index.tsx | 12 +- .../core/stores/utils/bindStoreToDevice.ts | 15 +- .../core/stores/utils/evictOldestEntries.ts | 5 +- packages/web/src/core/stores/utils/indexDB.ts | 31 +-- packages/web/src/core/subscriptions.ts | 31 +-- packages/web/src/core/utils/bitwise.ts | 5 +- packages/web/src/core/utils/color.test.ts | 12 +- .../web/src/core/utils/deepCompareConfig.ts | 6 +- packages/web/src/core/utils/dotPath.test.ts | 8 +- packages/web/src/core/utils/dotPath.ts | 8 +- packages/web/src/core/utils/eventBus.ts | 5 +- packages/web/src/core/utils/geo.ts | 13 +- packages/web/src/core/utils/github.ts | 4 +- packages/web/src/core/utils/ip.test.ts | 8 +- packages/web/src/core/utils/ip.ts | 4 +- packages/web/src/core/utils/signalColor.ts | 10 +- packages/web/src/core/utils/string.ts | 20 +- packages/web/src/pages/Connections/index.tsx | 65 ++----- .../src/pages/Connections/useConnections.ts | 84 ++------- packages/web/src/pages/Connections/utils.ts | 5 +- packages/web/src/pages/Map/index.tsx | 59 +----- packages/web/src/pages/Messages.tsx | 59 ++---- packages/web/src/pages/Nodes/index.tsx | 51 ++--- .../web/src/pages/Settings/DeviceConfig.tsx | 94 +++++----- .../web/src/pages/Settings/ModuleConfig.tsx | 135 +++++++------ .../web/src/pages/Settings/RadioConfig.tsx | 55 +++--- packages/web/src/pages/Settings/index.tsx | 47 +++-- packages/web/src/routes.tsx | 16 +- packages/web/src/tests/setup.ts | 32 +--- packages/web/src/tests/test-utils.tsx | 12 +- packages/web/src/validation/channel.test.ts | 12 +- .../web/src/validation/config/bluetooth.ts | 4 +- packages/web/src/validation/config/device.ts | 4 +- packages/web/src/validation/config/display.ts | 12 +- packages/web/src/validation/config/lora.ts | 8 +- packages/web/src/validation/config/network.ts | 8 +- .../src/validation/config/security.test.ts | 20 +- .../web/src/validation/config/security.ts | 4 +- .../moduleConfig/ambientLighting.ts | 4 +- .../web/src/validation/moduleConfig/audio.ts | 4 +- .../validation/moduleConfig/cannedMessage.ts | 4 +- .../moduleConfig/detectionSensor.ts | 4 +- .../moduleConfig/externalNotification.ts | 4 +- .../validation/moduleConfig/neighborInfo.ts | 4 +- .../web/src/validation/moduleConfig/serial.ts | 8 +- .../validation/moduleConfig/storeForward.ts | 4 +- packages/web/src/validation/pskSchema.ts | 26 +-- packages/web/vite.config.ts | 3 +- tests/utils/transportContract.ts | 8 +- 247 files changed, 1533 insertions(+), 4043 deletions(-) create mode 100755 .husky/pre-commit create mode 100644 packages/web/.oxfmtrc.json create mode 100644 packages/web/.oxlintrc.json diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 000000000..cb2c84d5c --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +pnpm lint-staged diff --git a/.vscode/settings.json b/.vscode/settings.json index ad6de87f9..2253a76ea 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,10 +1,6 @@ { "editor.formatOnSave": true, - "editor.codeActionsOnSave": { - "source.fixAll.biome": "explicit" - }, "search.exclude": { "**/i18n/locales/*-*/**": true }, - "vitest.workspaceConfig": "vitest.config.ts" -} +} \ No newline at end of file diff --git a/package.json b/package.json index d72baa52f..ab7c8f971 100644 --- a/package.json +++ b/package.json @@ -12,21 +12,25 @@ "url": "https://github.com/meshtastic/web/issues" }, "homepage": "https://meshtastic.org", - "simple-git-hooks": { - "pre-commit": "pnpm run check:fix" + "lint-staged": { + "*.{ts,tsx}": [ + "oxlint", + "oxfmt" + ] }, "scripts": { "preinstall": "npx only-allow pnpm", - "lint": "biome lint", - "lint:fix": "biome lint --write", - "format": "biome format", - "format:fix": "biome format . --write", - "check": "biome check", - "check:fix": "biome check --write", + "lint": "oxlint", + "lint:fix": "oxlint --fix", + "format": "oxfmt --check .", + "format:fix": "oxfmt .", + "check": "pnpm lint && pnpm format", + "check:fix": "pnpm lint:fix && pnpm format:fix", "build:all": "pnpm run --filter '*' build", "clean:all": "pnpm run --filter '*' clean", "publish:packages": "pnpm run build --filter 'packages/transport-*'", - "test": "vitest" + "test": "vitest", + "prepare": "husky" }, "dependencies": { "@bufbuild/protobuf": "^2.9.0", @@ -35,8 +39,11 @@ "tslog": "^4.9.3" }, "devDependencies": { - "@biomejs/biome": "2.2.4", "@types/node": "^24.3.1", + "husky": "^9.1.0", + "lint-staged": "^16.0.0", + "oxfmt": "^0.16.0", + "oxlint": "^1.41.0", "tsdown": "^0.15.0", "typescript": "^5.9.2", "vitest": "^3.2.4" @@ -46,8 +53,7 @@ "@serialport/bindings-cpp", "@tailwindcss/oxide", "core-js", - "esbuild", - "simple-git-hooks" + "esbuild" ] } } diff --git a/packages/core/src/meshDevice.ts b/packages/core/src/meshDevice.ts index 0be2b224c..c9083e81b 100755 --- a/packages/core/src/meshDevice.ts +++ b/packages/core/src/meshDevice.ts @@ -44,8 +44,7 @@ export class MeshDevice { constructor(transport: Transport, configId?: number) { this.log = new Logger({ name: "iMeshDevice", - prettyLogTemplate: - "{{hh}}:{{MM}}:{{ss}}:{{ms}}\t{{logLevelName}}\t[{{name}}]\t", + prettyLogTemplate: "{{hh}}:{{MM}}:{{ss}}:{{ms}}\t{{logLevelName}}\t[{{name}}]\t", }); this.transport = transport; @@ -107,9 +106,7 @@ export class MeshDevice { ): Promise { this.log.debug( Emitter[Emitter.SendText], - `📤 Sending message to ${destination ?? "broadcast"} on channel ${ - channel?.toString() ?? 0 - }`, + `📤 Sending message to ${destination ?? "broadcast"} on channel ${channel?.toString() ?? 0}`, ); const enc = new TextEncoder(); @@ -137,9 +134,7 @@ export class MeshDevice { ): Promise { this.log.debug( Emitter[Emitter.SendWaypoint], - `📤 Sending waypoint to ${destination} on channel ${ - channel?.toString() ?? 0 - }`, + `📤 Sending waypoint to ${destination} on channel ${channel?.toString() ?? 0}`, ); waypointMessage.id = this.generateRandId(); @@ -210,19 +205,13 @@ export class MeshDevice { meshPacket.rxTime = Math.trunc(Date.now() / 1000); this.handleMeshPacket(meshPacket); } - return await this.sendRaw( - toBinary(Protobuf.Mesh.ToRadioSchema, toRadioMessage), - meshPacket.id, - ); + return await this.sendRaw(toBinary(Protobuf.Mesh.ToRadioSchema, toRadioMessage), meshPacket.id); } /** * Sends raw packet over the radio */ - public async sendRaw( - toRadio: Uint8Array, - id: number = this.generateRandId(), - ): Promise { + public async sendRaw(toRadio: Uint8Array, id: number = this.generateRandId()): Promise { if (toRadio.length > 512) { throw new Error("Message longer than 512 bytes, it will not be sent!"); } @@ -266,9 +255,7 @@ export class MeshDevice { /** * Writes module config to device */ - public async setModuleConfig( - moduleConfig: Protobuf.ModuleConfig.ModuleConfig, - ): Promise { + public async setModuleConfig(moduleConfig: Protobuf.ModuleConfig.ModuleConfig): Promise { this.log.debug(Emitter[Emitter.SetModuleConfig], "⚙️ Setting module config"); const moduleConfigMessage = create(Protobuf.Admin.AdminMessageSchema, { @@ -289,10 +276,7 @@ export class MeshDevice { public async setCannedMessages( cannedMessages: Protobuf.CannedMessages.CannedMessageModuleConfig, ): Promise { - this.log.debug( - Emitter[Emitter.SetCannedMessages], - "⚙️ Setting CannedMessages", - ); + this.log.debug(Emitter[Emitter.SetCannedMessages], "⚙️ Setting CannedMessages"); const cannedMessagesMessage = create(Protobuf.Admin.AdminMessageSchema, { payloadVariant: { @@ -332,10 +316,7 @@ export class MeshDevice { * Sets devices ChannelSettings */ public async setChannel(channel: Protobuf.Channel.Channel): Promise { - this.log.debug( - Emitter[Emitter.SetChannel], - `📻 Setting Channel: ${channel.index}`, - ); + this.log.debug(Emitter[Emitter.SetChannel], `📻 Setting Channel: ${channel.index}`); const setChannelMessage = create(Protobuf.Admin.AdminMessageSchema, { payloadVariant: { @@ -373,9 +354,7 @@ export class MeshDevice { /** * Sets static position of device */ - public async setPosition( - positionMessage: Protobuf.Mesh.Position, - ): Promise { + public async setPosition(positionMessage: Protobuf.Mesh.Position): Promise { return await this.sendPacket( toBinary(Protobuf.Mesh.PositionSchema, positionMessage), Protobuf.Portnums.PortNum.POSITION_APP, @@ -387,10 +366,7 @@ export class MeshDevice { * Sets the fixed position of a device. Can be used to * position GPS-less devices. */ - public async setFixedPosition( - latitude: number, - longitude: number, - ): Promise { + public async setFixedPosition(latitude: number, longitude: number): Promise { const setPositionMessage = create(Protobuf.Admin.AdminMessageSchema, { payloadVariant: { case: "setFixedPosition", @@ -434,10 +410,7 @@ export class MeshDevice { * Gets specified channel information from the radio */ public async getChannel(index: number): Promise { - this.log.debug( - Emitter[Emitter.GetChannel], - `📻 Requesting Channel: ${index}`, - ); + this.log.debug(Emitter[Emitter.GetChannel], `📻 Requesting Channel: ${index}`); const getChannelRequestMessage = create(Protobuf.Admin.AdminMessageSchema, { payloadVariant: { @@ -456,9 +429,7 @@ export class MeshDevice { /** * Gets devices config */ - public async getConfig( - configType: Protobuf.Admin.AdminMessage_ConfigType, - ): Promise { + public async getConfig(configType: Protobuf.Admin.AdminMessage_ConfigType): Promise { this.log.debug(Emitter[Emitter.GetConfig], "⚙️ Requesting config"); const getRadioRequestMessage = create(Protobuf.Admin.AdminMessageSchema, { @@ -481,10 +452,7 @@ export class MeshDevice { public async getModuleConfig( moduleConfigType: Protobuf.Admin.AdminMessage_ModuleConfigType, ): Promise { - this.log.debug( - Emitter[Emitter.GetModuleConfig], - "⚙️ Requesting module config", - ); + this.log.debug(Emitter[Emitter.GetModuleConfig], "⚙️ Requesting module config"); const getRadioRequestMessage = create(Protobuf.Admin.AdminMessageSchema, { payloadVariant: { @@ -522,26 +490,17 @@ export class MeshDevice { * Gets devices metadata */ public async getMetadata(nodeNum: number): Promise { - this.log.debug( - Emitter[Emitter.GetMetadata], - `🏷️ Requesting metadata from ${nodeNum}`, - ); + this.log.debug(Emitter[Emitter.GetMetadata], `🏷️ Requesting metadata from ${nodeNum}`); - const getDeviceMetricsRequestMessage = create( - Protobuf.Admin.AdminMessageSchema, - { - payloadVariant: { - case: "getDeviceMetadataRequest", - value: true, - }, + const getDeviceMetricsRequestMessage = create(Protobuf.Admin.AdminMessageSchema, { + payloadVariant: { + case: "getDeviceMetadataRequest", + value: true, }, - ); + }); return await this.sendPacket( - toBinary( - Protobuf.Admin.AdminMessageSchema, - getDeviceMetricsRequestMessage, - ), + toBinary(Protobuf.Admin.AdminMessageSchema, getDeviceMetricsRequestMessage), Protobuf.Portnums.PortNum.ADMIN_APP, nodeNum, ChannelNumber.Admin, @@ -552,10 +511,7 @@ export class MeshDevice { * Clears specific channel with the designated index */ public async clearChannel(index: number): Promise { - this.log.debug( - Emitter[Emitter.ClearChannel], - `📻 Clearing Channel ${index}`, - ); + this.log.debug(Emitter[Emitter.ClearChannel], `📻 Clearing Channel ${index}`); const channel = create(Protobuf.Channel.ChannelSchema, { index, @@ -634,10 +590,7 @@ export class MeshDevice { * Removes a node from the internal NodeDB of the radio by node number */ public async removeNodeByNum(nodeNum: number): Promise { - this.log.debug( - Emitter[Emitter.RemoveNodeByNum], - `📻 Removing Node ${nodeNum} from NodeDB`, - ); + this.log.debug(Emitter[Emitter.RemoveNodeByNum], `📻 Removing Node ${nodeNum} from NodeDB`); const removeNodeByNum = create(Protobuf.Admin.AdminMessageSchema, { payloadVariant: { @@ -762,10 +715,7 @@ export class MeshDevice { * Triggers the device configure process */ public configure(): Promise { - this.log.debug( - Emitter[Emitter.Configure], - "⚙️ Requesting device configuration", - ); + this.log.debug(Emitter[Emitter.Configure], "⚙️ Requesting device configuration"); this.updateDeviceStatus(DeviceStatusEnum.DeviceConfiguring); const toRadio = create(Protobuf.Mesh.ToRadioSchema, { @@ -775,14 +725,12 @@ export class MeshDevice { }, }); - return this.sendRaw(toBinary(Protobuf.Mesh.ToRadioSchema, toRadio)).catch( - (e) => { - if (this.deviceStatus === DeviceStatusEnum.DeviceDisconnected) { - throw new Error("Device connection lost"); - } - throw e; - }, - ); + return this.sendRaw(toBinary(Protobuf.Mesh.ToRadioSchema, toRadio)).catch((e) => { + if (this.deviceStatus === DeviceStatusEnum.DeviceDisconnected) { + throw new Error("Device connection lost"); + } + throw e; + }); } /** @@ -810,10 +758,7 @@ export class MeshDevice { } this._heartbeatIntervalId = setInterval(() => { this.heartbeat().catch((err) => { - this.log.error( - Emitter[Emitter.Ping], - `⚠️ Unable to send heartbeat: ${err.message}`, - ); + this.log.error(Emitter[Emitter.Ping], `⚠️ Unable to send heartbeat: ${err.message}`); }); }, interval); } @@ -950,10 +895,7 @@ export class MeshDevice { case Protobuf.Portnums.PortNum.REMOTE_HARDWARE_APP: { this.events.onRemoteHardwarePacket.dispatch({ ...packetMetadata, - data: fromBinary( - Protobuf.RemoteHardware.HardwareMessageSchema, - dataPacket.payload, - ), + data: fromBinary(Protobuf.RemoteHardware.HardwareMessageSchema, dataPacket.payload), }); break; } @@ -975,10 +917,7 @@ export class MeshDevice { } case Protobuf.Portnums.PortNum.ROUTING_APP: { - routingPacket = fromBinary( - Protobuf.Mesh.RoutingSchema, - dataPacket.payload, - ); + routingPacket = fromBinary(Protobuf.Mesh.RoutingSchema, dataPacket.payload); this.events.onRoutingPacket.dispatch({ ...packetMetadata, @@ -986,9 +925,7 @@ export class MeshDevice { }); switch (routingPacket.variant.case) { case "errorReason": { - if ( - routingPacket.variant.value === Protobuf.Mesh.Routing_Error.NONE - ) { + if (routingPacket.variant.value === Protobuf.Mesh.Routing_Error.NONE) { this.queue.processAck(dataPacket.requestId); } else { this.queue.processError({ @@ -1014,15 +951,10 @@ export class MeshDevice { } case Protobuf.Portnums.PortNum.ADMIN_APP: { - adminMessage = fromBinary( - Protobuf.Admin.AdminMessageSchema, - dataPacket.payload, - ); + adminMessage = fromBinary(Protobuf.Admin.AdminMessageSchema, dataPacket.payload); switch (adminMessage.payloadVariant.case) { case "getChannelResponse": { - this.events.onChannelPacket.dispatch( - adminMessage.payloadVariant.value, - ); + this.events.onChannelPacket.dispatch(adminMessage.payloadVariant.value); break; } case "getOwnerResponse": { @@ -1033,15 +965,11 @@ export class MeshDevice { break; } case "getConfigResponse": { - this.events.onConfigPacket.dispatch( - adminMessage.payloadVariant.value, - ); + this.events.onConfigPacket.dispatch(adminMessage.payloadVariant.value); break; } case "getModuleConfigResponse": { - this.events.onModuleConfigPacket.dispatch( - adminMessage.payloadVariant.value, - ); + this.events.onModuleConfigPacket.dispatch(adminMessage.payloadVariant.value); break; } case "getDeviceMetadataResponse": { @@ -1124,10 +1052,7 @@ export class MeshDevice { case Protobuf.Portnums.PortNum.PAXCOUNTER_APP: { this.events.onPaxcounterPacket.dispatch({ ...packetMetadata, - data: fromBinary( - Protobuf.PaxCount.PaxcountSchema, - dataPacket.payload, - ), + data: fromBinary(Protobuf.PaxCount.PaxcountSchema, dataPacket.payload), }); break; } @@ -1159,10 +1084,7 @@ export class MeshDevice { case Protobuf.Portnums.PortNum.TELEMETRY_APP: { this.events.onTelemetryPacket.dispatch({ ...packetMetadata, - data: fromBinary( - Protobuf.Telemetry.TelemetrySchema, - dataPacket.payload, - ), + data: fromBinary(Protobuf.Telemetry.TelemetrySchema, dataPacket.payload), }); break; } @@ -1186,10 +1108,7 @@ export class MeshDevice { case Protobuf.Portnums.PortNum.TRACEROUTE_APP: { this.events.onTraceRoutePacket.dispatch({ ...packetMetadata, - data: fromBinary( - Protobuf.Mesh.RouteDiscoverySchema, - dataPacket.payload, - ), + data: fromBinary(Protobuf.Mesh.RouteDiscoverySchema, dataPacket.payload), }); break; } @@ -1197,10 +1116,7 @@ export class MeshDevice { case Protobuf.Portnums.PortNum.NEIGHBORINFO_APP: { this.events.onNeighborInfoPacket.dispatch({ ...packetMetadata, - data: fromBinary( - Protobuf.Mesh.NeighborInfoSchema, - dataPacket.payload, - ), + data: fromBinary(Protobuf.Mesh.NeighborInfoSchema, dataPacket.payload), }); break; } diff --git a/packages/core/src/utils/eventSystem.ts b/packages/core/src/utils/eventSystem.ts index f139c0d94..485043e61 100644 --- a/packages/core/src/utils/eventSystem.ts +++ b/packages/core/src/utils/eventSystem.ts @@ -76,9 +76,8 @@ export class EventSystem { * * @event onAtakPacket */ - public readonly onAtakPacket: SimpleEventDispatcher< - PacketMetadata - > = new SimpleEventDispatcher>(); + public readonly onAtakPacket: SimpleEventDispatcher> = + new SimpleEventDispatcher>(); /** * Fires when a new MeshPacket message containing a Text packet has been @@ -86,9 +85,8 @@ export class EventSystem { * * @event onMessagePacket */ - public readonly onMessagePacket: SimpleEventDispatcher< - PacketMetadata - > = new SimpleEventDispatcher>(); + public readonly onMessagePacket: SimpleEventDispatcher> = + new SimpleEventDispatcher>(); /** * Fires when a new MeshPacket message containing a Remote Hardware packet has @@ -98,9 +96,7 @@ export class EventSystem { */ public readonly onRemoteHardwarePacket: SimpleEventDispatcher< PacketMetadata - > = new SimpleEventDispatcher< - PacketMetadata - >(); + > = new SimpleEventDispatcher>(); /** * Fires when a new MeshPacket message containing a Position packet has been @@ -108,9 +104,8 @@ export class EventSystem { * * @event onPositionPacket */ - public readonly onPositionPacket: SimpleEventDispatcher< - PacketMetadata - > = new SimpleEventDispatcher>(); + public readonly onPositionPacket: SimpleEventDispatcher> = + new SimpleEventDispatcher>(); /** * Fires when a new MeshPacket message containing a User packet has been @@ -118,9 +113,8 @@ export class EventSystem { * * @event onUserPacket */ - public readonly onUserPacket: SimpleEventDispatcher< - PacketMetadata - > = new SimpleEventDispatcher>(); + public readonly onUserPacket: SimpleEventDispatcher> = + new SimpleEventDispatcher>(); /** * Fires when a new MeshPacket message containing a Routing packet has been @@ -128,9 +122,8 @@ export class EventSystem { * * @event onRoutingPacket */ - public readonly onRoutingPacket: SimpleEventDispatcher< - PacketMetadata - > = new SimpleEventDispatcher>(); + public readonly onRoutingPacket: SimpleEventDispatcher> = + new SimpleEventDispatcher>(); /** * Fires when the device receives a Metadata packet @@ -146,9 +139,8 @@ export class EventSystem { * * @event onCannedMessageModulePacket */ - public readonly onCannedMessageModulePacket: SimpleEventDispatcher< - PacketMetadata - > = new SimpleEventDispatcher>(); + public readonly onCannedMessageModulePacket: SimpleEventDispatcher> = + new SimpleEventDispatcher>(); /** * Fires when a new MeshPacket message containing a Waypoint packet has been @@ -156,9 +148,8 @@ export class EventSystem { * * @event onWaypointPacket */ - public readonly onWaypointPacket: SimpleEventDispatcher< - PacketMetadata - > = new SimpleEventDispatcher>(); + public readonly onWaypointPacket: SimpleEventDispatcher> = + new SimpleEventDispatcher>(); /** * Fires when a new MeshPacket message containing an Audio packet has been @@ -166,9 +157,8 @@ export class EventSystem { * * @event onAudioPacket */ - public readonly onAudioPacket: SimpleEventDispatcher< - PacketMetadata - > = new SimpleEventDispatcher>(); + public readonly onAudioPacket: SimpleEventDispatcher> = + new SimpleEventDispatcher>(); /** * Fires when a new MeshPacket message containing a Detection Sensor packet has been @@ -176,9 +166,8 @@ export class EventSystem { * * @event onDetectionSensorPacket */ - public readonly onDetectionSensorPacket: SimpleEventDispatcher< - PacketMetadata - > = new SimpleEventDispatcher>(); + public readonly onDetectionSensorPacket: SimpleEventDispatcher> = + new SimpleEventDispatcher>(); /** * Fires when a new MeshPacket message containing a Ping packet has been @@ -186,9 +175,8 @@ export class EventSystem { * * @event onPingPacket */ - public readonly onPingPacket: SimpleEventDispatcher< - PacketMetadata - > = new SimpleEventDispatcher>(); + public readonly onPingPacket: SimpleEventDispatcher> = + new SimpleEventDispatcher>(); /** * Fires when a new MeshPacket message containing a IP Tunnel packet has been @@ -196,9 +184,8 @@ export class EventSystem { * * @event onIpTunnelPacket */ - public readonly onIpTunnelPacket: SimpleEventDispatcher< - PacketMetadata - > = new SimpleEventDispatcher>(); + public readonly onIpTunnelPacket: SimpleEventDispatcher> = + new SimpleEventDispatcher>(); /** * Fires when a new MeshPacket message containing a Paxcounter packet has been @@ -216,9 +203,8 @@ export class EventSystem { * * @event onSerialPacket */ - public readonly onSerialPacket: SimpleEventDispatcher< - PacketMetadata - > = new SimpleEventDispatcher>(); + public readonly onSerialPacket: SimpleEventDispatcher> = + new SimpleEventDispatcher>(); /** * Fires when a new MeshPacket message containing a Store and Forward packet @@ -226,9 +212,8 @@ export class EventSystem { * * @event onStoreForwardPacket */ - public readonly onStoreForwardPacket: SimpleEventDispatcher< - PacketMetadata - > = new SimpleEventDispatcher>(); + public readonly onStoreForwardPacket: SimpleEventDispatcher> = + new SimpleEventDispatcher>(); /** * Fires when a new MeshPacket message containing a Store and Forward packet @@ -236,9 +221,8 @@ export class EventSystem { * * @event onRangeTestPacket */ - public readonly onRangeTestPacket: SimpleEventDispatcher< - PacketMetadata - > = new SimpleEventDispatcher>(); + public readonly onRangeTestPacket: SimpleEventDispatcher> = + new SimpleEventDispatcher>(); /** * Fires when a new MeshPacket message containing a Telemetry packet has been @@ -256,9 +240,8 @@ export class EventSystem { * * @event onZPSPacket */ - public readonly onZpsPacket: SimpleEventDispatcher< - PacketMetadata - > = new SimpleEventDispatcher>(); + public readonly onZpsPacket: SimpleEventDispatcher> = + new SimpleEventDispatcher>(); /** * Fires when a new MeshPacket message containing a Simulator packet has been @@ -266,9 +249,8 @@ export class EventSystem { * * @event onSimulatorPacket */ - public readonly onSimulatorPacket: SimpleEventDispatcher< - PacketMetadata - > = new SimpleEventDispatcher>(); + public readonly onSimulatorPacket: SimpleEventDispatcher> = + new SimpleEventDispatcher>(); /** * Fires when a new MeshPacket message containing a Trace Route packet has been @@ -296,9 +278,8 @@ export class EventSystem { * * @event onAtakPluginPacket */ - public readonly onAtakPluginPacket: SimpleEventDispatcher< - PacketMetadata - > = new SimpleEventDispatcher>(); + public readonly onAtakPluginPacket: SimpleEventDispatcher> = + new SimpleEventDispatcher>(); /** * Fires when a new MeshPacket message containing a Map Report packet has been @@ -306,9 +287,8 @@ export class EventSystem { * * @event onMapReportPacket */ - public readonly onMapReportPacket: SimpleEventDispatcher< - PacketMetadata - > = new SimpleEventDispatcher>(); + public readonly onMapReportPacket: SimpleEventDispatcher> = + new SimpleEventDispatcher>(); /** * Fires when a new MeshPacket message containing a Private packet has been @@ -316,9 +296,8 @@ export class EventSystem { * * @event onPrivatePacket */ - public readonly onPrivatePacket: SimpleEventDispatcher< - PacketMetadata - > = new SimpleEventDispatcher>(); + public readonly onPrivatePacket: SimpleEventDispatcher> = + new SimpleEventDispatcher>(); /** * Fires when a new MeshPacket message containing an ATAK Forwarder packet has been @@ -326,9 +305,8 @@ export class EventSystem { * * @event onAtakForwarderPacket */ - public readonly onAtakForwarderPacket: SimpleEventDispatcher< - PacketMetadata - > = new SimpleEventDispatcher>(); + public readonly onAtakForwarderPacket: SimpleEventDispatcher> = + new SimpleEventDispatcher>(); /** * Fires when a new MeshPacket message containing a ClientNotification packet has been @@ -361,8 +339,7 @@ export class EventSystem { * * @event onMeshHeartbeat */ - public readonly onMeshHeartbeat: SimpleEventDispatcher = - new SimpleEventDispatcher(); + public readonly onMeshHeartbeat: SimpleEventDispatcher = new SimpleEventDispatcher(); /** * Outputs any debug log data (currently serial connections only) diff --git a/packages/core/src/utils/queue.ts b/packages/core/src/utils/queue.ts index 65bff5691..469389d84 100644 --- a/packages/core/src/utils/queue.ts +++ b/packages/core/src/utils/queue.ts @@ -54,9 +54,7 @@ export class Queue { return; } - console.warn( - `Packet ${item.id} of type ${decoded.payloadVariant.case} timed out`, - ); + console.warn(`Packet ${item.id} of type ${decoded.payloadVariant.case} timed out`); reject({ id: item.id, @@ -82,11 +80,7 @@ export class Queue { } public processError(e: PacketError): void { - console.error( - `Error received for packet ${e.id}: ${ - Protobuf.Mesh.Routing_Error[e.error] - }`, - ); + console.error(`Error received for packet ${e.id}: ${Protobuf.Mesh.Routing_Error[e.error]}`); this.errorNotifier.dispatch(e); } @@ -98,9 +92,7 @@ export class Queue { return queueItem.promise; } - public async processQueue( - outputStream: WritableStream, - ): Promise { + public async processQueue(outputStream: WritableStream): Promise { if (this.lock) { return; } @@ -117,10 +109,7 @@ export class Queue { await writer.write(item.data); item.sent = true; } catch (error) { - if ( - error?.code === "ECONNRESET" || - error?.code === "ERR_INVALID_STATE" - ) { + if (error?.code === "ECONNRESET" || error?.code === "ERR_INVALID_STATE") { writer.releaseLock(); this.lock = false; throw error; diff --git a/packages/core/src/utils/transform/decodePacket.ts b/packages/core/src/utils/transform/decodePacket.ts index 068964039..781f0d619 100644 --- a/packages/core/src/utils/transform/decodePacket.ts +++ b/packages/core/src/utils/transform/decodePacket.ts @@ -26,10 +26,7 @@ export const decodePacket = (device: MeshDevice) => case "packet": { let decodedMessage: Protobuf.Mesh.FromRadio; try { - decodedMessage = fromBinary( - Protobuf.Mesh.FromRadioSchema, - chunk.data, - ); + decodedMessage = fromBinary(Protobuf.Mesh.FromRadioSchema, chunk.data); } catch (e) { device.log.error( Types.Emitter[Types.Emitter.HandleFromRadio], @@ -56,9 +53,7 @@ export const decodePacket = (device: MeshDevice) => } case "myInfo": { - device.events.onMyNodeInfo.dispatch( - decodedMessage.payloadVariant.value, - ); + device.events.onMyNodeInfo.dispatch(decodedMessage.payloadVariant.value); device.log.info( Types.Emitter[Types.Emitter.HandleFromRadio], "📱 Received Node info for this device", @@ -72,9 +67,7 @@ export const decodePacket = (device: MeshDevice) => `📱 Received Node Info packet for node: ${decodedMessage.payloadVariant.value.num}`, ); - device.events.onNodeInfoPacket.dispatch( - decodedMessage.payloadVariant.value, - ); + device.events.onNodeInfoPacket.dispatch(decodedMessage.payloadVariant.value); //TODO: HERE if (decodedMessage.payloadVariant.value.position) { @@ -117,9 +110,7 @@ export const decodePacket = (device: MeshDevice) => ); } - device.events.onConfigPacket.dispatch( - decodedMessage.payloadVariant.value, - ); + device.events.onConfigPacket.dispatch(decodedMessage.payloadVariant.value); break; } @@ -128,9 +119,7 @@ export const decodePacket = (device: MeshDevice) => Types.Emitter[Types.Emitter.HandleFromRadio], "Received onLogRecord", ); - device.events.onLogRecord.dispatch( - decodedMessage.payloadVariant.value, - ); + device.events.onLogRecord.dispatch(decodedMessage.payloadVariant.value); break; } @@ -141,9 +130,7 @@ export const decodePacket = (device: MeshDevice) => ); // Emit the configCompleteId event for MeshService to handle two-stage flow - device.events.onConfigComplete.dispatch( - decodedMessage.payloadVariant.value, - ); + device.events.onConfigComplete.dispatch(decodedMessage.payloadVariant.value); // For backward compatibility: if configId matches, update device status // MeshService will override this behavior for two-stage flow @@ -152,9 +139,7 @@ export const decodePacket = (device: MeshDevice) => Types.Emitter[Types.Emitter.HandleFromRadio], `⚙️ Config id matches device.configId: ${device.configId}`, ); - device.updateDeviceStatus( - Types.DeviceStatusEnum.DeviceConfigured, - ); + device.updateDeviceStatus(Types.DeviceStatusEnum.DeviceConfigured); } break; } @@ -179,9 +164,7 @@ export const decodePacket = (device: MeshDevice) => ); } - device.events.onModuleConfigPacket.dispatch( - decodedMessage.payloadVariant.value, - ); + device.events.onModuleConfigPacket.dispatch(decodedMessage.payloadVariant.value); break; } @@ -191,9 +174,7 @@ export const decodePacket = (device: MeshDevice) => `🔐 Received Channel: ${decodedMessage.payloadVariant.value.index}`, ); - device.events.onChannelPacket.dispatch( - decodedMessage.payloadVariant.value, - ); + device.events.onChannelPacket.dispatch(decodedMessage.payloadVariant.value); break; } @@ -203,9 +184,7 @@ export const decodePacket = (device: MeshDevice) => `🚧 Received Queue Status: ${decodedMessage.payloadVariant.value}`, ); - device.events.onQueueStatus.dispatch( - decodedMessage.payloadVariant.value, - ); + device.events.onQueueStatus.dispatch(decodedMessage.payloadVariant.value); break; } @@ -216,9 +195,8 @@ export const decodePacket = (device: MeshDevice) => case "metadata": { if ( - Number.parseFloat( - decodedMessage.payloadVariant.value.firmwareVersion, - ) < Constants.minFwVer + Number.parseFloat(decodedMessage.payloadVariant.value.firmwareVersion) < + Constants.minFwVer ) { device.log.fatal( Types.Emitter[Types.Emitter.HandleFromRadio], diff --git a/packages/core/src/utils/transform/fromDevice.ts b/packages/core/src/utils/transform/fromDevice.ts index a21cf6ccf..387d7f054 100644 --- a/packages/core/src/utils/transform/fromDevice.ts +++ b/packages/core/src/utils/transform/fromDevice.ts @@ -1,69 +1,61 @@ import type { DeviceOutput } from "../../types.ts"; -export const fromDeviceStream: () => TransformStream = - ( - // onReleaseEvent: SimpleEventDispatcher, - ) => { - let byteBuffer = new Uint8Array([]); - const textDecoder = new TextDecoder(); - return new TransformStream({ - transform(chunk: Uint8Array, controller): void { - // onReleaseEvent.subscribe(() => { - // controller.terminate(); - // }); - byteBuffer = new Uint8Array([...byteBuffer, ...chunk]); - let processingExhausted = false; - while (byteBuffer.length !== 0 && !processingExhausted) { - const framingIndex = byteBuffer.indexOf(0x94); - const framingByte2 = byteBuffer[framingIndex + 1]; - if (framingByte2 === 0xc3) { - if (byteBuffer.subarray(0, framingIndex).length) { - controller.enqueue({ - type: "debug", - data: textDecoder.decode(byteBuffer.subarray(0, framingIndex)), - }); - byteBuffer = byteBuffer.subarray(framingIndex); - } - - const msb = byteBuffer[2]; - const lsb = byteBuffer[3]; +export const fromDeviceStream: () => TransformStream = ( + // onReleaseEvent: SimpleEventDispatcher, +) => { + let byteBuffer = new Uint8Array([]); + const textDecoder = new TextDecoder(); + return new TransformStream({ + transform(chunk: Uint8Array, controller): void { + // onReleaseEvent.subscribe(() => { + // controller.terminate(); + // }); + byteBuffer = new Uint8Array([...byteBuffer, ...chunk]); + let processingExhausted = false; + while (byteBuffer.length !== 0 && !processingExhausted) { + const framingIndex = byteBuffer.indexOf(0x94); + const framingByte2 = byteBuffer[framingIndex + 1]; + if (framingByte2 === 0xc3) { + if (byteBuffer.subarray(0, framingIndex).length) { + controller.enqueue({ + type: "debug", + data: textDecoder.decode(byteBuffer.subarray(0, framingIndex)), + }); + byteBuffer = byteBuffer.subarray(framingIndex); + } - if ( - msb !== undefined && - lsb !== undefined && - byteBuffer.length >= 4 + (msb << 8) + lsb - ) { - const packet = byteBuffer.subarray(4, 4 + (msb << 8) + lsb); + const msb = byteBuffer[2]; + const lsb = byteBuffer[3]; - const malformedDetectorIndex = packet.indexOf(0x94); - if ( - malformedDetectorIndex !== -1 && - packet[malformedDetectorIndex + 1] === 0xc3 - ) { - console.warn( - `⚠️ Malformed packet found, discarding: ${byteBuffer - .subarray(0, malformedDetectorIndex - 1) - .toString()}`, - ); + if (msb !== undefined && lsb !== undefined && byteBuffer.length >= 4 + (msb << 8) + lsb) { + const packet = byteBuffer.subarray(4, 4 + (msb << 8) + lsb); - byteBuffer = byteBuffer.subarray(malformedDetectorIndex); - } else { - byteBuffer = byteBuffer.subarray(3 + (msb << 8) + lsb + 1); + const malformedDetectorIndex = packet.indexOf(0x94); + if (malformedDetectorIndex !== -1 && packet[malformedDetectorIndex + 1] === 0xc3) { + console.warn( + `⚠️ Malformed packet found, discarding: ${byteBuffer + .subarray(0, malformedDetectorIndex - 1) + .toString()}`, + ); - controller.enqueue({ - type: "packet", - data: packet, - }); - } + byteBuffer = byteBuffer.subarray(malformedDetectorIndex); } else { - /** Only partioal message in buffer, wait for the rest */ - processingExhausted = true; + byteBuffer = byteBuffer.subarray(3 + (msb << 8) + lsb + 1); + + controller.enqueue({ + type: "packet", + data: packet, + }); } } else { - /** Message not complete, only 1 byte in buffer */ + /** Only partioal message in buffer, wait for the rest */ processingExhausted = true; } + } else { + /** Message not complete, only 1 byte in buffer */ + processingExhausted = true; } - }, - }); - }; + } + }, + }); +}; diff --git a/packages/core/src/utils/transform/toDevice.ts b/packages/core/src/utils/transform/toDevice.ts index 7f6a3932f..6d1619dfe 100644 --- a/packages/core/src/utils/transform/toDevice.ts +++ b/packages/core/src/utils/transform/toDevice.ts @@ -1,18 +1,12 @@ /** * Pads packets with appropriate framing information before writing to the output stream. */ -export const toDeviceStream: () => TransformStream = - () => { - return new TransformStream({ - transform(chunk: Uint8Array, controller): void { - const bufLen = chunk.length; - const header = new Uint8Array([ - 0x94, - 0xc3, - (bufLen >> 8) & 0xff, - bufLen & 0xff, - ]); - controller.enqueue(new Uint8Array([...header, ...chunk])); - }, - }); - }; +export const toDeviceStream: () => TransformStream = () => { + return new TransformStream({ + transform(chunk: Uint8Array, controller): void { + const bufLen = chunk.length; + const header = new Uint8Array([0x94, 0xc3, (bufLen >> 8) & 0xff, bufLen & 0xff]); + controller.enqueue(new Uint8Array([...header, ...chunk])); + }, + }); +}; diff --git a/packages/core/src/utils/xmodem.ts b/packages/core/src/utils/xmodem.ts index 201de92f5..2d4afa083 100644 --- a/packages/core/src/utils/xmodem.ts +++ b/packages/core/src/utils/xmodem.ts @@ -74,11 +74,7 @@ export class Xmodem { this.rxBuffer[this.counter] = packet.buffer; return this.sendCommand(Protobuf.Xmodem.XModem_Control.ACK); } - return await this.sendCommand( - Protobuf.Xmodem.XModem_Control.NAK, - undefined, - packet.seq, - ); + return await this.sendCommand(Protobuf.Xmodem.XModem_Control.NAK, undefined, packet.seq); } case Protobuf.Xmodem.XModem_Control.STX: { break; diff --git a/packages/transport-deno/scripts/build_npm.ts b/packages/transport-deno/scripts/build_npm.ts index 5c88dbd6c..2b98e947d 100644 --- a/packages/transport-deno/scripts/build_npm.ts +++ b/packages/transport-deno/scripts/build_npm.ts @@ -13,8 +13,7 @@ await build({ // package.json properties name: "@meshtastic/transport-deno", version: Deno.args[0], - description: - "A Deno transport layer for your project, enabling seamless integration with npm.", + description: "A Deno transport layer for your project, enabling seamless integration with npm.", license: "GPL-3.0-only", repository: { type: "git", diff --git a/packages/transport-deno/src/transport.ts b/packages/transport-deno/src/transport.ts index eec906956..493867df0 100644 --- a/packages/transport-deno/src/transport.ts +++ b/packages/transport-deno/src/transport.ts @@ -20,9 +20,7 @@ export class TransportDeno implements Types.Transport { toDeviceStream.readable.pipeTo(this.connection.writable); this._toDevice = toDeviceStream.writable; - this._fromDevice = this.connection.readable.pipeThrough( - Utils.fromDeviceStream(), - ); + this._fromDevice = this.connection.readable.pipeThrough(Utils.fromDeviceStream()); } get toDevice(): WritableStream { diff --git a/packages/transport-http/src/transport.test.ts b/packages/transport-http/src/transport.test.ts index 3f50cc603..601701ab7 100644 --- a/packages/transport-http/src/transport.test.ts +++ b/packages/transport-http/src/transport.test.ts @@ -1,26 +1,14 @@ -import { - afterEach, - beforeEach, - describe, - expect, - it, - type MockInstance, - vi, -} from "vitest"; +import { afterEach, beforeEach, describe, expect, it, type MockInstance, vi } from "vitest"; import { runTransportContract } from "../../../tests/utils/transportContract.ts"; import { TransportHTTP } from "./transport.ts"; let abortTimeoutSpy: MockInstance | undefined; beforeEach(() => { abortTimeoutSpy = vi - .spyOn( - globalThis.AbortSignal as unknown as { timeout(ms: number): AbortSignal }, - "timeout", - ) + .spyOn(globalThis.AbortSignal as unknown as { timeout(ms: number): AbortSignal }, "timeout") .mockImplementation((ms: number) => { const ctrl = new AbortController(); - const abort = () => - ctrl.abort(new DOMException("Timeout reached", "TimeoutError")); + const abort = () => ctrl.abort(new DOMException("Timeout reached", "TimeoutError")); // Uses setTimeout so vi.useFakeTimers() can fast-forward it setTimeout(abort, ms); return ctrl.signal; @@ -128,23 +116,21 @@ describe("TransportHTTP (contract)", () => { vi.unstubAllGlobals(); }, create: async () => { - ( - globalThis as unknown as { __http: ReturnType } - ).__http = stubFetch(); + (globalThis as unknown as { __http: ReturnType }).__http = stubFetch(); const transport = await TransportHTTP.create("127.0.0.1:80", false); await tickNextTimer(); return transport; }, pushIncoming: async (bytes) => { - ( - globalThis as unknown as { __http: ReturnType } - ).__http.pushIncoming(bytes); + (globalThis as unknown as { __http: ReturnType }).__http.pushIncoming( + bytes, + ); await tickNextTimer(); }, assertLastWritten: (bytes) => { - ( - globalThis as unknown as { __http: ReturnType } - ).__http.assertLastWritten(bytes); + (globalThis as unknown as { __http: ReturnType }).__http.assertLastWritten( + bytes, + ); }, triggerDisconnect: async () => { ( diff --git a/packages/transport-http/src/transport.ts b/packages/transport-http/src/transport.ts index 3bdf57dba..9bd73319d 100644 --- a/packages/transport-http/src/transport.ts +++ b/packages/transport-http/src/transport.ts @@ -34,8 +34,7 @@ export class TransportHTTP implements Types.Transport { private inflightReadController?: AbortController; - private lastStatus: Types.DeviceStatusEnum = - Types.DeviceStatusEnum.DeviceDisconnected; + private lastStatus: Types.DeviceStatusEnum = Types.DeviceStatusEnum.DeviceDisconnected; private closingByUser = false; /** @@ -44,10 +43,7 @@ export class TransportHTTP implements Types.Transport { * @param address Hostname or IP address (with optional port). * @param tls Use HTTPS if true, HTTP otherwise. */ - public static async create( - address: string, - tls?: boolean, - ): Promise { + public static async create(address: string, tls?: boolean): Promise { const connectionUrl = `${tls ? "https" : "http"}://${address}`; await fetch(`${connectionUrl}/api/v1/toradio`, { method: "OPTIONS", @@ -90,10 +86,7 @@ export class TransportHTTP implements Types.Transport { // Start polling immediately void this.safePoll(); - this.interval = setInterval( - () => void this.safePoll(), - this.fetchInterval, - ); + this.interval = setInterval(() => void this.safePoll(), this.fetchInterval); }, cancel: () => { if (this.interval) { @@ -112,10 +105,7 @@ export class TransportHTTP implements Types.Transport { const inflight = new AbortController(); this.inflightReadController = inflight; - const signal = AbortSignal.any([ - inflight.signal, - AbortSignal.timeout(READ_TIMEOUT_MS), - ]); + const signal = AbortSignal.any([inflight.signal, AbortSignal.timeout(READ_TIMEOUT_MS)]); try { const response = await fetch( @@ -127,9 +117,7 @@ export class TransportHTTP implements Types.Transport { }, ); if (!response.ok) { - throw new Error( - `fromradio ${response.status} ${response.statusText}`, - ); + throw new Error(`fromradio ${response.status} ${response.statusText}`); } this.emitStatus(Types.DeviceStatusEnum.DeviceConnected); @@ -217,10 +205,8 @@ export class TransportHTTP implements Types.Transport { private isTimeoutOrAbort(err: unknown): boolean { return ( - (err instanceof DOMException && - (err.name === "AbortError" || err.name === "TimeoutError")) || - (err instanceof Error && - (err.name === "AbortError" || err.name === "TimeoutError")) + (err instanceof DOMException && (err.name === "AbortError" || err.name === "TimeoutError")) || + (err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError")) ); } diff --git a/packages/transport-node-serial/src/transport.test.ts b/packages/transport-node-serial/src/transport.test.ts index d62908536..1136012bf 100644 --- a/packages/transport-node-serial/src/transport.test.ts +++ b/packages/transport-node-serial/src/transport.test.ts @@ -20,16 +20,8 @@ class FakeSerialPort extends Duplex { _read() {} - _write( - chunk: Buffer, - _encoding: BufferEncoding, - callback: (error?: Error | null) => void, - ) { - this.lastWritten = new Uint8Array( - chunk.buffer, - chunk.byteOffset, - chunk.byteLength, - ); + _write(chunk: Buffer, _encoding: BufferEncoding, callback: (error?: Error | null) => void) { + this.lastWritten = new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength); callback(); } @@ -69,16 +61,10 @@ function stubCoreTransforms() { // Utils.toDeviceStream is a getter const transform = Utils.toDeviceStream; - vi.spyOn(Utils, "toDeviceStream", "get").mockReturnValue( - toDevice as unknown as typeof transform, - ); + vi.spyOn(Utils, "toDeviceStream", "get").mockReturnValue(toDevice as unknown as typeof transform); vi.spyOn(Utils, "fromDeviceStream").mockImplementation( - () => - fromDeviceFactory() as unknown as TransformStream< - Uint8Array, - Types.DeviceOutput - >, + () => fromDeviceFactory() as unknown as TransformStream, ); return { @@ -105,30 +91,24 @@ describe("TransportNodeSerial (contract)", () => { }, create: async () => { const fakePort = new FakeSerialPort(); - const transport = new TransportNodeSerial( - fakePort as unknown as SerialPort, - ); + const transport = new TransportNodeSerial(fakePort as unknown as SerialPort); await Promise.resolve(); - (globalThis as unknown as { __fakePort: FakeSerialPort }).__fakePort = - fakePort; + (globalThis as unknown as { __fakePort: FakeSerialPort }).__fakePort = fakePort; return transport; }, pushIncoming: async (bytes) => { - ( - globalThis as unknown as { __fakePort: FakeSerialPort } - ).__fakePort.pushIncoming(bytes); + (globalThis as unknown as { __fakePort: FakeSerialPort }).__fakePort.pushIncoming(bytes); await Promise.resolve(); }, assertLastWritten: (bytes) => { - const port = (globalThis as unknown as { __fakePort: FakeSerialPort }) - .__fakePort; + const port = (globalThis as unknown as { __fakePort: FakeSerialPort }).__fakePort; expect(port.lastWritten).toBeDefined(); expect(port.lastWritten).toEqual(bytes); }, triggerDisconnect: async () => { - ( - globalThis as unknown as { __fakePort: FakeSerialPort } - ).__fakePort.emitErrorOnce("test-disconnect"); + (globalThis as unknown as { __fakePort: FakeSerialPort }).__fakePort.emitErrorOnce( + "test-disconnect", + ); await Promise.resolve(); }, }); @@ -147,9 +127,7 @@ describe("TransportNodeSerial (extras)", () => { it("emits DeviceDisconnected with reason 'port-closed' on close event", async () => { const fakePort = new FakeSerialPort(); - const transport = new TransportNodeSerial( - fakePort as unknown as SerialPort, - ); + const transport = new TransportNodeSerial(fakePort as unknown as SerialPort); const reader = transport.fromDevice.getReader(); await Promise.resolve(); @@ -157,17 +135,13 @@ describe("TransportNodeSerial (extras)", () => { const first = await reader.read(); expect(isStatusEvent(first.value)).toBe(true); if (isStatusEvent(first.value)) { - expect(first.value.data.status).toBe( - Types.DeviceStatusEnum.DeviceConnecting, - ); + expect(first.value.data.status).toBe(Types.DeviceStatusEnum.DeviceConnecting); } const second = await reader.read(); expect(isStatusEvent(second.value)).toBe(true); if (isStatusEvent(second.value)) { - expect(second.value.data.status).toBe( - Types.DeviceStatusEnum.DeviceConnected, - ); + expect(second.value.data.status).toBe(Types.DeviceStatusEnum.DeviceConnected); } fakePort.emitClose(); diff --git a/packages/transport-node-serial/src/transport.ts b/packages/transport-node-serial/src/transport.ts index 8bc0dd522..eb073621b 100644 --- a/packages/transport-node-serial/src/transport.ts +++ b/packages/transport-node-serial/src/transport.ts @@ -16,8 +16,7 @@ export class TransportNodeSerial implements Types.Transport { private port: SerialPort | undefined; private pipePromise?: Promise; private abortController: AbortController; - private lastStatus: Types.DeviceStatusEnum = - Types.DeviceStatusEnum.DeviceDisconnected; + private lastStatus: Types.DeviceStatusEnum = Types.DeviceStatusEnum.DeviceDisconnected; private closingByUser = false; /** @@ -26,10 +25,7 @@ export class TransportNodeSerial implements Types.Transport { * @param baudRate - Baud rate for the serial connection (default is 115200). * @returns A promise that resolves with a connected TransportNode instance. */ - public static create( - path: string, - baudRate = 115200, - ): Promise { + public static create(path: string, baudRate = 115200): Promise { return new Promise((resolve, reject) => { const port = new SerialPort({ path, @@ -94,13 +90,8 @@ export class TransportNodeSerial implements Types.Transport { if (this.closingByUser) { ctrl.close(); // graceful EOF on user } else { - this.emitStatus( - Types.DeviceStatusEnum.DeviceDisconnected, - "read-error", - ); - ctrl.error( - error instanceof Error ? error : new Error(String(error)), - ); + this.emitStatus(Types.DeviceStatusEnum.DeviceDisconnected, "read-error"); + ctrl.error(error instanceof Error ? error : new Error(String(error))); } try { await transformed.cancel(); @@ -124,10 +115,7 @@ export class TransportNodeSerial implements Types.Transport { return; } console.error("Error piping data to serial port:", error); - this.emitStatus( - Types.DeviceStatusEnum.DeviceDisconnected, - "write-error", - ); + this.emitStatus(Types.DeviceStatusEnum.DeviceDisconnected, "write-error"); try { this.port?.close(); } catch {} diff --git a/packages/transport-node/src/transport.test.ts b/packages/transport-node/src/transport.test.ts index 03ab4a497..d0d4799ae 100644 --- a/packages/transport-node/src/transport.test.ts +++ b/packages/transport-node/src/transport.test.ts @@ -20,16 +20,8 @@ class FakeSocket extends Duplex { _read() {} - _write( - chunk: Buffer, - _encoding: BufferEncoding, - callback: (error?: Error | null) => void, - ) { - this.lastWritten = new Uint8Array( - chunk.buffer, - chunk.byteOffset, - chunk.byteLength, - ); + _write(chunk: Buffer, _encoding: BufferEncoding, callback: (error?: Error | null) => void) { + this.lastWritten = new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength); callback(); } @@ -69,16 +61,10 @@ function stubCoreTransforms() { }); const transform = Utils.toDeviceStream; - vi.spyOn(Utils, "toDeviceStream", "get").mockReturnValue( - toDevice as unknown as typeof transform, - ); + vi.spyOn(Utils, "toDeviceStream", "get").mockReturnValue(toDevice as unknown as typeof transform); vi.spyOn(Utils, "fromDeviceStream").mockImplementation( - () => - fromDeviceFactory() as unknown as TransformStream< - Uint8Array, - Types.DeviceOutput - >, + () => fromDeviceFactory() as unknown as TransformStream, ); return { @@ -107,26 +93,22 @@ describe("TransportNode (contract)", () => { const fakeSocket = new FakeSocket(); const transport = new TransportNode(fakeSocket as unknown as Socket); await Promise.resolve(); - (globalThis as unknown as { __nodeSock: FakeSocket }).__nodeSock = - fakeSocket; + (globalThis as unknown as { __nodeSock: FakeSocket }).__nodeSock = fakeSocket; return transport; }, pushIncoming: async (bytes) => { - ( - globalThis as unknown as { __nodeSock: FakeSocket } - ).__nodeSock.pushIncoming(bytes); + (globalThis as unknown as { __nodeSock: FakeSocket }).__nodeSock.pushIncoming(bytes); await Promise.resolve(); }, assertLastWritten: (bytes) => { - const sock = (globalThis as unknown as { __nodeSock: FakeSocket }) - .__nodeSock; + const sock = (globalThis as unknown as { __nodeSock: FakeSocket }).__nodeSock; expect(sock.lastWritten).toBeDefined(); expect(sock.lastWritten).toEqual(bytes); }, triggerDisconnect: async () => { - ( - globalThis as unknown as { __nodeSock: FakeSocket } - ).__nodeSock.emitErrorOnce("test-disconnect"); + (globalThis as unknown as { __nodeSock: FakeSocket }).__nodeSock.emitErrorOnce( + "test-disconnect", + ); await Promise.resolve(); }, }); @@ -153,17 +135,13 @@ describe("TransportNode (extras)", () => { const first = await reader.read(); expect(isStatusEvent(first.value)).toBe(true); if (isStatusEvent(first.value)) { - expect(first.value.data.status).toBe( - Types.DeviceStatusEnum.DeviceConnecting, - ); + expect(first.value.data.status).toBe(Types.DeviceStatusEnum.DeviceConnecting); } const second = await reader.read(); expect(isStatusEvent(second.value)).toBe(true); if (isStatusEvent(second.value)) { - expect(second.value.data.status).toBe( - Types.DeviceStatusEnum.DeviceConnected, - ); + expect(second.value.data.status).toBe(Types.DeviceStatusEnum.DeviceConnected); } fakeSocket.emitClose(); diff --git a/packages/transport-node/src/transport.ts b/packages/transport-node/src/transport.ts index 9d41167c2..4333e18a8 100644 --- a/packages/transport-node/src/transport.ts +++ b/packages/transport-node/src/transport.ts @@ -16,8 +16,7 @@ export class TransportNode implements Types.Transport { private socket: Socket | undefined; private pipePromise?: Promise; private abortController: AbortController; - private lastStatus: Types.DeviceStatusEnum = - Types.DeviceStatusEnum.DeviceDisconnected; + private lastStatus: Types.DeviceStatusEnum = Types.DeviceStatusEnum.DeviceDisconnected; private closingByUser = false; private errored = false; @@ -29,11 +28,7 @@ export class TransportNode implements Types.Transport { * @param timeout - TCP socket timeout in milliseconds (defaults to 60000). * @returns A promise that resolves with a connected TransportNode instance. */ - public static create( - hostname: string, - port = 4403, - timeout = 60000, - ): Promise { + public static create(hostname: string, port = 4403, timeout = 60000): Promise { return new Promise((resolve, reject) => { const socket = new Socket(); @@ -65,10 +60,7 @@ export class TransportNode implements Types.Transport { this.socket?.removeAllListeners(); this.socket?.destroy(); if (!this.closingByUser) { - this.emitStatus( - Types.DeviceStatusEnum.DeviceDisconnected, - "socket-error", - ); + this.emitStatus(Types.DeviceStatusEnum.DeviceDisconnected, "socket-error"); } }); @@ -82,10 +74,7 @@ export class TransportNode implements Types.Transport { }); this.socket.on("timeout", () => { - this.emitStatus( - Types.DeviceStatusEnum.DeviceDisconnected, - "socket-timeout", - ); + this.emitStatus(Types.DeviceStatusEnum.DeviceDisconnected, "socket-timeout"); this.socket?.removeAllListeners(); this.socket?.destroy(); }); @@ -94,18 +83,13 @@ export class TransportNode implements Types.Transport { if (this.closingByUser) { return; // suppress close-derived disconnect in user flow } - this.emitStatus( - Types.DeviceStatusEnum.DeviceDisconnected, - "socket-closed", - ); + this.emitStatus(Types.DeviceStatusEnum.DeviceDisconnected, "socket-closed"); }); this.abortController = new AbortController(); const abortController = this.abortController; - const fromDeviceSource = Readable.toWeb( - connection, - ) as ReadableStream; + const fromDeviceSource = Readable.toWeb(connection) as ReadableStream; const transformed = fromDeviceSource.pipeThrough(Utils.fromDeviceStream()); this._fromDevice = new ReadableStream({ @@ -129,14 +113,9 @@ export class TransportNode implements Types.Transport { if (this.closingByUser || this.errored) { ctrl.close(); } else { - this.emitStatus( - Types.DeviceStatusEnum.DeviceDisconnected, - "read-error", - ); - - ctrl.error( - error instanceof Error ? error : new Error(String(error)), - ); + this.emitStatus(Types.DeviceStatusEnum.DeviceDisconnected, "read-error"); + + ctrl.error(error instanceof Error ? error : new Error(String(error))); } try { await transformed.cancel(); diff --git a/packages/transport-web-bluetooth/src/transport.test.ts b/packages/transport-web-bluetooth/src/transport.test.ts index c7825ecab..96080b17d 100644 --- a/packages/transport-web-bluetooth/src/transport.test.ts +++ b/packages/transport-web-bluetooth/src/transport.test.ts @@ -28,11 +28,7 @@ function stubWebBluetooth() { const fromRadioCharacteristic: BluetoothRemoteGATTCharacteristic = { async readValue() { const next = incomingQueue.shift() ?? new Uint8Array(); - return new DataView( - next.buffer, - next.byteOffset, - next.byteLength, - ) as unknown as DataView; + return new DataView(next.buffer, next.byteOffset, next.byteLength) as unknown as DataView; }, addEventListener() {}, removeEventListener() {}, @@ -58,11 +54,7 @@ function stubWebBluetooth() { const u8 = bufferSource instanceof ArrayBuffer ? new Uint8Array(bufferSource) - : new Uint8Array( - bufferSource.buffer, - bufferSource.byteOffset, - bufferSource.byteLength, - ); + : new Uint8Array(bufferSource.buffer, bufferSource.byteOffset, bufferSource.byteLength); lastWritten = new Uint8Array(u8); }, } as unknown as BluetoothRemoteGATTCharacteristic; @@ -104,20 +96,10 @@ function stubWebBluetooth() { return primaryService; }, device: { - addEventListener: ( - ...args: Parameters - ) => - deviceEmitter.addEventListener( - args[0] as string, - args[1] as (e: Event) => void, - ), - removeEventListener: ( - ...args: Parameters - ) => - deviceEmitter.removeEventListener( - args[0] as string, - args[1] as (e: Event) => void, - ), + addEventListener: (...args: Parameters) => + deviceEmitter.addEventListener(args[0] as string, args[1] as (e: Event) => void), + removeEventListener: (...args: Parameters) => + deviceEmitter.removeEventListener(args[0] as string, args[1] as (e: Event) => void), } as unknown as BluetoothDevice, } as unknown as BluetoothRemoteGATTServer; @@ -134,10 +116,7 @@ function stubWebBluetooth() { }, }; - vi.stubGlobal( - "navigator", - Object.assign({}, globalThis.navigator, fakeNavigator), - ); + vi.stubGlobal("navigator", Object.assign({}, globalThis.navigator, fakeNavigator)); // helper actions for tests/contract return { @@ -165,25 +144,20 @@ describe("TransportWebBluetooth (contract)", () => { name: "TransportWebBluetooth", setup: () => {}, teardown: () => { - ( - globalThis as unknown as { __ble?: ReturnType } - ).__ble?.cleanup(); - ( - globalThis as unknown as { __ble?: ReturnType } - ).__ble = undefined; + (globalThis as unknown as { __ble?: ReturnType }).__ble?.cleanup(); + (globalThis as unknown as { __ble?: ReturnType }).__ble = undefined; vi.restoreAllMocks(); vi.unstubAllGlobals(); }, create: async () => { - ( - globalThis as unknown as { __ble: ReturnType } - ).__ble = stubWebBluetooth(); + (globalThis as unknown as { __ble: ReturnType }).__ble = + stubWebBluetooth(); return await TransportWebBluetooth.create(); }, pushIncoming: async (bytes) => { - ( - globalThis as unknown as { __ble: ReturnType } - ).__ble.pushIncoming(bytes); + (globalThis as unknown as { __ble: ReturnType }).__ble.pushIncoming( + bytes, + ); await Promise.resolve(); }, assertLastWritten: (bytes) => { diff --git a/packages/transport-web-bluetooth/src/transport.ts b/packages/transport-web-bluetooth/src/transport.ts index 00c0e0c4c..8434d66a2 100644 --- a/packages/transport-web-bluetooth/src/transport.ts +++ b/packages/transport-web-bluetooth/src/transport.ts @@ -28,8 +28,7 @@ export class TransportWebBluetooth implements Types.Transport { private fromNumCharacteristic: BluetoothRemoteGATTCharacteristic; private gattServer: BluetoothRemoteGATTServer; - private lastStatus: Types.DeviceStatusEnum = - Types.DeviceStatusEnum.DeviceDisconnected; + private lastStatus: Types.DeviceStatusEnum = Types.DeviceStatusEnum.DeviceDisconnected; private closingByUser = false; private reading = false; @@ -46,10 +45,7 @@ export class TransportWebBluetooth implements Types.Transport { if (this.closingByUser) { return; } - this.emitStatus( - Types.DeviceStatusEnum.DeviceDisconnected, - "gatt-disconnected", - ); + this.emitStatus(Types.DeviceStatusEnum.DeviceDisconnected, "gatt-disconnected"); }; private onFromNumChanged = () => { void this.readFromRadio(); @@ -68,9 +64,7 @@ export class TransportWebBluetooth implements Types.Transport { /** * Creates a transport from an existing, user-provided {@link BluetoothDevice}. */ - public static async createFromDevice( - device: BluetoothDevice, - ): Promise { + public static async createFromDevice(device: BluetoothDevice): Promise { return await TransportWebBluetooth.prepareConnection(device); } @@ -80,17 +74,13 @@ export class TransportWebBluetooth implements Types.Transport { * * @throws if required services or characteristics are missing. */ - public static async prepareConnection( - device: BluetoothDevice, - ): Promise { + public static async prepareConnection(device: BluetoothDevice): Promise { const gattServer = await device.gatt?.connect(); if (!gattServer) { throw new Error("Failed to connect to GATT server"); } - const service = await gattServer.getPrimaryService( - TransportWebBluetooth.ServiceUuid, - ); + const service = await gattServer.getPrimaryService(TransportWebBluetooth.ServiceUuid); const toRadioCharacteristic = await service.getCharacteristic( TransportWebBluetooth.ToRadioUuid, ); @@ -101,11 +91,7 @@ export class TransportWebBluetooth implements Types.Transport { TransportWebBluetooth.FromNumUuid, ); - if ( - !toRadioCharacteristic || - !fromRadioCharacteristic || - !fromNumCharacteristic - ) { + if (!toRadioCharacteristic || !fromRadioCharacteristic || !fromNumCharacteristic) { throw new Error("Failed to find required characteristics"); } @@ -137,10 +123,7 @@ export class TransportWebBluetooth implements Types.Transport { this.fromDeviceController = ctrl; this.emitStatus(Types.DeviceStatusEnum.DeviceConnecting); - this.gattServer.device.addEventListener( - "gattserverdisconnected", - this.onGattDisconnected, - ); + this.gattServer.device.addEventListener("gattserverdisconnected", this.onGattDisconnected); try { await this.fromNumCharacteristic.startNotifications(); @@ -152,10 +135,7 @@ export class TransportWebBluetooth implements Types.Transport { // prime once in case data already queued void this.readFromRadio(); } catch { - this.emitStatus( - Types.DeviceStatusEnum.DeviceDisconnected, - "notify-failed", - ); + this.emitStatus(Types.DeviceStatusEnum.DeviceDisconnected, "notify-failed"); this.gattServer.device.removeEventListener( "gattserverdisconnected", this.onGattDisconnected, @@ -171,10 +151,7 @@ export class TransportWebBluetooth implements Types.Transport { await this.toRadioCharacteristic.writeValue(ab); void this.readFromRadio(); // ensure we read any response } catch (error) { - this.emitStatus( - Types.DeviceStatusEnum.DeviceDisconnected, - "write-error", - ); + this.emitStatus(Types.DeviceStatusEnum.DeviceDisconnected, "write-error"); throw error; } }, @@ -205,10 +182,7 @@ export class TransportWebBluetooth implements Types.Transport { "characteristicvaluechanged", this.onFromNumChanged, ); - this.gattServer.device.removeEventListener( - "gattserverdisconnected", - this.onGattDisconnected, - ); + this.gattServer.device.removeEventListener("gattserverdisconnected", this.onGattDisconnected); this.gattServer.disconnect(); } finally { @@ -238,10 +212,7 @@ export class TransportWebBluetooth implements Types.Transport { } } catch (error) { if (!this.closingByUser) { - this.emitStatus( - Types.DeviceStatusEnum.DeviceDisconnected, - "read-error", - ); + this.emitStatus(Types.DeviceStatusEnum.DeviceDisconnected, "read-error"); } throw error; } finally { diff --git a/packages/transport-web-serial/src/transport.test.ts b/packages/transport-web-serial/src/transport.test.ts index 403a3f38f..2d9b04813 100644 --- a/packages/transport-web-serial/src/transport.test.ts +++ b/packages/transport-web-serial/src/transport.test.ts @@ -27,11 +27,7 @@ function stubCoreTransforms() { const restoreFrom = vi .spyOn(Utils, "fromDeviceStream") .mockImplementation( - () => - fromDeviceFactory() as unknown as TransformStream< - Uint8Array, - Types.DeviceOutput - >, + () => fromDeviceFactory() as unknown as TransformStream, ); return { @@ -47,18 +43,12 @@ function stubNavigatorSerial() { const handlers = new Set(); const serialStub = { - addEventListener: ( - type: string, - handler: EventListenerOrEventListenerObject, - ) => { + addEventListener: (type: string, handler: EventListenerOrEventListenerObject) => { if (type === "disconnect") { handlers.add(handler as any as SerialDisconnectHandler); } }, - removeEventListener: ( - type: string, - handler: EventListenerOrEventListenerObject, - ) => { + removeEventListener: (type: string, handler: EventListenerOrEventListenerObject) => { if (type === "disconnect") { handlers.delete(handler as any as SerialDisconnectHandler); } @@ -182,9 +172,7 @@ describe("TransportWebSerial (contract)", () => { expect((globalThis as any).__ws.fake.lastWritten).toEqual(bytes); }, triggerDisconnect: async () => { - (globalThis as any).__ws.serial.dispatchDisconnect( - (globalThis as any).__ws.fake, - ); + (globalThis as any).__ws.serial.dispatchDisconnect((globalThis as any).__ws.fake); await Promise.resolve(); }, }); @@ -230,10 +218,7 @@ describe("TransportWebSerial (extras)", () => { let saw = false; for (let i = 0; i < 6; i++) { const { value } = await reader.read(); - if ( - value?.type === "status" && - value.data.reason === "serial-disconnected" - ) { + if (value?.type === "status" && value.data.reason === "serial-disconnected") { saw = true; break; } diff --git a/packages/transport-web-serial/src/transport.ts b/packages/transport-web-serial/src/transport.ts index ff81aee13..a6d8547d8 100644 --- a/packages/transport-web-serial/src/transport.ts +++ b/packages/transport-web-serial/src/transport.ts @@ -16,8 +16,7 @@ export class TransportWebSerial implements Types.Transport { private abortController: AbortController; private portReadable: ReadableStream; - private lastStatus: Types.DeviceStatusEnum = - Types.DeviceStatusEnum.DeviceDisconnected; + private lastStatus: Types.DeviceStatusEnum = Types.DeviceStatusEnum.DeviceDisconnected; private closingByUser = false; /** @@ -68,10 +67,7 @@ export class TransportWebSerial implements Types.Transport { } console.error("Error piping data to serial port:", err); this.connection.close().catch(() => {}); - this.emitStatus( - Types.DeviceStatusEnum.DeviceDisconnected, - "write-error", - ); + this.emitStatus(Types.DeviceStatusEnum.DeviceDisconnected, "write-error"); }); this._toDevice = toDeviceTransform.writable; @@ -83,18 +79,13 @@ export class TransportWebSerial implements Types.Transport { this.emitStatus(Types.DeviceStatusEnum.DeviceConnecting); - const transformed = this.portReadable.pipeThrough( - Utils.fromDeviceStream(), - ); + const transformed = this.portReadable.pipeThrough(Utils.fromDeviceStream()); const reader = transformed.getReader(); const onOsDisconnect = (ev: Event) => { const { port } = ev as unknown as { port?: SerialPort }; if (port && port === this.connection) { - this.emitStatus( - Types.DeviceStatusEnum.DeviceDisconnected, - "serial-disconnected", - ); + this.emitStatus(Types.DeviceStatusEnum.DeviceDisconnected, "serial-disconnected"); } }; navigator.serial.addEventListener("disconnect", onOsDisconnect); @@ -112,10 +103,7 @@ export class TransportWebSerial implements Types.Transport { ctrl.close(); } catch (error) { if (!this.closingByUser) { - this.emitStatus( - Types.DeviceStatusEnum.DeviceDisconnected, - "read-error", - ); + this.emitStatus(Types.DeviceStatusEnum.DeviceDisconnected, "read-error"); } ctrl.error(error instanceof Error ? error : new Error(String(error))); try { @@ -210,19 +198,13 @@ export class TransportWebSerial implements Types.Transport { return; } console.error("Error piping data to serial port (reconnect):", error); - this.emitStatus( - Types.DeviceStatusEnum.DeviceDisconnected, - "write-error", - ); + this.emitStatus(Types.DeviceStatusEnum.DeviceDisconnected, "write-error"); }); this.emitStatus(Types.DeviceStatusEnum.DeviceConnected, "reconnected"); } catch (error) { // Couldn’t re-pipe - this.emitStatus( - Types.DeviceStatusEnum.DeviceDisconnected, - "reconnect-failed", - ); + this.emitStatus(Types.DeviceStatusEnum.DeviceDisconnected, "reconnect-failed"); throw error; } } diff --git a/packages/ui/src/components/theme-provider.tsx b/packages/ui/src/components/theme-provider.tsx index c63f43c20..f60713135 100644 --- a/packages/ui/src/components/theme-provider.tsx +++ b/packages/ui/src/components/theme-provider.tsx @@ -36,8 +36,7 @@ export function ThemeProvider({ root.classList.remove("light", "dark"); if (theme === "system") { - const systemTheme = window.matchMedia("(prefers-color-scheme: dark)") - .matches + const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; @@ -68,9 +67,7 @@ export const useTheme = () => { // If the provider is missing, context will be initialState (setTheme is a no-op) if (context.setTheme === initialState.setTheme) { - throw new Error( - "useTheme must be used within a ThemeProvider: provider is missing", - ); + throw new Error("useTheme must be used within a ThemeProvider: provider is missing"); } return context; diff --git a/packages/ui/src/components/ui/badge.tsx b/packages/ui/src/components/ui/badge.tsx index d9ebd4afc..c1c7d2348 100644 --- a/packages/ui/src/components/ui/badge.tsx +++ b/packages/ui/src/components/ui/badge.tsx @@ -9,14 +9,12 @@ const badgeVariants = cva( { variants: { variant: { - default: - "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90", + default: "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90", secondary: "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", destructive: "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", - outline: - "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", + outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", }, }, defaultVariants: { @@ -30,16 +28,11 @@ function Badge({ variant, asChild = false, ...props -}: React.ComponentProps<"span"> & - VariantProps & { asChild?: boolean }) { +}: React.ComponentProps<"span"> & VariantProps & { asChild?: boolean }) { const Comp = asChild ? Slot : "span"; return ( - + ); } diff --git a/packages/ui/src/components/ui/button.tsx b/packages/ui/src/components/ui/button.tsx index c6f2d89e2..ced02c50a 100644 --- a/packages/ui/src/components/ui/button.tsx +++ b/packages/ui/src/components/ui/button.tsx @@ -14,10 +14,8 @@ const buttonVariants = cva( "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", - secondary: - "bg-secondary text-secondary-foreground hover:bg-secondary/80", - ghost: - "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", + secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", link: "text-primary underline-offset-4 hover:underline", }, size: { diff --git a/packages/ui/src/components/ui/collapsible.tsx b/packages/ui/src/components/ui/collapsible.tsx index 849e7b66f..ecddbb596 100644 --- a/packages/ui/src/components/ui/collapsible.tsx +++ b/packages/ui/src/components/ui/collapsible.tsx @@ -1,31 +1,19 @@ import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"; -function Collapsible({ - ...props -}: React.ComponentProps) { +function Collapsible({ ...props }: React.ComponentProps) { return ; } function CollapsibleTrigger({ ...props }: React.ComponentProps) { - return ( - - ); + return ; } function CollapsibleContent({ ...props }: React.ComponentProps) { - return ( - - ); + return ; } export { Collapsible, CollapsibleTrigger, CollapsibleContent }; diff --git a/packages/ui/src/components/ui/dropdown-menu.tsx b/packages/ui/src/components/ui/dropdown-menu.tsx index a7a316565..b4ef3e17e 100644 --- a/packages/ui/src/components/ui/dropdown-menu.tsx +++ b/packages/ui/src/components/ui/dropdown-menu.tsx @@ -4,29 +4,20 @@ import type * as React from "react"; import { cn } from "@/lib/utils"; -function DropdownMenu({ - ...props -}: React.ComponentProps) { +function DropdownMenu({ ...props }: React.ComponentProps) { return ; } function DropdownMenuPortal({ ...props }: React.ComponentProps) { - return ( - - ); + return ; } function DropdownMenuTrigger({ ...props }: React.ComponentProps) { - return ( - - ); + return ; } function DropdownMenuContent({ @@ -49,12 +40,8 @@ function DropdownMenuContent({ ); } -function DropdownMenuGroup({ - ...props -}: React.ComponentProps) { - return ( - - ); +function DropdownMenuGroup({ ...props }: React.ComponentProps) { + return ; } function DropdownMenuItem({ @@ -109,12 +96,7 @@ function DropdownMenuCheckboxItem({ function DropdownMenuRadioGroup({ ...props }: React.ComponentProps) { - return ( - - ); + return ; } function DropdownMenuRadioItem({ @@ -152,10 +134,7 @@ function DropdownMenuLabel({ ); @@ -174,25 +153,17 @@ function DropdownMenuSeparator({ ); } -function DropdownMenuShortcut({ - className, - ...props -}: React.ComponentProps<"span">) { +function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<"span">) { return ( ); } -function DropdownMenuSub({ - ...props -}: React.ComponentProps) { +function DropdownMenuSub({ ...props }: React.ComponentProps) { return ; } diff --git a/packages/ui/src/components/ui/sheet.tsx b/packages/ui/src/components/ui/sheet.tsx index 577a724e2..5b8cce588 100644 --- a/packages/ui/src/components/ui/sheet.tsx +++ b/packages/ui/src/components/ui/sheet.tsx @@ -10,21 +10,15 @@ function Sheet({ ...props }: React.ComponentProps) { return ; } -function SheetTrigger({ - ...props -}: React.ComponentProps) { +function SheetTrigger({ ...props }: React.ComponentProps) { return ; } -function SheetClose({ - ...props -}: React.ComponentProps) { +function SheetClose({ ...props }: React.ComponentProps) { return ; } -function SheetPortal({ - ...props -}: React.ComponentProps) { +function SheetPortal({ ...props }: React.ComponentProps) { return ; } @@ -101,10 +95,7 @@ function SheetFooter({ className, ...props }: React.ComponentProps<"div">) { ); } -function SheetTitle({ - className, - ...props -}: React.ComponentProps) { +function SheetTitle({ className, ...props }: React.ComponentProps) { return ( { const handleKeyDown = (event: KeyboardEvent) => { - if ( - event.key === SIDEBAR_KEYBOARD_SHORTCUT && - (event.metaKey || event.ctrlKey) - ) { + if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) { event.preventDefault(); toggleSidebar(); } @@ -253,11 +245,7 @@ function Sidebar({ ); } -function SidebarTrigger({ - className, - onClick, - ...props -}: React.ComponentProps) { +function SidebarTrigger({ className, onClick, ...props }: React.ComponentProps) { const { toggleSidebar } = useSidebar(); return ( @@ -318,10 +306,7 @@ function SidebarInset({ className, ...props }: React.ComponentProps<"main">) { ); } -function SidebarInput({ - className, - ...props -}: React.ComponentProps) { +function SidebarInput({ className, ...props }: React.ComponentProps) { return ( ) { ); } -function SidebarSeparator({ - className, - ...props -}: React.ComponentProps) { +function SidebarSeparator({ className, ...props }: React.ComponentProps) { return ( ) { +function SidebarGroupContent({ className, ...props }: React.ComponentProps<"div">) { return (
) { +function SidebarMenuBadge({ className, ...props }: React.ComponentProps<"div">) { return (
- {showIcon && ( - - )} + {showIcon && } ) { ); } -function SidebarMenuSubItem({ - className, - ...props -}: React.ComponentProps<"li">) { +function SidebarMenuSubItem({ className, ...props }: React.ComponentProps<"li">) { return (
  • ) { +function Tooltip({ ...props }: React.ComponentProps) { return ( @@ -26,9 +24,7 @@ function Tooltip({ ); } -function TooltipTrigger({ - ...props -}: React.ComponentProps) { +function TooltipTrigger({ ...props }: React.ComponentProps) { return ; } diff --git a/packages/ui/src/hooks/use-mobile.ts b/packages/ui/src/hooks/use-mobile.ts index a93d58393..502fd3239 100644 --- a/packages/ui/src/hooks/use-mobile.ts +++ b/packages/ui/src/hooks/use-mobile.ts @@ -3,9 +3,7 @@ import * as React from "react"; const MOBILE_BREAKPOINT = 768; export function useIsMobile() { - const [isMobile, setIsMobile] = React.useState( - undefined, - ); + const [isMobile, setIsMobile] = React.useState(undefined); React.useEffect(() => { const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`); diff --git a/packages/ui/src/lib/components/Sidebar/AppSidebar.tsx b/packages/ui/src/lib/components/Sidebar/AppSidebar.tsx index 2bca97d28..12893e563 100644 --- a/packages/ui/src/lib/components/Sidebar/AppSidebar.tsx +++ b/packages/ui/src/lib/components/Sidebar/AppSidebar.tsx @@ -2,11 +2,7 @@ import type { LucideIcon } from "lucide-react"; import { ChevronRight } from "lucide-react"; import type * as React from "react"; import { Badge } from "@/components/ui/badge"; -import { - Collapsible, - CollapsibleContent, - CollapsibleTrigger, -} from "@/components/ui/collapsible"; +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { Sidebar, SidebarContent, @@ -60,7 +56,7 @@ const AppSidebar = ({ navigationLabel, items, footer, - variant, + variant: _variant, ...props }: SidebarSectionProps & AppSidebarProps) => { const { state } = useSidebar(); @@ -69,17 +65,11 @@ const AppSidebar = ({ return (
    {logo && ( - {logo.alt} + {logo.alt} )} {title && (

    - {subLink.icon && ( - - )} + {subLink.icon && } {subLink.name} - {subLink.count !== undefined && - subLink.count > 0 && ( - - {subLink.count} - - )} + {subLink.count !== undefined && subLink.count > 0 && ( + + {subLink.count} + + )} ))} @@ -184,10 +171,7 @@ const AppSidebar = ({ {link.icon && } {link.name} {link.count !== undefined && link.count > 0 && ( - + {link.count} )} @@ -200,11 +184,7 @@ const AppSidebar = ({ {children && ( -
    - {children} -
    +
    {children}
    )} diff --git a/packages/ui/src/lib/components/theme-toggle.tsx b/packages/ui/src/lib/components/theme-toggle.tsx index 242a95958..bcbc752bb 100644 --- a/packages/ui/src/lib/components/theme-toggle.tsx +++ b/packages/ui/src/lib/components/theme-toggle.tsx @@ -21,15 +21,9 @@ export function ThemeToggle() { - setTheme("light")}> - Light - - setTheme("dark")}> - Dark - - setTheme("system")}> - System - + setTheme("light")}>Light + setTheme("dark")}>Dark + setTheme("system")}>System ); diff --git a/packages/web/.oxfmtrc.json b/packages/web/.oxfmtrc.json new file mode 100644 index 000000000..ff601bd11 --- /dev/null +++ b/packages/web/.oxfmtrc.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxfmt/configuration_schema.json", + "useTabs": false, + "tabWidth": 2, + "printWidth": 80, + "ignorePatterns": [ + "node_modules", + "dist", + "protobufs", + "**/locales/*-*/*.json", + "**/packages/ui/" + ] +} diff --git a/packages/web/.oxlintrc.json b/packages/web/.oxlintrc.json new file mode 100644 index 000000000..2792d77ee --- /dev/null +++ b/packages/web/.oxlintrc.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json", + "plugins": ["react", "react-perf", "jsx-a11y", "typescript", "import"], + "rules": { + "no-debugger": "error", + "@typescript-eslint/no-explicit-any": "error", + "@typescript-eslint/no-unused-vars": "error", + "import/no-unused-modules": "error" + }, + "ignorePatterns": [ + "node_modules", + "dist", + "protobufs", + "**/locales/*-*/*.json", + "**/packages/ui/" + ], + "overrides": [ + { + "files": ["**/*.test.ts", "**/*.test.tsx", "**/__tests__/**/*.{ts,tsx}"], + "rules": { + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-non-null-assertion": "off" + } + } + ] +} diff --git a/packages/web/package.json b/packages/web/package.json index 7ad289ac3..d55c5aaef 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -17,8 +17,12 @@ "setup:certs": "mkcert localhost 127.0.0.1 ::1", "build": "vite build", "build:analyze": "BUNDLE_ANALYZE=true pnpm run build", - "check": "biome check src/", - "check:fix": "biome check --write src/", + "lint": "oxlint", + "lint:fix": "oxlint --fix", + "format": "oxfmt --check .", + "format:fix": "oxfmt .", + "check": "pnpm lint && pnpm format", + "check:fix": "pnpm lint:fix && pnpm format:fix", "dev": "vite", "dev:https": "USE_HTTPS=true vite", "test": "vitest", @@ -111,6 +115,8 @@ "tar": "^7.5.7", "testing-library": "^0.0.2", "typescript": "^5.9.3", - "vitest": "^3.2.4" + "vitest": "^3.2.4", + "oxfmt": "^0.16.0", + "oxlint": "^1.41.0" } } diff --git a/packages/web/src/App.tsx b/packages/web/src/App.tsx index 5fc115c6f..0f97c3284 100644 --- a/packages/web/src/App.tsx +++ b/packages/web/src/App.tsx @@ -59,6 +59,5 @@ export function App() {

    - // - ); + ); // } diff --git a/packages/web/src/DeviceWrapper.tsx b/packages/web/src/DeviceWrapper.tsx index d9ee3def3..05ae6c57b 100644 --- a/packages/web/src/DeviceWrapper.tsx +++ b/packages/web/src/DeviceWrapper.tsx @@ -8,8 +8,6 @@ export interface DeviceWrapperProps { export const DeviceWrapper = ({ children, deviceId }: DeviceWrapperProps) => { return ( - - {children} - + {children} ); }; diff --git a/packages/web/src/__mocks__/components/UI/Checkbox.tsx b/packages/web/src/__mocks__/components/UI/Checkbox.tsx index a194124dd..62150e991 100644 --- a/packages/web/src/__mocks__/components/UI/Checkbox.tsx +++ b/packages/web/src/__mocks__/components/UI/Checkbox.tsx @@ -1,21 +1,7 @@ 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/packages/web/src/components/BatteryStatus.tsx b/packages/web/src/components/BatteryStatus.tsx index 9f541ce64..71b64c62a 100644 --- a/packages/web/src/components/BatteryStatus.tsx +++ b/packages/web/src/components/BatteryStatus.tsx @@ -1,9 +1,4 @@ -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"; @@ -43,10 +38,7 @@ 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/packages/web/src/components/CommandPalette/index.tsx b/packages/web/src/components/CommandPalette/index.tsx index afda6b5c3..6098abb6e 100644 --- a/packages/web/src/components/CommandPalette/index.tsx +++ b/packages/web/src/components/CommandPalette/index.tsx @@ -8,12 +8,7 @@ import { CommandList, } from "@components/UI/Command.tsx"; import { usePinnedItems } from "@core/hooks/usePinnedItems.ts"; -import { - useAppStore, - useDevice, - useDeviceStore, - useNodeDB, -} from "@core/stores"; +import { useAppStore, useDevice, useDeviceStore, useNodeDB } from "@core/stores"; import { cn } from "@core/utils/cn.ts"; import { useNavigate } from "@tanstack/react-router"; import { useCommandState } from "cmdk"; @@ -62,12 +57,8 @@ 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 { getNode } = useNodeDB(); @@ -123,9 +114,7 @@ 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); @@ -268,10 +257,7 @@ export const CommandPalette = () => { }, [setCommandPaletteOpen]); return ( - + {t("emptyState")} diff --git a/packages/web/src/components/DeviceInfoPanel.tsx b/packages/web/src/components/DeviceInfoPanel.tsx index f962a382b..5f3325e28 100644 --- a/packages/web/src/components/DeviceInfoPanel.tsx +++ b/packages/web/src/components/DeviceInfoPanel.tsx @@ -107,9 +107,7 @@ 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", @@ -207,9 +205,7 @@ 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}
    ); } @@ -267,10 +238,8 @@ 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(() => { @@ -299,9 +268,7 @@ 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; } @@ -336,9 +303,7 @@ 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; } @@ -370,9 +335,7 @@ 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); @@ -397,9 +360,7 @@ 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]); @@ -410,9 +371,7 @@ 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")} )} @@ -477,9 +432,7 @@ export default function AddConnectionDialog({ {state.testStatus === "failure" && (
    - {t( - "addConnection.httpConnection.connectionTest.notReachable", - )} + {t("addConnection.httpConnection.connectionTest.notReachable")}
    )}
    @@ -489,8 +442,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(), @@ -503,12 +456,8 @@ export default function AddConnectionDialog({ <> - + ), validate: () => state.name.trim().length > 0 && !!state.btSelected, @@ -543,12 +489,8 @@ 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(), @@ -598,8 +537,7 @@ 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; } @@ -608,8 +546,7 @@ 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); }; @@ -625,9 +562,7 @@ 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 }) => ( @@ -647,20 +582,14 @@ 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/packages/web/src/components/Dialog/AddConnectionDialog/validation.ts b/packages/web/src/components/Dialog/AddConnectionDialog/validation.ts index e9d278b4c..a92848c1d 100644 --- a/packages/web/src/components/Dialog/AddConnectionDialog/validation.ts +++ b/packages/web/src/components/Dialog/AddConnectionDialog/validation.ts @@ -28,18 +28,13 @@ 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/packages/web/src/components/Dialog/ClearAllStoresDialog/ClearAllStoresDialog.test.tsx b/packages/web/src/components/Dialog/ClearAllStoresDialog/ClearAllStoresDialog.test.tsx index 50e6af3d4..bf05c3292 100644 --- a/packages/web/src/components/Dialog/ClearAllStoresDialog/ClearAllStoresDialog.test.tsx +++ b/packages/web/src/components/Dialog/ClearAllStoresDialog/ClearAllStoresDialog.test.tsx @@ -47,9 +47,7 @@ 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/packages/web/src/components/Dialog/ClearAllStoresDialog/ClearAllStoresDialog.tsx b/packages/web/src/components/Dialog/ClearAllStoresDialog/ClearAllStoresDialog.tsx index 731e0e8cb..78d049ddf 100644 --- a/packages/web/src/components/Dialog/ClearAllStoresDialog/ClearAllStoresDialog.tsx +++ b/packages/web/src/components/Dialog/ClearAllStoresDialog/ClearAllStoresDialog.tsx @@ -7,10 +7,7 @@ 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/packages/web/src/components/Dialog/ClientNotificationDialog/ClientNotificationDialog.tsx b/packages/web/src/components/Dialog/ClientNotificationDialog/ClientNotificationDialog.tsx index 6d50dbbc0..97cda7ca1 100644 --- a/packages/web/src/components/Dialog/ClientNotificationDialog/ClientNotificationDialog.tsx +++ b/packages/web/src/components/Dialog/ClientNotificationDialog/ClientNotificationDialog.tsx @@ -14,10 +14,7 @@ export interface ClientNotificationDialogProps { onOpenChange: (open: boolean) => void; } -export const ClientNotificationDialog = ({ - open, - onOpenChange, -}: ClientNotificationDialogProps) => { +export const ClientNotificationDialog = ({ open, onOpenChange }: ClientNotificationDialogProps) => { const { t } = useTranslation("dialog"); const { getClientNotification, removeClientNotification } = useDevice(); diff --git a/packages/web/src/components/Dialog/DeleteMessagesDialog/DeleteMessagesDialog.test.tsx b/packages/web/src/components/Dialog/DeleteMessagesDialog/DeleteMessagesDialog.test.tsx index 8486c8b37..6740bb548 100644 --- a/packages/web/src/components/Dialog/DeleteMessagesDialog/DeleteMessagesDialog.test.tsx +++ b/packages/web/src/components/Dialog/DeleteMessagesDialog/DeleteMessagesDialog.test.tsx @@ -46,19 +46,13 @@ 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/packages/web/src/components/Dialog/DeleteMessagesDialog/DeleteMessagesDialog.tsx b/packages/web/src/components/Dialog/DeleteMessagesDialog/DeleteMessagesDialog.tsx index d73d54891..d061dd3dc 100644 --- a/packages/web/src/components/Dialog/DeleteMessagesDialog/DeleteMessagesDialog.tsx +++ b/packages/web/src/components/Dialog/DeleteMessagesDialog/DeleteMessagesDialog.tsx @@ -8,10 +8,7 @@ export interface DeleteMessagesDialogProps { onOpenChange: (open: boolean) => void; } -export const DeleteMessagesDialog = ({ - open, - onOpenChange, -}: DeleteMessagesDialogProps) => { +export const DeleteMessagesDialog = ({ open, onOpenChange }: DeleteMessagesDialogProps) => { const { t } = useTranslation("dialog"); const messageStore = useMessages(); diff --git a/packages/web/src/components/Dialog/FactoryResetConfigDialog/FactoryResetConfigDialog.test.tsx b/packages/web/src/components/Dialog/FactoryResetConfigDialog/FactoryResetConfigDialog.test.tsx index ec205e93c..678f2a5a8 100644 --- a/packages/web/src/components/Dialog/FactoryResetConfigDialog/FactoryResetConfigDialog.test.tsx +++ b/packages/web/src/components/Dialog/FactoryResetConfigDialog/FactoryResetConfigDialog.test.tsx @@ -33,9 +33,7 @@ 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/packages/web/src/components/Dialog/FactoryResetConfigDialog/FactoryResetConfigDialog.tsx b/packages/web/src/components/Dialog/FactoryResetConfigDialog/FactoryResetConfigDialog.tsx index b350447d2..21d8061d6 100644 --- a/packages/web/src/components/Dialog/FactoryResetConfigDialog/FactoryResetConfigDialog.tsx +++ b/packages/web/src/components/Dialog/FactoryResetConfigDialog/FactoryResetConfigDialog.tsx @@ -8,10 +8,7 @@ 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/packages/web/src/components/Dialog/FactoryResetDeviceDialog/FactoryResetDeviceDialog.test.tsx b/packages/web/src/components/Dialog/FactoryResetDeviceDialog/FactoryResetDeviceDialog.test.tsx index 01492e02f..9e0354e80 100644 --- a/packages/web/src/components/Dialog/FactoryResetDeviceDialog/FactoryResetDeviceDialog.test.tsx +++ b/packages/web/src/components/Dialog/FactoryResetDeviceDialog/FactoryResetDeviceDialog.test.tsx @@ -61,9 +61,7 @@ describe("FactoryResetDeviceDialog", () => { ); render(); - fireEvent.click( - screen.getByRole("button", { name: "Factory Reset Device" }), - ); + fireEvent.click(screen.getByRole("button", { name: "Factory Reset Device" })); // Called immediately expect(mockFactoryResetDevice).toHaveBeenCalledTimes(1); diff --git a/packages/web/src/components/Dialog/FactoryResetDeviceDialog/FactoryResetDeviceDialog.tsx b/packages/web/src/components/Dialog/FactoryResetDeviceDialog/FactoryResetDeviceDialog.tsx index e171c8ba9..bf03e9692 100644 --- a/packages/web/src/components/Dialog/FactoryResetDeviceDialog/FactoryResetDeviceDialog.tsx +++ b/packages/web/src/components/Dialog/FactoryResetDeviceDialog/FactoryResetDeviceDialog.tsx @@ -1,10 +1,5 @@ import { toast } from "@core/hooks/useToast.ts"; -import { - useDevice, - useDeviceStore, - useMessageStore, - useNodeDBStore, -} from "@core/stores"; +import { useDevice, useDeviceStore, useMessageStore, useNodeDBStore } from "@core/stores"; import { useTranslation } from "react-i18next"; import { DialogWrapper } from "../DialogWrapper.tsx"; @@ -13,10 +8,7 @@ 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/packages/web/src/components/Dialog/ImportDialog.tsx b/packages/web/src/components/Dialog/ImportDialog.tsx index 71affa792..bfbcf6f9d 100644 --- a/packages/web/src/components/Dialog/ImportDialog.tsx +++ b/packages/web/src/components/Dialog/ImportDialog.tsx @@ -47,8 +47,7 @@ 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,11 +55,7 @@ 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, "/"); @@ -75,43 +70,35 @@ export const ImportDialog = ({ open, onOpenChange }: ImportDialogProps) => { setImportIndex(newImportChannelArray); setUpdateConfig(newChannelSet?.loraConfig !== undefined); setValidUrl(true); - } catch (_error) { + } catch { setValidUrl(false); setChannelSet(undefined); } }, [importDialogInput, t]); const apply = () => { - 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, - }); - - if ( - !deepCompareConfig( - channels.get(importIndex[index] ?? 0), - payload, - true, - ) - ) { - setChange( - { type: "channel", index: importIndex[index] ?? 0 }, - payload, - channels.get(importIndex[index] ?? 0), - ); - } - }, - ); + 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, + }); + + if (!deepCompareConfig(channels.get(importIndex[index] ?? 0), payload, true)) { + setChange( + { type: "channel", index: importIndex[index] ?? 0 }, + payload, + channels.get(importIndex[index] ?? 0), + ); + } + }); if (channelSet?.loraConfig && updateConfig) { const payload = { @@ -146,23 +133,14 @@ export const ImportDialog = ({ open, onOpenChange }: ImportDialogProps) => { {t("import.title")} - , br:
    }} - /> + , br:
    }} />
    { setImportDialogInput(e.target.value); }} @@ -191,10 +169,7 @@ export const ImportDialog = ({ open, onOpenChange }: ImportDialogProps) => { {t("import.channelSlot")}
    {channelSet?.settings.map((channel, index) => ( -
    +
    diff --git a/packages/web/src/components/Dialog/LocationResponseDialog.tsx b/packages/web/src/components/Dialog/LocationResponseDialog.tsx index 0fa9aff91..b313cbd20 100644 --- a/packages/web/src/components/Dialog/LocationResponseDialog.tsx +++ b/packages/web/src/components/Dialog/LocationResponseDialog.tsx @@ -27,13 +27,10 @@ export const LocationResponseDialog = ({ const from = getNode(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; @@ -70,23 +67,18 @@ 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/packages/web/src/components/Dialog/ManagedModeDialog.tsx b/packages/web/src/components/Dialog/ManagedModeDialog.tsx index 275ccfa5e..da94e46c4 100644 --- a/packages/web/src/components/Dialog/ManagedModeDialog.tsx +++ b/packages/web/src/components/Dialog/ManagedModeDialog.tsx @@ -18,11 +18,7 @@ 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); @@ -47,9 +43,7 @@ export const ManagedModeDialog = ({ onChange={() => setConfirmState(!confirmState)} name="confirmUnderstanding" > -

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

    +

    {t("managedMode.confirmUnderstanding")}

    diff --git a/packages/web/src/components/Dialog/NewDeviceDialog.tsx b/packages/web/src/components/Dialog/NewDeviceDialog.tsx index e6170123a..2d1021786 100644 --- a/packages/web/src/components/Dialog/NewDeviceDialog.tsx +++ b/packages/web/src/components/Dialog/NewDeviceDialog.tsx @@ -8,12 +8,7 @@ import { DialogHeader, DialogTitle, } from "@components/UI/Dialog.tsx"; -import { - Tabs, - TabsContent, - TabsList, - TabsTrigger, -} from "@components/UI/Tabs.tsx"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@components/UI/Tabs.tsx"; import { type BrowserFeature, useBrowserFeatureDetection, @@ -63,9 +58,7 @@ const ErrorMessage = ({ missingFeatures, tabId }: FeatureErrorProps) => { return null; } - const browserFeatures = missingFeatures.filter( - (feature) => feature !== "Secure Context", - ); + const browserFeatures = missingFeatures.filter((feature) => feature !== "Secure Context"); const needsSecureContext = missingFeatures.includes("Secure Context"); const needsFeature = @@ -133,17 +126,13 @@ export const NewDeviceDialog = ({ open, onOpenChange }: NewDeviceProps) => { id: "BLE", label: t("newDeviceDialog.tabBluetooth"), element: BLE, - isDisabled: - unsupported.includes("Web Bluetooth") || - unsupported.includes("Secure Context"), + isDisabled: unsupported.includes("Web Bluetooth") || unsupported.includes("Secure Context"), }, { id: "Serial", label: t("newDeviceDialog.tabSerial"), element: Serial, - isDisabled: - unsupported.includes("Web Serial") || - unsupported.includes("Secure Context"), + isDisabled: unsupported.includes("Web Serial") || unsupported.includes("Secure Context"), }, ]; diff --git a/packages/web/src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx b/packages/web/src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx index e2eaa0a7d..16a292f46 100644 --- a/packages/web/src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx +++ b/packages/web/src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx @@ -50,10 +50,7 @@ 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 { getNode } = useNodeDB(); @@ -64,12 +61,8 @@ export const NodeDetailsDialog = ({ const node = getNode(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) { @@ -168,8 +161,7 @@ export const NodeDetailsDialog = ({ 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", @@ -202,27 +194,17 @@ export const NodeDetailsDialog = ({
    - -
    @@ -243,9 +225,7 @@ export const NodeDetailsDialog = ({ - {isIgnoredState - ? t("nodeDetails.unignoreNode") - : t("nodeDetails.ignoreNode")} + {isIgnoredState ? t("nodeDetails.unignoreNode") : t("nodeDetails.ignoreNode")} @@ -275,9 +255,7 @@ export const NodeDetailsDialog = ({
    -

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

    +

    {t("nodeDetails.details")}

    @@ -291,9 +269,10 @@ export const NodeDetailsDialog = ({ @@ -312,44 +291,35 @@ export const NodeDetailsDialog = ({ - +
    {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")}
                             
    @@ -368,9 +338,7 @@ export const NodeDetailsDialog = ({
    -

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

    +

    {t("nodeDetails.position")}

    {node.position ? ( @@ -387,8 +355,7 @@ export const NodeDetailsDialog = ({ target="_blank" rel="noreferrer" > - {node.position.latitudeI / 1e7},{" "} - {node.position.longitudeI / 1e7} + {node.position.latitudeI / 1e7}, {node.position.longitudeI / 1e7} @@ -407,11 +374,7 @@ export const NodeDetailsDialog = ({ ) : (

    {t("unknown.longName")}

    )} - @@ -436,9 +399,7 @@ export const NodeDetailsDialog = ({ )} @@ -457,9 +418,7 @@ export const NodeDetailsDialog = ({

    -
    -                      {JSON.stringify(node, null, 2)}
    -                    
    +
    {JSON.stringify(node, null, 2)}
    diff --git a/packages/web/src/components/Dialog/PKIBackupDialog.tsx b/packages/web/src/components/Dialog/PKIBackupDialog.tsx index 2c19721e2..78ed2271c 100644 --- a/packages/web/src/components/Dialog/PKIBackupDialog.tsx +++ b/packages/web/src/components/Dialog/PKIBackupDialog.tsx @@ -19,25 +19,19 @@ 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 { getMyNode } = useNodeDB(); 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); @@ -133,9 +127,7 @@ export const PkiBackupDialog = ({ {t("pkiBackup.title")} {t("pkiBackup.secureBackup")} - - {t("pkiBackup.loseKeysWarning")} - + {t("pkiBackup.loseKeysWarning")} diff --git a/packages/web/src/components/Dialog/PkiRegenerateDialog.tsx b/packages/web/src/components/Dialog/PkiRegenerateDialog.tsx index 8a07969de..7caada0e1 100644 --- a/packages/web/src/components/Dialog/PkiRegenerateDialog.tsx +++ b/packages/web/src/components/Dialog/PkiRegenerateDialog.tsx @@ -46,11 +46,7 @@ export const PkiRegenerateDialog = ({ {dialogText.description} - diff --git a/packages/web/src/components/Dialog/QRDialog.tsx b/packages/web/src/components/Dialog/QRDialog.tsx index b882c2850..acd084421 100644 --- a/packages/web/src/components/Dialog/QRDialog.tsx +++ b/packages/web/src/components/Dialog/QRDialog.tsx @@ -24,12 +24,7 @@ export interface QRDialogProps { channels: Map; } -export const QRDialog = ({ - open, - onOpenChange, - loraConfig, - channels, -}: QRDialogProps) => { +export const QRDialog = ({ open, onOpenChange, loraConfig, channels }: QRDialogProps) => { const { t } = useTranslation("dialog"); const [selectedChannels, setSelectedChannels] = useState([0]); const [qrCodeUrl, setQrCodeUrl] = useState(""); @@ -49,16 +44,12 @@ export const QRDialog = ({ 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 ( @@ -89,14 +80,9 @@ export const QRDialog = ({ 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/packages/web/src/components/Dialog/RebootDialog.test.tsx b/packages/web/src/components/Dialog/RebootDialog.test.tsx index edf4afcea..c90a7e825 100644 --- a/packages/web/src/components/Dialog/RebootDialog.test.tsx +++ b/packages/web/src/components/Dialog/RebootDialog.test.tsx @@ -1,10 +1,5 @@ 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"; @@ -54,16 +49,10 @@ 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, }; }); @@ -81,12 +70,8 @@ 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", () => { @@ -199,8 +184,6 @@ 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/packages/web/src/components/Dialog/RebootDialog.tsx b/packages/web/src/components/Dialog/RebootDialog.tsx index b6e4ed6f4..70ae70152 100644 --- a/packages/web/src/components/Dialog/RebootDialog.tsx +++ b/packages/web/src/components/Dialog/RebootDialog.tsx @@ -96,11 +96,7 @@ export const RebootDialog = ({ open, onOpenChange }: RebootDialogProps) => { {!isScheduled ? ( <> - setIsOTA(checked)} - className="px-2" - > + setIsOTA(checked)} className="px-2"> {t("reboot.ota")}
    @@ -137,9 +133,7 @@ export const RebootDialog = ({ open, onOpenChange }: RebootDialogProps) => { ) : (
    - +
    -
    diff --git a/packages/web/src/components/Dialog/RemoveNodeDialog.tsx b/packages/web/src/components/Dialog/RemoveNodeDialog.tsx index c51a5e468..36029ff2f 100644 --- a/packages/web/src/components/Dialog/RemoveNodeDialog.tsx +++ b/packages/web/src/components/Dialog/RemoveNodeDialog.tsx @@ -8,10 +8,7 @@ export interface RemoveNodeDialogProps { onOpenChange: (open: boolean) => void; } -export const RemoveNodeDialog = ({ - open, - onOpenChange, -}: RemoveNodeDialogProps) => { +export const RemoveNodeDialog = ({ open, onOpenChange }: RemoveNodeDialogProps) => { const { t } = useTranslation("dialog"); const { connection } = useDevice(); const { getNode, removeNode } = useNodeDB(); diff --git a/packages/web/src/components/Dialog/ResetNodeDbDialog/ResetNodeDbDialog.test.tsx b/packages/web/src/components/Dialog/ResetNodeDbDialog/ResetNodeDbDialog.test.tsx index 0898578d7..33b9b7ace 100644 --- a/packages/web/src/components/Dialog/ResetNodeDbDialog/ResetNodeDbDialog.test.tsx +++ b/packages/web/src/components/Dialog/ResetNodeDbDialog/ResetNodeDbDialog.test.tsx @@ -48,9 +48,7 @@ describe("ResetNodeDbDialog", () => { ); render(); - fireEvent.click( - screen.getByRole("button", { name: "Reset Node Database" }), - ); + fireEvent.click(screen.getByRole("button", { name: "Reset Node Database" })); // Called immediately expect(mockResetNodes).toHaveBeenCalledTimes(1); diff --git a/packages/web/src/components/Dialog/ResetNodeDbDialog/ResetNodeDbDialog.tsx b/packages/web/src/components/Dialog/ResetNodeDbDialog/ResetNodeDbDialog.tsx index def4ff4ff..9b274369c 100644 --- a/packages/web/src/components/Dialog/ResetNodeDbDialog/ResetNodeDbDialog.tsx +++ b/packages/web/src/components/Dialog/ResetNodeDbDialog/ResetNodeDbDialog.tsx @@ -8,10 +8,7 @@ export interface ResetNodeDbDialogProps { onOpenChange: (open: boolean) => void; } -export const ResetNodeDbDialog = ({ - open, - onOpenChange, -}: ResetNodeDbDialogProps) => { +export const ResetNodeDbDialog = ({ open, onOpenChange }: ResetNodeDbDialogProps) => { const { t } = useTranslation("dialog"); const { connection } = useDevice(); const { removeAllNodeErrors, removeAllNodes } = useNodeDB(); diff --git a/packages/web/src/components/Dialog/TracerouteResponseDialog.tsx b/packages/web/src/components/Dialog/TracerouteResponseDialog.tsx index cf27fe2d2..387c8fd4b 100644 --- a/packages/web/src/components/Dialog/TracerouteResponseDialog.tsx +++ b/packages/web/src/components/Dialog/TracerouteResponseDialog.tsx @@ -32,13 +32,10 @@ export const TracerouteResponseDialog = ({ const snrBack = (traceroute?.data.snrBack ?? []).map((snr) => snr / 4); const from = getNode(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 = getNode(traceroute?.from ?? 0); // The destination of the traceroute = the "from" node of the mesh packet diff --git a/packages/web/src/components/Dialog/UnsafeRolesDialog/UnsafeRolesDialog.tsx b/packages/web/src/components/Dialog/UnsafeRolesDialog/UnsafeRolesDialog.tsx index b3978c199..6d5370226 100644 --- a/packages/web/src/components/Dialog/UnsafeRolesDialog/UnsafeRolesDialog.tsx +++ b/packages/web/src/components/Dialog/UnsafeRolesDialog/UnsafeRolesDialog.tsx @@ -20,16 +20,12 @@ 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/"; @@ -63,17 +59,11 @@ export const UnsafeRolesDialog = ({ onChange={() => setConfirmState(!confirmState)} name="confirmUnderstanding" > - - {t("unsafeRoles.confirmUnderstanding")} - + {t("unsafeRoles.confirmUnderstanding")}
    - @@ -77,16 +63,9 @@ export const Channels = ({ onFormInit }: ConfigProps) => { {allChannels.map((channel) => ( - + }> - + ))} diff --git a/packages/web/src/components/PageComponents/Connections/ConnectionStatusBadge.tsx b/packages/web/src/components/PageComponents/Connections/ConnectionStatusBadge.tsx index cc42a258d..221335e43 100644 --- a/packages/web/src/components/PageComponents/Connections/ConnectionStatusBadge.tsx +++ b/packages/web/src/components/PageComponents/Connections/ConnectionStatusBadge.tsx @@ -1,11 +1,7 @@ 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; @@ -30,13 +26,8 @@ export function ConnectionStatusBadge({ } return ( ); } diff --git a/packages/web/src/components/PageComponents/Map/Layers/HeatmapLayer.tsx b/packages/web/src/components/PageComponents/Map/Layers/HeatmapLayer.tsx index e4ba7467d..f25e7b50d 100644 --- a/packages/web/src/components/PageComponents/Map/Layers/HeatmapLayer.tsx +++ b/packages/web/src/components/PageComponents/Map/Layers/HeatmapLayer.tsx @@ -13,11 +13,7 @@ export interface HeatmapLayerProps { mode: HeatmapMode; } -export const HeatmapLayer = ({ - id, - filteredNodes, - mode, -}: HeatmapLayerProps) => { +export const HeatmapLayer = ({ id, filteredNodes, mode }: HeatmapLayerProps) => { const data: FeatureCollection = useMemo(() => { const features: Feature[] = filteredNodes .filter((node) => hasPos(node.position)) @@ -45,9 +41,7 @@ export const HeatmapLayer = ({ 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 @@ -69,17 +63,7 @@ export const HeatmapLayer = ({ 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/packages/web/src/components/PageComponents/Map/Layers/NodesLayer.tsx b/packages/web/src/components/PageComponents/Map/Layers/NodesLayer.tsx index d4f22bc5f..88d9365c3 100644 --- a/packages/web/src/components/PageComponents/Map/Layers/NodesLayer.tsx +++ b/packages/web/src/components/PageComponents/Map/Layers/NodesLayer.tsx @@ -50,8 +50,7 @@ export const NodesLayer = ({ () => popupState?.type !== "node" ? undefined - : (filteredNodes.find((node) => node.num === popupState.num) ?? - undefined), + : (filteredNodes.find((node) => node.num === popupState.num) ?? undefined), [popupState, filteredNodes], ); @@ -78,9 +77,7 @@ 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/packages/web/src/components/PageComponents/Map/Layers/PrecisionLayer.tsx b/packages/web/src/components/PageComponents/Map/Layers/PrecisionLayer.tsx index 22b8d0241..14a0da56d 100644 --- a/packages/web/src/components/PageComponents/Map/Layers/PrecisionLayer.tsx +++ b/packages/web/src/components/PageComponents/Map/Layers/PrecisionLayer.tsx @@ -36,10 +36,7 @@ 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); @@ -63,9 +60,7 @@ 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 }) => { @@ -96,13 +91,7 @@ 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]; @@ -180,9 +176,7 @@ 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) => { @@ -246,12 +240,7 @@ function generateNeighborLines( 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) { @@ -301,9 +290,7 @@ 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 }; }), }, @@ -325,10 +312,7 @@ export const SNRLayer = ({ })) : []; - const featureCollection = generateNeighborLines([ - ...remotePairs, - ...directPairs, - ]); + const featureCollection = generateNeighborLines([...remotePairs, ...directPairs]); return ( diff --git a/packages/web/src/components/PageComponents/Map/Layers/WaypointLayer.tsx b/packages/web/src/components/PageComponents/Map/Layers/WaypointLayer.tsx index 9c7479879..ead76e733 100644 --- a/packages/web/src/components/PageComponents/Map/Layers/WaypointLayer.tsx +++ b/packages/web/src/components/PageComponents/Map/Layers/WaypointLayer.tsx @@ -82,11 +82,7 @@ export const WaypointLayer = ({ offset={[0, 25]} onClose={() => setPopupState(undefined)} > - {}} - /> + {}} /> , ); } diff --git a/packages/web/src/components/PageComponents/Map/Markers/StackBadge.tsx b/packages/web/src/components/PageComponents/Map/Markers/StackBadge.tsx index 8ddc0faa5..405ad1ec5 100644 --- a/packages/web/src/components/PageComponents/Map/Markers/StackBadge.tsx +++ b/packages/web/src/components/PageComponents/Map/Markers/StackBadge.tsx @@ -8,13 +8,7 @@ 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 ( { 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") } /> @@ -127,15 +118,12 @@ export const NodeDetail = ({ node }: NodeDetailProps) => {
    {node.lastHeard > 0 && (
    - {t("nodeDetail.status.heard")}{" "} - + {t("nodeDetail.status.heard")}
    )}
    @@ -153,13 +141,9 @@ export const NodeDetail = ({ node }: NodeDetailProps) => {
    - {Number.isNaN(node.hopsAway) - ? t("unit.hopsAway.unknown") - : node.hopsAway} -
    -
    - {node.hopsAway === 1 ? t("unit.hop.one") : t("unit.hop.plural")} + {Number.isNaN(node.hopsAway) ? t("unit.hopsAway.unknown") : node.hopsAway}
    +
    {node.hopsAway === 1 ? t("unit.hop.one") : t("unit.hop.plural")}
    {node.position?.altitude && (
    @@ -182,17 +166,13 @@ 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/packages/web/src/components/PageComponents/Map/Popups/WaypointDetail.tsx b/packages/web/src/components/PageComponents/Map/Popups/WaypointDetail.tsx index 20c3c0eef..1ffcf5125 100644 --- a/packages/web/src/components/PageComponents/Map/Popups/WaypointDetail.tsx +++ b/packages/web/src/components/PageComponents/Map/Popups/WaypointDetail.tsx @@ -2,12 +2,7 @@ import { TimeAgo } from "@components/generic/TimeAgo"; import { Separator } from "@components/UI/Separator.tsx"; import type { WaypointWithMetadata } from "@core/stores"; import { useNodeDB } 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/core"; import { ClockFadingIcon, @@ -88,14 +83,10 @@ export const WaypointDetail = ({ waypoint, myNode }: WaypointDetailProps) => {
    - - {t("waypointDetail.createdDate")} - + {t("waypointDetail.createdDate")}
    -
    @@ -109,9 +100,7 @@ export const WaypointDetail = ({ waypoint, myNode }: WaypointDetailProps) => { {t("waypointDetail.updated")}
    -
    @@ -143,9 +132,7 @@ 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")}
    @@ -179,8 +166,7 @@ export const WaypointDetail = ({ waypoint, myNode }: WaypointDetailProps) => { {t("waypointDetail.lockedTo")}
    - {getNode(waypoint.lockedTo)?.user?.longName ?? - t("unknown.longName")} + {getNode(waypoint.lockedTo)?.user?.longName ?? t("unknown.longName")}
    )} diff --git a/packages/web/src/components/PageComponents/Map/Tools/MapLayerTool.tsx b/packages/web/src/components/PageComponents/Map/Tools/MapLayerTool.tsx index 3b9b1cef7..dd550dd4c 100644 --- a/packages/web/src/components/PageComponents/Map/Tools/MapLayerTool.tsx +++ b/packages/web/src/components/PageComponents/Map/Tools/MapLayerTool.tsx @@ -1,10 +1,6 @@ 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"; @@ -44,12 +40,7 @@ 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 && (
    @@ -162,9 +151,7 @@ export function MapLayerTool({ checked={heatmapMode === "density"} onChange={() => setHeatmapMode("density")} /> - - {t("layerTool.density")} - + {t("layerTool.density")}
    {t("nodeDetails.uptime")} - +