Seperate electron and web target building
This commit is contained in:
committed by
József Farkas
parent
517aed1b1c
commit
983eb72892
4
shared/src/agent-config.json
Normal file
4
shared/src/agent-config.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"isKeymapsMenuExpanded": true,
|
||||
"isMacrosMenuExpanded": true
|
||||
}
|
||||
7
shared/src/components/add-on/add-on.component.html
Normal file
7
shared/src/components/add-on/add-on.component.html
Normal 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...
|
||||
5
shared/src/components/add-on/add-on.component.scss
Normal file
5
shared/src/components/add-on/add-on.component.scss
Normal file
@@ -0,0 +1,5 @@
|
||||
:host {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
22
shared/src/components/add-on/add-on.component.ts
Normal file
22
shared/src/components/add-on/add-on.component.ts
Normal 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');
|
||||
}
|
||||
}
|
||||
10
shared/src/components/add-on/add-on.routes.ts
Normal file
10
shared/src/components/add-on/add-on.routes.ts
Normal 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
|
||||
}
|
||||
];
|
||||
2
shared/src/components/add-on/index.ts
Normal file
2
shared/src/components/add-on/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './add-on.component';
|
||||
export * from './add-on.routes';
|
||||
1
shared/src/components/keyboard/index.ts
Normal file
1
shared/src/components/keyboard/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './slider';
|
||||
1
shared/src/components/keyboard/slider/index.ts
Normal file
1
shared/src/components/keyboard/slider/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { KeyboardSliderComponent } from './keyboard-slider.component';
|
||||
@@ -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 |
@@ -0,0 +1,8 @@
|
||||
svg-keyboard {
|
||||
width: 95%;
|
||||
max-width: 1400px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
transform: translateX(-101%);
|
||||
user-select: none;
|
||||
}
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
35
shared/src/components/keymap/add/keymap-add.component.html
Normal file
35
shared/src/components/keymap/add/keymap-add.component.html
Normal 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>
|
||||
61
shared/src/components/keymap/add/keymap-add.component.scss
Normal file
61
shared/src/components/keymap/add/keymap-add.component.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
50
shared/src/components/keymap/add/keymap-add.component.ts
Normal file
50
shared/src/components/keymap/add/keymap-add.component.ts
Normal 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));
|
||||
}
|
||||
}
|
||||
2
shared/src/components/keymap/edit/index.ts
Normal file
2
shared/src/components/keymap/edit/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { KeymapEditComponent } from './keymap-edit.component';
|
||||
export { KeymapEditGuard } from './keymap-edit-guard.service';
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
11
shared/src/components/keymap/edit/keymap-edit.component.scss
Normal file
11
shared/src/components/keymap/edit/keymap-edit.component.scss
Normal file
@@ -0,0 +1,11 @@
|
||||
:host {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.not-found {
|
||||
margin-top: 30px;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
44
shared/src/components/keymap/edit/keymap-edit.component.ts
Normal file
44
shared/src/components/keymap/edit/keymap-edit.component.ts
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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.';
|
||||
}
|
||||
}
|
||||
3
shared/src/components/keymap/index.ts
Normal file
3
shared/src/components/keymap/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './add/keymap-add.component';
|
||||
export * from './edit/keymap-edit.component';
|
||||
export * from './header/keymap-header.component';
|
||||
1
shared/src/components/layers/index.ts
Normal file
1
shared/src/components/layers/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './layers.component';
|
||||
8
shared/src/components/layers/layers.component.html
Normal file
8
shared/src/components/layers/layers.component.html
Normal 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>
|
||||
40
shared/src/components/layers/layers.component.scss
Normal file
40
shared/src/components/layers/layers.component.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
shared/src/components/layers/layers.component.ts
Normal file
31
shared/src/components/layers/layers.component.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
1
shared/src/components/macro/action-editor/index.ts
Normal file
1
shared/src/components/macro/action-editor/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { MacroActionEditorComponent } from './macro-action-editor.component';
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { MacroDelayTabComponent } from './macro-delay.component';
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
4
shared/src/components/macro/action-editor/tab/index.ts
Normal file
4
shared/src/components/macro/action-editor/tab/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { MacroDelayTabComponent } from './delay';
|
||||
export { MacroKeyTabComponent } from './key';
|
||||
export { MacroMouseTabComponent } from './mouse';
|
||||
export { MacroTextTabComponent } from './text';
|
||||
@@ -0,0 +1 @@
|
||||
export { MacroKeyTabComponent } from './macro-key.component';
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { MacroMouseTabComponent } from './macro-mouse.component';
|
||||
@@ -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>
|
||||
@@ -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%;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { MacroTextTabComponent } from './macro-text.component';
|
||||
@@ -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>
|
||||
@@ -0,0 +1,11 @@
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.macro__text-input {
|
||||
width: 100%;
|
||||
min-height: 10rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
17
shared/src/components/macro/edit/macro-edit.component.html
Normal file
17
shared/src/components/macro/edit/macro-edit.component.html
Normal 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>
|
||||
11
shared/src/components/macro/edit/macro-edit.component.scss
Normal file
11
shared/src/components/macro/edit/macro-edit.component.scss
Normal file
@@ -0,0 +1,11 @@
|
||||
:host {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.not-found {
|
||||
margin-top: 30px;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
59
shared/src/components/macro/edit/macro-edit.component.ts
Normal file
59
shared/src/components/macro/edit/macro-edit.component.ts
Normal 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));
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
55
shared/src/components/macro/header/macro-header.component.ts
Normal file
55
shared/src/components/macro/header/macro-header.component.ts
Normal 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));
|
||||
}
|
||||
|
||||
}
|
||||
8
shared/src/components/macro/index.ts
Normal file
8
shared/src/components/macro/index.ts
Normal 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';
|
||||
1
shared/src/components/macro/item/index.ts
Normal file
1
shared/src/components/macro/item/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { MacroItemComponent } from './macro-item.component';
|
||||
17
shared/src/components/macro/item/macro-item.component.html
Normal file
17
shared/src/components/macro/item/macro-item.component.html
Normal 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>
|
||||
86
shared/src/components/macro/item/macro-item.component.scss
Normal file
86
shared/src/components/macro/item/macro-item.component.scss
Normal 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;
|
||||
}
|
||||
217
shared/src/components/macro/item/macro-item.component.ts
Normal file
217
shared/src/components/macro/item/macro-item.component.ts
Normal 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(', ');
|
||||
}
|
||||
}
|
||||
37
shared/src/components/macro/list/macro-list.component.html
Normal file
37
shared/src/components/macro/list/macro-list.component.html
Normal 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>
|
||||
153
shared/src/components/macro/list/macro-list.component.scss
Normal file
153
shared/src/components/macro/list/macro-list.component.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
143
shared/src/components/macro/list/macro-list.component.ts
Normal file
143
shared/src/components/macro/list/macro-list.component.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
20
shared/src/components/macro/macro.routes.ts
Normal file
20
shared/src/components/macro/macro.routes.ts
Normal 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
|
||||
}
|
||||
];
|
||||
2
shared/src/components/macro/not-found/index.ts
Normal file
2
shared/src/components/macro/not-found/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { MacroNotFoundComponent } from './macro-not-found.component';
|
||||
export { MacroNotFoundGuard } from './macro-not-found-guard.service';
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<div class="not-found">
|
||||
You don't have any macros. Try to add one!
|
||||
</div>
|
||||
@@ -0,0 +1,5 @@
|
||||
.not-found {
|
||||
margin-top: 30px;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -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 { }
|
||||
1
shared/src/components/notification/index.ts
Normal file
1
shared/src/components/notification/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './notification.component';
|
||||
@@ -0,0 +1,3 @@
|
||||
<span class="text">Keymap removed</span>
|
||||
<a href="#" class="action action--undo">Undo</a>
|
||||
<span class="dismiss">×</span>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
12
shared/src/components/notification/notification.component.ts
Normal file
12
shared/src/components/notification/notification.component.ts
Normal 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() {
|
||||
}
|
||||
}
|
||||
1
shared/src/components/popover/index.ts
Normal file
1
shared/src/components/popover/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './popover.component';
|
||||
82
shared/src/components/popover/popover.component.html
Normal file
82
shared/src/components/popover/popover.component.html
Normal 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>
|
||||
135
shared/src/components/popover/popover.component.scss
Normal file
135
shared/src/components/popover/popover.component.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
182
shared/src/components/popover/popover.component.ts
Normal file
182
shared/src/components/popover/popover.component.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
7
shared/src/components/popover/tab/index.ts
Normal file
7
shared/src/components/popover/tab/index.ts
Normal 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';
|
||||
1
shared/src/components/popover/tab/keymap/index.ts
Normal file
1
shared/src/components/popover/tab/keymap/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './keymap-tab.component';
|
||||
@@ -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>
|
||||
@@ -0,0 +1,43 @@
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> span {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
margin-top: 2px;
|
||||
|
||||
b {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 7px;
|
||||
}
|
||||
|
||||
select2 {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
> div:last-child {
|
||||
margin-top: 10px;
|
||||
|
||||
img {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty {
|
||||
display: flex;
|
||||
|
||||
img {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { ChangeDetectionStrategy, Component, 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;
|
||||
}
|
||||
}
|
||||
1
shared/src/components/popover/tab/keypress/index.ts
Normal file
1
shared/src/components/popover/tab/keypress/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './keypress-tab.component';
|
||||
@@ -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>
|
||||
@@ -0,0 +1,78 @@
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
|
||||
.scancode-options {
|
||||
margin-bottom: 10px;
|
||||
margin-top: 2px;
|
||||
> b {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.modifier-options {
|
||||
> b {
|
||||
position: relative;
|
||||
top: -9px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.btn-toolbar {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.long-press-container {
|
||||
display: flex;
|
||||
margin-top: 3rem;
|
||||
|
||||
> b {
|
||||
margin-right: 0.6em;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.secondary-role {
|
||||
width: 135px;
|
||||
}
|
||||
|
||||
icon {
|
||||
margin-left: 0.6em;
|
||||
}
|
||||
}
|
||||
|
||||
.setting-label {
|
||||
&.disabled {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.disabled-state--text {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
margin-top: -4rem;
|
||||
color: #31708f;
|
||||
padding-right: 40px;
|
||||
|
||||
.fa {
|
||||
font-size: 2.6rem;
|
||||
float: left;
|
||||
padding: 1rem 1.5rem 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
.scancode-options,
|
||||
.modifier-options,
|
||||
.long-press-container {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.disabled-state--text {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,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());
|
||||
}
|
||||
}
|
||||
60
shared/src/components/popover/tab/keypress/longPress.json
Normal file
60
shared/src/components/popover/tab/keypress/longPress.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
594
shared/src/components/popover/tab/keypress/scancodes.json
Normal file
594
shared/src/components/popover/tab/keypress/scancodes.json
Normal file
File diff suppressed because it is too large
Load Diff
1
shared/src/components/popover/tab/layer/index.ts
Normal file
1
shared/src/components/popover/tab/layer/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './layer-tab.component';
|
||||
@@ -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>
|
||||
@@ -0,0 +1,22 @@
|
||||
:host {
|
||||
display: flex;
|
||||
margin: 0 -5px;
|
||||
|
||||
&.no-base {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
> span,
|
||||
> select {
|
||||
margin: 0 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
background-color: #fff;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 4px;
|
||||
padding: 4px 20px 4px 8px;
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import { Component, 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;
|
||||
}
|
||||
}
|
||||
1
shared/src/components/popover/tab/macro/index.ts
Normal file
1
shared/src/components/popover/tab/macro/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './macro-tab.component';
|
||||
@@ -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>
|
||||
@@ -0,0 +1,40 @@
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> span {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.macro-selector {
|
||||
display: flex;
|
||||
margin-top: 2px;
|
||||
|
||||
b {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 7px;
|
||||
}
|
||||
|
||||
select2 {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.macro-action-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 200px;
|
||||
max-height: 300px;
|
||||
margin: 20px 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ddd;
|
||||
|
||||
.list-group {
|
||||
margin-bottom: 0;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import { Component, 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();
|
||||
}
|
||||
}
|
||||
1
shared/src/components/popover/tab/mouse/index.ts
Normal file
1
shared/src/components/popover/tab/mouse/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './mouse-tab.component';
|
||||
110
shared/src/components/popover/tab/mouse/mouse-tab.component.html
Normal file
110
shared/src/components/popover/tab/mouse/mouse-tab.component.html
Normal 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>
|
||||
@@ -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;
|
||||
}
|
||||
100
shared/src/components/popover/tab/mouse/mouse-tab.component.ts
Normal file
100
shared/src/components/popover/tab/mouse/mouse-tab.component.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
1
shared/src/components/popover/tab/none/index.ts
Normal file
1
shared/src/components/popover/tab/none/index.ts
Normal 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
Reference in New Issue
Block a user