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:
committed by
László Monda
parent
97770f67c0
commit
0f558e4132
@@ -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;
|
||||
}
|
||||
+12
@@ -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>
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
button {
|
||||
display: inline-block;
|
||||
margin: 0 0 0 0.25rem;
|
||||
}
|
||||
|
||||
.fa-circle {
|
||||
color: #c00;
|
||||
}
|
||||
+84
@@ -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';
|
||||
Reference in New Issue
Block a user