Seperate electron and web target building

This commit is contained in:
Farkas József
2017-01-20 02:03:27 +01:00
committed by József Farkas
parent 517aed1b1c
commit 983eb72892
276 changed files with 2154 additions and 95 deletions

View File

@@ -0,0 +1,4 @@
{
"isKeymapsMenuExpanded": true,
"isMacrosMenuExpanded": true
}

View File

@@ -0,0 +1,7 @@
<div class="row">
<h1 class="col-xs-12 pane-title">
<i class="fa fa-puzzle-piece"></i>
<span class="macro__name pane-title__name">{{ name$ | async }}</span>
</h1>
</div>
To be done...

View File

@@ -0,0 +1,5 @@
:host {
width: 100%;
height: 100%;
display: block;
}

View File

@@ -0,0 +1,22 @@
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs/Observable';
@Component({
selector: 'add-on',
template: require('./add-on.component.html'),
styles: [require('./add-on.component.scss')],
host: {
'class': 'container-fluid'
}
})
export class AddOnComponent {
private name$: Observable<string>;
constructor(private route: ActivatedRoute) {
this.name$ = route
.params
.select<string>('name');
}
}

View File

@@ -0,0 +1,10 @@
import { Routes } from '@angular/router';
import { AddOnComponent } from './add-on.component';
export const addOnRoutes: Routes = [
{
path: 'add-on/:name',
component: AddOnComponent
}
];

View File

@@ -0,0 +1,2 @@
export * from './add-on.component';
export * from './add-on.routes';

View File

@@ -0,0 +1 @@
export * from './slider';

View File

@@ -0,0 +1 @@
export { KeyboardSliderComponent } from './keyboard-slider.component';

View File

@@ -0,0 +1,10 @@
<svg-keyboard *ngFor="let layer of layers; let index = index; trackBy: trackKeyboard"
[@layerState]="layerAnimationState[index]"
[moduleConfig]="layer.modules"
[keybindAnimationEnabled]="keybindAnimationEnabled"
[capturingEnabled]="capturingEnabled"
(keyClick)="keyClick.emit($event)"
(keyHover)="keyHover.emit($event)"
(capture)="capture.emit($event)"
>
</svg-keyboard>

After

Width:  |  Height:  |  Size: 398 B

View File

@@ -0,0 +1,8 @@
svg-keyboard {
width: 95%;
max-width: 1400px;
position: absolute;
left: 0;
transform: translateX(-101%);
user-select: none;
}

View File

@@ -0,0 +1,116 @@
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
OnChanges,
Output,
SimpleChanges,
animate,
keyframes,
state,
style,
transition,
trigger
} from '@angular/core';
import { Layer } from '../../../config-serializer/config-items/Layer';
type AnimationKeyboard =
'leftIn' |
'leftOut' |
'rightIn' |
'rightOut';
@Component({
selector: 'keyboard-slider',
template: require('./keyboard-slider.component.html'),
styles: [require('./keyboard-slider.component.scss')],
changeDetection: ChangeDetectionStrategy.OnPush,
// We use 101%, because there was still a trace of the keyboard in the screen when animation was done
animations: [
trigger('layerState', [
state('leftIn, rightIn', style({
transform: 'translateX(-50%)',
left: '50%'
})),
state('leftOut', style({
transform: 'translateX(-101%)',
left: '0'
})),
state('rightOut', style({
transform: 'translateX(0)',
left: '101%'
})),
transition('leftOut => leftIn, rightOut => leftIn', [
animate('400ms ease-out', keyframes([
style({ transform: 'translateX(0%)', left: '101%', offset: 0 }),
style({ transform: 'translateX(-50%)', left: '50%', offset: 1 })
]))
]),
transition('leftIn => leftOut, rightIn => leftOut', [
animate('400ms ease-out', keyframes([
style({ transform: 'translateX(-50%)', left: '50%', offset: 0 }),
style({ transform: 'translateX(-101%)', left: '0%', offset: 1 })
]))
]),
transition('* => rightIn', [
animate('400ms ease-out', keyframes([
style({ transform: 'translateX(-101%)', left: '0%', offset: 0 }),
style({ transform: 'translateX(-50%)', left: '50%', offset: 1 })
]))
]),
transition('* => rightOut', [
animate('400ms ease-out', keyframes([
style({ transform: 'translateX(-50%)', left: '50%', offset: 0 }),
style({ transform: 'translateX(0%)', left: '101%', offset: 1 })
]))
]),
transition(':leave', [
animate('2000ms ease-out', keyframes([
style({ opacity: 1, offset: 0 }),
style({ opacity: 0, offset: 1 })
]))
])
])
]
})
export class KeyboardSliderComponent implements OnChanges {
@Input() layers: Layer[];
@Input() currentLayer: number;
@Input() keybindAnimationEnabled: boolean;
@Input() capturingEnabled: boolean;
@Output() keyClick = new EventEmitter();
@Output() keyHover = new EventEmitter();
@Output() capture = new EventEmitter();
private layerAnimationState: AnimationKeyboard[];
ngOnChanges(changes: SimpleChanges) {
if (changes['layers']) {
this.layerAnimationState = this.layers.map<AnimationKeyboard>(() => 'leftOut');
this.layerAnimationState[this.currentLayer] = 'leftIn';
}
const layerChange = changes['currentLayer'];
if (layerChange) {
const prevValue = layerChange.isFirstChange() ? layerChange.currentValue : layerChange.previousValue;
this.onLayerChange(prevValue, layerChange.currentValue);
}
}
trackKeyboard(index: number) {
return index;
}
onLayerChange(oldIndex: number, index: number): void {
if (index > oldIndex) {
this.layerAnimationState[oldIndex] = 'leftOut';
this.layerAnimationState[index] = 'leftIn';
} else {
this.layerAnimationState[oldIndex] = 'rightOut';
this.layerAnimationState[index] = 'rightIn';
}
}
}

View File

@@ -0,0 +1,35 @@
<h1>
<i class="fa fa-keyboard-o"></i>
<span>Add new keymap</span>
</h1>
<div class="keymap__search clearfix">
<div class="input-group">
<span class="input-group-addon" id="sizing-addon1">
<i class="fa fa-search"></i>
</span>
<input type="text" class="form-control" placeholder="Search ..." (input)="filterKeyboards($event.target.value)">
</div>
<div class="keymap__search_amount">
{{ presets.length }} / {{ (presetsAll$ | async).length }} keymaps shown
</div>
</div>
<div class="keymap__list">
<div #keyboard class="keymap__list_item" *ngFor="let keymap of presets">
<h2>{{ keymap.name }}</h2>
<p class="keymap__description">
{{ keymap.description }}
</p>
<svg-keyboard-wrap
[keymap]="keymap"
[popoverEnabled]="false"
[tooltipEnabled]="true"
>
</svg-keyboard-wrap>
<div class="btn-group btn-group-lg">
<button class="btn btn-default" (click)="addKeymap(keymap)">Add keymap</button>
</div>
</div>
</div>
<div *ngIf="presets.length === 0">
Sorry, no keyboard found under this search query.
</div>

View File

@@ -0,0 +1,61 @@
:host {
overflow-y: auto;
display: block;
height: 100%;
}
.uhk__layer-switcher--wrapper {
position: relative;
&:before {
content: attr(data-title);
display: inline-block;
position: absolute;
bottom: -0.3em;
right: 100%;
font-size: 2.4rem;
padding-right: 0.25em;
margin: 0;
}
}
.keymap {
&__search {
margin-top: 10px;
.input-group {
width: 100%;
max-width: 350px;
float: left;
}
&_amount {
float: left;
margin: 7px 0 0 20px;
}
}
&__description {
margin-bottom: 20px;
}
&__list {
margin-top: 40px;
&_item {
margin-bottom: 50px;
}
.btn-group-lg {
margin: 30px 0 0;
width: 100%;
text-align: center;
.btn {
float: none;
padding-left: 50px;
padding-right: 50px;
}
}
}
}

View File

