refactore: create feature modules (#387)

* add @angular/cli to the project

* increase nodejs version -> 8.2.1

* add lerna

* merge web and shared module

* move electron module into packages as uhk-agent

Electron agent functionality is not working

* delete symlinker

* convert private properties to public of component if used in html

* revert uhk-message.component

* fix component path

* fix the correct name of the uhk-message.component.scss

* building web and electron module

* delete uhk-renderer package

* handle device connect disconnect state

* add privilege detection

* fix set privilege functionality

* turn back download keymap functionality

* add bootstrap, select2 js and fix null pointer exception

* turn back upload data to keyboard

* fix send keymap

* fix test-serializer

* add missing package.json

* merging

* fix appveyor build

* fix linting

* turn back electron storage service

* commit the missing electron-datastorage-repository

* update node to 8.3.0 in .nvmrc and log node version in appveyor build

* set exact version number in appveyor build

* vertical align privilege and missing device components

* set back node version to 8 in appveyor

* move node-usb dependency from usb dir to root

maybe it is fix the appveyor build

* revert usb to root

* fix electron builder script

* fix electron builder script

* turn off electron devtools

* remove CTRL+U functionality

* fix CTRL+o

* fix lint error

* turnoff store freeze

* start process when got `Error: EPERM: operation not permitted` error

* move files from root usb dir -> packages/usb
This commit is contained in:
Róbert Kiss
2017-08-19 20:02:17 +02:00
committed by László Monda
parent 97770f67c0
commit 0f558e4132
524 changed files with 25606 additions and 5036 deletions
@@ -0,0 +1 @@
export * from './popover.component';
@@ -0,0 +1,82 @@
<div class="popover"
#popover
[@popover]="animationState"
[ngClass]="{'leftArrow': leftArrow, 'rightArrow': rightArrow}"
[style.top.px]="topPosition"
[style.left.px]="leftPosition"
>
<div class="arrowCustom"></div>
<div class="popover-title menu-tabs">
<ul class="nav nav-tabs popover-menu">
<li #keypress [class.active]="activeTab === tabName.Keypress" (click)="selectTab(tabName.Keypress)">
<a class="menu-tabs--item">
<i class="fa fa-keyboard-o"></i>
<span>Keypress</span>
</a>
</li>
<li #layer [class.active]="activeTab === tabName.Layer" (click)="selectTab(tabName.Layer)">
<a class="menu-tabs--item">
<i class="fa fa-clone"></i>
<span>Layer</span>
</a>
</li>
<li #mouse [class.active]="activeTab === tabName.Mouse" (click)="selectTab(tabName.Mouse)">
<a class="menu-tabs--item">
<i class="fa fa-mouse-pointer"></i>
<span>Mouse</span>
</a>
</li>
<li #macro [class.active]="activeTab === tabName.Macro" (click)="selectTab(tabName.Macro)">
<a class="menu-tabs--item">
<i class="fa fa-play"></i>
<span>Macro</span>
</a>
</li>
<li #keymap [class.active]="activeTab === tabName.Keymap" (click)="selectTab(tabName.Keymap)">
<a class="menu-tabs--item">
<i class="fa fa-keyboard-o"></i>
<span>Keymap</span>
</a>
</li>
<li #none [class.active]="activeTab === tabName.None" (click)="selectTab(tabName.None)">
<a class="menu-tabs--item">
<i class="fa fa-ban"></i>
<span>None</span>
</a>
</li>
</ul>
</div>
<div [ngSwitch]="activeTab">
<keypress-tab #tab *ngSwitchCase="tabName.Keypress" class="popover-content"
[defaultKeyAction]="defaultKeyAction"
[longPressEnabled]="true"
(validAction)="keyActionValid=$event"
></keypress-tab>
<layer-tab #tab *ngSwitchCase="tabName.Layer" class="popover-content"
[defaultKeyAction]="defaultKeyAction"
[currentLayer]="currentLayer"
(validAction)="keyActionValid=$event"
></layer-tab>
<mouse-tab #tab *ngSwitchCase="tabName.Mouse" class="popover-content"
[defaultKeyAction]="defaultKeyAction"
(validAction)="keyActionValid=$event"
></mouse-tab>
<macro-tab #tab *ngSwitchCase="tabName.Macro" class="popover-content"
[defaultKeyAction]="defaultKeyAction"
(validAction)="keyActionValid=$event"
></macro-tab>
<keymap-tab #tab *ngSwitchCase="tabName.Keymap" class="popover-content"
[defaultKeyAction]="defaultKeyAction"
[keymaps]="keymaps$ | async"
(validAction)="keyActionValid=$event"
></keymap-tab>
<none-tab #tab *ngSwitchCase="tabName.None" class="popover-content"
(validAction)="keyActionValid=$event"
></none-tab>
</div>
<div class="popover-action">
<button class="btn btn-sm btn-default" type="button" (click)="onCancelClick()"> Cancel </button>
<button class="btn btn-sm btn-primary" [class.disabled]="!keyActionValid" type="button" (click)="onRemapKey()"> Remap Key </button>
</div>
</div>
<div class="popover-overlay" [class.display]="visible" (click)="onOverlay()"></div>
@@ -0,0 +1,135 @@
.popover {
display: flex;
flex-direction: column;
padding: 0;
max-width: 568px;
width: 100%;
user-select: none;
&.leftArrow {
.arrowCustom {
transform: none;
left: 22px;
}
}
&.rightArrow {
.arrowCustom {
transform: none;
right: 22px;
left: auto;
}
}
> .container-fluid {
overflow: hidden;
}
}
.nav-tabs > li {
overflow: hidden;
}
.arrowCustom {
position: absolute;
top: -16px;
left: 50%;
transform: translateX(-50%);
width: 41px;
height: 16px;
&:before {
content: '';
width: 0;
height: 0;
border-left: 21px solid transparent;
border-right: 21px solid transparent;
border-bottom: 17px solid rgba(0, 0, 0, 0.2);
display: block;
position: absolute;
top: -1px;
}
&:after {
content: '';
width: 0;
height: 0;
border-left: 20px solid transparent;
border-right: 20px solid transparent;
border-bottom: 16px solid #f7f7f7;
display: block;
position: absolute;
top: 0;
}
}
.popover-action {
padding: 8px 14px;
margin: 0;
font-size: 14px;
background-color: #f7f7f7;
border-top: 1px solid #ebebeb;
border-radius: 0 0 5px 5px;
text-align: right;
}
.popover-title {
&.menu-tabs {
padding: 0.5rem 0.5rem 0;
display: block;
.nav-tabs {
position: relative;
top: 1px;
display: flex;
.menu-tabs--item {
display: flex;
align-items: center;
cursor: pointer;
i {
margin-right: 0.25em;
}
}
}
}
}
.popover-content {
padding: 10px 24px;
}
.popover-overlay {
position: fixed;
width: 100%;
height: 0;
top: 0;
left: 0;
z-index: 1050;
background: rgba(0, 0, 0, 0);
transition: background 200ms ease-out, height 0ms 200ms linear;
&.display {
height: 100%;
background: rgba(0, 0, 0, 0.2);
transition: background 200ms ease-out;
}
}
.select2-item {
position: relative;
font-size: 1.5rem;
&.keymap-name--wrapper {
padding-left: 50px;
}
.layout-segment-code {
height: 2rem;
position: absolute;
left: 0;
top: 50%;
margin-top: -1rem;
}
}
@@ -0,0 +1,200 @@
import {
Component,
ElementRef,
EventEmitter,
HostListener,
Input,
OnChanges,
Output,
SimpleChanges,
ViewChild
} from '@angular/core';
import { animate, keyframes, state, style, transition, trigger } from '@angular/animations';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import 'rxjs/add/operator/combineLatest';
import 'rxjs/add/operator/map';
import {
KeyAction,
KeystrokeAction,
MouseAction,
PlayMacroAction,
SwitchKeymapAction,
SwitchLayerAction
} from '../../config-serializer/config-items/key-action';
import { Keymap } from '../../config-serializer/config-items/keymap';
import { Tab } from './tab/tab';
import { AppState } from '../../store';
import { getKeymaps } from '../../store/reducers/user-configuration';
enum TabName {
Keypress,
Layer,
Mouse,
Macro,
Keymap,
None
}
@Component({
selector: 'popover',
templateUrl: './popover.component.html',
styleUrls: ['./popover.component.scss'],
animations: [
trigger('popover', [
state('closed', style({
transform: 'translateY(30px)',
visibility: 'hidden',
opacity: 0
})),
state('opened', style({
transform: 'translateY(0)',
visibility: 'visible',
opacity: 1
})),
transition('opened => closed', [
animate('200ms ease-out', keyframes([
style({ transform: 'translateY(0)', visibility: 'visible', opacity: 1, offset: 0 }),
style({ transform: 'translateY(30px)', visibility: 'hidden', opacity: 0, offset: 1 })
]))
]),
transition('closed => opened', [
style({
visibility: 'visible'
}),
animate('200ms ease-out', keyframes([
style({ transform: 'translateY(30px)', opacity: 0, offset: 0 }),
style({ transform: 'translateY(0)', opacity: 1, offset: 1 })
]))
])
])
]
})
export class PopoverComponent implements OnChanges {
@Input() defaultKeyAction: KeyAction;
@Input() currentKeymap: Keymap;
@Input() currentLayer: number;
@Input() keyPosition: ClientRect;
@Input() wrapPosition: ClientRect;
@Input() visible: boolean;
@Output() cancel = new EventEmitter<any>();
@Output() remap = new EventEmitter<KeyAction>();
@ViewChild('tab') selectedTab: Tab;
@ViewChild('popover') popoverHost: ElementRef;
tabName = TabName;
keyActionValid: boolean;
activeTab: TabName;
keymaps$: Observable<Keymap[]>;
leftArrow: boolean = false;
rightArrow: boolean = false;
topPosition: number = 0;
leftPosition: number = 0;
animationState: string;
private readonly currentKeymap$ = new BehaviorSubject<Keymap>(undefined);
constructor(store: Store<AppState>) {
this.animationState = 'closed';
this.keymaps$ = store.let(getKeymaps())
.combineLatest(this.currentKeymap$)
.map(([keymaps, currentKeymap]: [Keymap[], Keymap]) =>
keymaps.filter((keymap: Keymap) => currentKeymap.abbreviation !== keymap.abbreviation)
);
}
ngOnChanges(change: SimpleChanges) {
if (this.keyPosition && this.wrapPosition && (change['keyPosition'] || change['wrapPosition'])) {
this.calculatePosition();
}
if (change['defaultKeyAction']) {
let tab: TabName;
if (this.defaultKeyAction instanceof KeystrokeAction) {
tab = TabName.Keypress;
} else if (this.defaultKeyAction instanceof SwitchLayerAction) {
tab = TabName.Layer;
} else if (this.defaultKeyAction instanceof MouseAction) {
tab = TabName.Mouse;
} else if (this.defaultKeyAction instanceof PlayMacroAction) {
tab = TabName.Macro;
} else if (this.defaultKeyAction instanceof SwitchKeymapAction) {
tab = TabName.Keymap;
} else {
tab = TabName.None;
}
this.selectTab(tab);
}
if (change['visible']) {
if (change['visible'].currentValue) {
this.animationState = 'opened';
} else {
this.animationState = 'closed';
}
}
if (change.currentKeymap) {
this.currentKeymap$.next(this.currentKeymap);
}
}
onCancelClick(): void {
this.cancel.emit(undefined);
}
onRemapKey(): void {
if (this.keyActionValid) {
try {
const keyAction = this.selectedTab.toKeyAction();
this.remap.emit(keyAction);
} catch (e) {
// TODO: show error dialog
console.error(e);
}
}
}
@HostListener('keydown.escape')
onEscape(): void {
this.cancel.emit();
}
selectTab(tab: TabName): void {
this.activeTab = tab;
}
onOverlay() {
this.cancel.emit(undefined);
}
private calculatePosition() {
const offsetLeft: number = this.wrapPosition.left + 265; // 265 is a width of the side menu with a margin
const popover: HTMLElement = this.popoverHost.nativeElement;
let newLeft: number = this.keyPosition.left + (this.keyPosition.width / 2);
this.leftArrow = newLeft < offsetLeft;
this.rightArrow = (newLeft + popover.offsetWidth) > offsetLeft + this.wrapPosition.width;
if (this.leftArrow) {
newLeft = this.keyPosition.left;
} else if (this.rightArrow) {
newLeft = this.keyPosition.left - popover.offsetWidth + this.keyPosition.width;
} else {
newLeft -= popover.offsetWidth / 2;
}
// 7 is a space between a bottom key position and a popover
this.topPosition = this.keyPosition.top + this.keyPosition.height + 7 + window.scrollY;
this.leftPosition = newLeft;
}
}
@@ -0,0 +1,7 @@
export * from './tab';
export { KeymapTabComponent } from './keymap';
export { KeypressTabComponent } from './keypress';
export { LayerTabComponent } from './layer';
export { MacroTabComponent } from './macro';
export { MouseTabComponent } from './mouse';
export { NoneTabComponent } from './none';
@@ -0,0 +1 @@
export * from './keymap-tab.component';
@@ -0,0 +1,22 @@
<ng-template [ngIf]="keymapOptions.length === 0">
<span> No keymaps are available to choose from. Create a keymap first! </span>
</ng-template>
<ng-template [ngIf]="keymapOptions.length > 0">
<div>
<b>Switch to keymap:</b>
<select2
[data]="keymapOptions"
[value]="selectedKeymap?.abbreviation || -1"
(valueChanged)="onChange($event)"
[width]="'100%'"
></select2>
</div>
<div>
<div class="empty" *ngIf="!selectedKeymap?.abbreviation">
<img src="assets/images/base-layer--blank.svg">
</div>
<svg-keyboard *ngIf="selectedKeymap?.abbreviation"
[moduleConfig]="selectedKeymap.layers[0].modules">
</svg-keyboard>
</div>
</ng-template>
@@ -0,0 +1,43 @@
:host {
display: flex;
flex-direction: column;
> span {
text-align: center;
}
> div {
display: flex;
margin-top: 2px;
b {
display: flex;
align-items: center;
margin-right: 7px;
}
select2 {
flex: 1;
}
}
> div:last-child {
margin-top: 10px;
img {
max-height: 100%;
max-width: 100%;
}
}
}
.empty {
display: flex;
img {
display: flex;
width: 100%;
height: 100%;
position: relative;
}
}
@@ -0,0 +1,77 @@
import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { Select2OptionData } from 'ng2-select2/ng2-select2';
import { KeyAction, SwitchKeymapAction } from '../../../../config-serializer/config-items/key-action';
import { Keymap } from '../../../../config-serializer/config-items/keymap';
import { Tab } from '../tab';
@Component({
selector: 'keymap-tab',
templateUrl: './keymap-tab.component.html',
styleUrls: ['./keymap-tab.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class KeymapTabComponent extends Tab implements OnChanges {
@Input() defaultKeyAction: KeyAction;
@Input() keymaps: Keymap[];
keymapOptions: Array<Select2OptionData>;
selectedKeymap: Keymap;
constructor() {
super();
this.keymapOptions = [];
}
ngOnChanges(changes: SimpleChanges) {
if (changes.keymaps) {
this.keymapOptions = this.keymaps
.map((keymap: Keymap): Select2OptionData => {
return {
id: keymap.abbreviation,
text: keymap.name
};
});
if (this.keymaps.length > 0) {
this.selectedKeymap = this.keymaps[0];
}
}
this.fromKeyAction(this.defaultKeyAction);
this.validAction.emit(true);
}
// TODO: change to the correct type when the wrapper has added it.
onChange(event: any) {
if (event.value === '-1') {
this.selectedKeymap = undefined;
} else {
this.selectedKeymap = this.keymaps.find((keymap: Keymap) => keymap.abbreviation === event.value);
}
}
keyActionValid(): boolean {
return !!this.selectedKeymap;
}
fromKeyAction(keyAction: KeyAction): boolean {
if (!(keyAction instanceof SwitchKeymapAction)) {
return false;
}
const switchKeymapAction: SwitchKeymapAction = <SwitchKeymapAction>keyAction;
this.selectedKeymap = this.keymaps
.find((keymap: Keymap) => keymap.abbreviation === switchKeymapAction.keymapAbbreviation);
}
toKeyAction(): SwitchKeymapAction {
if (!this.keyActionValid()) {
throw new Error('KeyAction is not valid. No selected keymap!');
}
const keymapAction = new SwitchKeymapAction();
keymapAction.keymapAbbreviation = this.selectedKeymap.abbreviation;
return keymapAction;
}
}
@@ -0,0 +1 @@
export * from './keypress-tab.component';
@@ -0,0 +1,53 @@
<div class="scancode-options">
<b class="setting-label">Scancode:</b>
<select2
[data]="scanCodeGroups"
[value]="selectedScancodeOption.id"
(valueChanged)="onScancodeChange($event)"
[width]="200"
[options]="options"
></select2>
<capture-keystroke-button (capture)="onKeysCapture($event)" tabindex="0"></capture-keystroke-button>
</div>
<div class="modifier-options">
<b class="setting-label">Modifiers:</b>
<div class="btn-toolbar modifiers">
<div class="btn-group btn-group-sm modifiers__left">
<button type="button" class="btn btn-default"
*ngFor="let modifier of leftModifiers; let index = index"
[class.btn-primary]="leftModifierSelects[index]"
(click)="toggleModifier(false, index)"
>
{{modifier}}
</button>
</div>
<div class="btn-group btn-group-sm modifiers__right">
<button type="button" class="btn btn-default"
*ngFor="let modifier of rightModifiers; let index = index"
[class.btn-primary]="rightModifierSelects[index]"
(click)="toggleModifier(true, index)"
>
{{modifier}}
</button>
</div>
</div>
</div>
<div class="long-press-container" *ngIf="longPressEnabled">
<b class="setting-label">Long press action:</b>
<select2 #longPressSelect
[data]="longPressGroups"
[value]="selectedLongPressIndex.toString()"
(valueChanged)="onLongpressChange($event)"
[width]="140"
></select2>
<icon name="question-circle"
data-toggle="tooltip"
title="This action activates when another key gets pressed while holding this key."
data-placement="bottom"></icon>
</div>
<div class="disabled-state--text">
<i class="fa fa-info-circle"></i>
When a key is configured as layer switcher key, you can't assign other functions to it.
To assign a scancode to the key, set the <em>Layer action</em> to <em>None</em>.
</div>
@@ -0,0 +1,78 @@
:host {
display: flex;
flex-direction: column;
position: relative;
.scancode-options {
margin-bottom: 10px;
margin-top: 2px;
> b {
position: relative;
top: 2px;
}
}
.modifier-options {
> b {
position: relative;
top: -9px;
margin-right: 4px;
}
.btn-toolbar {
display: inline-block;
}
}
.long-press-container {
display: flex;
margin-top: 3rem;
> b {
margin-right: 0.6em;
align-items: center;
display: flex;
}
.secondary-role {
width: 135px;
}
icon {
margin-left: 0.6em;
}
}
.setting-label {
&.disabled {
color: #999;
}
}
.disabled-state--text {
display: none;
position: absolute;
top: 50%;
margin-top: -4rem;
color: #31708f;
padding-right: 40px;
.fa {
font-size: 2.6rem;
float: left;
padding: 1rem 1.5rem 2rem;
}
}
&.disabled {
.scancode-options,
.modifier-options,
.long-press-container {
visibility: hidden;
}
.disabled-state--text {
display: block;
}
}
}
@@ -0,0 +1,231 @@
import { Component, Input, OnChanges } from '@angular/core';
import { Select2OptionData, Select2TemplateFunction } from 'ng2-select2';
import { KeyAction, KeystrokeAction } from '../../../../config-serializer/config-items/key-action';
import { Tab } from '../tab';
import { MapperService } from '../../../../services/mapper.service';
import { KeystrokeType } from '../../../../config-serializer/config-items/key-action/keystroke-type';
@Component({
selector: 'keypress-tab',
templateUrl: './keypress-tab.component.html',
styleUrls: ['./keypress-tab.component.scss']
})
export class KeypressTabComponent extends Tab implements OnChanges {
@Input() defaultKeyAction: KeyAction;
@Input() longPressEnabled: boolean;
leftModifiers: string[];
rightModifiers: string[];
leftModifierSelects: boolean[];
rightModifierSelects: boolean[];
scanCodeGroups: Array<Select2OptionData>;
longPressGroups: Array<Select2OptionData>;
options: Select2Options;
selectedScancodeOption: Select2OptionData;
selectedLongPressIndex: number;
constructor(private mapper: MapperService) {
super();
this.leftModifiers = ['LShift', 'LCtrl', 'LSuper', 'LAlt'];
this.rightModifiers = ['RShift', 'RCtrl', 'RSuper', 'RAlt'];
this.scanCodeGroups = [{
id: '0',
text: 'None'
}];
this.scanCodeGroups = this.scanCodeGroups.concat(require('./scancodes.json'));
this.longPressGroups = require('./longPress.json');
this.leftModifierSelects = Array(this.leftModifiers.length).fill(false);
this.rightModifierSelects = Array(this.rightModifiers.length).fill(false);
this.selectedScancodeOption = this.scanCodeGroups[0];
this.selectedLongPressIndex = -1;
this.options = {
templateResult: this.scanCodeTemplateResult,
matcher: (term: string, text: string, data: Select2OptionData) => {
let found = text.toUpperCase().indexOf(term.toUpperCase()) > -1;
if (!found && data.additional && data.additional.explanation) {
found = data.additional.explanation.toUpperCase().indexOf(term.toUpperCase()) > -1;
}
return found;
}
};
}
ngOnChanges() {
this.fromKeyAction(this.defaultKeyAction);
this.validAction.emit(this.keyActionValid());
}
keyActionValid(keystrokeAction?: KeystrokeAction): boolean {
if (!keystrokeAction) {
keystrokeAction = this.toKeyAction();
}
return (keystrokeAction) ? (keystrokeAction.scancode > 0 || keystrokeAction.modifierMask > 0) : false;
}
onKeysCapture(event: { code: number, left: boolean[], right: boolean[] }) {
if (event.code) {
this.selectedScancodeOption = this.findScancodeOptionByScancode(event.code, KeystrokeType.basic);
} else {
this.selectedScancodeOption = this.scanCodeGroups[0];
}
this.leftModifierSelects = event.left;
this.rightModifierSelects = event.right;
this.validAction.emit(this.keyActionValid());
}
fromKeyAction(keyAction: KeyAction): boolean {
if (!(keyAction instanceof KeystrokeAction)) {
return false;
}
const keystrokeAction: KeystrokeAction = <KeystrokeAction>keyAction;
// Restore selectedScancodeOption
this.selectedScancodeOption = this.findScancodeOptionByScancode(keystrokeAction.scancode || 0, keystrokeAction.type);
const leftModifiersLength: number = this.leftModifiers.length;
// Restore modifiers
for (let i = 0; i < leftModifiersLength; ++i) {
this.leftModifierSelects[this.mapper.modifierMapper(i)] = ((keystrokeAction.modifierMask >> i) & 1) === 1;
}
for (let i = leftModifiersLength; i < leftModifiersLength + this.rightModifierSelects.length; ++i) {
const index: number = this.mapper.modifierMapper(i) - leftModifiersLength;
this.rightModifierSelects[index] = ((keystrokeAction.modifierMask >> i) & 1) === 1;
}
// Restore longPressAction
if (keystrokeAction.longPressAction !== undefined) {
this.selectedLongPressIndex = this.mapper.modifierMapper(keystrokeAction.longPressAction);
}
return true;
}
toKeyAction(): KeystrokeAction {
const keystrokeAction: KeystrokeAction = new KeystrokeAction();
const scTypePair = this.toScancodeTypePair(this.selectedScancodeOption);
keystrokeAction.scancode = scTypePair[0];
if (scTypePair[1] === 'media') {
keystrokeAction.type = KeystrokeType.shortMedia;
} else {
keystrokeAction.type = KeystrokeType[scTypePair[1]];
}
keystrokeAction.modifierMask = 0;
const modifiers = this.leftModifierSelects.concat(this.rightModifierSelects).map(x => x ? 1 : 0);
for (let i = 0; i < modifiers.length; ++i) {
keystrokeAction.modifierMask |= modifiers[i] << this.mapper.modifierMapper(i);
}
keystrokeAction.longPressAction = this.selectedLongPressIndex === -1
? undefined
: this.mapper.modifierMapper(this.selectedLongPressIndex);
if (this.keyActionValid(keystrokeAction)) {
return keystrokeAction;
}
}
scanCodeTemplateResult: Select2TemplateFunction = (state: Select2OptionData): JQuery | string => {
if (!state.id) {
return state.text;
}
if (state.additional && state.additional.explanation) {
return jQuery(
'<span class="select2-item">'
+ '<span>' + state.text + '</span>'
+ '<span class="scancode--searchterm"> '
+ state.additional.explanation
+ '</span>' +
'</span>'
);
} else {
return jQuery('<span class="select2-item">' + state.text + '</span>');
}
}
toggleModifier(right: boolean, index: number) {
const modifierSelects: boolean[] = right ? this.rightModifierSelects : this.leftModifierSelects;
modifierSelects[index] = !modifierSelects[index];
this.validAction.emit(this.keyActionValid());
}
onLongpressChange(event: { value: string }) {
this.selectedLongPressIndex = +event.value;
}
onScancodeChange(event: { value: string }) {
const id: string = event.value;
// ng2-select2 should provide the selectedOption in an upcoming release
// TODO: change this when it has become available
this.selectedScancodeOption = this.findScancodeOptionById(id);
this.validAction.emit(this.keyActionValid());
}
private findScancodeOptionBy(predicate: (option: Select2OptionData) => boolean): Select2OptionData {
let selectedOption: Select2OptionData;
const scanCodeGroups: Select2OptionData[] = [...this.scanCodeGroups];
while (scanCodeGroups.length > 0) {
const scanCodeGroup = scanCodeGroups.shift();
if (predicate(scanCodeGroup)) {
selectedOption = scanCodeGroup;
break;
}
if (scanCodeGroup.children) {
scanCodeGroups.push(...scanCodeGroup.children);
}
}
return selectedOption;
}
private findScancodeOptionById(id: string): Select2OptionData {
return this.findScancodeOptionBy(option => option.id === id);
}
private findScancodeOptionByScancode(scancode: number, type: KeystrokeType): Select2OptionData {
const typeToFind: string =
(type === KeystrokeType.shortMedia || type === KeystrokeType.longMedia) ? 'media' : KeystrokeType[type];
return this.findScancodeOptionBy((option: Select2OptionData) => {
const additional = option.additional;
if (additional && additional.scancode === scancode && additional.type === typeToFind) {
return true;
} else if ((!additional || additional.scancode === undefined) && +option.id === scancode) {
return true;
} else {
return false;
}
});
}
private toScancodeTypePair(option: Select2OptionData): [number, string] {
let scanCode: number;
let type: string;
if (option.additional) {
scanCode = option.additional.scancode;
type = option.additional.type || 'basic';
} else {
type = 'basic';
}
if (scanCode === undefined) {
scanCode = +option.id;
}
return [scanCode, type];
}
}
@@ -0,0 +1,60 @@
[
{
"id": "-1",
"text": "None"
},
{
"text": "Modifier",
"children": [
{
"id": "0",
"text": "LShift"
},
{
"id": "1",
"text": "LCtrl"
},
{
"id": "2",
"text": "LSuper"
},
{
"id": "3",
"text": "LAlt"
},
{
"id": "4",
"text": "RShift"
},
{
"id": "5",
"text": "RCtrl"
},
{
"id": "6",
"text": "RSuper"
},
{
"id": "7",
"text": "RAlt"
}
]
},
{
"text": "Layer switcher",
"children": [
{
"id": "8",
"text": "Mod"
},
{
"id": "9",
"text": "Mouse"
},
{
"id": "10",
"text": "Fn"
}
]
}
]
File diff suppressed because it is too large Load Diff
@@ -0,0 +1 @@
export * from './layer-tab.component';
@@ -0,0 +1,20 @@
<ng-template [ngIf]="!isNotBase">
<select (change)="toggleChanged($event.target.value)">
<option *ngFor="let item of toggleData" [value]="item.id" [selected]="toggle === item.id">
{{ item.text }}
</option>
</select>
<span>the</span>
<select (change)="layerChanged($event.target.value)">
<option *ngFor="let item of layerData" [value]="item.id" [selected]="layer === item.id">
{{ item.text }}
</option>
</select>
<span [ngSwitch]="toggle">
<ng-template [ngSwitchCase]="true">layer by pressing this key.</ng-template>
<ng-template ngSwitchDefault>layer by holding this key.</ng-template>
</span>
</ng-template>
<ng-template [ngIf]="isNotBase">
<span> Layer switching is only possible from the base layer. </span>
</ng-template>
@@ -0,0 +1,22 @@
:host {
display: flex;
margin: 0 -5px;
&.no-base {
justify-content: center;
}
> span,
> select {
margin: 0 5px;
display: flex;
align-items: center;
}
}
select {
background-color: #fff;
border: 1px solid #aaa;
border-radius: 4px;
padding: 4px 20px 4px 8px;
}
@@ -0,0 +1,97 @@
import { Component, HostBinding, Input, OnChanges, SimpleChanges } from '@angular/core';
import { KeyAction, LayerName, SwitchLayerAction } from '../../../../config-serializer/config-items/key-action';
import { Tab } from '../tab';
@Component({
selector: 'layer-tab',
templateUrl: './layer-tab.component.html',
styleUrls: ['./layer-tab.component.scss']
})
export class LayerTabComponent extends Tab implements OnChanges {
@Input() defaultKeyAction: KeyAction;
@Input() currentLayer: number;
@HostBinding('class.no-base') isNotBase: boolean;
toggleData: { id: boolean, text: string }[] = [
{
id: false,
text: 'Activate'
},
{
id: true,
text: 'Toggle'
}
];
layerData: { id: number, text: string }[] = [
{
id: 0,
text: 'Mod'
},
{
id: 1,
text: 'Fn'
},
{
id: 2,
text: 'Mouse'
}
];
toggle: boolean;
layer: LayerName;
constructor() {
super();
this.toggle = false;
this.layer = LayerName.mod;
}
ngOnChanges(changes: SimpleChanges) {
if (changes['defaultKeyAction']) {
this.fromKeyAction(this.defaultKeyAction);
}
if (changes['currentLayer']) {
this.isNotBase = this.currentLayer > 0;
}
this.validAction.emit(true);
}
keyActionValid(): boolean {
return !this.isNotBase;
}
fromKeyAction(keyAction: KeyAction): boolean {
if (!(keyAction instanceof SwitchLayerAction)) {
return false;
}
const switchLayerAction: SwitchLayerAction = <SwitchLayerAction>keyAction;
this.toggle = switchLayerAction.isLayerToggleable;
this.layer = switchLayerAction.layer;
return true;
}
toKeyAction(): SwitchLayerAction {
const keyAction = new SwitchLayerAction();
keyAction.isLayerToggleable = this.toggle;
keyAction.layer = this.layer;
if (!this.keyActionValid()) {
throw new Error('KeyAction is invalid!');
}
return keyAction;
}
toggleChanged(value: string) {
this.toggle = value === 'true';
}
layerChanged(value: number) {
this.layer = +value;
}
}
@@ -0,0 +1 @@
export * from './macro-tab.component';
@@ -0,0 +1,16 @@
<ng-template [ngIf]="macroOptions.length === 0">
<span> No macros are available to choose from. Create a macro first! </span>
</ng-template>
<ng-template [ngIf]="macroOptions.length > 0">
<div class="macro-selector">
<b> Play macro: </b>
<select2 [data]="macroOptions" [value]="macroOptions[selectedMacroIndex].id" (valueChanged)="onChange($event)" [width]="'100%'"></select2>
</div>
<div class="macro-action-container">
<div class="list-group">
<macro-item *ngFor="let macroAction of macros[selectedMacroIndex].macroActions"
[macroAction]="macroAction" [editable]="false">
</macro-item>
</div>
</div>
</ng-template>
@@ -0,0 +1,40 @@
:host {
display: flex;
flex-direction: column;
> span {
text-align: center;
}
.macro-selector {
display: flex;
margin-top: 2px;
b {
display: flex;
align-items: center;
margin-right: 7px;
}
select2 {
flex: 1;
}
}
.macro-action-container {
display: flex;
flex-direction: column;
min-height: 200px;
max-height: 300px;
margin: 20px 0;
overflow-x: hidden;
overflow-y: auto;
border-radius: 4px;
border: 1px solid #ddd;
.list-group {
margin-bottom: 0;
border: 0;
}
}
}
@@ -0,0 +1,83 @@
import { Component, Input, OnChanges, OnDestroy, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { Subscription } from 'rxjs/Subscription';
import { Select2OptionData } from 'ng2-select2/ng2-select2';
import { KeyAction, PlayMacroAction } from '../../../../config-serializer/config-items/key-action';
import { Macro } from '../../../../config-serializer/config-items/macro';
import { Tab } from '../tab';
import { AppState } from '../../../../store/index';
import { getMacros } from '../../../../store/reducers/user-configuration';
@Component({
selector: 'macro-tab',
templateUrl: './macro-tab.component.html',
styleUrls: ['./macro-tab.component.scss']
})
export class MacroTabComponent extends Tab implements OnInit, OnChanges, OnDestroy {
@Input() defaultKeyAction: KeyAction;
macros: Macro[];
macroOptions: Array<Select2OptionData>;
selectedMacroIndex: number;
private subscription: Subscription;
constructor(store: Store<AppState>) {
super();
this.subscription = store.let(getMacros())
.subscribe((macros: Macro[]) => this.macros = macros);
this.macroOptions = [];
this.selectedMacroIndex = 0;
}
ngOnInit() {
this.macroOptions = this.macros.map(function (macro: Macro, index: number): Select2OptionData {
return {
id: index.toString(),
text: macro.name
};
});
}
ngOnChanges() {
this.fromKeyAction(this.defaultKeyAction);
this.validAction.emit(true);
}
// TODO: change to the correct type when the wrapper has added it.
onChange(event: any) {
this.selectedMacroIndex = +event.value;
}
keyActionValid(): boolean {
return this.selectedMacroIndex >= 0;
}
fromKeyAction(keyAction: KeyAction): boolean {
if (!(keyAction instanceof PlayMacroAction)) {
return false;
}
const playMacroAction: PlayMacroAction = <PlayMacroAction>keyAction;
this.selectedMacroIndex = this.macros.findIndex(macro => playMacroAction.macroId === macro.id);
return true;
}
toKeyAction(): PlayMacroAction {
if (!this.keyActionValid()) {
throw new Error('KeyAction is not valid. No selected macro!');
}
const keymapAction = new PlayMacroAction();
keymapAction.macroId = this.macros[this.selectedMacroIndex].id;
return keymapAction;
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
@@ -0,0 +1 @@
export * from './mouse-tab.component';
@@ -0,0 +1,110 @@
<div class="mouse-action col-sm-4">
<ul class="nav nav-pills nav-stacked">
<li *ngFor="let page of pages; let i = index" [class.active]="selectedPageIndex === i" (click)="changePage(i)">
<a> {{ page }}</a>
</li>
</ul>
</div>
<div class="details col-sm-8" [ngSwitch]="selectedPageIndex">
<div *ngSwitchCase="0" class="mouse__config mouse__config--move text-center">
<div class="row">
<button type="button" class="btn btn-default btn-lg"
[class.btn-primary]="mouseActionParam === MouseActionParam.moveUp"
(click)="setMouseActionParam(MouseActionParam.moveUp)">
<i class="fa fa-arrow-up"></i>
</button>
</div>
<div class="row">
<button type="button" class="btn btn-default btn-lg"
[class.btn-primary]="mouseActionParam === MouseActionParam.moveLeft"
(click)="setMouseActionParam(MouseActionParam.moveLeft)">
<i class="fa fa-arrow-left"></i>
</button>
<button type="button" class="btn btn-default btn-lg btn-placeholder">
<i class="fa fa-square"></i>
</button>
<button type="button" class="btn btn-default btn-lg"
[class.btn-primary]="mouseActionParam === MouseActionParam.moveRight"
(click)="setMouseActionParam(MouseActionParam.moveRight)">
<i class="fa fa-arrow-right"></i>
</button>
</div>
<div class="row">
<button type="button" class="btn btn-default btn-lg"
[class.btn-primary]="mouseActionParam === MouseActionParam.moveDown"
(click)="setMouseActionParam(MouseActionParam.moveDown)">
<i class="fa fa-arrow-down"></i>
</button>
</div>
</div>
<div *ngSwitchCase="1" class="mouse__config mouse__config--scroll text-center">
<div class="row">
<button type="button" class="btn btn-default btn-lg"
[class.btn-primary]="mouseActionParam === MouseActionParam.scrollUp"
(click)="setMouseActionParam(MouseActionParam.scrollUp)">
<i class="fa fa-angle-double-up"></i>
</button>
</div>
<div class="row">
<button type="button" class="btn btn-default btn-lg"
[class.btn-primary]="mouseActionParam === MouseActionParam.scrollLeft"
(click)="setMouseActionParam(MouseActionParam.scrollLeft)">
<i class="fa fa-angle-double-left"></i>
</button>
<button type="button" class="btn btn-default btn-lg btn-placeholder">
<i class="fa fa-square"></i>
</button>
<button type="button" class="btn btn-default btn-lg"
[class.btn-primary]="mouseActionParam === MouseActionParam.scrollRight"
(click)="setMouseActionParam(MouseActionParam.scrollRight)">
<i class="fa fa-angle-double-right"></i>
</button>
</div>
<div class="row">
<button type="button" class="btn btn-default btn-lg"
[class.btn-primary]="mouseActionParam === MouseActionParam.scrollDown"
(click)="setMouseActionParam(MouseActionParam.scrollDown)">
<i class="fa fa-angle-double-down"></i>
</button>
</div>
</div>
<div *ngSwitchCase="2" class="mouse__config mouse__config--click">
<div class="btn-group col-xs-12" role="group">
<button type="button" class="btn btn-default col-xs-4"
[class.btn-primary]="mouseActionParam === MouseActionParam.leftClick"
(click)="setMouseActionParam(MouseActionParam.leftClick)">Left</button>
<button type="button" class="btn btn-default col-xs-4"
[class.btn-primary]="mouseActionParam === MouseActionParam.middleClick"
(click)="setMouseActionParam(MouseActionParam.middleClick)">Middle</button>
<button type="button" class="btn btn-default col-xs-4"
[class.btn-primary]="mouseActionParam === MouseActionParam.rightClick"
(click)="setMouseActionParam(MouseActionParam.rightClick)">Right</button>
</div>
</div>
<div *ngSwitchCase="3" class="mouse__config mouse__config--speed text-center">
<div class="help-text--mouse-speed text-left">
<p>Press this key along with mouse movement/scrolling to accelerate/decelerate the speed of the action.</p>
</div>
<div class="btn-group btn-group-lg" role="group">
<button class="btn btn-default"
[class.btn-primary]="mouseActionParam === MouseActionParam.decelerate"
(click)="setMouseActionParam(MouseActionParam.decelerate)"
>
-
<span>Decelerate</span>
</button>
<button class="btn btn-default"
[class.btn-primary]="mouseActionParam === MouseActionParam.accelerate"
(click)="setMouseActionParam(MouseActionParam.accelerate)"
>
+
<span>Accelerate</span>
</button>
</div>
<div class="help-text--mouse-speed last-help text-left">
<p>You can set the multiplier in the <a [routerLink]="['/settings']" title="Settings">settings</a>.</p>
</div>
</div>
<div *ngSwitchDefault>
</div>
</div>
@@ -0,0 +1,85 @@
@import '../../../../../styles/variables';
:host {
display: flex;
&.popover-content {
padding: 10px;
display: flex;
align-items: center;
}
.mouse-action {
.nav {
border-right: 1px solid #ccc;
li {
a {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
&.selected {
font-style: italic;
}
}
&.active {
a {
&.selected {
font-style: normal;
}
&:after {
content: '';
display: block;
position: absolute;
width: 0;
height: 0;
top: 0;
right: -4rem;
border-color: transparent transparent transparent $icon-hover;
border-style: solid;
border-width: 2rem;
}
}
}
}
}
}
.help-text--mouse-speed {
margin-bottom: 2rem;
font-size: 0.9em;
color: #666;
p {
margin: 0;
}
}
.details {
.btn-placeholder {
visibility: hidden;
}
}
}
.mouse__config--speed {
.btn-default {
font-size: 25px;
line-height: 22px;
padding-top: 4px;
padding-bottom: 4px;
span {
font-size: 13px;
display: block;
text-align: center;
}
}
}
.help-text--mouse-speed.last-help {
margin-bottom: 0;
margin-top: 2rem;
}
@@ -0,0 +1,98 @@
import { Component, Input, OnChanges } from '@angular/core';
import { KeyAction, MouseAction, MouseActionParam } from '../../../../config-serializer/config-items/key-action';
import { Tab } from '../tab';
@Component({
selector: 'mouse-tab',
templateUrl: './mouse-tab.component.html',
styleUrls: ['./mouse-tab.component.scss']
})
export class MouseTabComponent extends Tab implements OnChanges {
@Input() defaultKeyAction: KeyAction;
/* tslint:disable:variable-name: It is an enum type. So it can start with uppercase. */
MouseActionParam = MouseActionParam;
/* tslint:enable:variable-name*/
mouseActionParam: MouseActionParam;
selectedPageIndex: number;
pages: string[];
constructor() {
super();
this.selectedPageIndex = 0;
this.pages = ['Move', 'Scroll', 'Click', 'Speed'];
}
ngOnChanges() {
this.fromKeyAction(this.defaultKeyAction);
this.validAction.emit(this.keyActionValid());
}
keyActionValid(): boolean {
return this.mouseActionParam !== undefined;
}
fromKeyAction(keyAction: KeyAction): boolean {
if (!(keyAction instanceof MouseAction)) {
return false;
}
const mouseAction: MouseAction = <MouseAction>keyAction;
this.mouseActionParam = mouseAction.mouseAction;
if (mouseAction.mouseAction === MouseActionParam.moveUp) {
this.selectedPageIndex = 0;
}
switch (mouseAction.mouseAction) {
case MouseActionParam.moveDown:
case MouseActionParam.moveUp:
case MouseActionParam.moveLeft:
case MouseActionParam.moveRight:
this.selectedPageIndex = 0;
break;
case MouseActionParam.scrollDown:
case MouseActionParam.scrollUp:
case MouseActionParam.scrollLeft:
case MouseActionParam.scrollRight:
this.selectedPageIndex = 1;
break;
case MouseActionParam.leftClick:
case MouseActionParam.middleClick:
case MouseActionParam.rightClick:
this.selectedPageIndex = 2;
break;
case MouseActionParam.decelerate:
case MouseActionParam.accelerate:
this.selectedPageIndex = 3;
break;
default:
return false;
}
return true;
}
toKeyAction(): MouseAction {
const mouseAction: MouseAction = new MouseAction();
mouseAction.mouseAction = this.mouseActionParam;
return mouseAction;
}
changePage(index: number) {
if (index < -1 || index > 3) {
console.error(`Invalid index error: ${index}`);
return;
}
this.selectedPageIndex = index;
this.mouseActionParam = undefined;
this.validAction.emit(false);
}
setMouseActionParam(mouseActionParam: MouseActionParam) {
this.mouseActionParam = mouseActionParam;
this.validAction.emit(true);
}
}
@@ -0,0 +1 @@
export * from './none-tab.component';
@@ -0,0 +1 @@
This key is unassigned and has no functionality.
@@ -0,0 +1,5 @@
:host {
display: flex;
justify-content: center;
padding: 2rem 0;
}
@@ -0,0 +1,28 @@
import { Component, OnInit } from '@angular/core';
import { Tab } from '../tab';
@Component({
selector: 'none-tab',
templateUrl: './none-tab.component.html',
styleUrls: ['./none-tab.component.scss']
})
export class NoneTabComponent extends Tab implements OnInit {
ngOnInit() {
this.validAction.emit(true);
}
keyActionValid(): boolean {
return true;
}
fromKeyAction(): boolean {
return false;
}
toKeyAction(): undefined {
return undefined;
}
}
@@ -0,0 +1,11 @@
import { EventEmitter, Output } from '@angular/core';
import { KeyAction } from '../../../config-serializer/config-items/key-action';
export abstract class Tab {
@Output() validAction = new EventEmitter<boolean>();
abstract keyActionValid(): boolean;
abstract fromKeyAction(keyAction: KeyAction): boolean;
abstract toKeyAction(): KeyAction;
}
@@ -0,0 +1,12 @@
<button type="button" class="btn btn-sm btn--capture-keystroke"
[ngClass]="{'btn-default': !record, 'btn-info': record}"
(click)="start()"
>
<i class="fa fa-circle"></i>
<ng-template [ngIf]="!record">
Capture keystroke
</ng-template>
<ng-template [ngIf]="record">
Capturing ...
</ng-template>
</button>
@@ -0,0 +1,8 @@
button {
display: inline-block;
margin: 0 0 0 0.25rem;
}
.fa-circle {
color: #c00;
}
@@ -0,0 +1,84 @@
import { Component, EventEmitter, HostListener, Output } from '@angular/core';
import { CaptureService } from '../../../../services/capture.service';
@Component({
selector: 'capture-keystroke-button',
templateUrl: './capture-keystroke-button.component.html',
styleUrls: ['./capture-keystroke-button.component.scss']
})
export class CaptureKeystrokeButtonComponent {
@Output() capture = new EventEmitter<any>();
record: boolean;
private first: boolean; // enable usage of Enter to start capturing
private scanCodePressed: boolean;
constructor(private captureService: CaptureService) {
this.record = false;
this.captureService.initModifiers();
this.captureService.populateMapping();
this.scanCodePressed = false;
}
@HostListener('keyup', ['$event'])
onKeyUp(e: KeyboardEvent) {
if (this.scanCodePressed) {
e.preventDefault();
this.scanCodePressed = false;
} else if (this.record && !this.first) {
e.preventDefault();
this.saveScanCode();
}
}
@HostListener('keydown', ['$event'])
onKeyDown(e: KeyboardEvent) {
const code: number = e.keyCode;
const enter = 13;
if (this.record) {
e.preventDefault();
e.stopPropagation();
this.first = false;
if (this.captureService.hasMap(code)) {
this.saveScanCode(this.captureService.getMap(code));
this.scanCodePressed = true;
} else {
this.captureService.setModifier((e.location === 1), code);
}
} else if (code === enter) {
this.record = true;
this.first = true;
}
}
@HostListener('focusout')
onFocusOut() {
this.record = false;
this.reset();
}
start(): void {
this.record = true;
}
private saveScanCode(code?: number) {
this.record = false;
const left: boolean[] = this.captureService.getModifiers(true);
const right: boolean[] = this.captureService.getModifiers(false);
this.capture.emit({
code,
left,
right
});
this.reset();
}
private reset() {
this.first = false;
this.captureService.initModifiers();
}
}
@@ -0,0 +1 @@
export * from './capture-keystroke-button.component';
@@ -0,0 +1,13 @@
<div [ngSwitch]="name">
<span *ngSwitchCase="'option-vertical'" class="glyphicon glyphicon-option-vertical" aria-hidden="true"></span>
<i *ngSwitchCase="'square'" class="fa fa-square"></i>
<i *ngSwitchCase="'hand-pointer'" class="fa fa-hand-pointer-o"></i>
<i *ngSwitchCase="'hand-rock'" class="fa fa-hand-rock-o"></i>
<i *ngSwitchCase="'hand-paper'" class="fa fa-hand-paper-o"></i>
<i *ngSwitchCase="'mouse-pointer'" class="fa fa-mouse-pointer"></i>
<i *ngSwitchCase="'clock'" class="fa fa-clock-o"></i>
<i *ngSwitchCase="'font'" class="fa fa-font"></i>
<i *ngSwitchCase="'trash'" class="glyphicon glyphicon-trash action--trash"></i>
<i *ngSwitchCase="'pencil'" class="glyphicon glyphicon-pencil action--edit"></i>
<i *ngSwitchCase="'question-circle'" class ="fa fa-question-circle"></i>
</div>
@@ -0,0 +1,24 @@
@import '../../../../../styles/variables';
:host {
display: flex;
align-items: center;
}
.action {
&--edit {
&:hover {
color: $icon-hover;
cursor: pointer;
}
}
&--trash {
&:hover {
color: #d9534f;
cursor: pointer;
}
}
}
@@ -0,0 +1,16 @@
import { Component, Input, OnInit } from '@angular/core';
@Component({
selector: 'icon',
templateUrl: './icon.component.html',
styleUrls: ['./icon.component.scss']
})
export class IconComponent implements OnInit {
@Input() name: string;
constructor() { }
ngOnInit() { }
}
@@ -0,0 +1 @@
export * from './icon.component';