fix(user-config): Layer switcher key behaviour on non-base layers (#440)

* refactor(user-config): Optimize imports

* feat(user-config): Clone SwitchLayerAction to destination layer

* fix(user-config): Fix Keymap SwitchLayerAction normalization

* test(user-config): Remove spy callThrough

* build: Add uhk-common test runner

* build: delete test serialization files

* fix(user-config): Add missing "type": "basic" properties to the user-config.json

* test(user-config): Add KeyMacroAction tests

* fix(user-config): Delete SwitchLayerAction from non destination layer

* fix(user-config): Keymap normalize delete SwitchLayerActions from non base layers

* ci: turn of uhk-web tests

* ci: turn off karma watch mode in uhk-web test
This commit is contained in:
Róbert Kiss
2017-10-13 12:25:57 +02:00
committed by László Monda
parent 46b97a9b62
commit 7baf9ad009
16 changed files with 948 additions and 8637 deletions
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
@@ -1,8 +1,7 @@
import { UhkBuffer } from '../uhk-buffer';
import { Layer } from './layer';
import { Macro } from './macro';
import { SwitchLayerAction } from './key-action/switch-layer-action';
import { KeyAction } from './key-action/key-action';
import { KeyActionHelper, SwitchLayerAction } from './key-action';
import { UserConfiguration } from './user-configuration';
export class Keymap {
@@ -97,30 +96,64 @@ export class Keymap {
}
private normalize() {
// Removes all the SwitchLayerActions from any non base layer
for (let i = 1; i < this.layers.length; ++i) {
for (const module of this.layers[i].modules) {
module.keyActions = module.keyActions.map(keyAction => {
if (keyAction instanceof SwitchLayerAction) {
return undefined;
}
return keyAction;
});
}
if (this.layers.length < 1) {
return;
}
// Adds the SwitchLayerActions from the base layer to any none base layer
const baseLayerModules = this.layers[0].modules;
for (let i = 0; i < baseLayerModules.length; ++i) {
baseLayerModules[i].keyActions.forEach((keyAction: KeyAction, keyActionIndex: number) => {
if (keyAction instanceof SwitchLayerAction) {
for (let j = 1; j < this.layers.length; ++j) {
this.layers[j].modules[i].keyActions[keyActionIndex] = new SwitchLayerAction(keyAction);
for (let moduleId = 0; moduleId < this.layers[0].modules.length && moduleId < 2; moduleId++) {
const baseModule = this.layers[0].modules[moduleId];
for (let keyActionId = 0; keyActionId < baseModule.keyActions.length; keyActionId++) {
const baseKeyAction = baseModule.keyActions[keyActionId];
if (baseKeyAction instanceof SwitchLayerAction) {
const destinationLayerId = baseKeyAction.layer + 1;
if (this.layers.length < destinationLayerId) {
// TODO: What should we do???
console.error(`${this.name} has not enough layer. Need: ${destinationLayerId}`);
}
}
});
for (let currentLayerId = 1; currentLayerId < this.layers.length; currentLayerId++) {
const currentLayer = this.layers[currentLayerId];
if (currentLayer.modules.length < moduleId) {
// TODO: What should we do???
console.error(`${this.name}.layers[${currentLayerId}] has not enough module. Need: ${moduleId}`);
continue;
}
const currentModule = currentLayer.modules[moduleId];
const currentKeyAction = currentModule.keyActions[keyActionId];
if (baseKeyAction instanceof SwitchLayerAction) {
if (currentLayerId - 1 === baseKeyAction.layer) {
if (currentKeyAction instanceof SwitchLayerAction) {
if (currentKeyAction.layer === baseKeyAction.layer &&
currentKeyAction.isLayerToggleable === baseKeyAction.isLayerToggleable) {
continue;
}
// tslint:disable-next-line: max-line-length
const error = `${this.name}.layers[${currentLayerId}]modules[${moduleId}].keyActions[${keyActionId}]` +
` is different switch layer. ${currentKeyAction} will be override with ${baseKeyAction}`;
console.warn(error);
} else {
// tslint:disable-next-line: max-line-length
const error = `${this.name}.layers[${currentLayerId}]modules[${moduleId}].keyActions[${keyActionId}]` +
` is not switch layer. ${currentKeyAction} will be override with ${baseKeyAction}`;
console.warn(error);
}
currentModule.keyActions[keyActionId] = KeyActionHelper.createKeyAction(baseKeyAction);
}
}
else {
if (currentKeyAction instanceof SwitchLayerAction) {
// tslint:disable-next-line: max-line-length
const error = `${this.name}.layers[${currentLayerId}]modules[${moduleId}].keyActions[${keyActionId}]` +
` is switch layer action, but the base key action is not switch layer action, so will delete`;
console.warn(error);
currentModule.keyActions[keyActionId] = null;
}
}
}
}
}
}
}
@@ -0,0 +1,29 @@
import { KeyMacroAction } from './key-macro-action';
import { binaryDefaultHelper, jsonDefaultHelper } from '../../../../test/serializer-test-helper';
import { MacroSubAction } from './macro-action';
import { KeystrokeType } from '../key-action';
describe('key-macro-action', () => {
it('should be instantiate', () => {
const action = new KeyMacroAction();
expect(action).toBeTruthy();
});
describe('full serialization', () => {
it('should json match', () => {
const action = new KeyMacroAction();
action.action = MacroSubAction.hold;
action.type = KeystrokeType.basic;
action.scancode = 100;
jsonDefaultHelper(action);
});
it('should binary match', () => {
const action = new KeyMacroAction();
action.action = MacroSubAction.hold;
action.type = KeystrokeType.basic;
action.scancode = 100;
binaryDefaultHelper(action);
});
});
});
@@ -0,0 +1,257 @@
import { UserConfiguration } from '../user-configuration';
describe('keymap', () => {
it('should normalize SwitchLayerAction if non base layer action is not SwitchLayerAction', () => {
const inputJsonConfig = {
dataModelVersion: 1,
moduleConfigurations: [],
macros: [],
keymaps: [
{
isDefault: true,
abbreviation: 'QWR',
name: 'QWERTY',
description: '',
layers: [
{
modules: [{
id: 0,
pointerRole: 'move',
keyActions: [
{
keyActionType: 'switchLayer',
layer: 'mod',
toggle: false
}
]
}]
},
{
modules: [{
id: 0,
pointerRole: 'move',
keyActions: [
{
keyActionType: 'keystroke',
type: 'basic',
scancode: 44
}
]
}]
},
{
modules: [{
id: 0,
pointerRole: 'move',
keyActions: [
null
]
}]
},
{
modules: [{
id: 0,
pointerRole: 'move',
keyActions: [
null
]
}]
}
]
}
]
};
const expectedJsonConfig = {
dataModelVersion: 1,
moduleConfigurations: [],
macros: [],
keymaps: [
{
isDefault: true,
abbreviation: 'QWR',
name: 'QWERTY',
description: '',
layers: [
{
modules: [{
id: 0,
pointerRole: 'move',
keyActions: [
{
keyActionType: 'switchLayer',
layer: 'mod',
toggle: false
}
]
}]
},
{
modules: [{
id: 0,
pointerRole: 'move',
keyActions: [
{
keyActionType: 'switchLayer',
layer: 'mod',
toggle: false
}
]
}]
},
{
modules: [{
id: 0,
pointerRole: 'move',
keyActions: [
null
]
}]
},
{
modules: [{
id: 0,
pointerRole: 'move',
keyActions: [
null
]
}]
}
]
}
]
};
spyOn(console, 'warn');
const inputUserConfig = new UserConfiguration().fromJsonObject(inputJsonConfig);
expect(inputUserConfig.toJsonObject()).toEqual(expectedJsonConfig);
// tslint:disable-next-line: max-line-length
expect(console.warn).toHaveBeenCalledWith('QWERTY.layers[1]modules[0].keyActions[0] is not switch layer. <KeystrokeAction type="basic" scancode="44"> will be override with <SwitchLayerAction layer="0" toggle="false">');
});
it('should normalize SwitchLayerAction if non base layer action is other SwitchLayerAction', () => {
const inputJsonConfig = {
dataModelVersion: 1,
moduleConfigurations: [],
macros: [],
keymaps: [
{
isDefault: true,
abbreviation: 'QWR',
name: 'QWERTY',
description: '',
layers: [
{
modules: [{
id: 0,
pointerRole: 'move',
keyActions: [
{
keyActionType: 'switchLayer',
layer: 'mod',
toggle: false
}
]
}]
},
{
modules: [{
id: 0,
pointerRole: 'move',
keyActions: [
{
keyActionType: 'switchLayer',
layer: 'fn',
toggle: false
}
]
}]
},
{
modules: [{
id: 0,
pointerRole: 'move',
keyActions: [
null
]
}]
},
{
modules: [{
id: 0,
pointerRole: 'move',
keyActions: [
null
]
}]
}
]
}
]
};
const expectedJsonConfig = {
dataModelVersion: 1,
moduleConfigurations: [],
macros: [],
keymaps: [
{
isDefault: true,
abbreviation: 'QWR',
name: 'QWERTY',
description: '',
layers: [
{
modules: [{
id: 0,
pointerRole: 'move',
keyActions: [
{
keyActionType: 'switchLayer',
layer: 'mod',
toggle: false
}
]
}]
},
{
modules: [{
id: 0,
pointerRole: 'move',
keyActions: [
{
keyActionType: 'switchLayer',
layer: 'mod',
toggle: false
}
]
}]
},
{
modules: [{
id: 0,
pointerRole: 'move',
keyActions: [
null
]
}]
},
{
modules: [{
id: 0,
pointerRole: 'move',
keyActions: [
null
]
}]
}
]
}
]
};
spyOn(console, 'warn');
const inputUserConfig = new UserConfiguration().fromJsonObject(inputJsonConfig);
expect(inputUserConfig.toJsonObject()).toEqual(expectedJsonConfig);
// tslint:disable-next-line: max-line-length
expect(console.warn).toHaveBeenCalledWith('QWERTY.layers[1]modules[0].keyActions[0] is different switch layer. <SwitchLayerAction layer="1" toggle="false"> will be override with <SwitchLayerAction layer="0" toggle="false">');
});
});
+2 -1
View File
@@ -6,7 +6,7 @@
"ng": "ng",
"start": "ng serve",
"build": "ng build --prod --aot --base-href=\"\"",
"test": "ng test",
"test": "ng test --watch false",
"lint": "ng lint",
"e2e": "ng e2e",
"build:renderer": "webpack --config webpack.config.js",
@@ -66,6 +66,7 @@
"karma-jasmine": "1.1.0",
"karma-jasmine-html-reporter": "0.2.2",
"less-loader": "4.0.5",
"lodash": "4.17.4",
"ng2-dragula": "1.5.0",
"ng2-select2": "1.0.0-beta.10",
"ngrx-store-freeze": "0.1.9",
@@ -7074,7 +7074,8 @@
"macroActionType": "key",
"action": "press",
"scancode": 15,
"modifierMask": 1
"modifierMask": 1,
"type": "basic"
},
{
"macroActionType": "text",
@@ -7083,7 +7084,8 @@
{
"macroActionType": "key",
"action": "press",
"scancode": 40
"scancode": 40,
"type": "basic"
}
]
},
@@ -7099,4 +7101,4 @@
]
}
]
}
}
@@ -0,0 +1,396 @@
import { reducer, initialState } from './user-configuration';
import { KeystrokeAction, KeystrokeType, SwitchLayerAction, UserConfiguration, LayerName, Keymap } from 'uhk-common';
import { getDefaultUserConfig } from '../../../../test/user-config-helper';
import { KeymapActions } from '../actions';
describe('user-configuration reducer', () => {
it('should be initiate with default state', () => {
const result = reducer(undefined, {} as any);
expect(result).toEqual(initialState);
});
describe('SAVE_KEY', () => {
it('should process KeyStrokeAction', () => {
const defaultUserConfig = new UserConfiguration().fromJsonObject(getDefaultUserConfig());
const state = new UserConfiguration().fromJsonObject(getDefaultUserConfig());
const keystrokeAction = new KeystrokeAction({_scancode: 100, type: KeystrokeType.basic} as any);
const saveKeyAction: KeymapActions.SaveKeyAction = {
type: KeymapActions.SAVE_KEY,
payload: {
keymap: new Keymap(defaultUserConfig.keymaps[0]),
layer: 0,
module: 0,
key: 0,
keyAction: keystrokeAction
}
};
const result = reducer(state, saveKeyAction);
const expectedKeyAction = <KeystrokeAction>result.keymaps[0].layers[0].modules[0].keyActions[0];
expect(expectedKeyAction).toEqual(keystrokeAction);
expect(result).not.toBe(defaultUserConfig);
// check key actions not changed on other layers
for (let i = 1; i < result.keymaps[0].layers.length; i++) {
const keyAction = result.keymaps[0].layers[i].modules[0].keyActions[0];
const defaultKeyAction = defaultUserConfig.keymaps[0].layers[i].modules[0].keyActions[0];
expect(keyAction).toEqual(defaultKeyAction);
}
});
it('should copy the SwitchLayerAction to the destination layer', () => {
const defaultUserConfig = new UserConfiguration().fromJsonObject(getDefaultUserConfig());
const state = new UserConfiguration().fromJsonObject(getDefaultUserConfig());
const destinationLayerId = LayerName.mod;
const switchLayerAction = new SwitchLayerAction({isLayerToggleable: false, layer: destinationLayerId} as any);
const saveKeyAction: KeymapActions.SaveKeyAction = {
type: KeymapActions.SAVE_KEY,
payload: {
keymap: new Keymap(defaultUserConfig.keymaps[0]),
layer: 0,
module: 0,
key: 0,
keyAction: switchLayerAction
}
};
const result = reducer(state, saveKeyAction);
expect(result).not.toBe(defaultUserConfig);
expect(result.toJsonObject()).toEqual({
dataModelVersion: 4,
moduleConfigurations: [
{
id: 1,
initialPointerSpeed: 1,
pointerAcceleration: 5,
maxPointerSpeed: 200
}
],
keymaps: [
{
isDefault: true,
abbreviation: 'QWR',
name: 'QWERTY',
description: '',
layers: [
{
modules: [
{
id: 0,
pointerRole: 'move',
keyActions: [
{
keyActionType: 'switchLayer',
layer: 'mod',
toggle: false
},
{
keyActionType: 'keystroke',
type: 'basic',
scancode: 37
},
{
'keyActionType': 'switchLayer',
'layer': 'mod',
'toggle': false
}
]
},
{
id: 1,
pointerRole: 'move',
keyActions: [
{
keyActionType: 'keystroke',
type: 'basic',
scancode: 53
},
{
keyActionType: 'keystroke',
type: 'basic',
scancode: 30
},
null
]
},
{
id: 2,
pointerRole: 'scroll',
keyActions: []
}
]
},
{
modules: [
{
id: 0,
pointerRole: 'none',
keyActions: [
{
keyActionType: 'switchLayer',
layer: 'mod',
toggle: false
},
{
keyActionType: 'keystroke',
type: 'basic',
scancode: 65
},
{
'keyActionType': 'switchLayer',
'layer': 'mod',
'toggle': false
}
]
},
{
id: 1,
pointerRole: 'none',
keyActions: [
{
keyActionType: 'keystroke',
type: 'basic',
scancode: 41
},
{
keyActionType: 'keystroke',
type: 'basic',
scancode: 58
},
null
]
}
]
},
{
modules: [
{
id: 0,
pointerRole: 'move',
keyActions: [
null,
null,
null
]
},
{
id: 1,
pointerRole: 'scroll',
keyActions: [
null,
{
keyActionType: 'switchKeymap',
keymapAbbreviation: 'DVO'
},
null
]
}
]
},
{
modules: [
{
id: 0,
pointerRole: 'move',
keyActions: [
null,
null,
null
]
},
{
id: 1,
pointerRole: 'move',
keyActions: [
null,
null,
null
]
}
]
}
]
}
],
macros: []
});
});
it('should copy the SwitchLayerAction to the destination layer and clear the modified', () => {
const defaultUserConfig = new UserConfiguration().fromJsonObject(getDefaultUserConfig());
const state = new UserConfiguration().fromJsonObject(getDefaultUserConfig());
const destinationLayerId = LayerName.fn;
const switchLayerAction = new SwitchLayerAction({isLayerToggleable: false, layer: destinationLayerId} as any);
const saveKeyAction: KeymapActions.SaveKeyAction = {
type: KeymapActions.SAVE_KEY,
payload: {
keymap: new Keymap(defaultUserConfig.keymaps[0]),
layer: 0,
module: 0,
key: 2,
keyAction: switchLayerAction
}
};
const result = reducer(state, saveKeyAction);
expect(result).not.toBe(defaultUserConfig);
expect(result.toJsonObject()).toEqual({
dataModelVersion: 4,
moduleConfigurations: [
{
id: 1,
initialPointerSpeed: 1,
pointerAcceleration: 5,
maxPointerSpeed: 200
}
],
keymaps: [
{
isDefault: true,
abbreviation: 'QWR',
name: 'QWERTY',
description: '',
layers: [
{
modules: [
{
id: 0,
pointerRole: 'move',
keyActions: [
{
keyActionType: 'keystroke',
type: 'basic',
scancode: 36
},
{
keyActionType: 'keystroke',
type: 'basic',
scancode: 37
},
{
'keyActionType': 'switchLayer',
'layer': 'fn',
'toggle': false
}
]
},
{
id: 1,
pointerRole: 'move',
keyActions: [
{
keyActionType: 'keystroke',
type: 'basic',
scancode: 53
},
{
keyActionType: 'keystroke',
type: 'basic',
scancode: 30
},
null
]
},
{
id: 2,
pointerRole: 'scroll',
keyActions: []
}
]
},
{
modules: [
{
id: 0,
pointerRole: 'none',
keyActions: [
{
keyActionType: 'keystroke',
type: 'basic',
scancode: 64
},
{
keyActionType: 'keystroke',
type: 'basic',
scancode: 65
},
null
]
},
{
id: 1,
pointerRole: 'none',
keyActions: [
{
keyActionType: 'keystroke',
type: 'basic',
scancode: 41
},
{
keyActionType: 'keystroke',
type: 'basic',
scancode: 58
},
null
]
}
]
},
{
modules: [
{
id: 0,
pointerRole: 'move',
keyActions: [
null,
null,
{
keyActionType: 'switchLayer',
layer: 'fn',
toggle: false
}
]
},
{
id: 1,
pointerRole: 'scroll',
keyActions: [
null,
{
keyActionType: 'switchKeymap',
keymapAbbreviation: 'DVO'
},
null
]
}
]
},
{
modules: [
{
id: 0,
pointerRole: 'move',
keyActions: [
null,
null,
null
]
},
{
id: 1,
pointerRole: 'move',
keyActions: [
null,
null,
null
]
}
]
}
]
}
],
macros: []
});
});
});
});
@@ -4,7 +4,7 @@ import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/map';
import { Keymap, KeyActionHelper, Layer, Macro, Module, UserConfiguration } from 'uhk-common';
import { KeyAction, Keymap, KeyActionHelper, Layer, Macro, Module, SwitchLayerAction, UserConfiguration } from 'uhk-common';
import { KeymapActions, MacroActions } from '../actions';
import { AppState } from '../index';
import { ActionTypes } from '../actions/user-config';
@@ -107,21 +107,31 @@ export function reducer(state = initialState, action: Action & { payload?: any }
break;
case KeymapActions.SAVE_KEY: {
const keyIndex: number = action.payload.key;
const layerIndex: number = action.payload.layer;
const moduleIndex: number = action.payload.module;
const newKeyAction = KeyActionHelper.createKeyAction(action.payload.keyAction);
const newKeymap: Keymap = Object.assign(new Keymap(), action.payload.keymap);
newKeymap.layers = newKeymap.layers.slice();
const layerIndex: number = action.payload.layer;
const newLayer: Layer = Object.assign(new Layer(), newKeymap.layers[layerIndex]);
newKeymap.layers[layerIndex] = newLayer;
newKeymap.layers = newKeymap.layers.map((layer, index) => {
const newLayer = Object.assign(new Layer(), layer);
const moduleIndex: number = action.payload.module;
const newModule: Module = Object.assign(new Module(), newLayer.modules[moduleIndex]);
newLayer.modules = newLayer.modules.slice();
newLayer.modules[moduleIndex] = newModule;
const keyIndex: number = action.payload.key;
newModule.keyActions = newModule.keyActions.slice();
newModule.keyActions[keyIndex] = KeyActionHelper.createKeyAction(action.payload.keyAction);
if (index === layerIndex) {
setKeyActionToLayer(newLayer, moduleIndex, keyIndex, newKeyAction);
}
// If the key action is a SwitchLayerAction then set the same SwitchLayerAction
// on the target layer
else if (newKeyAction instanceof SwitchLayerAction) {
if (index - 1 === newKeyAction.layer) {
const clonedAction = KeyActionHelper.createKeyAction(action.payload.keyAction);
setKeyActionToLayer(newLayer, moduleIndex, keyIndex, clonedAction);
}else {
setKeyActionToLayer(newLayer, moduleIndex, keyIndex, null);
}
}
return newLayer;
});
changedUserConfiguration.keymaps = state.keymaps.map(keymap => {
if (keymap.abbreviation === newKeymap.abbreviation) {
@@ -357,3 +367,12 @@ function checkExistence(layers: Layer[], property: string, value: any): Layer[]
return newLayers;
}
function setKeyActionToLayer(newLayer: Layer, moduleIndex: number, keyIndex: number, newKeyAction: KeyAction): void {
const newModule: Module = Object.assign(new Module(), newLayer.modules[moduleIndex]);
newLayer.modules = newLayer.modules.slice();
newLayer.modules[moduleIndex] = newModule;
newModule.keyActions = newModule.keyActions.slice();
newModule.keyActions[keyIndex] = newKeyAction;
}
+164
View File
@@ -0,0 +1,164 @@
import { cloneDeep } from 'lodash';
const defaultUserConfig = {
dataModelVersion: 4,
moduleConfigurations: [
{
id: 1,
initialPointerSpeed: 1,
pointerAcceleration: 5,
maxPointerSpeed: 200
}
],
keymaps: [
{
isDefault: true,
abbreviation: 'QWR',
name: 'QWERTY',
description: '',
layers: [
{
modules: [
{
id: 0,
pointerRole: 'move',
keyActions: [
{
keyActionType: 'keystroke',
type: 'basic',
scancode: 36
},
{
keyActionType: 'keystroke',
type: 'basic',
scancode: 37
},
{
'keyActionType': 'switchLayer',
'layer': 'mod',
'toggle': false
}
]
},
{
id: 1,
pointerRole: 'move',
keyActions: [
{
keyActionType: 'keystroke',
type: 'basic',
scancode: 53
},
{
keyActionType: 'keystroke',
type: 'basic',
scancode: 30
},
null
]
},
{
id: 2,
pointerRole: 'scroll',
keyActions: []
}
]
},
{
modules: [
{
id: 0,
pointerRole: 'none',
keyActions: [
{
keyActionType: 'keystroke',
type: 'basic',
scancode: 64
},
{
keyActionType: 'keystroke',
type: 'basic',
scancode: 65
},
{
keyActionType: 'switchLayer',
layer: 'mod',
toggle: false
}
]
},
{
id: 1,
pointerRole: 'none',
keyActions: [
{
keyActionType: 'keystroke',
type: 'basic',
scancode: 41
},
{
keyActionType: 'keystroke',
type: 'basic',
scancode: 58
},
null
]
}
]
},
{
modules: [
{
id: 0,
pointerRole: 'move',
keyActions: [
null,
null,
null
]
},
{
id: 1,
pointerRole: 'scroll',
keyActions: [
null,
{
keyActionType: 'switchKeymap',
keymapAbbreviation: 'DVO'
},
null
]
}
]
},
{
modules: [
{
id: 0,
pointerRole: 'move',
keyActions: [
null,
null,
null
]
},
{
id: 1,
pointerRole: 'move',
keyActions: [
null,
null,
null
]
}
]
}
]
}
],
macros: []
};
export function getDefaultUserConfig() {
return cloneDeep(defaultUserConfig);
}