feat: Display/hide left keyboard half and merged/unmerged state (#987)
* feat: Display/hide left keyboard half and merged/unmerged state * feat: improve the animation * feat: decrease left fade animation time
This commit is contained in:
committed by
László Monda
parent
a409c219d8
commit
cbccaba1c5
@@ -118,13 +118,13 @@ function createWindow() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Emitted when the window is closed.
|
// Emitted when the window is closed.
|
||||||
win.on('closed', () => {
|
win.on('closed', async () => {
|
||||||
// Dereference the window object, usually you would store windows
|
// Dereference the window object, usually you would store windows
|
||||||
// in an array if your app supports multi windows, this is the time
|
// in an array if your app supports multi windows, this is the time
|
||||||
// when you should delete the corresponding element.
|
// when you should delete the corresponding element.
|
||||||
logger.info('[Electron Main] win closed');
|
logger.info('[Electron Main] win closed');
|
||||||
win = null;
|
win = null;
|
||||||
deviceService.close();
|
await deviceService.close();
|
||||||
deviceService = null;
|
deviceService = null;
|
||||||
appUpdateService = null;
|
appUpdateService = null;
|
||||||
appService = null;
|
appService = null;
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ import {
|
|||||||
UpdateFirmwareData
|
UpdateFirmwareData
|
||||||
} from 'uhk-common';
|
} from 'uhk-common';
|
||||||
import { snooze, UhkHidDevice, UhkOperations } from 'uhk-usb';
|
import { snooze, UhkHidDevice, UhkOperations } from 'uhk-usb';
|
||||||
import { Subscription, interval, from } from 'rxjs';
|
|
||||||
import { distinctUntilChanged, startWith, switchMap, tap } from 'rxjs/operators';
|
|
||||||
import { emptyDir } from 'fs-extra';
|
import { emptyDir } from 'fs-extra';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
@@ -35,7 +33,8 @@ import {
|
|||||||
* - Read UserConfiguration from the UHK Device
|
* - Read UserConfiguration from the UHK Device
|
||||||
*/
|
*/
|
||||||
export class DeviceService {
|
export class DeviceService {
|
||||||
private pollTimer$: Subscription;
|
private _pollerAllowed: boolean;
|
||||||
|
private _uhkDevicePolling: boolean;
|
||||||
private queueManager = new QueueManager();
|
private queueManager = new QueueManager();
|
||||||
|
|
||||||
constructor(private logService: LogService,
|
constructor(private logService: LogService,
|
||||||
@@ -43,7 +42,11 @@ export class DeviceService {
|
|||||||
private device: UhkHidDevice,
|
private device: UhkHidDevice,
|
||||||
private operations: UhkOperations,
|
private operations: UhkOperations,
|
||||||
private rootDir: string) {
|
private rootDir: string) {
|
||||||
this.pollUhkDevice();
|
this.startPollUhkDevice();
|
||||||
|
this.uhkDevicePoller()
|
||||||
|
.catch(error => {
|
||||||
|
this.logService.error('[DeviceService] UHK Device poller error', error);
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.on(IpcEvents.device.saveUserConfiguration, (...args: any[]) => {
|
ipcMain.on(IpcEvents.device.saveUserConfiguration, (...args: any[]) => {
|
||||||
this.queueManager.add({
|
this.queueManager.add({
|
||||||
@@ -72,7 +75,7 @@ export class DeviceService {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on(IpcEvents.device.startConnectionPoller, this.pollUhkDevice.bind(this));
|
ipcMain.on(IpcEvents.device.startConnectionPoller, this.startPollUhkDevice.bind(this));
|
||||||
|
|
||||||
ipcMain.on(IpcEvents.device.recoveryDevice, (...args: any[]) => {
|
ipcMain.on(IpcEvents.device.recoveryDevice, (...args: any[]) => {
|
||||||
this.queueManager.add({
|
this.queueManager.add({
|
||||||
@@ -103,6 +106,8 @@ export class DeviceService {
|
|||||||
let response: ConfigurationReply;
|
let response: ConfigurationReply;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
await this.stopPollUhkDevice();
|
||||||
|
|
||||||
await this.device.waitUntilKeyboardBusy();
|
await this.device.waitUntilKeyboardBusy();
|
||||||
const result = await this.operations.loadConfigurations();
|
const result = await this.operations.loadConfigurations();
|
||||||
const modules: HardwareModules = await this.getHardwareModules(false);
|
const modules: HardwareModules = await this.getHardwareModules(false);
|
||||||
@@ -123,6 +128,7 @@ export class DeviceService {
|
|||||||
};
|
};
|
||||||
} finally {
|
} finally {
|
||||||
this.device.close();
|
this.device.close();
|
||||||
|
this.startPollUhkDevice();
|
||||||
}
|
}
|
||||||
|
|
||||||
event.sender.send(IpcEvents.device.loadConfigurationReply, JSON.stringify(response));
|
event.sender.send(IpcEvents.device.loadConfigurationReply, JSON.stringify(response));
|
||||||
@@ -136,8 +142,7 @@ export class DeviceService {
|
|||||||
leftModuleInfo: await this.operations.getLeftModuleVersionInfo(),
|
leftModuleInfo: await this.operations.getLeftModuleVersionInfo(),
|
||||||
rightModuleInfo: await this.operations.getRightModuleVersionInfo()
|
rightModuleInfo: await this.operations.getRightModuleVersionInfo()
|
||||||
};
|
};
|
||||||
}
|
} catch (err) {
|
||||||
catch (err) {
|
|
||||||
if (!catchError) {
|
if (!catchError) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
@@ -151,8 +156,8 @@ export class DeviceService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public close(): void {
|
public async close(): Promise<void> {
|
||||||
this.stopPollTimer();
|
await this.stopPollUhkDevice();
|
||||||
this.logService.info('[DeviceService] Device connection checker stopped.');
|
this.logService.info('[DeviceService] Device connection checker stopped.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +173,7 @@ export class DeviceService {
|
|||||||
this.logService.debug('Device right firmware version:', hardwareModules.rightModuleInfo.firmwareVersion);
|
this.logService.debug('Device right firmware version:', hardwareModules.rightModuleInfo.firmwareVersion);
|
||||||
this.logService.debug('Device left firmware version:', hardwareModules.leftModuleInfo.firmwareVersion);
|
this.logService.debug('Device left firmware version:', hardwareModules.leftModuleInfo.firmwareVersion);
|
||||||
|
|
||||||
this.stopPollTimer();
|
await this.stopPollUhkDevice();
|
||||||
this.device.resetDeviceCache();
|
this.device.resetDeviceCache();
|
||||||
|
|
||||||
if (data.firmware) {
|
if (data.firmware) {
|
||||||
@@ -179,8 +184,7 @@ export class DeviceService {
|
|||||||
|
|
||||||
await this.operations.updateRightFirmware(firmwarePathData.rightFirmwarePath);
|
await this.operations.updateRightFirmware(firmwarePathData.rightFirmwarePath);
|
||||||
await this.operations.updateLeftModule(firmwarePathData.leftFirmwarePath);
|
await this.operations.updateLeftModule(firmwarePathData.leftFirmwarePath);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
const packageJsonPath = path.join(this.rootDir, 'packages/firmware/package.json');
|
const packageJsonPath = path.join(this.rootDir, 'packages/firmware/package.json');
|
||||||
const packageJson = await getPackageJsonFromPathAsync(packageJsonPath);
|
const packageJson = await getPackageJsonFromPathAsync(packageJsonPath);
|
||||||
this.logService.debug('New firmware version:', packageJson.firmwareVersion);
|
this.logService.debug('New firmware version:', packageJson.firmwareVersion);
|
||||||
@@ -205,7 +209,7 @@ export class DeviceService {
|
|||||||
|
|
||||||
await snooze(500);
|
await snooze(500);
|
||||||
|
|
||||||
this.pollUhkDevice();
|
this.startPollUhkDevice();
|
||||||
|
|
||||||
event.sender.send(IpcEvents.device.updateFirmwareReply, response);
|
event.sender.send(IpcEvents.device.updateFirmwareReply, response);
|
||||||
}
|
}
|
||||||
@@ -214,7 +218,7 @@ export class DeviceService {
|
|||||||
const response = new FirmwareUpgradeIpcResponse();
|
const response = new FirmwareUpgradeIpcResponse();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.stopPollTimer();
|
await this.stopPollUhkDevice();
|
||||||
|
|
||||||
await this.operations.updateRightFirmware();
|
await this.operations.updateRightFirmware();
|
||||||
|
|
||||||
@@ -236,28 +240,51 @@ export class DeviceService {
|
|||||||
await this.device.enableUsbStackTest();
|
await this.device.enableUsbStackTest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private startPollUhkDevice(): void {
|
||||||
|
this._pollerAllowed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async stopPollUhkDevice(): Promise<void> {
|
||||||
|
return new Promise<void>(async resolve => {
|
||||||
|
this._pollerAllowed = false;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (!this._uhkDevicePolling) {
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
await snooze(100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HID API not support device attached and detached event.
|
* HID API not support device attached and detached event.
|
||||||
* This method check the keyboard is attached to the computer or not.
|
* This method check the keyboard is attached to the computer or not.
|
||||||
* Every second check the HID device list.
|
* The halves are connected and merged or not.
|
||||||
|
* Every 250ms check the HID device list.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private pollUhkDevice(): void {
|
private async uhkDevicePoller(): Promise<void> {
|
||||||
if (this.pollTimer$) {
|
let savedState: DeviceConnectionState;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.pollTimer$ = interval(1000)
|
while (true) {
|
||||||
.pipe(
|
if (this._pollerAllowed) {
|
||||||
startWith(0),
|
|
||||||
switchMap(() => from(this.device.getDeviceConnectionStateAsync())),
|
this._uhkDevicePolling = true;
|
||||||
distinctUntilChanged<DeviceConnectionState>(isEqual),
|
|
||||||
tap((state: DeviceConnectionState) => {
|
const state = await this.device.getDeviceConnectionStateAsync();
|
||||||
|
if (!isEqual(state, savedState)) {
|
||||||
|
savedState = state;
|
||||||
this.win.webContents.send(IpcEvents.device.deviceConnectionStateChanged, state);
|
this.win.webContents.send(IpcEvents.device.deviceConnectionStateChanged, state);
|
||||||
this.logService.info('[DeviceService] Device connection state changed to:', state);
|
this.logService.info('[DeviceService] Device connection state changed to:', state);
|
||||||
})
|
}
|
||||||
)
|
|
||||||
.subscribe();
|
this._uhkDevicePolling = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await snooze(250);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async saveUserConfiguration(event: Electron.Event, args: Array<string>): Promise<void> {
|
private async saveUserConfiguration(event: Electron.Event, args: Array<string>): Promise<void> {
|
||||||
@@ -265,32 +292,23 @@ export class DeviceService {
|
|||||||
const data: SaveUserConfigurationData = JSON.parse(args[0]);
|
const data: SaveUserConfigurationData = JSON.parse(args[0]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
await this.stopPollUhkDevice();
|
||||||
await backupUserConfiguration(data);
|
await backupUserConfiguration(data);
|
||||||
|
|
||||||
const buffer = mapObjectToUserConfigBinaryBuffer(data.configuration);
|
const buffer = mapObjectToUserConfigBinaryBuffer(data.configuration);
|
||||||
await this.operations.saveUserConfiguration(buffer);
|
await this.operations.saveUserConfiguration(buffer);
|
||||||
|
|
||||||
response.success = true;
|
response.success = true;
|
||||||
}
|
} catch (error) {
|
||||||
catch (error) {
|
|
||||||
this.logService.error('[DeviceService] Transferring error', error);
|
this.logService.error('[DeviceService] Transferring error', error);
|
||||||
response.error = { message: error.message };
|
response.error = { message: error.message };
|
||||||
} finally {
|
} finally {
|
||||||
this.device.close();
|
this.device.close();
|
||||||
|
this.startPollUhkDevice();
|
||||||
}
|
}
|
||||||
|
|
||||||
event.sender.send(IpcEvents.device.saveUserConfigurationReply, response);
|
event.sender.send(IpcEvents.device.saveUserConfigurationReply, response);
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
private stopPollTimer(): void {
|
|
||||||
if (!this.pollTimer$) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.pollTimer$.unsubscribe();
|
|
||||||
this.pollTimer$ = null;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { UdevRulesInfo } from './udev-rules-info';
|
import { UdevRulesInfo } from './udev-rules-info';
|
||||||
|
import { HalvesInfo } from './halves-info';
|
||||||
|
|
||||||
export interface DeviceConnectionState {
|
export interface DeviceConnectionState {
|
||||||
connected: boolean;
|
connected: boolean;
|
||||||
@@ -6,4 +7,5 @@ export interface DeviceConnectionState {
|
|||||||
bootloaderActive: boolean;
|
bootloaderActive: boolean;
|
||||||
zeroInterfaceAvailable: boolean;
|
zeroInterfaceAvailable: boolean;
|
||||||
udevRulesInfo: UdevRulesInfo;
|
udevRulesInfo: UdevRulesInfo;
|
||||||
|
halvesInfo: HalvesInfo;
|
||||||
}
|
}
|
||||||
|
|||||||
4
packages/uhk-common/src/models/halves-info.ts
Normal file
4
packages/uhk-common/src/models/halves-info.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export interface HalvesInfo {
|
||||||
|
areHalvesMerged: boolean;
|
||||||
|
isLeftHalfConnected: boolean;
|
||||||
|
}
|
||||||
@@ -11,3 +11,4 @@ export * from './hardware-module-info';
|
|||||||
export * from './save-user-configuration-data';
|
export * from './save-user-configuration-data';
|
||||||
export * from './udev-rules-info';
|
export * from './udev-rules-info';
|
||||||
export * from './update-firmware-data';
|
export * from './update-firmware-data';
|
||||||
|
export * from './halves-info';
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Device, devices, HID } from 'node-hid';
|
|||||||
import { pathExists } from 'fs-extra';
|
import { pathExists } from 'fs-extra';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { platform } from 'os';
|
import { platform } from 'os';
|
||||||
import { CommandLineArgs, DeviceConnectionState, isEqualArray, LogService, UdevRulesInfo } from 'uhk-common';
|
import { CommandLineArgs, DeviceConnectionState, HalvesInfo, isEqualArray, LogService, UdevRulesInfo } from 'uhk-common';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ConfigBufferId,
|
ConfigBufferId,
|
||||||
@@ -89,7 +89,8 @@ export class UhkHidDevice {
|
|||||||
connected: false,
|
connected: false,
|
||||||
zeroInterfaceAvailable: false,
|
zeroInterfaceAvailable: false,
|
||||||
hasPermission: this.hasPermission(),
|
hasPermission: this.hasPermission(),
|
||||||
udevRulesInfo: await this.getUdevInfoAsync()
|
udevRulesInfo: await this.getUdevInfoAsync(),
|
||||||
|
halvesInfo: { areHalvesMerged: true, isLeftHalfConnected: true }
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const dev of devs) {
|
for (const dev of devs) {
|
||||||
@@ -105,6 +106,12 @@ export class UhkHidDevice {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (result.connected && result.hasPermission && result.zeroInterfaceAvailable) {
|
||||||
|
result.halvesInfo = await this.getHalvesStates();
|
||||||
|
} else if (!result.connected) {
|
||||||
|
this._device = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,6 +260,15 @@ export class UhkHidDevice {
|
|||||||
await this.write(transfer);
|
await this.write(transfer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getHalvesStates(): Promise<HalvesInfo> {
|
||||||
|
const buffer = await this.write(Buffer.from([UsbCommand.GetDeviceState]));
|
||||||
|
|
||||||
|
return {
|
||||||
|
areHalvesMerged: buffer[2] !== 0,
|
||||||
|
isLeftHalfConnected: buffer[3] !== 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the stored version of HID device. If not exist try to initialize.
|
* Return the stored version of HID device. If not exist try to initialize.
|
||||||
* @returns {HID}
|
* @returns {HID}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<svg-keyboard *ngFor="let layer of layers; let index = index; trackBy: trackKeyboard"
|
<svg-keyboard *ngFor="let layer of layers; let index = index; trackBy: trackKeyboard"
|
||||||
[@layerState]="layerAnimationState[index]"
|
[@layerState]="layerAnimationState[index]"
|
||||||
[moduleConfig]="layer.modules"
|
[moduleConfig]="layer.modules"
|
||||||
[halvesSplit]="halvesSplit"
|
[halvesInfo]="halvesInfo"
|
||||||
[capturingEnabled]="capturingEnabled"
|
[capturingEnabled]="capturingEnabled"
|
||||||
[selectedKey]="selectedKey"
|
[selectedKey]="selectedKey"
|
||||||
[selected]="selectedKey?.layerId === index"
|
[selected]="selectedKey?.layerId === index"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 833 B After Width: | Height: | Size: 831 B |
@@ -1,5 +1,5 @@
|
|||||||
svg-keyboard {
|
svg-keyboard {
|
||||||
width: 95%;
|
width: 99%;
|
||||||
max-width: 1400px;
|
max-width: 1400px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
|
||||||
import { animate, keyframes, state, style, transition, trigger } from '@angular/animations';
|
import { animate, keyframes, state, style, transition, trigger } from '@angular/animations';
|
||||||
import { Layer } from 'uhk-common';
|
import { HalvesInfo, Layer } from 'uhk-common';
|
||||||
|
|
||||||
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
||||||
import {
|
import {
|
||||||
@@ -83,7 +83,7 @@ export class KeyboardSliderComponent implements OnChanges {
|
|||||||
@Input() layers: Layer[];
|
@Input() layers: Layer[];
|
||||||
@Input() currentLayer: number;
|
@Input() currentLayer: number;
|
||||||
@Input() capturingEnabled: boolean;
|
@Input() capturingEnabled: boolean;
|
||||||
@Input() halvesSplit: boolean;
|
@Input() halvesInfo: HalvesInfo;
|
||||||
@Input() selectedKey: { layerId: number, moduleId: number, keyId: number };
|
@Input() selectedKey: { layerId: number, moduleId: number, keyId: number };
|
||||||
@Input() keyboardLayout = KeyboardLayout.ANSI;
|
@Input() keyboardLayout = KeyboardLayout.ANSI;
|
||||||
@Input() description: string;
|
@Input() description: string;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
[deletable]="deletable$ | async"
|
[deletable]="deletable$ | async"
|
||||||
(downloadClick)="downloadKeymap()"></keymap-header>
|
(downloadClick)="downloadKeymap()"></keymap-header>
|
||||||
<svg-keyboard-wrap [keymap]="keymap"
|
<svg-keyboard-wrap [keymap]="keymap"
|
||||||
[halvesSplit]="keyboardSplit"
|
[halvesInfo]="halvesInfo$ | async"
|
||||||
[keyboardLayout]="keyboardLayout$ | async"
|
[keyboardLayout]="keyboardLayout$ | async"
|
||||||
[allowLayerDoubleTap]="allowLayerDoubleTap$ | async"
|
[allowLayerDoubleTap]="allowLayerDoubleTap$ | async"
|
||||||
[lastEditedKey]="lastEditedKey$ | async"
|
[lastEditedKey]="lastEditedKey$ | async"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostListener, OnDestroy } from '@angular/core';
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { Keymap } from 'uhk-common';
|
import { HalvesInfo, Keymap } from 'uhk-common';
|
||||||
|
|
||||||
import { Observable, Subscription } from 'rxjs';
|
import { Observable, Subscription } from 'rxjs';
|
||||||
import { combineLatest, first, map, pluck, switchMap } from 'rxjs/operators';
|
import { combineLatest, first, map, pluck, switchMap } from 'rxjs/operators';
|
||||||
@@ -15,7 +15,8 @@ import {
|
|||||||
layerDoubleTapSupported,
|
layerDoubleTapSupported,
|
||||||
AppState,
|
AppState,
|
||||||
getKeyboardLayout,
|
getKeyboardLayout,
|
||||||
lastEditedKey
|
lastEditedKey,
|
||||||
|
getHalvesInfo
|
||||||
} from '../../../store';
|
} from '../../../store';
|
||||||
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
||||||
import { EditDescriptionAction, SelectKeymapAction } from '../../../store/actions/keymap';
|
import { EditDescriptionAction, SelectKeymapAction } from '../../../store/actions/keymap';
|
||||||
@@ -33,13 +34,12 @@ import { LastEditedKey } from '../../../models';
|
|||||||
})
|
})
|
||||||
export class KeymapEditComponent implements OnDestroy {
|
export class KeymapEditComponent implements OnDestroy {
|
||||||
|
|
||||||
keyboardSplit: boolean;
|
|
||||||
|
|
||||||
deletable$: Observable<boolean>;
|
deletable$: Observable<boolean>;
|
||||||
keymap$: Observable<Keymap>;
|
keymap$: Observable<Keymap>;
|
||||||
keyboardLayout$: Observable<KeyboardLayout>;
|
keyboardLayout$: Observable<KeyboardLayout>;
|
||||||
allowLayerDoubleTap$: Observable<boolean>;
|
allowLayerDoubleTap$: Observable<boolean>;
|
||||||
lastEditedKey$: Observable<LastEditedKey>;
|
lastEditedKey$: Observable<LastEditedKey>;
|
||||||
|
halvesInfo$: Observable<HalvesInfo>;
|
||||||
keymap: Keymap;
|
keymap: Keymap;
|
||||||
|
|
||||||
private routeSubscription: Subscription;
|
private routeSubscription: Subscription;
|
||||||
@@ -67,6 +67,7 @@ export class KeymapEditComponent implements OnDestroy {
|
|||||||
this.keyboardLayout$ = store.select(getKeyboardLayout);
|
this.keyboardLayout$ = store.select(getKeyboardLayout);
|
||||||
this.allowLayerDoubleTap$ = store.select(layerDoubleTapSupported);
|
this.allowLayerDoubleTap$ = store.select(layerDoubleTapSupported);
|
||||||
this.lastEditedKey$ = store.select(lastEditedKey);
|
this.lastEditedKey$ = store.select(lastEditedKey);
|
||||||
|
this.halvesInfo$ = store.select(getHalvesInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
@@ -94,11 +95,6 @@ export class KeymapEditComponent implements OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('window:keydown.alt.s', ['$event'])
|
|
||||||
toggleKeyboardSplit() {
|
|
||||||
this.keyboardSplit = !this.keyboardSplit;
|
|
||||||
}
|
|
||||||
|
|
||||||
descriptionChanged(event: ChangeKeymapDescription): void {
|
descriptionChanged(event: ChangeKeymapDescription): void {
|
||||||
this.store.dispatch(new EditDescriptionAction(event));
|
this.store.dispatch(new EditDescriptionAction(event));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
[keyActions]="moduleConfig[i].keyActions"
|
[keyActions]="moduleConfig[i].keyActions"
|
||||||
[selectedKey]="selectedKey"
|
[selectedKey]="selectedKey"
|
||||||
[@split]="moduleAnimationStates[i]"
|
[@split]="moduleAnimationStates[i]"
|
||||||
|
[@fadeKeyboard]="moduleVisibilityAnimationStates[i]"
|
||||||
[selected]="selectedKey?.moduleId === i"
|
[selected]="selectedKey?.moduleId === i"
|
||||||
[lastEdited]="lastEditedKey?.moduleId === i"
|
[lastEdited]="lastEditedKey?.moduleId === i"
|
||||||
[lastEditedKeyId]="lastEditedKey?.key"
|
[lastEditedKeyId]="lastEditedKey?.key"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -1,7 +1,7 @@
|
|||||||
import { Component, EventEmitter, Input, Output, SimpleChanges, ChangeDetectionStrategy } from '@angular/core';
|
import { Component, EventEmitter, Input, Output, SimpleChanges, ChangeDetectionStrategy } from '@angular/core';
|
||||||
import { animate, state, trigger, style, transition } from '@angular/animations';
|
import { animate, state, trigger, style, transition } from '@angular/animations';
|
||||||
import { DomSanitizer, SafeStyle } from '@angular/platform-browser';
|
import { DomSanitizer, SafeStyle } from '@angular/platform-browser';
|
||||||
import { Module } from 'uhk-common';
|
import { HalvesInfo, Module } from 'uhk-common';
|
||||||
|
|
||||||
import { SvgModule } from '../module';
|
import { SvgModule } from '../module';
|
||||||
import { SvgModuleProviderService } from '../../../services/svg-module-provider.service';
|
import { SvgModuleProviderService } from '../../../services/svg-module-provider.service';
|
||||||
@@ -23,13 +23,23 @@ import { LastEditedKey } from '../../../models';
|
|||||||
animations: [
|
animations: [
|
||||||
trigger('split', [
|
trigger('split', [
|
||||||
state('rotateLeft', style({
|
state('rotateLeft', style({
|
||||||
transform: 'translate(-3%, 15%) rotate(4deg) scale(0.92, 0.92)'
|
transform: 'translate(2%, 30%) rotate(10.8deg) scale(0.80, 0.80)'
|
||||||
})),
|
})),
|
||||||
state('rotateRight', style({
|
state('rotateRight', style({
|
||||||
transform: 'translate(3%, 15%) rotate(-4deg) scale(0.92, 0.92)'
|
transform: 'translate(-2%, 30.7%) rotate(-10deg) scale(0.80, 0.80)'
|
||||||
})),
|
})),
|
||||||
transition('* <=> *', animate(500))
|
transition('* <=> *', animate(500))
|
||||||
]),
|
]),
|
||||||
|
trigger('fadeKeyboard', [
|
||||||
|
state('visible', style({
|
||||||
|
opacity: 1
|
||||||
|
})),
|
||||||
|
state('invisible', style({
|
||||||
|
opacity: 0
|
||||||
|
})),
|
||||||
|
transition('visible => invisible', animate(500)),
|
||||||
|
transition('invisible => visible', animate(500))
|
||||||
|
]),
|
||||||
trigger('fadeSeparator', [
|
trigger('fadeSeparator', [
|
||||||
state('visible', style({
|
state('visible', style({
|
||||||
opacity: 1
|
opacity: 1
|
||||||
@@ -37,8 +47,8 @@ import { LastEditedKey } from '../../../models';
|
|||||||
state('invisible', style({
|
state('invisible', style({
|
||||||
opacity: 0
|
opacity: 0
|
||||||
})),
|
})),
|
||||||
transition('visible => invisible', animate(500)),
|
transition('visible => invisible', animate('200ms')),
|
||||||
transition('invisible => visible', animate(1500))
|
transition('invisible => visible', animate('200ms 500ms'))
|
||||||
])
|
])
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
@@ -47,7 +57,7 @@ export class SvgKeyboardComponent {
|
|||||||
@Input() capturingEnabled: boolean;
|
@Input() capturingEnabled: boolean;
|
||||||
@Input() selectedKey: { layerId: number, moduleId: number, keyId: number };
|
@Input() selectedKey: { layerId: number, moduleId: number, keyId: number };
|
||||||
@Input() selected: boolean;
|
@Input() selected: boolean;
|
||||||
@Input() halvesSplit: boolean;
|
@Input() halvesInfo: HalvesInfo;
|
||||||
@Input() keyboardLayout = KeyboardLayout.ANSI;
|
@Input() keyboardLayout = KeyboardLayout.ANSI;
|
||||||
@Input() description: string;
|
@Input() description: string;
|
||||||
@Input() showDescription = false;
|
@Input() showDescription = false;
|
||||||
@@ -60,6 +70,7 @@ export class SvgKeyboardComponent {
|
|||||||
modules: SvgModule[];
|
modules: SvgModule[];
|
||||||
viewBox: string;
|
viewBox: string;
|
||||||
moduleAnimationStates: string[];
|
moduleAnimationStates: string[];
|
||||||
|
moduleVisibilityAnimationStates: string[];
|
||||||
separator: SvgSeparator;
|
separator: SvgSeparator;
|
||||||
separatorStyle: SafeStyle;
|
separatorStyle: SafeStyle;
|
||||||
separatorAnimation = 'visible';
|
separatorAnimation = 'visible';
|
||||||
@@ -68,7 +79,6 @@ export class SvgKeyboardComponent {
|
|||||||
private sanitizer: DomSanitizer) {
|
private sanitizer: DomSanitizer) {
|
||||||
this.modules = [];
|
this.modules = [];
|
||||||
this.viewBox = '-520 582 1100 470';
|
this.viewBox = '-520 582 1100 470';
|
||||||
this.halvesSplit = false;
|
|
||||||
this.moduleAnimationStates = [];
|
this.moduleAnimationStates = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +87,7 @@ export class SvgKeyboardComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges) {
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
if (changes.halvesSplit) {
|
if (changes.halvesInfo) {
|
||||||
this.updateModuleAnimationStates();
|
this.updateModuleAnimationStates();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,12 +120,19 @@ export class SvgKeyboardComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private updateModuleAnimationStates() {
|
private updateModuleAnimationStates() {
|
||||||
if (this.halvesSplit) {
|
if (this.halvesInfo.areHalvesMerged) {
|
||||||
this.moduleAnimationStates = ['rotateRight', 'rotateLeft'];
|
|
||||||
this.separatorAnimation = 'invisible';
|
|
||||||
} else {
|
|
||||||
this.moduleAnimationStates = [];
|
this.moduleAnimationStates = [];
|
||||||
this.separatorAnimation = 'visible';
|
this.separatorAnimation = 'visible';
|
||||||
|
} else {
|
||||||
|
this.moduleAnimationStates = ['rotateRight', 'rotateLeft'];
|
||||||
|
this.separatorAnimation = 'invisible';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.halvesInfo.isLeftHalfConnected) {
|
||||||
|
this.moduleVisibilityAnimationStates = ['visible', 'visible'];
|
||||||
|
} else {
|
||||||
|
this.moduleVisibilityAnimationStates = ['visible', 'invisible'];
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
[currentLayer]="currentLayer"
|
[currentLayer]="currentLayer"
|
||||||
[capturingEnabled]="popoverEnabled"
|
[capturingEnabled]="popoverEnabled"
|
||||||
[selectedKey]="selectedKey"
|
[selectedKey]="selectedKey"
|
||||||
[halvesSplit]="halvesSplit"
|
[halvesInfo]="halvesInfo"
|
||||||
[keyboardLayout]="keyboardLayout"
|
[keyboardLayout]="keyboardLayout"
|
||||||
[description]="keymap.description"
|
[description]="keymap.description"
|
||||||
[lastEditedKey]="lastEditedKey"
|
[lastEditedKey]="lastEditedKey"
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { Store } from '@ngrx/store';
|
|||||||
import {
|
import {
|
||||||
camelCaseToSentence,
|
camelCaseToSentence,
|
||||||
capitalizeFirstLetter,
|
capitalizeFirstLetter,
|
||||||
|
HalvesInfo,
|
||||||
KeyAction,
|
KeyAction,
|
||||||
Keymap,
|
Keymap,
|
||||||
KeystrokeAction,
|
KeystrokeAction,
|
||||||
@@ -65,7 +66,7 @@ export class SvgKeyboardWrapComponent implements OnInit, OnChanges {
|
|||||||
@Input() keymap: Keymap;
|
@Input() keymap: Keymap;
|
||||||
@Input() popoverEnabled: boolean = true;
|
@Input() popoverEnabled: boolean = true;
|
||||||
@Input() tooltipEnabled: boolean = false;
|
@Input() tooltipEnabled: boolean = false;
|
||||||
@Input() halvesSplit: boolean;
|
@Input() halvesInfo: HalvesInfo;
|
||||||
@Input() keyboardLayout: KeyboardLayout.ANSI;
|
@Input() keyboardLayout: KeyboardLayout.ANSI;
|
||||||
@Input() allowLayerDoubleTap: boolean;
|
@Input() allowLayerDoubleTap: boolean;
|
||||||
@Input() lastEditedKey: LastEditedKey;
|
@Input() lastEditedKey: LastEditedKey;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Router } from '@angular/router';
|
|||||||
import { Action, Store } from '@ngrx/store';
|
import { Action, Store } from '@ngrx/store';
|
||||||
import { Actions, Effect, ofType } from '@ngrx/effects';
|
import { Actions, Effect, ofType } from '@ngrx/effects';
|
||||||
import { EMPTY, Observable, of, timer } from 'rxjs';
|
import { EMPTY, Observable, of, timer } from 'rxjs';
|
||||||
import { map, mergeMap, switchMap, tap, withLatestFrom } from 'rxjs/operators';
|
import { distinctUntilChanged, map, mergeMap, switchMap, tap, withLatestFrom } from 'rxjs/operators';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FirmwareUpgradeIpcResponse,
|
FirmwareUpgradeIpcResponse,
|
||||||
@@ -73,6 +73,14 @@ export class DeviceEffects {
|
|||||||
|
|
||||||
return this.router.navigate(['/detection']);
|
return this.router.navigate(['/detection']);
|
||||||
}),
|
}),
|
||||||
|
distinctUntilChanged((
|
||||||
|
[prevAction, prevRoute, prevConnected],
|
||||||
|
[currAction, currRoute, currConnected]) => {
|
||||||
|
|
||||||
|
return prevConnected === currConnected &&
|
||||||
|
prevAction.payload.hasPermission === currAction.payload.hasPermission &&
|
||||||
|
prevAction.payload.zeroInterfaceAvailable === currAction.payload.zeroInterfaceAvailable;
|
||||||
|
}),
|
||||||
switchMap(([action, route, connected]) => {
|
switchMap(([action, route, connected]) => {
|
||||||
const payload = action.payload;
|
const payload = action.payload;
|
||||||
|
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ export const bootloaderActive = createSelector(deviceState, fromDevice.bootloade
|
|||||||
export const firmwareUpgradeFailed = createSelector(deviceState, fromDevice.firmwareUpgradeFailed);
|
export const firmwareUpgradeFailed = createSelector(deviceState, fromDevice.firmwareUpgradeFailed);
|
||||||
export const firmwareUpgradeSuccess = createSelector(deviceState, fromDevice.firmwareUpgradeSuccess);
|
export const firmwareUpgradeSuccess = createSelector(deviceState, fromDevice.firmwareUpgradeSuccess);
|
||||||
export const getUpdateUdevRules = createSelector(deviceState, fromDevice.updateUdevRules);
|
export const getUpdateUdevRules = createSelector(deviceState, fromDevice.updateUdevRules);
|
||||||
|
export const getHalvesInfo = createSelector(deviceState, fromDevice.halvesInfo);
|
||||||
|
|
||||||
export const getPrivilegePageState = createSelector(appState, getUpdateUdevRules, (app, updateUdevRules): PrivilagePageSate => {
|
export const getPrivilegePageState = createSelector(appState, getUpdateUdevRules, (app, updateUdevRules): PrivilagePageSate => {
|
||||||
const permissionSetupFailed = !!app.permissionError;
|
const permissionSetupFailed = !!app.permissionError;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Action } from '@ngrx/store';
|
import { Action } from '@ngrx/store';
|
||||||
import { HardwareModules, UdevRulesInfo } from 'uhk-common';
|
import { HardwareModules, UdevRulesInfo, HalvesInfo } from 'uhk-common';
|
||||||
|
|
||||||
import * as Device from '../actions/device';
|
import * as Device from '../actions/device';
|
||||||
import * as App from '../actions/app';
|
import * as App from '../actions/app';
|
||||||
@@ -24,6 +24,7 @@ export interface State {
|
|||||||
log: Array<XtermLog>;
|
log: Array<XtermLog>;
|
||||||
restoringUserConfiguration: boolean;
|
restoringUserConfiguration: boolean;
|
||||||
hasBackupUserConfiguration: boolean;
|
hasBackupUserConfiguration: boolean;
|
||||||
|
halvesInfo: HalvesInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialState: State = {
|
export const initialState: State = {
|
||||||
@@ -47,7 +48,8 @@ export const initialState: State = {
|
|||||||
},
|
},
|
||||||
log: [{ message: '', cssClass: XtermCssClass.standard }],
|
log: [{ message: '', cssClass: XtermCssClass.standard }],
|
||||||
restoringUserConfiguration: false,
|
restoringUserConfiguration: false,
|
||||||
hasBackupUserConfiguration: false
|
hasBackupUserConfiguration: false,
|
||||||
|
halvesInfo: { isLeftHalfConnected: true, areHalvesMerged: true }
|
||||||
};
|
};
|
||||||
|
|
||||||
export function reducer(state = initialState, action: Action): State {
|
export function reducer(state = initialState, action: Action): State {
|
||||||
@@ -60,7 +62,8 @@ export function reducer(state = initialState, action: Action): State {
|
|||||||
hasPermission: data.hasPermission,
|
hasPermission: data.hasPermission,
|
||||||
zeroInterfaceAvailable: data.zeroInterfaceAvailable,
|
zeroInterfaceAvailable: data.zeroInterfaceAvailable,
|
||||||
bootloaderActive: data.bootloaderActive,
|
bootloaderActive: data.bootloaderActive,
|
||||||
udevRuleInfo: data.udevRulesInfo
|
udevRuleInfo: data.udevRulesInfo,
|
||||||
|
halvesInfo: data.halvesInfo
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,3 +252,4 @@ export const bootloaderActive = (state: State) => state.bootloaderActive;
|
|||||||
export const firmwareUpgradeFailed = (state: State) => state.firmwareUpdateFailed;
|
export const firmwareUpgradeFailed = (state: State) => state.firmwareUpdateFailed;
|
||||||
export const firmwareUpgradeSuccess = (state: State) => state.firmwareUpdateSuccess;
|
export const firmwareUpgradeSuccess = (state: State) => state.firmwareUpdateSuccess;
|
||||||
export const updateUdevRules = (state: State) => state.udevRuleInfo === UdevRulesInfo.Different;
|
export const updateUdevRules = (state: State) => state.udevRuleInfo === UdevRulesInfo.Different;
|
||||||
|
export const halvesInfo = (state: State) => state.halvesInfo;
|
||||||
|
|||||||
Reference in New Issue
Block a user