From 0c32251c346516066af70cbd411e9b12001167f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kiss=20R=C3=B3bert?= Date: Mon, 13 Jan 2025 21:13:12 +0100 Subject: [PATCH] feat: delete device pairings --- .../uhk-agent/src/services/device.service.ts | 74 +++++++++++++++++++ .../uhk-common/src/models/ipc-response.ts | 4 + packages/uhk-common/src/util/ipcEvents.ts | 4 + packages/uhk-usb/src/constants.ts | 1 + packages/uhk-usb/src/uhk-operations.ts | 6 ++ .../host-connections.component.html | 49 ++++++++---- .../host-connections.component.ts | 46 ++++++++---- .../models/erase-ble-settings-button-state.ts | 5 ++ packages/uhk-web/src/app/models/index.ts | 1 + .../app/services/device-renderer.service.ts | 19 +++++ .../uhk-web/src/app/store/actions/device.ts | 28 +++++++ .../uhk-web/src/app/store/effects/device.ts | 69 +++++++++++++++++ .../src/app/store/effects/user-config.ts | 6 +- packages/uhk-web/src/app/store/index.ts | 14 ++++ .../uhk-web/src/app/store/reducers/device.ts | 47 +++++++++++- .../store/reducers/dongle-pairing.reducer.ts | 7 ++ 16 files changed, 351 insertions(+), 29 deletions(-) create mode 100644 packages/uhk-web/src/app/models/erase-ble-settings-button-state.ts diff --git a/packages/uhk-agent/src/services/device.service.ts b/packages/uhk-agent/src/services/device.service.ts index 6ef5893300a..5bd764f1fb3 100644 --- a/packages/uhk-agent/src/services/device.service.ts +++ b/packages/uhk-agent/src/services/device.service.ts @@ -3,6 +3,7 @@ import { emptyDir } from 'fs-extra'; import { cloneDeep, isEqual } from 'lodash'; import os from 'os'; import { + AreBleAddressesPairedIpcResponse, ALL_UHK_DEVICES, BackupUserConfigurationInfo, ChangeKeyboardLayoutIpcResponse, @@ -126,6 +127,15 @@ export class DeviceService { this.logService.misc('[DeviceService] Cannot query udev info:', error); }); + ipcMain.on(IpcEvents.device.areBleAddressesPaired, (...args: any[]) => { + this.queueManager.add({ + method: this.areBleAddressesPaired, + bind: this, + params: args, + asynchronous: true + }); + }); + ipcMain.on(IpcEvents.device.changeKeyboardLayout, (...args: any[]) => { this.queueManager.add({ method: this.changeKeyboardLayout, @@ -144,6 +154,15 @@ export class DeviceService { }); }); + ipcMain.on(IpcEvents.device.eraseBleSettings, (...args: any[]) => { + this.queueManager.add({ + method: this.eraseBleSettings, + bind: this, + params: args, + asynchronous: true + }); + }); + ipcMain.on(IpcEvents.device.toggleI2cDebugging, this.toggleI2cDebugging.bind(this)); ipcMain.on(IpcEvents.device.saveUserConfiguration, (...args: any[]) => { @@ -236,6 +255,37 @@ export class DeviceService { logService.misc('[DeviceService] init success'); } + public async areBleAddressesPaired(event: Electron.IpcMainEvent, args: Array): Promise { + this.logService.misc('[DeviceService] Check BLE Addresses are paired'); + + const response: AreBleAddressesPairedIpcResponse = { + success: true, + addresses: {}, + }; + + try { + await this.stopPollUhkDevice(); + + const addresses: string[] = args[0]; + for (const address of addresses) { + response.addresses[address] = await this.device.isPairedWith(convertBleStringToNumberArray(address)); + } + } + catch (error) { + this.logService.error('[DeviceService] Check BLE Addresses pairing failed'); + response.success = false; + response.error = { + message: error.message, + }; + } + finally { + this.savedState = undefined; + this.startPollUhkDevice(); + } + + event.sender.send(IpcEvents.device.areBleAddressesPairedReply, response); + } + /** * Return with the actual UserConfiguration from UHK Device * @returns {Promise} @@ -770,6 +820,30 @@ export class DeviceService { } } + public async eraseBleSettings(event: Electron.IpcMainEvent): Promise { + this.logService.misc('[DeviceService] erase BLE Settings'); + const response: IpcResponse = { + success: true, + }; + try { + await this.stopPollUhkDevice(); + await this.operations.eraseBleSettings(); + this.logService.misc('[DeviceService] erase BLE settings success'); + } + catch(error) { + this.logService.error('[DeviceService] erase BLE settings failed', error); + response.success = false; + response.error = { + message: error.message, + }; + } + finally { + this.startPollUhkDevice(); + } + + event.sender.send(IpcEvents.device.eraseBleSettingsReply, response); + } + public async startDonglePairing(event: Electron.IpcMainEvent): Promise { this.logService.misc('[DeviceService] start Dongle pairing'); try { diff --git a/packages/uhk-common/src/models/ipc-response.ts b/packages/uhk-common/src/models/ipc-response.ts index 9e615d1bafe..dbe8370358c 100644 --- a/packages/uhk-common/src/models/ipc-response.ts +++ b/packages/uhk-common/src/models/ipc-response.ts @@ -16,3 +16,7 @@ export class FirmwareUpgradeIpcResponse extends IpcResponse { userConfigSaved?: boolean; firmwareDowngraded?: boolean; } + +export interface AreBleAddressesPairedIpcResponse extends IpcResponse { + addresses: Record; +} diff --git a/packages/uhk-common/src/util/ipcEvents.ts b/packages/uhk-common/src/util/ipcEvents.ts index 9627bb8e7da..a1079597761 100644 --- a/packages/uhk-common/src/util/ipcEvents.ts +++ b/packages/uhk-common/src/util/ipcEvents.ts @@ -22,9 +22,13 @@ export class AutoUpdate { } export class Device { + public static readonly areBleAddressesPaired = 'device-are-ble-addresses-paired'; + public static readonly areBleAddressesPairedReply = 'device-are-ble-addresses-paired-reply'; public static readonly changeKeyboardLayout = 'device-change-keyboard-layout'; public static readonly changeKeyboardLayoutReply = 'device-change-keyboard-layout-reply'; public static readonly dongleVersionInfoLoaded = 'device-dongle-version-info-loaded'; + public static readonly eraseBleSettings = 'device-erase-ble-settings'; + public static readonly eraseBleSettingsReply = 'device-erase-ble-settings-reply'; public static readonly hardwareModulesLoaded = 'device-hardware-modules-loaded'; public static readonly setPrivilegeOnLinux = 'set-privilege-on-linux'; public static readonly setPrivilegeOnLinuxReply = 'set-privilege-on-linux-reply'; diff --git a/packages/uhk-usb/src/constants.ts b/packages/uhk-usb/src/constants.ts index 0743769e621..5cfcb051ac2 100644 --- a/packages/uhk-usb/src/constants.ts +++ b/packages/uhk-usb/src/constants.ts @@ -33,6 +33,7 @@ export enum UsbCommand { UnpairAll = 0x1a, IsPaired = 0x1b, EnterPairingMode = 0x1c, + EraseBleSettings = 0x1d, } export enum EepromOperation { diff --git a/packages/uhk-usb/src/uhk-operations.ts b/packages/uhk-usb/src/uhk-operations.ts index fe0a8ca8639..fc6df83dd57 100644 --- a/packages/uhk-usb/src/uhk-operations.ts +++ b/packages/uhk-usb/src/uhk-operations.ts @@ -73,6 +73,12 @@ export class UhkOperations { private device: UhkHidDevice) { } + public async eraseBleSettings(): Promise { + this.logService.usbOps('[UhkHidDevice] USB[T]: Erase BLE settings.'); + const transfer = Buffer.from([UsbCommand.EraseBleSettings]); + await this.device.write(transfer); + } + public async jumpToBootloaderModule(module: ModuleSlotToId): Promise { this.logService.usbOps(`[UhkHidDevice] USB[T]: Jump to bootloader. Module: ${ModuleSlotToId[module].toString()}`); const transfer = Buffer.from([UsbCommand.JumpToModuleBootloader, module]); diff --git a/packages/uhk-web/src/app/components/device/host-connections/host-connections.component.html b/packages/uhk-web/src/app/components/device/host-connections/host-connections.component.html index 13273cc1cb5..f7ef98ab829 100644 --- a/packages/uhk-web/src/app/components/device/host-connections/host-connections.component.html +++ b/packages/uhk-web/src/app/components/device/host-connections/host-connections.component.html @@ -53,21 +53,44 @@

> - +
+ + + +
+ + diff --git a/packages/uhk-web/src/app/components/device/host-connections/host-connections.component.ts b/packages/uhk-web/src/app/components/device/host-connections/host-connections.component.ts index 243dfa8edfa..bf65319b821 100644 --- a/packages/uhk-web/src/app/components/device/host-connections/host-connections.component.ts +++ b/packages/uhk-web/src/app/components/device/host-connections/host-connections.component.ts @@ -1,18 +1,20 @@ import { ChangeDetectorRef } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core'; import { DragulaService } from '@ert78gb/ng2-dragula'; -import { faCircleNodes, faTrash } from '@fortawesome/free-solid-svg-icons'; +import { faCircleExclamation, faCircleNodes, faSpinner, faTrash } from '@fortawesome/free-solid-svg-icons'; import { Store } from '@ngrx/store'; import { Subscription } from 'rxjs'; import { HostConnection } from 'uhk-common'; +import { EraseBleSettingsButtonState } from '../../../models'; +import { CheckAreHostConnectionsPairedAction, EraseBleSettingAction } from '../../../store/actions/device'; import { DeleteHostConnectionAction } from '../../../store/actions/dongle-pairing.action'; import { RenameHostConnectionAction, ReorderHostConnectionsAction, SetHostConnectionSwitchoverAction, } from '../../../store/actions/user-config'; -import { AppState, getHostConnections, isDonglePairing } from '../../../store/index'; +import { AppState, getEraseBleSettingsButtonState, getHostConnections, getHostConnectionPairState } from '../../../store/index'; @Component({ selector: 'host-connections', @@ -24,19 +26,25 @@ import { AppState, getHostConnections, isDonglePairing } from '../../../store/in }) export class HostConnectionsComponent implements OnInit, OnDestroy { faCircleNodes = faCircleNodes; + faSpinner = faSpinner; faTrash = faTrash; + faCircleExclamation = faCircleExclamation; + hostConnectionPairState: Record = {}; + eraseBleSettingsButtonState: EraseBleSettingsButtonState; hostConnections: HostConnection[] = [] as HostConnection[]; - isDonglePairing: boolean; dragAndDropGroup = 'HOST_CONNECTION'; + private hostConnectionPairStateSubscription: Subscription; + private eraseBleSettingsSubscription: Subscription; private hostConnectionsSubscription: Subscription; - private isDonglePairingSubscription: Subscription; constructor(private dragulaService: DragulaService, private cdRef: ChangeDetectorRef, private store: Store) { + this.store.dispatch(new CheckAreHostConnectionsPairedAction()); + dragulaService.createGroup(this.dragAndDropGroup, { moves: (el, container, handle) => { if (!handle) { @@ -57,30 +65,38 @@ export class HostConnectionsComponent implements OnInit, OnDestroy { } ngOnInit(): void { + this.eraseBleSettingsSubscription = this.store.select(getEraseBleSettingsButtonState) + .subscribe(eraseBleSettingsButtonState => { + this.eraseBleSettingsButtonState = eraseBleSettingsButtonState; + this.cdRef.markForCheck(); + }); + this.hostConnectionPairStateSubscription = this.store.select(getHostConnectionPairState) + .subscribe(hostConnectionPairState => { + this.hostConnectionPairState = hostConnectionPairState; + this.cdRef.markForCheck(); + }); this.hostConnectionsSubscription = this.store.select(getHostConnections) .subscribe(hostConnections => { this.hostConnections = hostConnections; this.cdRef.markForCheck(); }); - this.isDonglePairingSubscription = this.store.select(isDonglePairing) - .subscribe(isDonglePairing => { - this.isDonglePairing = isDonglePairing; - this.cdRef.markForCheck(); - }); } ngOnDestroy(): void { this.dragulaService.destroy(this.dragAndDropGroup); - if(this.hostConnectionsSubscription) { - this.hostConnectionsSubscription.unsubscribe(); - } - this.isDonglePairingSubscription?.unsubscribe(); + this.eraseBleSettingsSubscription?.unsubscribe(); + this.hostConnectionPairStateSubscription?.unsubscribe(); + this.hostConnectionsSubscription?.unsubscribe(); } deleteHostConnection(index: number, hostConnection: HostConnection): void { this.store.dispatch(new DeleteHostConnectionAction({index, hostConnection})); } + eraseBleSettings(): void { + this.store.dispatch(new EraseBleSettingAction()); + } + renameHostConnection(index: number, newName: string): void { this.store.dispatch(new RenameHostConnectionAction({ index, @@ -95,4 +111,8 @@ export class HostConnectionsComponent implements OnInit, OnDestroy { setHostConnectionSwitchover(index: number, checked: boolean): void { this.store.dispatch(new SetHostConnectionSwitchoverAction({index, checked})); } + + showNotPairedTooltip(hostConnection: HostConnection): boolean { + return hostConnection.hasAddress() && !this.hostConnectionPairState[hostConnection.address]; + } } diff --git a/packages/uhk-web/src/app/models/erase-ble-settings-button-state.ts b/packages/uhk-web/src/app/models/erase-ble-settings-button-state.ts new file mode 100644 index 00000000000..09741e8553d --- /dev/null +++ b/packages/uhk-web/src/app/models/erase-ble-settings-button-state.ts @@ -0,0 +1,5 @@ +export interface EraseBleSettingsButtonState { + disabled: boolean; + erasing: boolean; + visible: boolean; +} diff --git a/packages/uhk-web/src/app/models/index.ts b/packages/uhk-web/src/app/models/index.ts index 7c0c7400a68..e67ce60e2f4 100644 --- a/packages/uhk-web/src/app/models/index.ts +++ b/packages/uhk-web/src/app/models/index.ts @@ -6,6 +6,7 @@ export * from './delete-host-connection-payload'; export * from './device-ui-states'; export * from './dongle-pairing-state'; export * from './duplicate-macro-action-payload'; +export * from './erase-ble-settings-button-state'; export * from './exchange-keys-action.model'; export * from './firmware-upgrade-state'; export * from './firmware-upgrade-steps'; diff --git a/packages/uhk-web/src/app/services/device-renderer.service.ts b/packages/uhk-web/src/app/services/device-renderer.service.ts index c5d836b17f2..7df46dafa73 100644 --- a/packages/uhk-web/src/app/services/device-renderer.service.ts +++ b/packages/uhk-web/src/app/services/device-renderer.service.ts @@ -2,6 +2,7 @@ import { Injectable, NgZone } from '@angular/core'; import { Action, Store } from '@ngrx/store'; import { + AreBleAddressesPairedIpcResponse, ChangeKeyboardLayoutIpcResponse, DeviceConnectionState, DeviceVersionInformation, @@ -33,8 +34,10 @@ import { import { IpcCommonRenderer } from './ipc-common-renderer'; import { ChangeKeyboardLayoutReplyAction, + CheckAreHostConnectionsPairedReplyAction, ConnectionStateChangedAction, DongleVersionInfoLoadedAction, + EraseBleSettingReplyAction, CurrentlyUpdateSkipModuleAction, CurrentlyUpdatingModuleAction, HardwareModulesLoadedAction, @@ -65,6 +68,10 @@ export class DeviceRendererService { this.logService.misc('[DeviceRendererService] init success '); } + areBleAddressesPaired(addresses: string[]): void { + this.ipcRenderer.send(IpcEvents.device.areBleAddressesPaired, addresses); + } + changeKeyboardLayout(layout: KeyboardLayout, hardwareConfiguration: HardwareConfiguration): void { this.ipcRenderer.send(IpcEvents.device.changeKeyboardLayout, layout, hardwareConfiguration.toJsonObject()); } @@ -77,6 +84,10 @@ export class DeviceRendererService { }); } + eraseBleSettings(): void { + this.ipcRenderer.send(IpcEvents.device.eraseBleSettings); + } + setPrivilegeOnLinux(): void { this.ipcRenderer.send(IpcEvents.device.setPrivilegeOnLinux); } @@ -137,6 +148,10 @@ export class DeviceRendererService { } private registerEvents(): void { + this.ipcRenderer.on(IpcEvents.device.areBleAddressesPairedReply, (event: string, response: AreBleAddressesPairedIpcResponse) => { + this.dispachStoreAction(new CheckAreHostConnectionsPairedReplyAction(response)); + }); + this.ipcRenderer.on(IpcEvents.device.changeKeyboardLayoutReply, (event: string, response: ChangeKeyboardLayoutIpcResponse) => { this.dispachStoreAction(new ChangeKeyboardLayoutReplyAction(response)); }); @@ -153,6 +168,10 @@ export class DeviceRendererService { this.dispachStoreAction(new DeleteHostConnectionFailedAction(message)); }); + this.ipcRenderer.on(IpcEvents.device.eraseBleSettingsReply, (event: string, response: IpcResponse) => { + this.dispachStoreAction(new EraseBleSettingReplyAction(response)); + }); + this.ipcRenderer.on(IpcEvents.device.hardwareModulesLoaded, (event: string, response: HardwareModules) => { this.dispachStoreAction(new HardwareModulesLoadedAction(response)); }); diff --git a/packages/uhk-web/src/app/store/actions/device.ts b/packages/uhk-web/src/app/store/actions/device.ts index 0fadf100f70..b7ab09d742e 100644 --- a/packages/uhk-web/src/app/store/actions/device.ts +++ b/packages/uhk-web/src/app/store/actions/device.ts @@ -1,5 +1,6 @@ import { Action } from '@ngrx/store'; import { + AreBleAddressesPairedIpcResponse, BackupUserConfiguration, ChangeKeyboardLayoutIpcResponse, ConfigSizesInfo, @@ -19,8 +20,12 @@ import { UpdateFirmwareSuccessPayload } from '../../models/update-firmware-succe export enum ActionTypes { ChangeKeyboardLayout = '[device] change keyboard layout', + CheckAreHostConnectionsPaired = '[device] check are host connections paired', + CheckAreHostConnectionsPairedReply = '[device] check are host connections paired reply', ChangeKeyboardLayoutReply = '[device] change keyboard layout reply', DongleVersionInfoLoaded = '[device] dongle version info loaded', + EraseBleSettings = '[device] erase ble settings', + EraseBleSettingsReply = '[device] erase ble settings reply', SetPrivilegeOnLinux = '[device] set privilege on linux', SetPrivilegeOnLinuxReply = '[device] set privilege on linux reply', ConnectionStateChanged = '[device] connection state changed', @@ -65,6 +70,16 @@ export class ChangeKeyboardLayoutAction implements Action { constructor(public layout: KeyboardLayout) {} } +export class CheckAreHostConnectionsPairedAction implements Action { + type = ActionTypes.CheckAreHostConnectionsPaired; +} + +export class CheckAreHostConnectionsPairedReplyAction implements Action { + type = ActionTypes.CheckAreHostConnectionsPairedReply; + + constructor(public payload: AreBleAddressesPairedIpcResponse) {} +} + export class DongleVersionInfoLoadedAction implements Action { type = ActionTypes.DongleVersionInfoLoaded; @@ -77,6 +92,15 @@ export class ChangeKeyboardLayoutReplyAction implements Action { constructor(public payload: ChangeKeyboardLayoutIpcResponse) {} } +export class EraseBleSettingAction implements Action { + type = ActionTypes.EraseBleSettings; +} + +export class EraseBleSettingReplyAction implements Action { + type = ActionTypes.EraseBleSettingsReply; + + constructor(public payload: IpcResponse) {} +} export class SetPrivilegeOnLinuxAction implements Action { type = ActionTypes.SetPrivilegeOnLinux; @@ -288,7 +312,11 @@ export class SkipFirmwareUpgradeAction implements Action { export type Actions = ChangeKeyboardLayoutAction | ChangeKeyboardLayoutReplyAction + | CheckAreHostConnectionsPairedAction + | CheckAreHostConnectionsPairedReplyAction | DongleVersionInfoLoadedAction + | EraseBleSettingAction + | EraseBleSettingReplyAction | SetPrivilegeOnLinuxAction | SetPrivilegeOnLinuxReplyAction | ConnectionStateChangedAction diff --git a/packages/uhk-web/src/app/store/effects/device.ts b/packages/uhk-web/src/app/store/effects/device.ts index 230c87b6ba0..d50eae5e31c 100644 --- a/packages/uhk-web/src/app/store/effects/device.ts +++ b/packages/uhk-web/src/app/store/effects/device.ts @@ -24,8 +24,11 @@ import { ActionTypes, ChangeKeyboardLayoutAction, ChangeKeyboardLayoutReplyAction, + CheckAreHostConnectionsPairedAction, + CheckAreHostConnectionsPairedReplyAction, ConnectionStateChangedAction, EnableUsbStackTestAction, + EraseBleSettingReplyAction, HideSaveToKeyboardButton, ReadConfigSizesAction, RecoveryDeviceAction, @@ -53,12 +56,16 @@ import { SetupPermissionErrorAction, ShowNotificationAction } from '../actions/app'; +import { + ActionTypes as DongleActions, +} from '../actions/dongle-pairing.action'; import { AppState, deviceConnected, disableUpdateAgentProtection, getConnectedDevice, getHardwareConfiguration, + getHostConnections, getRouterState, getShowFirmwareUpgradePanel, getUserConfiguration @@ -106,6 +113,41 @@ export class DeviceEffects { ) ); + checkAreHostConnectionsPaired$ = createEffect(() => this.actions$ + .pipe( + ofType(ActionTypes.CheckAreHostConnectionsPaired, DongleActions.DonglePairingSuccess), + withLatestFrom(this.store.select(getHostConnections)), + tap(([_, hostConnections]) => { + const addresses = []; + for (const hostConnection of hostConnections) { + if (hostConnection.hasAddress()) { + addresses.push(hostConnection.address); + } + } + + this.deviceRendererService.areBleAddressesPaired(addresses); + }), + ), + { dispatch: false }, + ); + + checkAreHostConnectionsPairedReply$ = createEffect(() => this.actions$ + .pipe( + ofType(ActionTypes.CheckAreHostConnectionsPairedReply), + map(action => action.payload), + switchMap(response => { + if (response.success) { + return EMPTY; + } + + return of(new ShowNotificationAction({ + message: response.error?.message, + type: NotificationType.Error, + })); + }) + ) + ); + deviceConnectionStateChange$ = createEffect(() => this.actions$ .pipe( ofType(ActionTypes.ConnectionStateChanged), @@ -190,6 +232,33 @@ export class DeviceEffects { ) ); + eraseBleSettings$ = createEffect(() => this.actions$ + .pipe( + ofType(ActionTypes.EraseBleSettings), + tap(() => { + this.deviceRendererService.eraseBleSettings(); + }), + ), + { dispatch: false }, + ); + + eraseBleSettingsReply$ = createEffect(() => this.actions$ + .pipe( + ofType(ActionTypes.EraseBleSettingsReply), + map(action => action.payload), + switchMap(response => { + if (response.success) { + return of(new CheckAreHostConnectionsPairedAction()); + } + + return of(new ShowNotificationAction({ + type: NotificationType.Error, + message: response.error.message + })); + }) + ), + ); + setPrivilegeOnLinux$ = createEffect(() => this.actions$ .pipe( ofType(ActionTypes.SetPrivilegeOnLinux), diff --git a/packages/uhk-web/src/app/store/effects/user-config.ts b/packages/uhk-web/src/app/store/effects/user-config.ts index 2a88694c064..55c51155965 100644 --- a/packages/uhk-web/src/app/store/effects/user-config.ts +++ b/packages/uhk-web/src/app/store/effects/user-config.ts @@ -21,7 +21,6 @@ import { } from 'uhk-common'; import { EmptyAction } from '../actions/app'; -import { SaveConfigurationAction } from '../actions/device'; import { ActionTypes, ApplyUserConfigurationFromFileAction, @@ -49,9 +48,11 @@ import { NavigateTo } from '../actions/app'; import { + BackupUserConfigurationAction, + CheckAreHostConnectionsPairedAction, HardwareModulesLoadedAction, ShowSaveToKeyboardButtonAction, - BackupUserConfigurationAction + SaveConfigurationAction, } from '../actions/device'; import { DeviceRendererService } from '../../services/device-renderer.service'; import { UndoUserConfigData } from '../../models/undo-user-config-data'; @@ -175,6 +176,7 @@ export class UserConfigEffects { const userConfig = getUserConfigFromDeviceResponse(data.userConfiguration); this.logService.config('[UserConfigEffect] Loaded user configuration', userConfig); result.push(new LoadUserConfigSuccessAction(userConfig)); + result.push(new CheckAreHostConnectionsPairedAction()); if (route.state && !route.state.url.startsWith('/device/firmware') diff --git a/packages/uhk-web/src/app/store/index.ts b/packages/uhk-web/src/app/store/index.ts index 7ee9a9b4c1b..78a04dbcdaa 100644 --- a/packages/uhk-web/src/app/store/index.ts +++ b/packages/uhk-web/src/app/store/index.ts @@ -41,6 +41,7 @@ import { DongleOperations, DonglePairingState, DonglePairingStates, + EraseBleSettingsButtonState, FirmwareUpgradeState, HistoryFileInfo, MacroMenuItem, @@ -226,6 +227,7 @@ export const deviceConnected = createSelector( return !!device.connectedDevice; }); export const hasDevicePermission = createSelector(deviceState, fromDevice.hasDevicePermission); +export const getHostConnectionPairState = createSelector(deviceState, fromDevice.getHostConnectionPairState); export const getDeviceBleAddress = createSelector(deviceState, fromDevice.getDeviceBleAddress); export const getDevicePairedWithDongle = createSelector(deviceState, fromDevice.getDevicePairedWithDongle); export const getMissingDeviceState = createSelector(deviceState, fromDevice.getMissingDeviceState); @@ -417,6 +419,7 @@ export const saveToKeyboardState = createSelector(runningInElectron, saveToKeybo showButton: saveToKeyboard.showButton && !outOfSpaceWarning.show }; }); +export const getEraseBleSettingsButtonStateSelector = createSelector(deviceState, fromDevice.getEraseBleSettingsButtonState); export const firstAttemptOfSaveToKeyboard = createSelector( runningInElectron, getEverAttemptedSavingToKeyboard, @@ -530,6 +533,17 @@ export const getDonglePairingState = createSelector( } ); +export const getEraseBleSettingsButtonState = createSelector( + getEraseBleSettingsButtonStateSelector, + isDonglePairing, + (eraseBleSettings, donglePairing): EraseBleSettingsButtonState => { + return { + ...eraseBleSettings, + disabled: eraseBleSettings.disabled || donglePairing, + }; + } +); + export const getSideMenuPageState = createSelector( runningInElectron, updatingFirmware, diff --git a/packages/uhk-web/src/app/store/reducers/device.ts b/packages/uhk-web/src/app/store/reducers/device.ts index 479a60c0266..c045dbf64ac 100644 --- a/packages/uhk-web/src/app/store/reducers/device.ts +++ b/packages/uhk-web/src/app/store/reducers/device.ts @@ -8,6 +8,7 @@ import { getDefaultHalvesInfo, HalvesInfo, HardwareModules, + isVersionGte, isVersionGtMinor, LeftSlotModules, RightSlotModules, @@ -16,7 +17,7 @@ import { UHK_DEVICE_IDS_TYPE, UhkDeviceProduct } from 'uhk-common'; -import { DeviceUiStates, RecoverPageState } from '../../models'; +import { DeviceUiStates, EraseBleSettingsButtonState, RecoverPageState } from '../../models'; import { MissingDeviceState } from '../../models/missing-device-state'; import { RestoreConfigurationState } from '../../models/restore-configuration-state'; import { getVersions } from '../../util'; @@ -36,6 +37,8 @@ export interface State { hasPermission: boolean; bootloaderActive: boolean; deviceConnectionStateLoaded: boolean; + hostConnectionPairState: Record; + isErasingBleSettings: boolean; keyboardHalvesAlwaysJoined: boolean; leftHalfBootloaderActive: boolean; leftHalfDetected: boolean; @@ -62,6 +65,8 @@ export const initialState: State = { hasPermission: true, bootloaderActive: false, deviceConnectionStateLoaded: false, + hostConnectionPairState: {}, + isErasingBleSettings: false, keyboardHalvesAlwaysJoined: false, leftHalfBootloaderActive: false, leftHalfDetected: false, @@ -140,6 +145,38 @@ export function reducer(state = initialState, action: Action): State { }; } + case Device.ActionTypes.EraseBleSettings: { + return { + ...state, + isErasingBleSettings: true + }; + } + + case Device.ActionTypes.CheckAreHostConnectionsPairedReply: { + const response = (action).payload; + + return { + ...state, + hostConnectionPairState: response.addresses, + isErasingBleSettings: false, + }; + } + + case Device.ActionTypes.EraseBleSettingsReply: { + const response = (action).payload; + + // After the deletion Agent queries the host connections, + // so the CheckAreHostConnectionsPairedReply action will set the isErasingBleSettings + if (response.success) { + return state; + } + + return { + ...state, + isErasingBleSettings: false + }; + } + case Device.ActionTypes.SavingConfiguration: { return { ...state, @@ -330,6 +367,13 @@ export const getMissingDeviceState = (state: State): MissingDeviceState => { }; }; export const getSaveToKeyboardState = (state: State) => state.saveToKeyboard; +export const getEraseBleSettingsButtonState = (state: State): EraseBleSettingsButtonState => { + return { + disabled: state.isErasingBleSettings, + erasing: state.isErasingBleSettings, + visible: isVersionGte(state.modules.rightModuleInfo.deviceProtocolVersion, '4.14.0') + }; +}; export const getHardwareModules = (state: State) => state.modules; export const getHasBackupUserConfiguration = (state: State) => { return (state.backupUserConfiguration?.info === BackupUserConfigurationInfo.LastCompatible @@ -379,6 +423,7 @@ export const deviceUiState = (state: State): DeviceUiStates | undefined => { }; export const getConnectedDevice = (state: State) => state.connectedDevice; +export const getHostConnectionPairState = (state: State): Record => state.hostConnectionPairState; export const getSkipFirmwareUpgrade = (state: State) => state.skipFirmwareUpgrade; export const isKeyboardLayoutChanging = (state: State) => state.isKeyboardLayoutChanging; export const keyboardHalvesAlwaysJoined = (state: State) => state.keyboardHalvesAlwaysJoined; diff --git a/packages/uhk-web/src/app/store/reducers/dongle-pairing.reducer.ts b/packages/uhk-web/src/app/store/reducers/dongle-pairing.reducer.ts index 9a01d2b0e94..cdc6129d997 100644 --- a/packages/uhk-web/src/app/store/reducers/dongle-pairing.reducer.ts +++ b/packages/uhk-web/src/app/store/reducers/dongle-pairing.reducer.ts @@ -36,6 +36,13 @@ export function reducer(state = initialState, action: Action): State { }; } + case Device.ActionTypes.EraseBleSettingsReply: { + return { + ...state, + state: DonglePairingStates.Idle, + } + } + case Device.ActionTypes.SaveToKeyboardFailed: { if (state.state !== DonglePairingStates.SavingToKeyboard) { return state;