@@ -0,0 +1,50 @@
import { Component, OnDestroy } from '@angular/core';
import { Store } from '@ngrx/store';
import 'rxjs/add/operator/combineLatest';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
import { Keymap } from '../../../config-serializer/config-items/Keymap';
import { AppState } from '../../../store';
import { KeymapActions } from '../../../store/actions';
@Component({
selector: 'keymap-add',
template: require('./keymap-add.component.html'),
styles: [require('./keymap-add.component.scss')],
host: {
'class': 'container-fluid'
}
})
export class KeymapAddComponent implements OnDestroy {
private presets: Keymap[];
private presetsAll$: Observable<Keymap[]>;
private filterExpression$: BehaviorSubject<string>;
private subscription: Subscription;
constructor(private store: Store<AppState>) {
this.presetsAll$ = store.select((appState: AppState) => appState.presetKeymaps);
this.filterExpression$ = new BehaviorSubject('');
this.subscription = this.presetsAll$.combineLatest(
this.filterExpression$,
(keymaps: Keymap[], filterExpression: string) => {
return keymaps.filter((keymap: Keymap) => keymap.name.toLocaleLowerCase().includes(filterExpression));
}
).subscribe(keymaps => this.presets = keymaps);
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
filterKeyboards(filterExpression: string) {
this.filterExpression$.next(filterExpression);
}
addKeymap(keymap: Keymap) {
this.store.dispatch(KeymapActions.addKeymap(keymap));
}
}

View File

@@ -0,0 +1,2 @@
export { KeymapEditComponent } from './keymap-edit.component';
export { KeymapEditGuard } from './keymap-edit-guard.service';

View File

@@ -0,0 +1,30 @@
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/let';
import 'rxjs/add/operator/switchMap';
import { Store } from '@ngrx/store';
import { AppState } from '../../../store/index';
import { getKeymapEntities } from '../../../store/reducers';
@Injectable()
export class KeymapEditGuard implements CanActivate {
constructor(private store: Store<AppState>, private router: Router) { }
canActivate(): Observable<boolean> {
return this.store
.let(getKeymapEntities())
.do(keymaps => {
const defaultKeymap = keymaps.find(keymap => keymap.isDefault);
this.router.navigate(['/keymap', defaultKeymap.abbreviation]);
})
.switchMap(() => Observable.of(false));
}
}

View File

@@ -0,0 +1,8 @@
<template [ngIf]="keymap$ | async">
<keymap-header [keymap]="keymap$ | async" [deletable]="deletable$ | async"></keymap-header>
<svg-keyboard-wrap [keymap]="keymap$ | async"></svg-keyboard-wrap>
</template>
<div *ngIf="!(keymap$ | async)" class="not-found">
Sorry, there is no keymap with this abbreviation.
</div>

View File

@@ -0,0 +1,11 @@
:host {
width: 100%;
display: block;
}
.not-found {
margin-top: 30px;
font-size: 16px;
text-align: center;
}

View File

@@ -0,0 +1,44 @@
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import '@ngrx/core/add/operator/select';
import { Store } from '@ngrx/store';
import 'rxjs/add/operator/let';
import 'rxjs/add/operator/publishReplay';
import 'rxjs/add/operator/switchMap';
import { Observable } from 'rxjs/Observable';
import { Keymap } from '../../../config-serializer/config-items/Keymap';
import { AppState } from '../../../store';
import { getKeymap, getKeymapEntities } from '../../../store/reducers/keymap';
@Component({
selector: 'keymap-edit',
template: require('./keymap-edit.component.html'),
styles: [require('./keymap-edit.component.scss')],
host: {
'class': 'container-fluid'
}
})
export class KeymapEditComponent {
protected keymap$: Observable<Keymap>;
private deletable$: Observable<boolean>;
constructor(
private store: Store<AppState>,
private route: ActivatedRoute
) {
this.keymap$ = route
.params
.select<string>('abbr')
.switchMap((abbr: string) => store.let(getKeymap(abbr)))
.publishReplay(1)
.refCount();
this.deletable$ = store.let(getKeymapEntities())
.map((keymaps: Keymap[]) => keymaps.length > 1);
}
}

View File

@@ -0,0 +1,38 @@
<div class="row">
<h1 class="col-xs-12 pane-title">
<i class="fa fa-keyboard-o"></i>
<input #name cancelable
class="keymap__name pane-title__name"
type="text"
value="{{ keymap.name }}"
(change)="editKeymapName($event.target.value)"
(keyup.enter)="name.blur()"
/> keymap
(<input #abbr cancelable
class="keymap__abbrev pane-title__abbrev"
type="text"
value="{{ keymap.abbreviation }}"
(change)="editKeymapAbbr($event.target.value)"
(keyup.enter)="abbr.blur()"
[attr.maxLength]="3"
/>)
<i class="fa keymap__is-default"
[ngClass]="{'fa-star-o': !keymap.isDefault, 'fa-star': keymap.isDefault}"
[title]="starTitle"
(click)="setDefault()"
></i>
<i class="glyphicon glyphicon-trash keymap__remove pull-right" [title]="trashTitle"
[class.disabled]="!deletable"
data-toggle="tooltip"
data-placement="left"
data-original-title="Remove keymap"
(click)="removeKeymap()"
></i>
<i class="fa fa-files-o keymap__duplicate pull-right" title=""
data-toggle="tooltip"
data-placement="left"
data-original-title="Duplicate keymap"
(click)="duplicateKeymap()"
></i>
</h1>
</div>

View File

@@ -0,0 +1,71 @@
@import '../../../global-styles';
:host {
display: block;
}
.keymap {
&__is-default {
&.fa-star-o {
cursor: pointer;
&:hover {
color: $icon-hover;
}
}
}
&__remove {
font-size: 0.75em;
top: 8px;
&:not(.disabled):hover {
cursor: pointer;
color: $icon-hover-delete;
}
&.disabled {
opacity: 0.25;
}
}
&__duplicate {
font-size: 0.75em;
top: 7px;
margin-right: 15px;
position: relative;
&:hover {
cursor: pointer;
color: $icon-hover;
}
}
}
.pane-title {
margin-bottom: 1em;
&__name,
&__abbrev {
border: none;
border-bottom: 2px dotted #999;
padding: 0;
margin: 0 0.25rem;
&:focus {
box-shadow: 0 0 0 1px #ccc, 0 0 5px 0 #ccc;
border-color: transparent;
}
}
&__name {
width: 290px;
text-overflow: ellipsis;
}
&__abbrev {
width: 90px;
text-align: center;
}
}

View File

@@ -0,0 +1,90 @@
import {
ChangeDetectionStrategy,
Component,
ElementRef,
Input,
OnChanges,
Renderer,
SimpleChanges,
ViewChild
} from '@angular/core';
import { Store } from '@ngrx/store';
import { Keymap } from '../../../config-serializer/config-items/Keymap';
import { AppState } from '../../../store';
import { KeymapActions } from '../../../store/actions';
@Component({
selector: 'keymap-header',
template: require('./keymap-header.component.html'),
styles: [require('./keymap-header.component.scss')],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class KeymapHeaderComponent implements OnChanges {
@Input() keymap: Keymap;
@Input() deletable: boolean;
@ViewChild('name') keymapName: ElementRef;
@ViewChild('abbr') keymapAbbr: ElementRef;
private starTitle: string;
private trashTitle: string;
constructor(private store: Store<AppState>, private renderer: Renderer) { }
ngOnChanges(changes: SimpleChanges) {
if (changes['keymap']) {
this.setKeymapTitle();
}
if (changes['deletable']) {
this.setTrashTitle();
}
}
setDefault() {
if (!this.keymap.isDefault) {
this.store.dispatch(KeymapActions.setDefault(this.keymap.abbreviation));
}
}
removeKeymap() {
if (this.deletable) {
this.store.dispatch(KeymapActions.removeKeymap(this.keymap.abbreviation));
}
}
duplicateKeymap() {
this.store.dispatch(KeymapActions.duplicateKeymap(this.keymap));
}
editKeymapName(name: string) {
if (name.length === 0) {
this.renderer.setElementProperty(this.keymapName.nativeElement, 'value', this.keymap.name);
return;
}
this.store.dispatch(KeymapActions.editKeymapName(this.keymap.abbreviation, name));
}
editKeymapAbbr(newAbbr: string) {
if (newAbbr.length !== 3) {
this.renderer.setElementProperty(this.keymapAbbr.nativeElement, 'value', this.keymap.abbreviation);
return;
}
newAbbr = newAbbr.toUpperCase();
this.store.dispatch(KeymapActions.editKeymapAbbr(this.keymap.abbreviation, newAbbr));
}
setKeymapTitle(): void {
this.starTitle = this.keymap.isDefault
? 'This is the default keymap which gets activated when powering the keyboard.'
: 'Makes this keymap the default keymap which gets activated when powering the keyboard.';
}
setTrashTitle(): void {
this.trashTitle = this.deletable ? '' : 'The last keymap cannot be deleted.';
}
}

View File

@@ -0,0 +1,3 @@
export * from './add/keymap-add.component';
export * from './edit/keymap-edit.component';
export * from './header/keymap-header.component';

View File

@@ -0,0 +1 @@
export * from './layers.component';

View File

@@ -0,0 +1,8 @@
<div class="text-center">
<span class="uhk__layer-switcher--wrapper" data-title="Layers: ">
<button type="button" class="btn btn-default btn-lg" *ngFor="let button of buttons; let index = index" (click)="selectLayer(index)"
[class.btn-primary]="index === current">
{{ button }}
</button>
</span>
</div>

View File

@@ -0,0 +1,40 @@
:host {
display: block;
&.disabled {
button {
cursor: no-drop;
background: rgba(#ccc, 0.43);
pointer-events: none;
&.btn-primary {
background: #7c7c7c;
border-color: #7c7c7c;
}
}
}
}
button {
margin: 2px;
}
.uhk {
&__layer-switcher {
&--wrapper {
position: relative;
margin-bottom: 2rem;
&:before {
content: attr(data-title);
display: inline-block;
position: absolute;
bottom: -0.3em;
right: 100%;
font-size: 2.4rem;
padding-right: 0.25em;
margin: 0;
}
}
}
}

View File

@@ -0,0 +1,31 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
selector: 'layers',
template: require('./layers.component.html'),
styles: [require('./layers.component.scss')]
})
export class LayersComponent {
@Input() current: number;
@Output() select = new EventEmitter();
private buttons: string[];
constructor() {
this.buttons = ['Base', 'Mod', 'Fn', 'Mouse'];
this.current = 0;
}
selectLayer(index: number) {
if (this.current === index) {
return;
}
this.select.emit({
oldIndex: this.current,
index: index
});
this.current = index;
}
}

View File

@@ -0,0 +1 @@
export { MacroActionEditorComponent } from './macro-action-editor.component';

View File

@@ -0,0 +1,46 @@
<div class="action--editor">
<div class="row">
<div class="col-xs-4 editor__tab-links">
<ul class="nav nav-pills nav-stacked">
<li #macroText [class.active]="activeTab === TabName.Text" (click)="selectTab(TabName.Text)">
<a>
<i class="fa fa-font"></i>
<span>Type text</span>
</a>
</li>
<li #macroKeypress [class.active]="activeTab === TabName.Keypress" (click)="selectTab(TabName.Keypress)">
<a>
<i class="fa fa-keyboard-o"></i>
<span>Key action</span>
</a>
</li>
<li #macroMouse [class.active]="activeTab === TabName.Mouse" (click)="selectTab(TabName.Mouse)">
<a>
<i class="fa fa-mouse-pointer"></i>
<span>Mouse action</span>
</a>
</li>
<li #macroDelay [class.active]="activeTab === TabName.Delay" (click)="selectTab(TabName.Delay)">
<a>
<i class="fa fa-clock-o"></i>
<span>Delay</span>
</a>
</li>
</ul>
</div>
<div class="col-xs-8 editor__tabs" [ngSwitch]="activeTab">
<macro-text-tab #tab *ngSwitchCase="TabName.Text" [macroAction]="editableMacroAction"></macro-text-tab>
<macro-key-tab #tab *ngSwitchCase="TabName.Keypress" [macroAction]="editableMacroAction"></macro-key-tab>
<macro-mouse-tab #tab *ngSwitchCase="TabName.Mouse" [macroAction]="editableMacroAction"></macro-mouse-tab>
<macro-delay-tab #tab *ngSwitchCase="TabName.Delay" [macroAction]="editableMacroAction"></macro-delay-tab>
</div>
</div>
<div class="row">
<div class="col-xs-12 flex-button-wrapper editor__actions-container">
<div class="editor__actions">
<button class="btn btn-sm btn-default flex-button" type="button" (click)="onCancelClick()"> Cancel </button>
<button class="btn btn-sm btn-primary flex-button" type="button" (click)="onSaveClick()"> Save </button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,88 @@
@import '../../../global-styles';
:host {
display: block;
width: 100%;
}
.action--editor {
padding-top: 0;
padding-bottom: 0;
border-radius: 0;
border: 0;
}
.nav {
padding-bottom: 1rem;
li {
a {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
&.selected {
font-style: italic;
}
&:hover {
cursor: pointer;
}
}
&.active {
z-index: 2;
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;
}
}
}
}
}
.editor {
&__tabs,
&__tab-links {
padding-top: 1rem;
}
&__tabs {
border-left: 1px solid #ddd;
margin-left: -1.6rem;
padding-left: 3rem;
}
&__actions {
float: right;
&-container {
background: #f5f5f5;
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
padding: 1rem 1.5rem;
}
}
}
.flex-button-wrapper {
display: flex;
flex-direction: row-reverse;
}
.flex-button {
align-self: flex-end;
}

