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:
Róbert Kiss
2019-07-25 23:41:20 +02:00
committed by László Monda
parent a409c219d8
commit cbccaba1c5
18 changed files with 149 additions and 80 deletions

View File

@@ -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;

View File

@@ -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);
@@ -192,7 +196,7 @@ export class DeviceService {
response.success = true; response.success = true;
response.modules = await this.getHardwareModules(false); response.modules = await this.getHardwareModules(false);
} catch (error) { } catch (error) {
const err = {message: error.message, stack: error.stack}; const err = { message: error.message, stack: error.stack };
this.logService.error('[DeviceService] updateFirmware error', err); this.logService.error('[DeviceService] updateFirmware error', err);
response.modules = await this.getHardwareModules(true); response.modules = await this.getHardwareModules(true);
@@ -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,14 +218,14 @@ 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();
response.modules = await this.getHardwareModules(false); response.modules = await this.getHardwareModules(false);
response.success = true; response.success = true;
} catch (error) { } catch (error) {
const err = {message: error.message, stack: error.stack}; const err = { message: error.message, stack: error.stack };
this.logService.error('[DeviceService] updateFirmware error', err); this.logService.error('[DeviceService] updateFirmware error', err);
response.modules = await this.getHardwareModules(true); response.modules = await this.getHardwareModules(true);
@@ -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;
}
} }

View File

@@ -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;
} }

View File

@@ -0,0 +1,4 @@
export interface HalvesInfo {
areHalvesMerged: boolean;
isLeftHalfConnected: boolean;
}

View File

@@ -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';

View File

@@ -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}

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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"

View File

@@ -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));
} }

View File

@@ -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

View File

@@ -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'];
} }
} }

View File

@@ -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"

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;