Keymap data layer (#95)

This commit is contained in:
Nejc Zdovc
2016-09-18 12:38:42 +02:00
committed by József Farkas
parent fb8cd163ec
commit b78bc5850f
40 changed files with 748 additions and 195 deletions

View File

@@ -8,6 +8,8 @@
},
"license": "GPL-3.0",
"devDependencies": {
"@ngrx/store-devtools": "3.0.2",
"@ngrx/store-log-monitor": "3.0.2",
"@types/core-js": "^0.9.32",
"@types/jquery": "1.10.31",
"@types/node": "6.0.38",
@@ -40,6 +42,9 @@
"@angular/platform-browser": "2.0.0",
"@angular/platform-browser-dynamic": "2.0.0",
"@angular/router": "3.0.0",
"@ngrx/core": "1.2.0",
"@ngrx/effects": "2.0.0-beta.3",
"@ngrx/store": "2.2.1",
"bootstrap": "^3.3.7",
"browser-stdout": "^1.3.0",
"core-js": "2.4.1",

View File

@@ -1,13 +1,15 @@
import { NgModule } from '@angular/core';
import { NgModule, ReflectiveInjector } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { StoreModule } from '@ngrx/store';
import { DragulaModule } from 'ng2-dragula/ng2-dragula';
import { Select2Component } from 'ng2-select2/ng2-select2';
import { ContenteditableDirective } from './directives/contenteditable';
import { KeymapAddComponent, KeymapComponent } from './components/keymap';
import { KeymapAddComponent, KeymapComponent, KeymapHeaderComponent } from './components/keymap';
import { LayersComponent } from './components/layers';
import { LegacyLoaderComponent } from './components/legacy-loader';
import {
@@ -51,11 +53,30 @@ import { DataProviderService } from './services/data-provider.service';
import { MapperService } from './services/mapper.service';
import { UhkConfigurationService } from './services/uhk-configuration.service';
import { DataStorage } from './store/storage';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { StoreLogMonitorModule, useLogMonitor } from '@ngrx/store-log-monitor';
import { keymapReducer, macroReducer, presetReducer } from './store/reducers';
// Create DataStorage dependency injection
const storageProvider = ReflectiveInjector.resolve([DataStorage]);
const storageInjector = ReflectiveInjector.fromResolvedProviders(storageProvider);
const storageService: DataStorage = storageInjector.get(DataStorage);
// All reducers that are used in application
const storeConfig = {
keymaps: storageService.saveSate(keymapReducer),
macros: storageService.saveSate(macroReducer),
presetKeymaps: presetReducer
};
@NgModule({
declarations: [
Select2Component,
MainAppComponent,
KeymapComponent,
KeymapHeaderComponent,
LegacyLoaderComponent,
NotificationComponent,
SvgIconTextKeyComponent,
@@ -95,7 +116,15 @@ import { UhkConfigurationService } from './services/uhk-configuration.service';
BrowserModule,
FormsModule,
DragulaModule,
routing
routing,
StoreModule.provideStore(storeConfig, storageService.initialState()),
StoreDevtoolsModule.instrumentStore({
monitor: useLogMonitor({
visible: false,
position: 'right'
})
}),
StoreLogMonitorModule
],
providers: [
DataProviderService,

View File

@@ -10,23 +10,26 @@
<input type="text" class="form-control" placeholder="Search ..." (input)="filterKeyboards($event.target.value)">
</div>
<div class="keymap__search_amount">
{{ keymaps.length }} / {{ keymapsAll.length }} keymaps shown
{{ (presets$ | async).length }} / {{ (presetsAll$ | async).length }} keymaps shown
</div>
</div>
<div class="keymap__list">
<div #keyboard class="keymap__list_item" *ngFor="let keymap of keymaps">
<div #keyboard class="keymap__list_item" *ngFor="let keymap of presets$ | async">
<h2>{{ keymap.name }}</h2>
<p class="keymap__description">
{{ keymap.description }}
</p>
<svg-keyboard-wrap
[layers]="keymap.layers.elements"
[keymap]="keymap"
[popoverEnabled]="false"
[tooltipEnabled]="true"
>
</svg-keyboard-wrap>
<div class="btn-group btn-group-lg">
<button class="btn btn-default">Add keymap</button>
<button class="btn btn-default" (click)="addKeymap(keymap)">Add keymap</button>
</div>
</div>
</div>
<div *ngIf="(presets$ | async).length === 0">
Sorry, no keyboard found under this search query.
</div>

View File

@@ -1,8 +1,13 @@
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import 'rxjs/add/operator/publishReplay';
import { Observable } from 'rxjs/Observable';
import { ConnectableObservable } from 'rxjs/observable/ConnectableObservable';
import { Keymap } from '../../../config-serializer/config-items/Keymap';
import { Keymaps } from '../../../config-serializer/config-items/Keymaps';
import {DataProviderService} from '../../../services/data-provider.service';
import { AppState } from '../../../store';
import { KeymapActions } from '../../../store/actions';
@Component({
selector: 'keymap-add',
@@ -10,19 +15,32 @@ import {DataProviderService} from '../../../services/data-provider.service';
styles: [require('./keymap-add.component.scss')]
})
export class KeymapAddComponent {
private keymaps: Keymap[];
private keymapsAll: Keymap[];
private currentKeyboards: number;
private presets$: Observable<Keymap[]>;
private presetsAll$: Observable<Keymap[]>;
constructor(data: DataProviderService) {
let json: any = data.getDefaultKeymaps();
let all: Keymaps = new Keymaps().fromJsObject(json.keymaps);
this.keymaps = all.elements;
this.keymapsAll = this.keymaps.slice(0);
this.currentKeyboards = this.keymaps.length;
constructor(private store: Store<AppState>) {
let presetConnectable: ConnectableObservable<Keymap[]> = store
.select((appState: AppState) => appState.presetKeymaps)
.publishReplay();
this.presets$ = presetConnectable;
presetConnectable.connect();
this.presetsAll$ = store.select((appState: AppState) => appState.presetKeymaps);
}
filterKeyboards(value: string) {
this.keymaps = this.keymapsAll.filter((item: Keymap) => item.name.toLocaleLowerCase().indexOf(value) !== -1);
let presetConnectable: ConnectableObservable<Keymap[]> = this.presetsAll$
.map((keymaps: Keymap[]) => keymaps.filter(
(keymap: Keymap) => keymap.name.toLocaleLowerCase().includes(value))
)
.publishReplay();
this.presets$ = presetConnectable;
presetConnectable.connect();
}
addKeymap(keymap: Keymap) {
this.store.dispatch(KeymapActions.addKeymap(keymap));
}
}

View File

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

View File

@@ -0,0 +1,61 @@
.keymap {
&__is-default {
&.fa-star-o {
cursor: pointer;
&:hover {
color: #b9b6b6;
}
}
}
&__remove {
font-size: 0.75em;
top: 8px;
&:hover {
cursor: pointer;
color: #900;
}
}
&__duplicate {
font-size: 0.75em;
top: 7px;
margin-right: 15px;
position: relative;
&:hover {
cursor: pointer;
color: #900;
}
}
}
.pane-title {
margin-bottom: 1em;
&__name,
&__abbrev {
border: none;
border-bottom: 2px dotted #999;
padding: 0;
margin: 0 0.25rem;
&:focus {
box-shadow: 0 0 0 1px #ccc, 0 0 5px 0 #ccc;
border-color: transparent;
}
}
&__name {
width: 290px;
text-overflow: ellipsis;
}
&__abbrev {
width: 90px;
text-align: center;
}
}

View File

@@ -0,0 +1,44 @@
import { Component, Input } 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')]
})
export class KeymapHeaderComponent {
@Input() keymap: Keymap;
constructor(
private store: Store<AppState>
) { }
setDefault() {
if (!this.keymap.isDefault) {
this.store.dispatch(KeymapActions.setDefault(this.keymap.abbreviation));
}
}
removeKeymap() {
this.store.dispatch(KeymapActions.removeKeymap(this.keymap.abbreviation));
}
duplicateKeymap() {
this.store.dispatch(KeymapActions.duplicateKeymap(this.keymap));
}
editKeymapName(name: string) {
this.store.dispatch(KeymapActions.editKeymapName(this.keymap.abbreviation, name));
}
editKeymapAbbr(newAbbr: string) {
newAbbr = newAbbr.toUpperCase();
this.store.dispatch(KeymapActions.editKeymapAbbr(this.keymap.abbreviation, newAbbr));
}
}

View File

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

View File

@@ -1,13 +1,8 @@
<template [ngIf]="keymap">
<div class="row">
<h1 class="col-xs-12 pane-title">
<i class="fa fa-keyboard-o"></i>
<span class="keymap__name pane-title__name" contenteditable="true">{{keymap.name}}</span> keymap
(<span class="keymap__abbrev pane-title__abbrev" contenteditable="true">{{keymap.abbreviation}}</span>)
<i class="fa fa-star-o fa-star keymap__is-default"></i>
<i class="glyphicon glyphicon-trash keymap__remove pull-right" title=""
data-toggle="tooltip" data-placement="left" data-original-title="Remove keymap"></i>
</h1>
</div>
<svg-keyboard-wrap [layers]="layers.elements"></svg-keyboard-wrap>
</template>
<template [ngIf]="keymap$ | async">
<keymap-header [keymap]="keymap$ | async"></keymap-header>
<svg-keyboard-wrap [keymap]="keymap$ | async"></svg-keyboard-wrap>
</template>
<div *ngIf="!(keymap$ | async)" class="not-found">
Sorry, there is no keymap with this abbreviation.
</div>

View File

@@ -4,48 +4,9 @@
display: block;
}
.keymap {
&__is-default {
&.fa-star {
color: #333;
}
&.fa-star-o {
color: #333;
}
&:hover {
color: #555;
cursor: pointer;
}
}
&__remove {
font-size: 0.75em;
top: 0.3em;
&:hover {
cursor: pointer;
color: #900;
}
}
.not-found {
margin-top: 30px;
font-size: 16px;
text-align: center;
}
.pane-title {
margin-bottom: 1em;
&__name,
&__abbrev {
&[contenteditable=true] {
border: none;
border-bottom: 2px dotted #999;
padding: 0 0.5rem;
margin: 0 0.25rem;
&.active {
box-shadow: 0 0 0 1px #ccc, 0 0 5px 0 #ccc;
border-color: transparent;
}
}
}
}

View File

@@ -1,42 +1,38 @@
import { Component, OnInit } from '@angular/core';
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs/Subscription';
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 { ConnectableObservable } from 'rxjs/observable/ConnectableObservable';
import { Keymap } from '../../config-serializer/config-items/Keymap';
import { Layers } from '../../config-serializer/config-items/Layers';
import { UhkConfigurationService } from '../../services/uhk-configuration.service';
import { AppState } from '../../store';
import { getKeymap } from '../../store/reducers/keymap';
@Component({
selector: 'keymap',
template: require('./keymap.component.html'),
styles: [require('./keymap.component.scss')]
})
export class KeymapComponent implements OnInit {
private keymapId: number = 0;
private layers: Layers;
private keymap: Keymap;
private subParams: Subscription;
export class KeymapComponent {
private keymap$: Observable<Keymap>;
constructor(
private uhkConfigurationService: UhkConfigurationService,
private store: Store<AppState>,
private route: ActivatedRoute
) {
}
let keymapConnectable: ConnectableObservable<Keymap> = route
.params
.select<string>('abbr')
.switchMap((abbr: string) => store.let(getKeymap(abbr)))
.publishReplay();
ngOnInit() {
this.subParams = this.route.params.subscribe((params: { id: string }) => {
let id: number = +params.id;
if (!isNaN(id)) {
this.keymapId = id;
}
this.keymap = this.uhkConfigurationService.getUhkConfiguration().keymaps.elements[this.keymapId];
this.layers = this.keymap.layers;
});
}
ngOnDestroy() {
this.subParams.unsubscribe();
this.keymap$ = keymapConnectable;
keymapConnectable.connect();
}
}

View File

@@ -18,7 +18,7 @@ export const keymapRoutes: Routes = [
component: KeymapAddComponent
},
{
path: 'keymap/:id',
path: 'keymap/:abbr',
component: KeymapComponent
}
];

View File

@@ -46,7 +46,7 @@
<layer-tab #tab *ngSwitchCase="TabName.Layer" class="popover-content" [defaultKeyAction]="defaultKeyAction"></layer-tab>
<mouse-tab #tab *ngSwitchCase="TabName.Mouse" class="popover-content" [defaultKeyAction]="defaultKeyAction"></mouse-tab>
<macro-tab #tab *ngSwitchCase="TabName.Macro" class="popover-content" [defaultKeyAction]="defaultKeyAction"></macro-tab>
<keymap-tab #tab *ngSwitchCase="TabName.Keymap" class="popover-content" [defaultKeyAction]="defaultKeyAction"></keymap-tab>
<keymap-tab #tab *ngSwitchCase="TabName.Keymap" class="popover-content" [defaultKeyAction]="defaultKeyAction" [keymaps]="keymaps$ | async"></keymap-tab>
<none-tab #tab *ngSwitchCase="TabName.None" class="popover-content"></none-tab>
</div>
<div class="row">

View File

@@ -1,4 +1,6 @@
import {Component, EventEmitter, Input, OnInit, Output, ViewChild} from '@angular/core';
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
import { Store } from '@ngrx/store';
import {
KeyAction,
@@ -8,7 +10,12 @@ import {
SwitchKeymapAction,
SwitchLayerAction
} from '../../config-serializer/config-items/key-action';
import {Tab} from './tab/tab';
import { Keymap } from '../../config-serializer/config-items/Keymap';
import { Tab } from './tab/tab';
import { AppState } from '../../store';
import { Observable } from 'rxjs/Observable';
enum TabName {
Keypress,
@@ -38,11 +45,15 @@ export class PopoverComponent implements OnInit {
private TabName = TabName;
/* tslint:enable:no-unused-variable tslint:enable:variable-name */
private activeTab: TabName;
private keymaps$: Observable<Keymap[]>;
constructor() { }
constructor(private store: Store<AppState>) {
this.keymaps$ = store.select((appState: AppState) => appState.keymaps);
}
ngOnInit() {
let tab: TabName;
if (this.defaultKeyAction instanceof KeystrokeAction) {
tab = TabName.Keypress;
} else if (this.defaultKeyAction instanceof SwitchLayerAction) {
@@ -56,6 +67,7 @@ export class PopoverComponent implements OnInit {
} else {
tab = TabName.None;
}
this.selectTab(tab);
}
@@ -76,5 +88,4 @@ export class PopoverComponent implements OnInit {
selectTab(tab: TabName): void {
this.activeTab = tab;
}
}

View File

@@ -1,12 +1,17 @@
<div>
<b>Switch to keymap:</b>
<select2 [data]="keymapOptions" [value]="keymapOptions[selectedKeymapIndex + 1].id" (valueChanged)="onChange($event)" [width]="'100%'"></select2>
<select2
[data]="keymapOptions"
[value]="selectedKeymap?.abbreviation"
(valueChanged)="onChange($event)"
[width]="'100%'"
></select2>
</div>
<div>
<div *ngIf="selectedKeymapIndex === -1">
<div *ngIf="!selectedKeymap?.abbreviation">
<img src="./images/base-layer--blank.svg">
</div>
<svg-keyboard *ngIf="selectedKeymapIndex >= 0"
[moduleConfig]="keymaps[selectedKeymapIndex].layers.elements[0].modules.elements">
<svg-keyboard *ngIf="selectedKeymap?.abbreviation"
[moduleConfig]="selectedKeymap.layers.elements[0].modules.elements">
</svg-keyboard>
</div>

View File

@@ -1,56 +1,55 @@
import {Component, Input, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
import {Select2OptionData} from 'ng2-select2/ng2-select2';
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';
import {UhkConfigurationService} from '../../../../services/uhk-configuration.service';
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')]
styles: [require('./keymap-tab.component.scss')],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class KeymapTabComponent implements OnInit, Tab {
@Input() defaultKeyAction: KeyAction;
@Input() keymaps: Keymap[];
private keymaps: Keymap[];
private keymapOptions: Array<Select2OptionData>;
private selectedKeymapIndex: number;
private selectedKeymap: Keymap;
constructor(private uhkConfigurationService: UhkConfigurationService) {
this.keymaps = [];
constructor() {
this.keymapOptions = [];
this.selectedKeymapIndex = -1;
}
ngOnInit() {
this.keymaps = this.uhkConfigurationService.getUhkConfiguration().keymaps.elements;
this.keymapOptions.push({
id: '-1',
text: 'Switch to keymap'
});
this.keymapOptions = this.keymapOptions.concat(this.keymaps.map(function (keymap: Keymap): Select2OptionData {
this.keymapOptions = this.keymaps.map((keymap: Keymap): Select2OptionData => {
return {
id: keymap.id.toString(),
id: keymap.abbreviation,
text: keymap.name
};
}));
});
this.fromKeyAction(this.defaultKeyAction);
}
// TODO: change to the correct type when the wrapper has added it.
onChange(event: any) {
this.selectedKeymapIndex = +event.value;
if (event.value === '-1') {
this.selectedKeymap = undefined;
} else {
this.selectedKeymap = this.keymaps.find((keymap: Keymap) => keymap.abbreviation === event.value);
}
}
keyActionValid(): boolean {
return this.selectedKeymapIndex >= 0;
return !!this.selectedKeymap;
}
fromKeyAction(keyAction: KeyAction): boolean {
@@ -58,16 +57,17 @@ export class KeymapTabComponent implements OnInit, Tab {
return false;
}
let switchKeymapAction: SwitchKeymapAction = <SwitchKeymapAction>keyAction;
this.selectedKeymapIndex = this.keymaps.findIndex(keymap => switchKeymapAction.keymapId === keymap.id);
return true;
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!');
}
let keymapAction = new SwitchKeymapAction();
keymapAction.keymapId = this.keymaps[this.selectedKeymapIndex].id;
keymapAction.keymapAbbreviation = this.selectedKeymap.abbreviation;
return keymapAction;
}
}

View File

@@ -8,9 +8,9 @@
<i class="fa fa-chevron-up pull-right" (click)="toggleHide($event, 'keymap')"></i>
</div>
<ul [@toggler]="animation.keymap">
<li *ngFor="let keymap of keymaps" class="sidebar__level-2--item">
<li *ngFor="let keymap of keymaps$ | async" class="sidebar__level-2--item">
<div class="sidebar__level-2">
<a [routerLink]="['/keymap', keymap.id]">{{keymap.name}}</a>
<a [routerLink]="['/keymap', keymap.abbreviation]">{{keymap.name}}</a>
<i *ngIf="keymap.isDefault" class="fa fa-star sidebar__fav"></i>
</div>
</li>
@@ -25,7 +25,7 @@
<i class="fa fa-chevron-up pull-right" (click)="toggleHide($event, 'macro')"></i>
</div>
<ul [@toggler]="animation.macro">
<li *ngFor="let macro of macros" class="sidebar__level-2--item">
<li *ngFor="let macro of macros$ | async" class="sidebar__level-2--item">
<div class="sidebar__level-2">
<a [routerLink]="['/macro', macro.id]">{{macro.name}}</a>
</div>

View File

@@ -1,9 +1,11 @@
import { Component, OnInit, Renderer, animate, state, style, transition, trigger } from '@angular/core';
import { Component, Renderer, animate, state, style, transition, trigger } from '@angular/core';
import { Keymap } from '../../config-serializer/config-items/Keymap';
import { Macro } from '../../config-serializer/config-items/Macro';
import { UhkConfigurationService } from '../../services/uhk-configuration.service';
import { AppState } from '../../store';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
@Component({
animations: [
@@ -21,22 +23,20 @@ import { UhkConfigurationService } from '../../services/uhk-configuration.servic
template: require('./side-menu.component.html'),
styles: [require('./side-menu.component.scss')]
})
export class SideMenuComponent implements OnInit {
private keymaps: Keymap[];
private macros: Macro[];
export class SideMenuComponent {
private keymaps$: Observable<Keymap[]>;
private macros$: Observable<Macro[]>;
private animation: {[key: string]: 'active' | 'inactive'};
constructor(private uhkConfigurationService: UhkConfigurationService, private renderer: Renderer) {
constructor(private store: Store<AppState>, private renderer: Renderer) {
this.animation = {
keymap: 'active',
macro: 'active',
addon: 'active'
};
}
ngOnInit() {
this.keymaps = this.uhkConfigurationService.getUhkConfiguration().keymaps.elements;
this.macros = this.uhkConfigurationService.getUhkConfiguration().macros.elements;
this.keymaps$ = store.select(appState => appState.keymaps);
this.macros$ = store.select(appState => appState.macros);
}
toggleHide(event: Event, type: string) {

View File

@@ -145,8 +145,7 @@ export class SvgKeyboardKeyComponent implements OnInit, OnChanges {
} else if (this.keyAction instanceof SwitchKeymapAction) {
let keyAction: SwitchKeymapAction = this.keyAction as SwitchKeymapAction;
this.labelType = LabelTypes.SwitchKeymap;
let uhkConfiguration: UhkConfiguration = this.uhkConfigurationService.getUhkConfiguration();
this.labelSource = uhkConfiguration.getKeymap(keyAction.keymapId).abbreviation;
this.labelSource = keyAction.keymapAbbreviation;
} else if (this.keyAction instanceof PlayMacroAction) {
let keyAction: PlayMacroAction = this.keyAction as PlayMacroAction;
this.labelType = LabelTypes.IconText;

View File

@@ -1,11 +1,17 @@
import {
Component, Input, OnChanges, OnInit, animate,
Component, Input, OnChanges, SimpleChanges, animate,
state, style, transition, trigger
} from '@angular/core';
import { Store } from '@ngrx/store';
import { KeyAction, NoneAction } from '../../../config-serializer/config-items/key-action';
import { Keymap } from '../../../config-serializer/config-items/Keymap';
import { Layer } from '../../../config-serializer/config-items/Layer';
import { AppState } from '../../../store';
import { KeymapActions } from '../../../store/actions';
@Component({
selector: 'svg-keyboard-wrap',
template: require('./svg-keyboard-wrap.component.html'),
@@ -74,8 +80,8 @@ import { Layer } from '../../../config-serializer/config-items/Layer';
])
]
})
export class SvgKeyboardWrapComponent implements OnInit, OnChanges {
@Input() layers: Layer[];
export class SvgKeyboardWrapComponent implements OnChanges {
@Input() keymap: Keymap;
@Input() popoverEnabled: boolean = true;
@Input() tooltipEnabled: boolean = false;
@@ -84,8 +90,11 @@ export class SvgKeyboardWrapComponent implements OnInit, OnChanges {
private popoverInitKeyAction: KeyAction;
private currentLayer: number = 0;
private tooltipData: { posTop: number, posLeft: number, content: {name: string, value: string}[], shown: boolean };
private layers: Layer[];
constructor() {
constructor(
private store: Store<AppState>
) {
this.keyEditConfig = {
keyActions: undefined,
keyId: undefined
@@ -99,15 +108,15 @@ export class SvgKeyboardWrapComponent implements OnInit, OnChanges {
};
}
ngOnInit() {
this.layers[0].animation = 'leftIn';
}
ngOnChanges(changes: SimpleChanges) {
if (changes['keymap'].previousValue.abbreviation !== changes['keymap'].currentValue.abbreviation) {
this.layers = this.keymap.layers.elements;
this.currentLayer = 0;
ngOnChanges() {
this.currentLayer = 0;
if (this.layers.length > 0) {
this.layers.forEach(element => element.animation = 'none');
this.layers[0].animation = 'leftIn';
if (this.layers.length > 0) {
this.layers.forEach(element => element.animation = 'none');
this.layers[0].animation = 'leftIn';
}
}
}
@@ -137,6 +146,7 @@ export class SvgKeyboardWrapComponent implements OnInit, OnChanges {
onRemap(keyAction: KeyAction): void {
this.changeKeyAction(keyAction);
this.store.dispatch(KeymapActions.saveKey(this.keymap));
this.hidePopover();
}

View File

@@ -5,9 +5,6 @@ import { Layers } from './Layers';
export class Keymap extends Serializable<Keymap> {
@assertUInt8
id: number;
name: string;
description: string;
@@ -23,7 +20,7 @@ export class Keymap extends Serializable<Keymap> {
if (!keymap) {
return;
}
this.id = keymap.id;
this.name = keymap.name;
this.description = keymap.description;
this.abbreviation = keymap.abbreviation;
@@ -32,7 +29,6 @@ export class Keymap extends Serializable<Keymap> {
}
_fromJsObject(jsObject: any): Keymap {
this.id = jsObject.id;
this.isDefault = jsObject.isDefault;
this.abbreviation = jsObject.abbreviation;
this.name = jsObject.name;
@@ -42,7 +38,6 @@ export class Keymap extends Serializable<Keymap> {
}
_fromBinary(buffer: UhkBuffer): Keymap {
this.id = buffer.readUInt8();
this.isDefault = buffer.readBoolean();
this.abbreviation = buffer.readString();
this.name = buffer.readString();
@@ -53,7 +48,6 @@ export class Keymap extends Serializable<Keymap> {
_toJsObject(): any {
return {
id: this.id,
isDefault: this.isDefault,
abbreviation: this.abbreviation,
name: this.name,
@@ -63,7 +57,6 @@ export class Keymap extends Serializable<Keymap> {
}
_toBinary(buffer: UhkBuffer): void {
buffer.writeUInt8(this.id);
buffer.writeBoolean(this.isDefault);
buffer.writeString(this.abbreviation);
buffer.writeString(this.name);
@@ -72,6 +65,6 @@ export class Keymap extends Serializable<Keymap> {
}
toString(): string {
return `<Keymap id="${this.id}" name="${this.name}">`;
return `<Keymap abbreviation="${this.abbreviation}" name="${this.name}">`;
}
}

View File

@@ -88,10 +88,10 @@ export class UhkConfiguration extends Serializable<UhkConfiguration> {
return `<UhkConfiguration signature="${this.signature}">`;
}
getKeymap(keymapId: number): Keymap {
getKeymap(keymapAbbreviation: string): Keymap {
let keymaps: Keymap[] = this.keymaps.elements;
for (let i = 0; i < keymaps.length; ++i) {
if (keymapId === keymaps[i].id) {
if (keymapAbbreviation === keymaps[i].abbreviation) {
return keymaps[i];
}
}

View File

@@ -1,45 +1,43 @@
import { assertUInt8 } from '../../assert';
import { UhkBuffer } from '../../UhkBuffer';
import { KeyAction, KeyActionId, keyActionType } from './KeyAction';
export class SwitchKeymapAction extends KeyAction {
@assertUInt8
keymapId: number;
keymapAbbreviation: string;
constructor(other?: SwitchKeymapAction) {
super();
if (!other) {
return;
}
this.keymapId = other.keymapId;
this.keymapAbbreviation = other.keymapAbbreviation;
}
_fromJsObject(jsObject: any): SwitchKeymapAction {
this.assertKeyActionType(jsObject);
this.keymapId = jsObject.keymapId;
this.keymapAbbreviation = jsObject.keymapAbbreviation;
return this;
}
_fromBinary(buffer: UhkBuffer): SwitchKeymapAction {
this.readAndAssertKeyActionId(buffer);
this.keymapId = buffer.readUInt8();
this.keymapAbbreviation = buffer.readString();
return this;
}
_toJsObject(): any {
return {
keyActionType: keyActionType.SwitchKeymapAction,
keymapId: this.keymapId
keymapAbbreviation: this.keymapAbbreviation
};
}
_toBinary(buffer: UhkBuffer) {
buffer.writeUInt8(KeyActionId.SwitchKeymapAction);
buffer.writeUInt8(this.keymapId);
buffer.writeString(this.keymapAbbreviation);
}
toString(): string {
return `<SwitchKeymapAction keymapId="${this.keymapId}">`;
return `<SwitchKeymapAction keymapAbbreviation="${this.keymapAbbreviation}">`;
}
}

View File

@@ -1,8 +1,7 @@
{
"keymaps": [
{
"id": 0,
"isDefault": true,
"isDefault": false,
"abbreviation": "QTY",
"name": "QWERTY",
"description": "Maecenas sem dui, ullamcorper consequat pellentesque ut, mattis at velit. Duis scelerisque eleifend gravida. Aenean at mauris rhoncus, dictum mi vitae, semper eros. Quisque maximus est elit, at condimentum ligula consectetur vel. Aenean lorem felis, molestie id ex suscipit, sagittis mollis dui. Phasellus in felis in libero bibendum ornare. Duis vestibulum dolor sed diam tempor vulputate. Curabitur scelerisque pretium ipsum. Phasellus non orci vestibulum, vehicula lectus sit amet, lacinia velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In diam lacus, cursus at pretium vel, ullamcorper at ante.",
@@ -381,7 +380,7 @@
},
{
"keyActionType": "switchKeymap",
"keymapId": 2
"keymapAbbreviation": "VIM"
},
{
"keyActionType": "none"
@@ -791,7 +790,6 @@
]
},
{
"id": 1,
"isDefault": false,
"abbreviation": "VIM",
"name": "VIM",
@@ -813,7 +811,7 @@
},
{
"keyActionType": "switchKeymap",
"keymapId": 1
"keymapAbbreviation": "QTY"
}
]
},
@@ -879,7 +877,6 @@
]
},
{
"id": 2,
"isDefault": false,
"abbreviation": "DVR",
"name": "DVR",

View File

@@ -14,7 +14,6 @@
],
"keymaps": [
{
"id": 0,
"isDefault": true,
"abbreviation": "QTY",
"name": "QWERTY",
@@ -394,7 +393,7 @@
},
{
"keyActionType": "switchKeymap",
"keymapId": 2
"keymapAbbreviation": "VIM"
},
{
"keyActionType": "none"
@@ -809,7 +808,6 @@
]
},
{
"id": 1,
"isDefault": false,
"abbreviation": "VIM",
"name": "VIM",
@@ -831,7 +829,7 @@
},
{
"keyActionType": "switchKeymap",
"keymapId": 1
"keymapAbbreviation": "QTY"
}
]
},
@@ -912,7 +910,6 @@
]
},
{
"id": 2,
"isDefault": false,
"abbreviation": "DVR",
"name": "DVR",

View File

@@ -27,7 +27,6 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
<script src="vendor/bootstrap/js/bootstrap.min.js"></script> <!-- TODO remove in near future -->
<script src="vendor/sortablejs/Sortable.min.js"></script>
<script src="vendor/select2/js/select2.min.js"></script>
<script src="app.js"></script> <!-- TODO move to typescript and components -->
<script src="uhk.js"></script>
</body>
</html>

View File

@@ -5,4 +5,5 @@
</div>
<div class="github-fork-ribbon">
<a class="" href="https://github.com/UltimateHackingKeyboard/agent" title="Fork me on GitHub">Fork me on GitHub</a>
</div>
</div>
<ngrx-store-log-monitor toggleCommand="alt-t"></ngrx-store-log-monitor>

View File

@@ -12,7 +12,7 @@ export class DataProviderService {
}
getDefaultKeymaps(): any {
return require('json!../config-serializer/default-keymaps.json');
return require('json!../config-serializer/preset-keymaps.json');
}
getKeyboardSvgAttributes(): { viewBox: string, transform: string, fill: string } {

View File

@@ -0,0 +1,2 @@
export * from './keymap';
export * from './macro';

View File

@@ -0,0 +1,69 @@
import { Action } from '@ngrx/store';
import { Keymap } from '../../config-serializer/config-items/Keymap';
export namespace KeymapActions {
export const PREFIX = '[Keymap] ';
export const ADD = KeymapActions.PREFIX + 'Add keymap';
export const DUPLICATE = KeymapActions.PREFIX + 'Duplicate keymap';
export const EDIT_ABBR = KeymapActions.PREFIX + 'Edit keymap abbreviation';
export const EDIT_NAME = KeymapActions.PREFIX + 'Edit keymap title';
export const SAVE_KEY = KeymapActions.PREFIX + 'Save key action';
export const SET_DEFAULT = KeymapActions.PREFIX + 'Set default option';
export const REMOVE = KeymapActions.PREFIX + 'Remove keymap';
export function addKeymap(item: Keymap): Action {
return {
type: KeymapActions.ADD,
payload: item
};
}
export function setDefault(abbr: string): Action {
return {
type: KeymapActions.SET_DEFAULT,
payload: abbr
};
}
export function removeKeymap(abbr: string): Action {
return {
type: KeymapActions.REMOVE,
payload: abbr
};
}
export function duplicateKeymap(keymap: Keymap): Action {
return {
type: KeymapActions.DUPLICATE,
payload: keymap
};
}
export function editKeymapName(abbr: string, name: string): Action {
return {
type: KeymapActions.EDIT_NAME,
payload: {
abbr: abbr,
name: name
}
};
}
export function editKeymapAbbr(abbr: string, newAbbr: string): Action {
return {
type: KeymapActions.EDIT_ABBR,
payload: {
abbr: abbr,
newAbbr: newAbbr
}
};
}
export function saveKey(keymap: Keymap): Action {
return {
type: KeymapActions.SAVE_KEY,
payload: keymap
};
}
}

View File

@@ -0,0 +1,12 @@
import { Action } from '@ngrx/store';
export namespace MacroActions {
export const PREFIX = '[Macro] ';
export const GET_ALL = MacroActions.PREFIX + 'Get all macros';
export function getAll(): Action {
return {
type: MacroActions.GET_ALL
};
}
}

View File

@@ -0,0 +1,32 @@
import { Injectable } from '@angular/core';
import { Actions, Effect } from '@ngrx/effects';
import 'rxjs/add/operator/map';
import { KeymapActions } from '../actions/keymap';
@Injectable()
export class KeymapEffects {
@Effect()remove$: any = this.actions$
.ofType(KeymapActions.REMOVE)
.map(() => {
// TODO: Waiting for the fix: https://github.com/angular/angular/issues/10770
// If state is empty router.navigate(['/keymap']);
// Else router.navigate(['/keymap']);
});
@Effect() editAbbr$: any = this.actions$
.ofType(KeymapActions.EDIT_ABBR)
.map<string>(action => action.payload.abbr)
.map((abbr: string) => {
console.log(abbr);
// TODO: Waiting for the fix: https://github.com/angular/angular/issues/10770
// // router.navigate(['/keymap', abbr]);
});
constructor(
private actions$: Actions
) {}
}

9
src/store/index.ts Normal file
View File

@@ -0,0 +1,9 @@
import { Keymap } from '../config-serializer/config-items/Keymap';
import { Macro } from '../config-serializer/config-items/Macro';
// State interface for the application
export interface AppState {
keymaps: Keymap[];
macros: Macro[];
presetKeymaps: Keymap[];
}

View File

@@ -0,0 +1,5 @@
import keymapReducer from './keymap';
import macroReducer from './macro';
import presetReducer from './preset';
export { keymapReducer, macroReducer, presetReducer };

View File

@@ -0,0 +1,134 @@
import '@ngrx/core/add/operator/select';
import { Action } from '@ngrx/store';
import 'rxjs/add/operator/map';
import { Observable } from 'rxjs/Observable';
import { Keymap } from '../../config-serializer/config-items/Keymap';
import { KeymapActions } from '../actions';
import { AppState } from '../index';
const initialState: Keymap[] = [];
export default function(state = initialState, action: Action): Keymap[] {
switch (action.type) {
case KeymapActions.ADD:
case KeymapActions.DUPLICATE:
let newKeymap: Keymap = new Keymap(action.payload);
newKeymap.abbreviation = generateAbbr(state, newKeymap.abbreviation);
newKeymap.name = generateName(state, newKeymap.name);
newKeymap.isDefault = false;
return [...state, newKeymap];
case KeymapActions.EDIT_NAME:
let name: string = generateName(state, action.payload.name);
return state.map((keymap: Keymap) => {
if (keymap.abbreviation === action.payload.abbr) {
keymap.name = name;
}
return keymap;
});
case KeymapActions.EDIT_ABBR:
let abbr: string = generateAbbr(state, action.payload.newAbbr);
return state.map((keymap: Keymap) => {
if (keymap.abbreviation === action.payload.abbr) {
keymap.abbreviation = abbr;
}
return keymap;
});
case KeymapActions.SET_DEFAULT:
return state.map((keymap: Keymap) => {
keymap.isDefault = (keymap.abbreviation === action.payload);
return keymap;
});
case KeymapActions.REMOVE:
let isDefault: boolean;
let filtered: Keymap[] = state.filter((keymap: Keymap) => {
if (keymap.abbreviation === action.payload) {
isDefault = keymap.isDefault;
return false;
}
return true;
}
);
// If deleted one is default set default keymap to the first on the list of keymaps
if (isDefault && filtered.length > 0) {
filtered[0].isDefault = true;
}
return filtered;
case KeymapActions.SAVE_KEY:
let changedKeymap: Keymap = new Keymap;
return state.map((keymap: Keymap) => {
if (keymap.abbreviation === action.payload.abbreviation) {
keymap = Object.assign(changedKeymap, action.payload);
}
return keymap;
});
default: {
return state;
}
}
}
export function getKeymap(abbr: string) {
if (abbr === undefined) {
return getDefault();
}
return (state$: Observable<AppState>) => state$
.select(appState => appState.keymaps)
.map((keymaps: Keymap[]) =>
keymaps.find((keymap: Keymap) => keymap.abbreviation === abbr)
);
}
export function getDefault() {
return (state$: Observable<AppState>) => state$
.select(appState => appState.keymaps)
.map((keymaps: Keymap[]) =>
keymaps.find((keymap: Keymap) => keymap.isDefault)
);
}
function generateAbbr(keymaps: Keymap[], abbr: string): string {
const chars: string[] = '23456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
let position = 0;
while (keymaps.some((keymap: Keymap) => keymap.abbreviation === abbr)) {
abbr = abbr.substring(0, abbr.length - 1) + chars[position];
++position;
}
return abbr;
}
function generateName(keymaps: Keymap[], name: string) {
let suffix = 2;
const oldName: string = name;
while (keymaps.some((keymap: Keymap) => keymap.name === name)) {
name = oldName + ` (${suffix})`;
++suffix;
}
return name;
}

View File

@@ -0,0 +1,16 @@
import { Action } from '@ngrx/store';
import { Macro } from '../../config-serializer/config-items/Macro';
import { MacroActions } from '../actions';
const initialState: Macro[] = [];
export default function(state = initialState, action: Action): Macro[] {
switch (action.type) {
case MacroActions.GET_ALL:
break;
default: {
return state;
}
}
}

View File

@@ -0,0 +1,7 @@
import { Keymap } from '../../config-serializer/config-items/Keymap';
const initialState: Keymap[] = [];
export default function(state = initialState): Keymap[] {
return state;
}

View File

@@ -0,0 +1,10 @@
export class Electron {
initialState() {
// TODO implement load logic
}
/* tslint:disable:no-unused-variable */
saveSate(reducer: any): any {
// TODO implement save logic
}
}

View File

@@ -0,0 +1,37 @@
import { Injectable } from '@angular/core';
import { Action } from '@ngrx/store';
import { AppState } from '../index';
import { Electron } from './electron';
import { Local } from './local';
@Injectable()
export class DataStorage {
private _environment: Local | Electron;
constructor() {
this.detectEnvironment();
}
initialState() {
return this._environment.initialState();
}
detectEnvironment() {
// Electron
// TODO check if we can remove <any> when electron will be implemented (maybe use process.versions['electron'])
if (typeof window !== 'undefined' && (<any>window).process && (<any>window).process.type === 'renderer') {
this._environment = new Electron();
}
// Local storage
else {
this._environment = new Local();
}
}
saveSate(reducer: any): (state: AppState, action: Action) => AppState {
return this._environment.saveSate(reducer);
}
}

View File

@@ -0,0 +1,67 @@
import { Action } from '@ngrx/store';
import { Keymap } from '../../config-serializer/config-items/Keymap';
import { Keymaps } from '../../config-serializer/config-items/Keymaps';
import { Macro } from '../../config-serializer/config-items/Macro';
import { UhkConfiguration } from '../../config-serializer/config-items/UhkConfiguration';
import { KeymapActions, MacroActions } from '../actions';
import { AppState } from '../index';
export class Local {
initialState(): AppState {
let config: UhkConfiguration;
let presetAll: Keymaps;
// Load data from json
if (!localStorage.getItem('config')) {
const jsonUser: JSON = require('json!../../config-serializer/uhk-config.json');
const jsonPreset: any = require('json!../../config-serializer/preset-keymaps.json');
config = new UhkConfiguration().fromJsObject(jsonUser);
presetAll = new Keymaps().fromJsObject(jsonPreset.keymaps);
// Save to local storage
localStorage.setItem('config', JSON.stringify(config.toJsObject()));
localStorage.setItem('preset', JSON.stringify(presetAll.toJsObject()));
}
// Load data from local storage
else {
config = new UhkConfiguration().fromJsObject(
JSON.parse(localStorage.getItem('config'))
);
presetAll = new Keymaps().fromJsObject(
JSON.parse(localStorage.getItem('preset'))
);
}
return {
keymaps: config.keymaps.elements,
macros: config.macros.elements,
presetKeymaps: presetAll.elements
};
}
saveSate(reducer: any): (state: any, action: Action) => AppState {
return function (state: any, action: Action) {
let nextState = reducer(state, action);
let config: UhkConfiguration;
// Save elements to the UhkConfiguration
if (action.type.startsWith(KeymapActions.PREFIX) && state.length && state[0] instanceof Keymap) {
config = new UhkConfiguration().fromJsObject(
JSON.parse(localStorage.getItem('config'))
);
config.keymaps.elements = Object.values(nextState);
localStorage.setItem('config', JSON.stringify(config.toJsObject()));
} else if (action.type.startsWith(MacroActions.PREFIX) && state.length && state[0] instanceof Macro) {
config = new UhkConfiguration().fromJsObject(
JSON.parse(localStorage.getItem('config'))
);
config.macros.elements = Object.values(nextState);
localStorage.setItem('config', JSON.stringify(config.toJsObject()));
}
return nextState;
};
}
}