refactore: create feature modules (#387)

* add @angular/cli to the project

* increase nodejs version -> 8.2.1

* add lerna

* merge web and shared module

* move electron module into packages as uhk-agent

Electron agent functionality is not working

* delete symlinker

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

* revert uhk-message.component

* fix component path

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

* building web and electron module

* delete uhk-renderer package

* handle device connect disconnect state

* add privilege detection

* fix set privilege functionality

* turn back download keymap functionality

* add bootstrap, select2 js and fix null pointer exception

* turn back upload data to keyboard

* fix send keymap

* fix test-serializer

* add missing package.json

* merging

* fix appveyor build

* fix linting

* turn back electron storage service

* commit the missing electron-datastorage-repository

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

* set exact version number in appveyor build

* vertical align privilege and missing device components

* set back node version to 8 in appveyor

* move node-usb dependency from usb dir to root

maybe it is fix the appveyor build

* revert usb to root

* fix electron builder script

* fix electron builder script

* turn off electron devtools

* remove CTRL+U functionality

* fix CTRL+o

* fix lint error

* turnoff store freeze

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

* move files from root usb dir -> packages/usb
This commit is contained in:
Róbert Kiss
2017-08-19 20:02:17 +02:00
committed by László Monda
parent 97770f67c0
commit 0f558e4132
524 changed files with 25606 additions and 5036 deletions

View File

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

View File

@@ -0,0 +1,46 @@
<div class="action--editor">
<div class="row">
<div class="col-lg-3 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-12 col-lg-9 editor__tabs" [ngSwitch]="activeTab">
<macro-text-tab #tab *ngSwitchCase="TabName.Text" [macroAction]="editableMacroAction"></macro-text-tab>
<macro-key-tab #tab *ngSwitchCase="TabName.Keypress" [macroAction]="editableMacroAction"></macro-key-tab>
<macro-mouse-tab #tab *ngSwitchCase="TabName.Mouse" [macroAction]="editableMacroAction"></macro-mouse-tab>
<macro-delay-tab #tab *ngSwitchCase="TabName.Delay" [macroAction]="editableMacroAction"></macro-delay-tab>
</div>
</div>
<div class="row">
<div class="col-xs-12 flex-button-wrapper editor__actions-container">
<div class="editor__actions">
<button class="btn btn-sm btn-default flex-button" type="button" (click)="onCancelClick()"> Cancel </button>
<button class="btn btn-sm btn-primary flex-button" type="button" (click)="onSaveClick()"> Save </button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,88 @@
@import '../../../../styles/variables';
:host {
display: block;
width: 100%;
}
.action--editor {
padding-top: 0;
padding-bottom: 0;
border-radius: 0;
border: 0;
}
.nav {
padding-bottom: 1rem;
li {
a {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
&.selected {
font-style: italic;
}
&:hover {
cursor: pointer;
}
}
&.active {
z-index: 2;
a {
&.selected {
font-style: normal;
}
&:after {
content: '';
display: block;
position: absolute;
width: 0;
height: 0;
top: 0;
right: -4rem;
border-color: transparent transparent transparent $icon-hover;
border-style: solid;
border-width: 2rem;
}
}
}
}
}
.editor {
&__tabs,
&__tab-links {
padding-top: 1rem;
}
&__tabs {
border-left: 1px solid #ddd;
margin-left: -1.6rem;
padding-left: 3rem;
}
&__actions {
float: right;
&-container {
background: #f5f5f5;
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
padding: 1rem 1.5rem;
}
}
}
.flex-button-wrapper {
display: flex;
flex-direction: row-reverse;
}
.flex-button {
align-self: flex-end;
}

View File

@@ -0,0 +1,98 @@
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
import {
MacroAction,
DelayMacroAction,
KeyMacroAction,
ScrollMouseMacroAction,
MoveMouseMacroAction,
MouseButtonMacroAction,
TextMacroAction,
Helper as MacroActionHelper
} from '../../../config-serializer/config-items/macro-action';
import { MacroDelayTabComponent, MacroMouseTabComponent, MacroKeyTabComponent, MacroTextTabComponent } from './tab';
enum TabName {
Keypress,
Text,
Mouse,
Delay
}
@Component({
selector: 'macro-action-editor',
templateUrl: './macro-action-editor.component.html',
styleUrls: ['./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: MacroTextTabComponent | MacroKeyTabComponent | MacroMouseTabComponent | MacroDelayTabComponent;
editableMacroAction: MacroAction;
activeTab: TabName;
/* tslint:disable:variable-name: It is an enum type. So it can start with uppercase. */
TabName = TabName;
/* tslint:enable:variable-name */
ngOnInit() {
this.updateEditableMacroAction();
const tab: TabName = this.getTabName(this.editableMacroAction);
this.activeTab = tab;
}
ngOnChanges() {
this.ngOnInit();
}
onCancelClick(): void {
this.cancel.emit();
}
onSaveClick(): void {
try {
// TODO: Refactor after getKeyMacroAction has been added to all tabs
const action = this.selectedTab instanceof MacroKeyTabComponent ?
this.selectedTab.getKeyMacroAction() :
this.selectedTab.macroAction;
this.save.emit(action);
} catch (e) {
// TODO: show error dialog
console.error(e);
}
}
selectTab(tab: TabName): void {
this.activeTab = tab;
if (tab === this.getTabName(this.macroAction)) {
this.updateEditableMacroAction();
} else {
this.editableMacroAction = undefined;
}
}
getTabName(action: MacroAction): TabName {
if (action instanceof DelayMacroAction) {
return TabName.Delay;
} else if (action instanceof TextMacroAction) {
return TabName.Text;
} else if (action instanceof KeyMacroAction) {
return TabName.Keypress;
} else if (action instanceof MouseButtonMacroAction ||
action instanceof MoveMouseMacroAction ||
action instanceof ScrollMouseMacroAction) {
return TabName.Mouse;
}
return undefined;
}
private updateEditableMacroAction() {
const macroAction: MacroAction = this.macroAction ? this.macroAction : new TextMacroAction();
this.editableMacroAction = MacroActionHelper.createMacroAction(macroAction);
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,41 @@
import {
ChangeDetectionStrategy,
Component,
ElementRef,
Input,
OnInit,
ViewChild
} from '@angular/core';
import { DelayMacroAction } from '../../../../../config-serializer/config-items/macro-action';
const INITIAL_DELAY = 0.5; // In seconds
@Component({
selector: 'macro-delay-tab',
templateUrl: './macro-delay.component.html',
styleUrls: ['./macro-delay.component.scss'],
host: { 'class': 'macro__delay' },
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MacroDelayTabComponent implements OnInit {
@Input() macroAction: DelayMacroAction;
@ViewChild('macroDelayInput') input: ElementRef;
delay: number;
presets: number[] = [0.3, 0.5, 0.8, 1, 2, 3, 4, 5];
constructor() { }
ngOnInit() {
if (!this.macroAction) {
this.macroAction = new DelayMacroAction();
}
this.delay = this.macroAction.delay > 0 ? this.macroAction.delay / 1000 : INITIAL_DELAY;
}
setDelay(value: number): void {
this.delay = value;
this.macroAction.delay = this.delay * 1000;
}
}

View File

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

View File

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

View File

@@ -0,0 +1,32 @@
<div class="col-xs-12 macro-key__container">
<div class="col-xs-3 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-9 macro-key__action-container">
<div class="macro-key__action">
<h4 *ngIf="activeTab === TabName.Keypress">Press key</h4>
<h4 *ngIf="activeTab === TabName.Hold">Hold key</h4>
<h4 *ngIf="activeTab === TabName.Release">Release key</h4>
<keypress-tab #keypressTab [defaultKeyAction]="defaultKeyAction" [longPressEnabled]="false"></keypress-tab>
</div>
</div>
</div>

View File

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

View File

@@ -0,0 +1,74 @@
import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { KeystrokeAction } from '../../../../../config-serializer/config-items/key-action';
import { KeyMacroAction, MacroSubAction } from '../../../../../config-serializer/config-items/macro-action';
import { KeypressTabComponent, Tab } from '../../../../popover/tab';
enum TabName {
Keypress,
Hold,
Release
}
@Component({
selector: 'macro-key-tab',
templateUrl: './macro-key.component.html',
styleUrls: [
'../../macro-action-editor.component.scss',
'./macro-key.component.scss'
],
host: { 'class': 'macro__mouse' }
})
export class MacroKeyTabComponent implements OnInit {
@Input() macroAction: KeyMacroAction;
@ViewChild('tab') selectedTab: Tab;
@ViewChild('keypressTab') keypressTab: KeypressTabComponent;
/* tslint:disable:variable-name: It is an enum type. So it can start with uppercase. */
TabName = TabName;
/* tslint:enable:variable-name */
activeTab: TabName;
defaultKeyAction: KeystrokeAction;
ngOnInit() {
if (!this.macroAction) {
this.macroAction = new KeyMacroAction();
}
this.defaultKeyAction = new KeystrokeAction(<any>this.macroAction);
this.selectTab(this.getTabName(this.macroAction));
}
selectTab(tab: TabName): void {
this.activeTab = tab;
}
getTabName(macroAction: KeyMacroAction): TabName {
if (!macroAction.action) {
return TabName.Keypress;
} else if (macroAction.action === MacroSubAction.hold) {
return TabName.Hold;
} else if (macroAction.action === MacroSubAction.release) {
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');
}
}
getKeyMacroAction(): KeyMacroAction {
const keyMacroAction = Object.assign(new KeyMacroAction(), this.keypressTab.toKeyAction());
keyMacroAction.action = this.getActionType(this.activeTab);
return keyMacroAction;
}
}

View File

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

View File

@@ -0,0 +1,77 @@
<div class="col-xs-12 macro-mouse__container">
<div class="col-xs-3 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-9 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['x']"> 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['y']"> 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['x']"> 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['y']"> pixels
</div>
</div>
</div>
<div #tab *ngIf="activeTab === TabName.Click || activeTab === TabName.Hold || activeTab === TabName.Release">
<h4 *ngIf="activeTab === TabName.Click">Click mouse button</h4>
<h4 *ngIf="activeTab === TabName.Hold">Hold mouse button</h4>
<h4 *ngIf="activeTab === TabName.Release">Release mouse button</h4>
<div class="btn-group macro-mouse__buttons">
<button *ngFor="let buttonLabel of buttonLabels; let buttonIndex = index"
class="btn btn-default"
[class.btn-primary]="hasButton(buttonIndex)"
(click)="setMouseClick(buttonIndex)">{{buttonLabel}}</button>
</div>
</div>
</div>
</div>

View File

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

View File

@@ -0,0 +1,123 @@
import { Component, Input, OnInit, ViewChild } from '@angular/core';
import {
MouseButtonMacroAction,
MoveMouseMacroAction,
ScrollMouseMacroAction,
MacroSubAction
} from '../../../../../config-serializer/config-items/macro-action';
import { Tab } from '../../../../popover/tab';
type MouseMacroAction = MouseButtonMacroAction | MoveMouseMacroAction | ScrollMouseMacroAction;
enum TabName {
Move,
Scroll,
Click,
Hold,
Release
}
@Component({
selector: 'macro-mouse-tab',
templateUrl: './macro-mouse.component.html',
styleUrls: [
'../../macro-action-editor.component.scss',
'./macro-mouse.component.scss'
],
host: { 'class': 'macro__mouse' }
})
export class MacroMouseTabComponent implements OnInit {
@Input() macroAction: MouseMacroAction;
@ViewChild('tab') selectedTab: Tab;
/* tslint:disable:variable-name: It is an enum type. So it can start with uppercase. */
TabName = TabName;
/* tslint:enable:variable-name */
activeTab: TabName;
buttonLabels: string[];
private selectedButtons: boolean[];
constructor() {
this.buttonLabels = ['Left', 'Middle', 'Right'];
this.selectedButtons = Array(this.buttonLabels.length).fill(false);
}
ngOnInit() {
if (!this.macroAction) {
this.macroAction = new MouseButtonMacroAction();
this.macroAction.action = MacroSubAction.press;
}
const tabName = this.getTabName(this.macroAction);
this.selectTab(tabName);
const buttonActions = [TabName.Click, TabName.Hold, TabName.Release];
if (buttonActions.includes(this.activeTab)) {
this.selectedButtons = (<MouseButtonMacroAction>this.macroAction).getMouseButtons();
}
}
ngOnChanges() {
this.ngOnInit();
}
selectTab(tab: TabName): void {
this.activeTab = tab;
if (tab === this.getTabName(this.macroAction)) {
return;
}
switch (tab) {
case TabName.Scroll:
this.macroAction = new ScrollMouseMacroAction();
break;
case TabName.Move:
this.macroAction = new MoveMouseMacroAction();
break;
default:
this.macroAction = new MouseButtonMacroAction();
this.macroAction.action = this.getAction(tab);
break;
}
}
setMouseClick(index: number): void {
this.selectedButtons[index] = !this.selectedButtons[index];
(<MouseButtonMacroAction>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: ${TabName[tab]}`);
}
}
getTabName(action: MouseMacroAction): TabName {
if (action instanceof MouseButtonMacroAction) {
if (!action.action || action.isOnlyPressAction()) {
return TabName.Click;
} else if (action.isOnlyHoldAction()) {
return TabName.Hold;
} else if (action.isOnlyReleaseAction()) {
return TabName.Release;
}
} else if (action instanceof MoveMouseMacroAction) {
return TabName.Move;
} else if (action instanceof ScrollMouseMacroAction) {
return TabName.Scroll;
}
return TabName.Move;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,17 @@
<ng-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>
</ng-template>
<div *ngIf="!macro" class="not-found">
There is no macro with id {{ route.params.select('id') | async }}.
</div>

View File

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

View File

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

View File

@@ -0,0 +1,26 @@
<uhk-header>
<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"
(change)="editMacroName($event.target.value)"
(keyup.enter)="macroName.blur()"
/>
<i class="glyphicon glyphicon-trash macro__remove pull-right" title=""
data-toggle="tooltip"
data-placement="bottom"
html="true"
data-original-title="<span class='text-nowrap'>Delete macro</span>"
(click)="removeMacro()"
></i>
<i class="fa fa-files-o macro__duplicate pull-right" title=""
data-toggle="tooltip"
data-placement="bottom"
data-original-title="Duplicate macro"
(click)="duplicateMacro()"
></i>
</h1>
</div>
</uhk-header>

View File

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

View File

@@ -0,0 +1,73 @@
import {
AfterViewInit,
ChangeDetectionStrategy,
Component,
ElementRef,
Input,
OnChanges,
Renderer2,
SimpleChanges,
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',
templateUrl: './macro-header.component.html',
styleUrls: ['./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: Renderer2) { }
ngOnChanges(changes: SimpleChanges) {
if (this.isNew) {
this.setFocusOnName();
}
if (changes['macro']) {
this.setName();
}
}
ngAfterViewInit() {
if (this.isNew) {
this.setFocusOnName();
}
}
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.setName();
return;
}
this.store.dispatch(MacroActions.editMacroName(this.macro.id, name));
}
private setFocusOnName() {
this.macroName.nativeElement.select();
}
private setName(): void {
this.renderer.setProperty(this.macroName.nativeElement, 'value', this.macro.name);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,206 @@
import { Component, Input, Output, EventEmitter, OnChanges, OnInit, SimpleChanges } from '@angular/core';
import { animate, state, style, transition, trigger } from '@angular/animations';
import { KeyModifiers } from '../../../config-serializer/config-items/key-modifiers';
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',
templateUrl: './macro-item.component.html',
styleUrls: ['./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>();
title: string;
iconName: string;
editing: boolean;
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) {
this.cancelEdit();
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';
const 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
const 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, action.type) || ['Unknown']).join(' ');
if (scancode) {
this.title += scancode;
}
}
if (action.hasModifiers()) {
// Press/hold/release modifiers
for (let i = KeyModifiers.leftCtrl; i <= KeyModifiers.rightGui; i <<= 1) {
if (action.isModifierActive(i)) {
this.title += ' ' + KeyModifiers[i];
}
}
}
}
private setMouseMoveScrollActionContent(action: MacroAction): void {
let typedAction: any;
if (action instanceof MoveMouseMacroAction) {
// Move mouse pointer
this.iconName = 'mouse-pointer';
this.title = 'Move pointer';
typedAction = this.macroAction as MoveMouseMacroAction;
} else {
// Scroll mouse
this.iconName = 'mouse-pointer';
this.title = 'Scroll';
typedAction = this.macroAction as ScrollMouseMacroAction;
}
let needAnd: boolean;
if (Math.abs(typedAction.x) !== 0) {
this.title += ` by ${Math.abs(typedAction.x)}px ${typedAction.x > 0 ? 'left' : 'right'}`;
needAnd = true;
}
if (Math.abs(typedAction.y) !== 0) {
this.title += ` ${needAnd ? 'and' : 'by'} ${Math.abs(typedAction.y)}px ${typedAction.y > 0 ? 'down' : 'up'}`;
}
}
private setMouseButtonActionContent(action: MouseButtonMacroAction): void {
// Press/hold/release mouse buttons
if (action.isOnlyPressAction()) {
this.iconName = 'mouse-pointer';
this.title = 'Click mouse button: ';
} else if (action.isOnlyHoldAction()) {
this.iconName = 'hand-rock';
this.title = 'Hold mouse button: ';
} else if (action.isOnlyReleaseAction()) {
this.iconName = 'hand-paper';
this.title = 'Release mouse button: ';
}
const buttonLabels: string[] = ['Left', 'Middle', 'Right'];
const selectedButtons: boolean[] = action.getMouseButtons();
const selectedButtonLabels: string[] = [];
selectedButtons.forEach((isSelected, idx) => {
if (isSelected && buttonLabels[idx]) {
selectedButtonLabels.push(buttonLabels[idx]);
}
});
this.title += selectedButtonLabels.join(', ');
}
}

View File

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

View File

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

View File

@@ -0,0 +1,131 @@
import { Component, EventEmitter, Input, Output, QueryList, ViewChildren, forwardRef } from '@angular/core';
import { animate, state, style, transition, trigger } from '@angular/animations';
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 '../item';
@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',
templateUrl: './macro-list.component.html',
styleUrls: ['./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();
newMacro: Macro = undefined;
showNew: boolean = false;
private activeEdit: number = undefined;
private dragIndex: number;
constructor(dragulaService: DragulaService) {
/* tslint:disable:no-unused-variable: Used by Dragula. */
dragulaService.setOptions('macroActions', {
moves: function (el: any, container: any, handle: any) {
return handle.className.includes('action--movable');
}
});
dragulaService.drag.subscribe((value: any) => {
this.dragIndex = +value[1].getAttribute('data-index');
});
dragulaService.drop.subscribe((value: any) => {
if (value[4]) {
this.reorder.emit({
macroId: this.macro.id,
oldIndex: this.dragIndex,
newIndex: +value[4].getAttribute('data-index')
});
}
});
}
showNewAction() {
this.hideActiveEditor();
this.newMacro = undefined;
this.showNew = true;
}
hideNewAction() {
this.showNew = false;
}
addNewAction(macroAction: MacroAction) {
this.add.emit({
macroId: this.macro.id,
action: macroAction
});
this.newMacro = undefined;
this.showNew = false;
}
editAction(index: number) {
// Hide other editors when clicking edit button of a macro action
this.hideActiveEditor();
this.showNew = false;
this.activeEdit = index;
}
cancelAction() {
this.activeEdit = undefined;
}
saveAction(macroAction: MacroAction, index: number) {
this.edit.emit({
macroId: this.macro.id,
index: index,
action: macroAction
});
this.hideActiveEditor();
}
deleteAction(macroAction: MacroAction, index: number) {
this.delete.emit({
macroId: this.macro.id,
index: index,
action: macroAction
});
this.hideActiveEditor();
}
private hideActiveEditor() {
if (this.activeEdit !== undefined) {
this.macroItems.toArray()[this.activeEdit].cancelEdit();
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,31 @@
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 { getMacros } from '../../../store/reducers/user-configuration';
import { Macro } from '../../../config-serializer/config-items/macro';
@Injectable()
export class MacroNotFoundGuard implements CanActivate {
constructor(private store: Store<AppState>, private router: Router) { }
canActivate(): Observable<boolean> {
return this.store
.let(getMacros())
.map((macros: Macro[]) => {
const hasMacros = macros.length > 0;
if (hasMacros) {
this.router.navigate(['/macro', macros[0].id]);
}
return !hasMacros;
});
}
}

View File

@@ -0,0 +1,8 @@
<div class="container-fluid">
<uhk-header>
<h1>&nbsp;</h1>
</uhk-header>
<div class="not-found">
You don't have any macros. Try to add one!
</div>
</div>

View File

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

View File

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