View File

@@ -0,0 +1,107 @@
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
import {
EditableMacroAction,
MacroAction,
TextMacroAction,
macroActionType
} from '../../../config-serializer/config-items/macro-action';
import { MacroKeyTabComponent } from './tab/key';
enum TabName {
Keypress,
Text,
Mouse,
Delay
};
@Component({
selector: 'macro-action-editor',
template: require('./macro-action-editor.component.html'),
styles: [require('./macro-action-editor.component.scss')],
host: { 'class': 'macro-action-editor' }
})
export class MacroActionEditorComponent implements OnInit {
@Input() macroAction: MacroAction;
@Output() save = new EventEmitter<MacroAction>();
@Output() cancel = new EventEmitter<void>();
@ViewChild('tab') selectedTab: any;
private editableMacroAction: EditableMacroAction;
private activeTab: TabName;
/* tslint:disable:variable-name: It is an enum type. So it can start with uppercase. */
/* tslint:disable:no-unused-variable: It is used in the template. */
private TabName = TabName;
/* tslint:enable:no-unused-variable */
/* tslint:enable:variable-name */
ngOnInit() {
let macroAction: MacroAction = this.macroAction ? this.macroAction : new TextMacroAction();
this.editableMacroAction = new EditableMacroAction(macroAction.toJsonObject());
let tab: TabName = this.getTabName(this.editableMacroAction);
this.activeTab = tab;
}
onCancelClick(): void {
this.cancel.emit();
}
onSaveClick(): void {
try {
const action = this.editableMacroAction;
if (action.isKeyAction()) {
// Could updating the saved keys be done in a better way?
const tab = this.selectedTab as MacroKeyTabComponent;
action.fromKeyAction(tab.getKeyAction());
}
this.save.emit(action.toClass());
} catch (e) {
// TODO: show error dialog
console.error(e);
}
}
selectTab(tab: TabName): void {
this.activeTab = tab;
this.editableMacroAction.macroActionType = this.getTabMacroActionType(tab);
}
getTabName(action: EditableMacroAction): TabName {
switch (action.macroActionType) {
// Delay action
case macroActionType.DelayMacroAction:
return TabName.Delay;
// Text action
case macroActionType.TextMacroAction:
return TabName.Text;
// Keypress actions
case macroActionType.KeyMacroAction:
return TabName.Keypress;
// Mouse actions
case macroActionType.MouseButtonMacroAction:
case macroActionType.MoveMouseMacroAction:
case macroActionType.ScrollMouseMacroAction:
return TabName.Mouse;
default:
return TabName.Keypress;
}
}
getTabMacroActionType(tab: TabName): string {
switch (tab) {
case TabName.Delay:
return macroActionType.DelayMacroAction;
case TabName.Keypress:
return macroActionType.KeyMacroAction;
case TabName.Mouse:
return macroActionType.MouseButtonMacroAction;
case TabName.Text:
return macroActionType.TextMacroAction;
default:
throw new Error('Could not get macro action type');
}
}
}

View File

@@ -0,0 +1 @@
export { MacroDelayTabComponent } from './macro-delay.component';

View File

@@ -0,0 +1,26 @@
<div class="macro-delay">
<div class="row">
<div class="col-xs-12">
<h4>Enter delay in seconds</h4>
</div>
</div>
<div class="row">
<div class="col-xs-4">
<input #macroDelayInput
type="number"
min="0"
max="1000"
step="0.1"
placeholder="Delay amount"
class="form-control"
[attr.value]="delay"
(change)="setDelay($event)">
</div>
</div>
<div class="row macro-delay__presets">
<div class="col-xs-12">
<h6>Choose a preset</h6>
<button *ngFor="let delay of presets" class="btn btn-sm btn-default" (click)="setDelay(delay)">{{delay}}s</button>
</div>
</div>
</div>

View File

@@ -0,0 +1,16 @@
:host {
display: flex;
flex-direction: column;
position: relative;
}
.macro-delay {
&__presets {
margin-top: 1rem;
button {
margin-right: 0.25rem;
margin-bottom: 0.25rem;
}
}
}

View File

@@ -0,0 +1,40 @@
import {
ChangeDetectionStrategy,
Component,
ElementRef,
Input,
OnInit,
ViewChild
} from '@angular/core';
import { EditableMacroAction } from '../../../../../config-serializer/config-items/macro-action';
const INITIAL_DELAY = 0.5; // In seconds
@Component({
selector: 'macro-delay-tab',
template: require('./macro-delay.component.html'),
styles: [require('./macro-delay.component.scss')],
host: { 'class': 'macro__delay' },
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MacroDelayTabComponent implements OnInit {
@Input() macroAction: EditableMacroAction;
@ViewChild('macroDelayInput') input: ElementRef;
private delay: number;
/* tslint:disable:no-unused-variable: It is used in the template. */
private presets: number[] = [0.3, 0.5, 0.8, 1, 2, 3, 4, 5];
/* tslint:enable:no-unused-variable */
constructor() { }
ngOnInit() {
this.delay = this.macroAction.delay > 0 ? this.macroAction.delay / 1000 : INITIAL_DELAY;
}
setDelay(value: number): void {
this.delay = value;
this.macroAction.delay = this.delay * 1000;
}
}

View File

@@ -0,0 +1,4 @@
export { MacroDelayTabComponent } from './delay';
export { MacroKeyTabComponent } from './key';
export { MacroMouseTabComponent } from './mouse';
export { MacroTextTabComponent } from './text';

View File

@@ -0,0 +1 @@
export { MacroKeyTabComponent } from './macro-key.component';

View File

@@ -0,0 +1,32 @@
<div class="col-xs-12 macro-key__container">
<div class="col-xs-4 macro-key__types">
<ul class="nav nav-pills nav-stacked">
<li #keyMove [class.active]="activeTab === TabName.Keypress" (click)="selectTab(TabName.Keypress)">
<a>
<i class="fa fa-hand-pointer-o"></i>
<span>Press key</span>
</a>
</li>
<li #keyHold [class.active]="activeTab === TabName.Hold" (click)="selectTab(TabName.Hold)">
<a>
<i class="fa fa-hand-rock-o"></i>
<span>Hold key</span>
</a>
</li>
<li #keyRelease [class.active]="activeTab === TabName.Release" (click)="selectTab(TabName.Release)">
<a>
<i class="fa fa-hand-paper-o"></i>
<span>Release key</span>
</a>
</li>
</ul>
</div>
<div class="col-xs-8 macro-key__action-container">
<div class="macro-key__action">
<h4 *ngIf="activeTab === TabName.Keypress">Press key</h4>
<h4 *ngIf="activeTab === TabName.Hold">Hold key</h4>
<h4 *ngIf="activeTab === TabName.Release">Release key</h4>
<keypress-tab #keypressTab [defaultKeyAction]="defaultKeyAction" [longPressEnabled]="false"></keypress-tab>
</div>
</div>
</div>

View File

@@ -0,0 +1,25 @@
.macro-key {
&__container {
padding: 0;
}
&__types {
margin-left: 0;
padding: 0 0 1rem;
}
&__action {
&-container {
margin-top: -1rem;
padding-top: 1rem;
border-left: 1px solid #ddd;
}
padding-left: 3rem;
padding-bottom: 1rem;
}
}
.fa {
min-width: 14px;
}

View File

@@ -0,0 +1,74 @@
import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { KeyAction } from '../../../../../config-serializer/config-items/key-action';
import { EditableMacroAction, MacroSubAction } from '../../../../../config-serializer/config-items/macro-action';
import { KeypressTabComponent } from '../../../../popover/tab';
import { Tab } from '../../../../popover/tab';
enum TabName {
Keypress,
Hold,
Release
}
@Component({
selector: 'macro-key-tab',
template: require('./macro-key.component.html'),
styles: [
require('../../macro-action-editor.component.scss'),
require('./macro-key.component.scss')
],
host: { 'class': 'macro__mouse' }
})
export class MacroKeyTabComponent implements OnInit {
@Input() macroAction: EditableMacroAction;
@ViewChild('tab') selectedTab: Tab;
@ViewChild('keypressTab') keypressTab: KeypressTabComponent;
private defaultKeyAction: KeyAction;
private activeTab: TabName;
/* tslint:disable:variable-name: It is an enum type. So it can start with uppercase. */
/* tslint:disable:no-unused-variable: It is used in the template. */
private TabName = TabName;
/* tslint:enable:no-unused-variable */
/* tslint:enable:variable-name */
ngOnInit() {
this.defaultKeyAction = this.macroAction.toKeystrokeAction();
this.selectTab(this.getTabName(this.macroAction));
}
selectTab(tab: TabName): void {
this.activeTab = tab;
this.macroAction.action = this.getActionType(tab);
}
getTabName(action: EditableMacroAction): TabName {
if (!action.action || action.isOnlyPressAction()) {
return TabName.Keypress;
} else if (action.isOnlyHoldAction()) {
return TabName.Hold;
} else if (action.isOnlyReleaseAction()) {
return TabName.Release;
}
}
getActionType(tab: TabName): MacroSubAction {
switch (tab) {
case TabName.Keypress:
return MacroSubAction.press;
case TabName.Hold:
return MacroSubAction.hold;
case TabName.Release:
return MacroSubAction.release;
default:
throw new Error('Invalid tab type');
}
}
getKeyAction(): KeyAction {
return this.keypressTab.toKeyAction();
}
}

View File

@@ -0,0 +1 @@
export { MacroMouseTabComponent } from './macro-mouse.component';

View File

@@ -0,0 +1,77 @@
<div class="col-xs-12 macro-mouse__container">
<div class="col-xs-4 macro-mouse__types">
<ul class="nav nav-pills nav-stacked">
<li #mouseMove [class.active]="activeTab === TabName.Move" (click)="selectTab(TabName.Move)">
<a>
<i class="fa fa-arrows"></i>
<span>Move pointer</span>
</a>
</li>
<li #mouseScroll [class.active]="activeTab === TabName.Scroll" (click)="selectTab(TabName.Scroll)">
<a>
<i class="fa fa-arrows-v"></i>
<span>Scroll</span>
</a>
</li>
<li #mouseClick [class.active]="activeTab === TabName.Click" (click)="selectTab(TabName.Click)">
<a>
<i class="fa fa-mouse-pointer"></i>
<span>Click button</span>
</a>
</li>
<li #mouseHold [class.active]="activeTab === TabName.Hold" (click)="selectTab(TabName.Hold)">
<a>
<i class="fa fa-hand-rock-o"></i>
<span>Hold button</span>
</a>
</li>
<li #mouseRelease [class.active]="activeTab === TabName.Release" (click)="selectTab(TabName.Release)">
<a>
<i class="fa fa-hand-paper-o"></i>
<span>Release button</span>
</a>
</li>
</ul>
</div>
<div class="col-xs-8 macro-mouse__actions" [ngSwitch]="activeTab">
<div #tab *ngSwitchCase="TabName.Move">
<h4>Move pointer</h4>
<p>Use negative values to move down or left from current position.</p>
<div class="form-horizontal">
<div class="form-group">
<label for="move-mouse-x">X</label>
<input id="move-mouse-x" type="number" class="form-control" [(ngModel)]="macroAction.moveX"> pixels
</div>
<div class="form-group">
<label for="move-mouse-y">Y</label>
<input id="move-mouse-y" type="number" class="form-control" [(ngModel)]="macroAction.moveY"> pixels
</div>
</div>
</div>
<div #tab *ngSwitchCase="TabName.Scroll">
<h4>Scroll</h4>
<p>Use negative values to move down or left from current position.</p>
<div class="form-horizontal">
<div class="form-group">
<label for="scroll-mouse-x">X</label>
<input id="scroll-mouse-x" type="number" class="form-control" [(ngModel)]="macroAction.scrollX"> pixels
</div>
<div class="form-group">
<label for="scroll-mouse-y">Y</label>
<input id="scroll-mouse-y" type="number" class="form-control" [(ngModel)]="macroAction.scrollY"> pixels
</div>
</div>
</div>
<div #tab *ngIf="activeTab === TabName.Click || activeTab === TabName.Hold || activeTab === TabName.Release">
<h4 *ngIf="activeTab === TabName.Click">Click mouse button</h4>
<h4 *ngIf="activeTab === TabName.Hold">Hold mouse button</h4>
<h4 *ngIf="activeTab === TabName.Release">Release mouse button</h4>
<div class="btn-group macro-mouse__buttons">
<button *ngFor="let buttonLabel of buttonLabels; let buttonIndex = index"
class="btn btn-default"
[class.btn-primary]="hasButton(buttonIndex)"
(click)="setMouseClick(buttonIndex)">{{buttonLabel}}</button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,43 @@
.macro-mouse {
&__container {
padding: 0;
}
&__types {
border-right: 1px solid #ddd;
border-left: 0;
margin-top: -1rem;
margin-left: 0;
padding: 1rem 0;
}
&__actions {
padding-left: 3rem;
padding-bottom: 1rem;
}
&__buttons {
margin-top: 3rem;
margin-bottom: 1rem;
}
}
.fa {
min-width: 14px;
}
.form-horizontal {
.form-group {
margin: 0 0 0.5rem;
}
label {
display: inline-block;
margin-right: 0.5rem;
}
.form-control {
display: inline-block;
width: 60%;
}
}

View File

@@ -0,0 +1,107 @@
import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { EditableMacroAction, MacroSubAction, macroActionType } from '../../../../../config-serializer/config-items/macro-action';
import { Tab } from '../../../../popover/tab';
enum TabName {
Move,
Scroll,
Click,
Hold,
Release
}
@Component({
selector: 'macro-mouse-tab',
template: require('./macro-mouse.component.html'),
styles: [
require('../../macro-action-editor.component.scss'),
require('./macro-mouse.component.scss')
],
host: { 'class': 'macro__mouse' }
})
export class MacroMouseTabComponent implements OnInit {
@Input() macroAction: EditableMacroAction;
@ViewChild('tab') selectedTab: Tab;
private activeTab: TabName;
private buttonLabels: string[];
private selectedButtons: boolean[];
/* tslint:disable:variable-name: It is an enum type. So it can start with uppercase. */
/* tslint:disable:no-unused-variable: It is used in the template. */
private TabName = TabName;
/* tslint:enable:no-unused-variable */
/* tslint:enable:variable-name */
constructor() {
this.buttonLabels = ['Left', 'Middle', 'Right'];
this.selectedButtons = Array(this.buttonLabels.length).fill(false);
}
ngOnInit() {
const tabName = this.getTabName(this.macroAction);
this.selectTab(tabName);
const buttonActions = [TabName.Click, TabName.Hold, TabName.Release];
if (buttonActions.includes(this.activeTab)) {
this.selectedButtons = this.macroAction.getMouseButtons();
}
}
selectTab(tab: TabName): void {
this.activeTab = tab;
this.macroAction.macroActionType = this.getMacroActionType(tab);
if (this.macroAction.macroActionType === macroActionType.MouseButtonMacroAction) {
this.macroAction.action = this.getAction(tab);
}
}
setMouseClick(index: number): void {
this.selectedButtons[index] = !this.selectedButtons[index];
this.macroAction.setMouseButtons(this.selectedButtons);
}
hasButton(index: number): boolean {
return this.selectedButtons[index];
}
getAction(tab: TabName): MacroSubAction {
switch (tab) {
case TabName.Click:
return MacroSubAction.press;
case TabName.Hold:
return MacroSubAction.hold;
case TabName.Release:
return MacroSubAction.release;
default:
throw new Error('Invalid tab name');
}
}
getTabName(action: EditableMacroAction): TabName {
if (action.macroActionType === macroActionType.MouseButtonMacroAction) {
if (!action.action || action.isOnlyPressAction()) {
return TabName.Click;
} else if (action.isOnlyPressAction()) {
return TabName.Hold;
} else if (action.isOnlyReleaseAction()) {
return TabName.Release;
}
} else if (action.macroActionType === macroActionType.MoveMouseMacroAction) {
return TabName.Move;
} else if (action.macroActionType === macroActionType.ScrollMouseMacroAction) {
return TabName.Scroll;
}
return TabName.Move;
}
getMacroActionType(tab: TabName): string {
if (tab === TabName.Click || tab === TabName.Hold || tab === TabName.Release) {
return macroActionType.MouseButtonMacroAction;
} else if (tab === TabName.Move) {
return macroActionType.MoveMouseMacroAction;
} else if (tab === TabName.Scroll) {
return macroActionType.ScrollMouseMacroAction;
}
}
}

View File

@@ -0,0 +1 @@
export { MacroTextTabComponent } from './macro-text.component';

View File

@@ -0,0 +1,5 @@
<div>
<h4>Type text</h4>
<p>Input the text you want to type with this macro action.</p>
<textarea #macroTextInput name="macro-text" (change)="onTextChange()" class="macro__text-input">{{ macroAction.text }}</textarea>
</div>

View File

@@ -0,0 +1,11 @@
:host {
display: flex;
flex-direction: column;
position: relative;
}
.macro__text-input {
width: 100%;
min-height: 10rem;
margin-bottom: 1rem;
}

View File

@@ -0,0 +1,32 @@
import {
AfterViewInit,
Component,
ElementRef,
Input,
Renderer,
ViewChild
} from '@angular/core';
import { EditableMacroAction } from '../../../../../config-serializer/config-items/macro-action';
@Component({
selector: 'macro-text-tab',
template: require('./macro-text.component.html'),
styles: [require('./macro-text.component.scss')],
host: { 'class': 'macro__text' }
})
export class MacroTextTabComponent implements AfterViewInit {
@Input() macroAction: EditableMacroAction;
@ViewChild('macroTextInput') input: ElementRef;
constructor(private renderer: Renderer) {}
ngAfterViewInit() {
this.renderer.invokeElementMethod(this.input.nativeElement, 'focus');
}
onTextChange() {
this.macroAction.text = this.input.nativeElement.value;
}
}

View File

@@ -0,0 +1,17 @@
<template [ngIf]="macro">
<macro-header
[macro]="macro"
[isNew]="isNew"
></macro-header>
<macro-list
[macro]="macro"
(add)="addAction($event.macroId, $event.action)"
(edit)="editAction($event.macroId, $event.index, $event.action)"
(delete)="deleteAction($event.macroId, $event.index, $event.action)"
(reorder)="reorderAction($event.macroId, $event.oldIndex, $event.newIndex)"
></macro-list>
</template>
<div *ngIf="!macro" class="not-found">
There is no macro with id {{ route.params.select('id') | async }}.
</div>

View File

@@ -0,0 +1,11 @@
:host {
width: 100%;
height: 100%;
display: block;
}
.not-found {
margin-top: 30px;
font-size: 16px;
text-align: center;
}

View File

@@ -0,0 +1,59 @@
import { Component, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Store } from '@ngrx/store';
import { Subscription } from 'rxjs/Subscription';
import { Macro } from '../../../config-serializer/config-items/Macro';
import { MacroAction } from '../../../config-serializer/config-items/macro-action/MacroAction';
import { MacroActions } from '../../../store/actions';
import { AppState } from '../../../store/index';
import { getMacro } from '../../../store/reducers/macro';
@Component({
selector: 'macro-edit',
template: require('./macro-edit.component.html'),
styles: [require('./macro-edit.component.scss')],
host: {
'class': 'container-fluid'
}
})
export class MacroEditComponent implements OnDestroy {
private subscription: Subscription;
private macro: Macro;
private isNew: boolean;
constructor(private store: Store<AppState>, public route: ActivatedRoute) {
this.subscription = route
.params
.select<string>('id')
.switchMap((id: string) => store.let(getMacro(+id)))
.subscribe((macro: Macro) => {
this.macro = macro;
});
this.isNew = this.route.snapshot.params['empty'] === 'new';
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
addAction(macroId: number, action: MacroAction) {
this.store.dispatch(MacroActions.addMacroAction(macroId, action));
}
editAction(macroId: number, index: number, action: MacroAction) {
this.store.dispatch(MacroActions.saveMacroAction(macroId, index, action));
}
deleteAction(macroId: number, index: number, action: MacroAction) {
this.store.dispatch(MacroActions.deleteMacroAction(macroId, index, action));
}
reorderAction(macroId: number, oldIndex: number, newIndex: number) {
this.store.dispatch(MacroActions.reorderMacroAction(macroId, oldIndex, newIndex));
}
}

View File

@@ -0,0 +1,24 @@
<div class="row">
<h1 class="col-xs-12 pane-title">
<i class="fa fa-play"></i>
<input #macroName cancelable
class="pane-title__name"
type="text"
value="{{ macro.name }}"
(change)="editMacroName($event.target.value)"
(keyup.enter)="macroName.blur()"
/>
<i class="glyphicon glyphicon-trash macro__remove pull-right" title=""
data-toggle="tooltip"
data-placement="left"
data-original-title="Remove macro"
(click)="removeMacro()"
></i>
<i class="fa fa-files-o macro__duplicate pull-right" title=""
data-toggle="tooltip"
data-placement="left"
data-original-title="Duplicate macro"
(click)="duplicateMacro()"
></i>
</h1>
</div>

View File

@@ -0,0 +1,43 @@
@import '../../../global-styles';
.macro {
&__remove {
font-size: 0.75em;
top: 8px;
&:hover {
cursor: pointer;
color: $icon-hover-delete;
}
}
&__duplicate {
font-size: 0.75em;
top: 7px;
margin-right: 15px;
position: relative;
&:hover {
cursor: pointer;
color: $icon-hover;
}
}
}
.pane-title {
margin-bottom: 1em;
&__name {
border: none;
border-bottom: 2px dotted #999;
padding: 0;
margin: 0 0.25rem;
width: 330px;
text-overflow: ellipsis;
&:focus {
box-shadow: 0 0 0 1px #ccc, 0 0 5px 0 #ccc;
border-color: transparent;
}
}
}

View File

@@ -0,0 +1,55 @@
import {
AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, Input, OnChanges, Renderer,
ViewChild
} from '@angular/core';
import { Store } from '@ngrx/store';
import { Macro } from '../../../config-serializer/config-items/Macro';
import { MacroActions } from '../../../store/actions';
import { AppState } from '../../../store/index';
@Component({
selector: 'macro-header',
template: require('./macro-header.component.html'),
styles: [require('./macro-header.component.scss')],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MacroHeaderComponent implements AfterViewInit, OnChanges {
@Input() macro: Macro;
@Input() isNew: boolean;
@ViewChild('macroName') macroName: ElementRef;
constructor(private store: Store<AppState>, private renderer: Renderer) { }
ngOnChanges() {
if (this.isNew) {
this.renderer.invokeElementMethod(this.macroName.nativeElement, 'select', []);
}
}
ngAfterViewInit() {
if (this.isNew) {
this.renderer.invokeElementMethod(this.macroName.nativeElement, 'select', []);
}
}
removeMacro() {
this.store.dispatch(MacroActions.removeMacro(this.macro.id));
}
duplicateMacro() {
this.store.dispatch(MacroActions.duplicateMacro(this.macro));
}
editMacroName(name: string) {
if (name.length === 0) {
this.renderer.setElementProperty(this.macroName.nativeElement, 'value', this.macro.name);
return;
}
this.store.dispatch(MacroActions.editMacroName(this.macro.id, name));
}
}

View File

@@ -0,0 +1,8 @@
export * from './edit/macro-edit.component';
export * from './list/macro-list.component';
export * from './header/macro-header.component';
export * from './macro.routes';
export * from './not-found';
export * from './item';
export * from './action-editor';
export * from './action-editor/tab';

View File

@@ -0,0 +1 @@
export { MacroItemComponent } from './macro-item.component';

View File

@@ -0,0 +1,17 @@
<div class="list-group-item action--item" [class.is-editing]="editing">
<span *ngIf="movable" class="glyphicon glyphicon-option-vertical action--movable" aria-hidden="true"></span>
<div class="action--item--wrap" [class.pointer]="!editing && editable" (click)="editAction()">
<icon [name]="iconName"></icon>
<div class="action--title">{{ title }}</div>
<icon *ngIf="editable && macroAction && !editing" name="pencil"></icon>
</div>
<icon *ngIf="deletable" name="trash" (click)="deleteAction()"></icon>
</div>
<div class="list-group-item macro-action-editor__container"
[@toggler]="((editable && editing) || newItem) ? 'active' : 'inactive'">
<macro-action-editor
[macroAction]="macroAction"
(cancel)="cancelEdit()"
(save)="saveEditedAction($event)">
</macro-action-editor>
</div>

View File

@@ -0,0 +1,86 @@
@import '../../../global-styles';
:host {
overflow: hidden;
display: block;
&.macro-item:first-of-type {
.list-group-item {
border-radius: 4px 4px 0 0;
}
}
&.macro-item:last-of-type {
.list-group-item {
border-bottom: 0;
}
}
&.gu-transit {
opacity: 0.2;
.list-group-item {
background: #f5f5f5;
}
}
}
.action {
&--item {
display: flex;
flex-shrink: 0;
border: 0;
border-bottom: 1px solid #ddd;
icon {
margin: 0 5px;
}
> div {
display: flex;
flex: 1;
}
&:first-child {
border-radius: 0;
}
&.is-editing {
background: #f5f5f5;
}
&--wrap {
justify-content: space-between;
&.pointer {
&:hover {
cursor: pointer;
color: $icon-hover;
}
}
}
}
&--title {
display: flex;
flex: 1;
}
&--movable {
&:hover {
cursor: move;
}
}
}
.list-group-item {
margin-bottom: 0;
}
.macro-action-editor__container {
padding-top: 0;
padding-bottom: 0;
border-radius: 0;
border: none;
overflow: hidden;
}

View File

@@ -0,0 +1,217 @@
import {
Component,
EventEmitter,
Input,
OnChanges,
OnInit,
Output,
SimpleChanges,
animate,
state,
style,
transition,
trigger
} from '@angular/core';
import { KeyModifiers } from '../../../config-serializer/config-items/KeyModifiers';
import {
DelayMacroAction,
KeyMacroAction,
MacroAction,
MouseButtonMacroAction,
MoveMouseMacroAction,
ScrollMouseMacroAction,
TextMacroAction
} from '../../../config-serializer/config-items/macro-action';
import { MapperService } from '../../../services/mapper.service';
@Component({
animations: [
trigger('toggler', [
state('inactive', style({
height: '0px'
})),
state('active', style({
height: '*'
})),
transition('inactive <=> active', animate('500ms ease-out'))
])
],
selector: 'macro-item',
template: require('./macro-item.component.html'),
styles: [require('./macro-item.component.scss')],
host: { 'class': 'macro-item' }
})
export class MacroItemComponent implements OnInit, OnChanges {
@Input() macroAction: MacroAction;
@Input() editable: boolean;
@Input() deletable: boolean;
@Input() movable: boolean;
@Output() save = new EventEmitter<MacroAction>();
@Output() cancel = new EventEmitter<void>();
@Output() edit = new EventEmitter<void>();
@Output() delete = new EventEmitter<void>();
private title: string;
private iconName: string;
private editing: boolean;
private newItem: boolean = false;
constructor(private mapper: MapperService) { }
ngOnInit() {
this.updateView();
if (!this.macroAction) {
this.editing = true;
this.newItem = true;
}
}
ngOnChanges(changes: SimpleChanges) {
if (changes['macroAction']) {
this.updateView();
}
}
saveEditedAction(editedAction: MacroAction): void {
this.macroAction = editedAction;
this.editing = false;
this.updateView();
this.save.emit(editedAction);
}
editAction(): void {
if (!this.editable || this.editing) {
return;
}
this.editing = true;
this.edit.emit();
}
cancelEdit(): void {
this.editing = false;
this.cancel.emit();
}
deleteAction(): void {
this.delete.emit();
}
private updateView(): void {
if (!this.macroAction) {
this.title = 'New macro action';
} else if (this.macroAction instanceof DelayMacroAction) {
// Delay
this.iconName = 'clock';
let action: DelayMacroAction = this.macroAction as DelayMacroAction;
const delay = action.delay > 0 ? action.delay / 1000 : 0;
this.title = `Delay of ${delay}s`;
} else if (this.macroAction instanceof TextMacroAction) {
// Write text
let action: TextMacroAction = this.macroAction as TextMacroAction;
this.iconName = 'font';
this.title = `Write text: ${action.text}`;
} else if (this.macroAction instanceof KeyMacroAction) {
// Key pressed/held/released
const action: KeyMacroAction = this.macroAction as KeyMacroAction;
this.setKeyActionContent(action);
} else if (this.macroAction instanceof MouseButtonMacroAction) {
// Mouse button clicked/held/released
const action: MouseButtonMacroAction = this.macroAction as MouseButtonMacroAction;
this.setMouseButtonActionContent(action);
} else if (this.macroAction instanceof MoveMouseMacroAction || this.macroAction instanceof ScrollMouseMacroAction) {
// Mouse moved or scrolled
this.setMouseMoveScrollActionContent(this.macroAction);
} else {
this.title = this.macroAction.constructor.name;
}
}
private setKeyActionContent(action: KeyMacroAction): void {
if (!action.hasScancode() && !action.hasModifiers()) {
this.title = 'Invalid keypress';
return;
}
if (action.isPressAction()) {
// Press key
this.iconName = 'hand-pointer';
this.title = 'Press key: ';
} else if (action.isHoldAction()) {
// Hold key
this.iconName = 'hand-rock';
this.title = 'Hold key: ';
} else if (action.isReleaseAction()) {
// Release key
this.iconName = 'hand-paper';
this.title = 'Release key: ';
}
if (action.hasScancode()) {
const scancode: string = (this.mapper.scanCodeToText(action.scancode) || [ 'Unknown' ]).join(' ');
if (scancode) {
this.title += scancode;
}
}
if (action.hasModifiers()) {
// Press/hold/release modifiers
for (let i = KeyModifiers.leftCtrl; i !== KeyModifiers.rightGui; i <<= 1) {
if (action.isModifierActive(i)) {
this.title += ' ' + KeyModifiers[i];
}
}
}
}
private setMouseMoveScrollActionContent(action: MacroAction): void {
let typedAction: any;
if (action instanceof MoveMouseMacroAction) {
// Move mouse pointer
this.iconName = 'mouse-pointer';
this.title = 'Move pointer';
typedAction = this.macroAction as MoveMouseMacroAction;
} else {
// Scroll mouse
this.iconName = 'mouse-pointer';
this.title = 'Scroll';
typedAction = this.macroAction as ScrollMouseMacroAction;
}
let needAnd: boolean;
if (Math.abs(typedAction.x) !== 0) {
this.title += ` by ${Math.abs(typedAction.x)}px ${typedAction.x > 0 ? 'left' : 'right'}`;
needAnd = true;
}
if (Math.abs(typedAction.y) !== 0) {
this.title += ` ${needAnd ? 'and' : 'by'} ${Math.abs(typedAction.y)}px ${typedAction.y > 0 ? 'down' : 'up'}`;
}
}
private setMouseButtonActionContent(action: MouseButtonMacroAction): void {
// Press/hold/release mouse buttons
if (action.isOnlyPressAction()) {
this.iconName = 'mouse-pointer';
this.title = 'Click mouse button: ';
} else if (action.isOnlyHoldAction()) {
this.iconName = 'hand-rock';
this.title = 'Hold mouse button: ';
} else if (action.isOnlyReleaseAction()) {
this.iconName = 'hand-paper';
this.title = 'Release mouse button: ';
}
const buttonLabels: string[] = ['Left', 'Middle', 'Right'];
const selectedButtons: boolean[] = action.getMouseButtons();
const selectedButtonLabels: string[] = [];
selectedButtons.forEach((isSelected, idx) => {
if (isSelected && buttonLabels[idx]) {
selectedButtonLabels.push(buttonLabels[idx]);
}
});
this.title += selectedButtonLabels.join(', ');
}
}

View File

@@ -0,0 +1,37 @@
<div class="row list-container">
<div class="col-xs-10 col-xs-offset-1 list-group">
<div class="macro-actions-container" [dragula]="'macroActions'" [dragulaModel]="macro.macroActions">
<macro-item *ngFor="let macroAction of macro.macroActions; let macroActionIndex = index"
[macroAction]="macroAction"
[editable]="true"
[deletable]="true"
[movable]="true"
(save)="saveAction($event, macroActionIndex)"
(edit)="editAction(macroActionIndex)"
(cancel)="cancelAction()"
(delete)="deleteAction(macroAction, macroActionIndex)"
[attr.data-index]="macroActionIndex"
></macro-item>
<macro-item *ngIf="showNew"
[@togglerNew]="showNew ? 'active' : 'inactive'"
[macroAction]="newMacro"
[editable]="true"
[deletable]="false"
[movable]="false"
(save)="addNewAction($event)"
(cancel)="hideNewAction()"
></macro-item>
</div>
<div class="list-group add-new__action-container" [@toggler]="(!showNew) ? 'active' : 'inactive'">
<div class="list-group-item action--item add-new__action-item no-reorder clearfix">
<a class="add-new__action-item--link" (click)="showNewAction()">
<i class="fa fa-plus"></i> Add new macro action
</a>
<a class="add-new__action-item--link">
<i class="fa fa fa-circle"></i> Add new capture keystroke
</a>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,153 @@
@import '../../../global-styles';
:host {
display: flex;
flex-direction: column;
height: 100%;
.list-container {
display: flex;
flex: 1;
}
}
.main-wrapper {
width: 500px;
}
h1 {
margin-bottom: 3rem;
}
.action {
&--edit__form {
background-color: #fff;
margin-left: -0.5rem;
margin-right: -15px;
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid #ddd;
}
&--item {
padding-left: 8px;
&.active,
&.active:hover {
background-color: white;
font-weight: bold;
color: black;
border-color: black;
z-index: 10;
}
}
}
.list-group {
overflow: auto;
}
.macro__name {
border-bottom: 2px dotted #999;
padding: 0 0.5rem;
margin: 0 0.25rem;
}
.macro-settings {
border: 1px solid black;
border-top-color: #999;
z-index: 100;
.helper {
position: absolute;
display: block;
height: 13px;
background: #fff;
width: 100%;
left: 0;
top: -14px;
}
}
.action--item.active.callout,
.macro-settings.callout {
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.5);
}
.macro-actions-container {
margin-bottom: 0;
border-radius: 4px;
border: 1px solid #ddd;
border-bottom: 0;
}
.list-group-item .move-handle:hover {
cursor: move;
}
.flex-button-wrapper {
display: flex;
flex-direction: row-reverse;
}
.flex-button {
align-self: flex-end;
}
.add-new__action-container {
overflow: hidden;
flex-shrink: 0;
border-top: 1px solid #ddd;
}
.add-new__action-item {
border-radius: 0 0 4px 4px;
border-top: 0;
padding: 0;
&:hover {
cursor: pointer;
}
&--link {
width: 50%;
float: left;
padding: 10px 5px;
text-align: center;
color: $icon-hover;
&:first-of-type {
border-right: 1px solid #ddd;
}
&:hover {
text-decoration: none;
background: #e6e6e6;
}
}
.fa-circle {
color: #c00;
}
}
// Dragula styles
.gu {
&-mirror {
position: fixed;
margin: 0;
z-index: 9999;
opacity: 0.8;
}
&-hide {
display: none;
}
&-unselectable {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
}

View File

@@ -0,0 +1,143 @@
import {
Component,
EventEmitter,
Input,
Output,
QueryList,
ViewChildren,
animate,
forwardRef,
state,
style,
transition,
trigger
} from '@angular/core';
import { DragulaService } from 'ng2-dragula/ng2-dragula';
import { Macro } from '../../../config-serializer/config-items/Macro';
import { MacroAction } from '../../../config-serializer/config-items/macro-action';
import { MacroItemComponent } from './../index';
@Component({
animations: [
trigger('toggler', [
state('inactive', style({
height: '0px'
})),
state('active', style({
height: '*'
})),
transition('inactive <=> active', animate('500ms ease-out'))
]),
trigger('togglerNew', [
state('void', style({
height: '0px'
})),
state('active', style({
height: '*'
})),
transition(':enter', animate('500ms ease-out')),
transition(':leave', animate('500ms ease-out'))
])
],
selector: 'macro-list',
template: require('./macro-list.component.html'),
styles: [require('./macro-list.component.scss')],
viewProviders: [DragulaService]
})
export class MacroListComponent {
@Input() macro: Macro;
@ViewChildren(forwardRef(() => MacroItemComponent)) macroItems: QueryList<MacroItemComponent>;
@Output() add = new EventEmitter();
@Output() edit = new EventEmitter();
@Output() delete = new EventEmitter();
@Output() reorder = new EventEmitter();
private newMacro: Macro = undefined;
private activeEdit: number = undefined;
private dragIndex: number;
private showNew: boolean = false;
constructor(private dragulaService: DragulaService) {
/* tslint:disable:no-unused-variable: Used by Dragula. */
dragulaService.setOptions('macroActions', {
moves: function (el: any, container: any, handle: any) {
return handle.className.includes('action--movable');
}
});
dragulaService.drag.subscribe((value: any) => {
this.dragIndex = +value[1].getAttribute('data-index');
});
dragulaService.drop.subscribe((value: any) => {
if (value[4]) {
this.reorder.emit({
macroId: this.macro.id,
oldIndex: this.dragIndex,
newIndex: +value[4].getAttribute('data-index')
});
}
});
}
showNewAction() {
this.hideActiveEditor();
this.newMacro = undefined;
this.showNew = true;
}
hideNewAction() {
this.showNew = false;
}
addNewAction(macroAction: MacroAction) {
this.add.emit({
macroId: this.macro.id,
action: macroAction
});
this.newMacro = undefined;
this.showNew = false;
}
editAction(index: number) {
// Hide other editors when clicking edit button of a macro action
this.hideActiveEditor();
this.showNew = false;
this.activeEdit = index;
}
cancelAction() {
this.activeEdit = undefined;
}
saveAction(macroAction: MacroAction, index: number) {
this.edit.emit({
macroId: this.macro.id,
index: index,
action: macroAction
});
this.hideActiveEditor();
}
deleteAction(macroAction: MacroAction, index: number) {
this.delete.emit({
macroId: this.macro.id,
index: index,
action: macroAction
});
this.hideActiveEditor();
}
private hideActiveEditor() {
if (this.activeEdit !== undefined) {
this.macroItems.toArray()[this.activeEdit].cancelEdit();
}
}
}

View File

@@ -0,0 +1,20 @@
import { Routes } from '@angular/router';
import { MacroEditComponent } from './edit/macro-edit.component';
import { MacroNotFoundComponent, MacroNotFoundGuard } from './not-found';
export const macroRoutes: Routes = [
{
path: 'macro',
component: MacroNotFoundComponent,
canActivate: [MacroNotFoundGuard]
},
{
path: 'macro/:id',
component: MacroEditComponent
},
{
path: 'macro/:id/:empty',
component: MacroEditComponent
}
];

View File

@@ -0,0 +1,2 @@
export { MacroNotFoundComponent } from './macro-not-found.component';
export { MacroNotFoundGuard } from './macro-not-found-guard.service';

View File

@@ -0,0 +1,30 @@
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/let';
import 'rxjs/add/operator/map';
import { Store } from '@ngrx/store';
import { AppState } from '../../../store/index';
import { getMacroEntities } from '../../../store/reducers';
@Injectable()
export class MacroNotFoundGuard implements CanActivate {
constructor(private store: Store<AppState>, private router: Router) { }
canActivate(): Observable<boolean> {
return this.store
.let(getMacroEntities())
.map(macros => {
const hasMacros = macros.length > 0;
if (hasMacros) {
this.router.navigate(['/macro', macros[0].id]);
}
return !hasMacros;
});
}
}

View File

@@ -0,0 +1,3 @@
<div class="not-found">
You don't have any macros. Try to add one!
</div>

View File

@@ -0,0 +1,5 @@
.not-found {
margin-top: 30px;
font-size: 16px;
text-align: center;
}

View File

@@ -0,0 +1,8 @@
import { Component } from '@angular/core';
@Component({
selector: 'macro-not-found',
template: require('./macro-not-found.component.html'),
styles: [require('./macro-not-found.component.scss')]
})
export class MacroNotFoundComponent { }

View File

@@ -0,0 +1 @@
export * from './notification.component';

View File

@@ -0,0 +1,3 @@
<span class="text">Keymap removed</span>
<a href="#" class="action action--undo">Undo</a>
<span class="dismiss">&times;</span>

View File

@@ -0,0 +1,38 @@
:host {
padding: 1rem 1.5rem;
box-shadow: 0 0 0 1px #000;
border-radius: 0.5rem;
position: absolute;
top: 2rem;
right: 2rem;
z-index: 10000;
background-color: #333;
color: #eee;
display: none;
}
.action {
margin-left: 1rem;
margin-right: 1rem;
color: #5bc0de;
text-transform: uppercase;
font-weight: bold;
&:focus,
&:active,
&:hover {
text-decoration: none;
color: #5bc0de;
}
}
.dismiss {
position: relative;
bottom: 1px;
color: #ccc;
&:hover {
cursor: pointer;
color: #fff;
}
}

View File

@@ -0,0 +1,12 @@
import { Component } from '@angular/core';
@Component({
selector: 'notification',
template: require('./notification.component.html'),
styles: [require('./notification.component.scss')]
})
export class NotificationComponent {
constructor() {
}
}

View File

@@ -0,0 +1 @@
export * from './popover.component';

View File

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

View File

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

View File

@@ -0,0 +1,182 @@
import {
Component, ElementRef, EventEmitter, HostListener, Input, OnChanges, Output, SimpleChanges, ViewChild,
animate, keyframes, state, style, transition, trigger
} from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
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 { getKeymapEntities } from '../../store/reducers';
enum TabName {
Keypress,
Layer,
Mouse,
Macro,
Keymap,
None
}
@Component({
selector: 'popover',
template: require('./popover.component.html'),
styles: [require('./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;
public tabName = TabName;
public keyActionValid: boolean;
private activeTab: TabName;
private keymaps$: Observable<Keymap[]>;
private leftArrow: boolean = false;
private rightArrow: boolean = false;
private topPosition: number = 0;
private leftPosition: number = 0;
private animationState: string;
constructor(private store: Store<AppState>) {
this.animationState = 'closed';
this.keymaps$ = store.let(getKeymapEntities())
.map((keymaps: Keymap[]) =>
keymaps.filter((keymap: Keymap) => this.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';
}
}
}
onCancelClick(): void {
this.cancel.emit(undefined);
}
onRemapKey(): void {
if (this.keyActionValid) {
try {
let 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;
}
}

View File

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

View File

@@ -0,0 +1 @@
export * from './keymap-tab.component';

View File

@@ -0,0 +1,22 @@
<template [ngIf]="keymapOptions.length === 0">
<span> No keymaps are available to choose from. Create a keymap first! </span>
</template>
<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="./images/base-layer--blank.svg">
</div>
<svg-keyboard *ngIf="selectedKeymap?.abbreviation"
[moduleConfig]="selectedKeymap.layers[0].modules">
</svg-keyboard>
</div>
</template>

View File

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

View File

@@ -0,0 +1,77 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, OnInit, Output } 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',
template: require('./keymap-tab.component.html'),
styles: [require('./keymap-tab.component.scss')],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class KeymapTabComponent extends Tab implements OnInit, OnChanges {
@Input() defaultKeyAction: KeyAction;
@Input() keymaps: Keymap[];
private keymapOptions: Array<Select2OptionData>;
private selectedKeymap: Keymap;
constructor() {
super();
this.keymapOptions = [];
}
ngOnInit() {
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];
}
}
ngOnChanges() {
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;
}
}

View File

@@ -0,0 +1 @@
export * from './keypress-tab.component';

View File

@@ -0,0 +1,50 @@
<div class="scancode-options">
<b class="setting-label">Scancode:</b>
<select2
[data]="scanCodeGroups"
[value]="scanCode.toString()"
(valueChanged)="onScancodeChange($event)"
[width]="200"
[options]="options"
></select2>
<capture-keystroke-button (capture)="onKeysCapture($event)"></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" title="This action happens when the key is being held along with another key."></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>

View File

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

View File

@@ -0,0 +1,166 @@
import { Component, Input, EventEmitter, OnChanges, Output } 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';
@Component({
selector: 'keypress-tab',
template: require('./keypress-tab.component.html'),
styles: [require('./keypress-tab.component.scss')]
})
export class KeypressTabComponent extends Tab implements OnChanges {
@Input() defaultKeyAction: KeyAction;
@Input() longPressEnabled: boolean;
private leftModifiers: string[];
private rightModifiers: string[];
private leftModifierSelects: boolean[];
private rightModifierSelects: boolean[];
private scanCodeGroups: Array<Select2OptionData>;
private longPressGroups: Array<Select2OptionData>;
private options: Select2Options;
private scanCode: number;
private 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('json!./scancodes.json'));
this.longPressGroups = require('json!./longPress.json');
this.leftModifierSelects = Array(this.leftModifiers.length).fill(false);
this.rightModifierSelects = Array(this.rightModifiers.length).fill(false);
this.scanCode = 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.scanCode = event.code;
} else {
this.scanCode = 0;
}
this.leftModifierSelects = event.left;
this.rightModifierSelects = event.right;
this.validAction.emit(this.keyActionValid());
}
fromKeyAction(keyAction: KeyAction): boolean {
if (!(keyAction instanceof KeystrokeAction)) {
return false;
}
let keystrokeAction: KeystrokeAction = <KeystrokeAction>keyAction;
// Restore scancode
this.scanCode = keystrokeAction.scancode || 0;
let 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) {
let 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 {
let keystrokeAction: KeystrokeAction = new KeystrokeAction();
keystrokeAction.scancode = this.scanCode;
keystrokeAction.modifierMask = 0;
let 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) {
let 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}) {
this.scanCode = +event.value;
this.validAction.emit(this.keyActionValid());
}
}

View File

@@ -0,0 +1,60 @@
[
{
"id": "-1",
"text": "None"
},
{
"text": "Modifiers",
"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

View File

@@ -0,0 +1 @@
export * from './layer-tab.component';

View File

@@ -0,0 +1,20 @@
<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">
<template ngSwitchCase="true">layer by pressing this key.</template>
<template ngSwitchDefault="false">layer by holding this key.</template>
</span>
</template>
<template [ngIf]="isNotBase">
<span> Layer switching is only possible from the base layer. </span>
</template>

View File

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

View File

@@ -0,0 +1,97 @@
import { Component, EventEmitter, HostBinding, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { KeyAction, LayerName, SwitchLayerAction } from '../../../../config-serializer/config-items/key-action';
import { Tab } from '../tab';
@Component({
selector: 'layer-tab',
template: require('./layer-tab.component.html'),
styles: [require('./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'
}
];
private toggle: boolean;
private 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;
}
let switchLayerAction: SwitchLayerAction = <SwitchLayerAction>keyAction;
this.toggle = switchLayerAction.isLayerToggleable;
this.layer = switchLayerAction.layer;
return true;
}
toKeyAction(): SwitchLayerAction {
let 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;
}
}

View File

@@ -0,0 +1 @@
export * from './macro-tab.component';

View File

@@ -0,0 +1,16 @@
<template [ngIf]="macroOptions.length === 0">
<span> No macros are available to choose from. Create a macro first! </span>
</template>
<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>
</template>

View File

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

View File

@@ -0,0 +1,83 @@
import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output } 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 { getMacroEntities } from '../../../../store/reducers/macro';
@Component({
selector: 'macro-tab',
template: require('./macro-tab.component.html'),
styles: [require('./macro-tab.component.scss')]
})
export class MacroTabComponent extends Tab implements OnInit, OnChanges, OnDestroy {
@Input() defaultKeyAction: KeyAction;
private macros: Macro[];
private macroOptions: Array<Select2OptionData>;
private selectedMacroIndex: number;
private subscription: Subscription;
constructor(private store: Store<AppState>) {
super();
this.subscription = store.let(getMacroEntities())
.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();
}
}

View File

@@ -0,0 +1 @@
export * from './mouse-tab.component';

View File

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

View File

@@ -0,0 +1,85 @@
@import '../../../../global-styles';
: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;
}

View File

@@ -0,0 +1,100 @@
import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
import { KeyAction, MouseAction, MouseActionParam } from '../../../../config-serializer/config-items/key-action';
import { Tab } from '../tab';
@Component({
selector: 'mouse-tab',
template: require('./mouse-tab.component.html'),
styles: [require('./mouse-tab.component.scss')]
})
export class MouseTabComponent extends Tab implements OnChanges {
@Input() defaultKeyAction: KeyAction;
private mouseActionParam: MouseActionParam;
private selectedPageIndex: number;
/* tslint:disable:variable-name: It is an enum type. So it can start with uppercase. */
/* tslint:disable:no-unused-variable: It is used in the template. */
private MouseActionParam = MouseActionParam;
/* tslint:enable:no-unused-variable tslint:enable:variable-name */
private 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;
}
let 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 {
let 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);
}
}

View File

@@ -0,0 +1 @@
export * from './none-tab.component';

Some files were not shown because too many files have changed in this diff Show More