From b78bc5850f70ef2d73a793f42676bfc3e28ba791 Mon Sep 17 00:00:00 2001 From: Nejc Zdovc Date: Sun, 18 Sep 2016 12:38:42 +0200 Subject: [PATCH] Keymap data layer (#95) --- package.json | 5 + src/app.module.ts | 35 ++++- .../keymap/add/keymap-add.component.html | 11 +- .../keymap/add/keymap-add.component.ts | 42 ++++-- .../header/keymap-header.component.html | 30 ++++ .../header/keymap-header.component.scss | 61 ++++++++ .../keymap/header/keymap-header.component.ts | 44 ++++++ src/components/keymap/index.ts | 1 + src/components/keymap/keymap.component.html | 21 ++- src/components/keymap/keymap.component.scss | 47 +----- src/components/keymap/keymap.component.ts | 48 +++---- src/components/keymap/keymap.routes.ts | 2 +- src/components/popover/popover.component.html | 2 +- src/components/popover/popover.component.ts | 19 ++- .../tab/keymap/keymap-tab.component.html | 13 +- .../tab/keymap/keymap-tab.component.ts | 46 +++--- .../side-menu/side-menu.component.html | 6 +- .../side-menu/side-menu.component.ts | 20 +-- .../svg-keyboard-key.component.ts | 3 +- .../svg/wrap/svg-keyboard-wrap.component.ts | 34 +++-- src/config-serializer/config-items/Keymap.ts | 11 +- .../config-items/UhkConfiguration.ts | 4 +- .../key-action/SwitchKeymapAction.ts | 16 +-- ...fault-keymaps.json => preset-keymaps.json} | 9 +- src/config-serializer/uhk-config.json | 7 +- src/index.html | 1 - src/main-app/main-app.component.html | 3 +- src/services/data-provider.service.ts | 2 +- src/store/actions/index.ts | 2 + src/store/actions/keymap.ts | 69 +++++++++ src/store/actions/macro.ts | 12 ++ src/store/effects/keymap.ts | 32 +++++ src/store/index.ts | 9 ++ src/store/reducers/index.ts | 5 + src/store/reducers/keymap.ts | 134 ++++++++++++++++++ src/store/reducers/macro.ts | 16 +++ src/store/reducers/preset.ts | 7 + src/store/storage/electron.ts | 10 ++ src/store/storage/index.ts | 37 +++++ src/store/storage/local.ts | 67 +++++++++ 40 files changed, 748 insertions(+), 195 deletions(-) create mode 100644 src/components/keymap/header/keymap-header.component.html create mode 100644 src/components/keymap/header/keymap-header.component.scss create mode 100644 src/components/keymap/header/keymap-header.component.ts rename src/config-serializer/{default-keymaps.json => preset-keymaps.json} (99%) create mode 100644 src/store/actions/index.ts create mode 100644 src/store/actions/keymap.ts create mode 100644 src/store/actions/macro.ts create mode 100644 src/store/effects/keymap.ts create mode 100644 src/store/index.ts create mode 100644 src/store/reducers/index.ts create mode 100644 src/store/reducers/keymap.ts create mode 100644 src/store/reducers/macro.ts create mode 100644 src/store/reducers/preset.ts create mode 100644 src/store/storage/electron.ts create mode 100644 src/store/storage/index.ts create mode 100644 src/store/storage/local.ts diff --git a/package.json b/package.json index 9c689faf..1fc3eb8e 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/app.module.ts b/src/app.module.ts index 6022dd55..d888defb 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -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, diff --git a/src/components/keymap/add/keymap-add.component.html b/src/components/keymap/add/keymap-add.component.html index 212216b8..72b6a8d4 100644 --- a/src/components/keymap/add/keymap-add.component.html +++ b/src/components/keymap/add/keymap-add.component.html @@ -10,23 +10,26 @@
- {{ keymaps.length }} / {{ keymapsAll.length }} keymaps shown + {{ (presets$ | async).length }} / {{ (presetsAll$ | async).length }} keymaps shown
-
+

{{ keymap.name }}

{{ keymap.description }}

- +
+
+
+ Sorry, no keyboard found under this search query.
\ No newline at end of file diff --git a/src/components/keymap/add/keymap-add.component.ts b/src/components/keymap/add/keymap-add.component.ts index 4668eddf..13cdd84f 100644 --- a/src/components/keymap/add/keymap-add.component.ts +++ b/src/components/keymap/add/keymap-add.component.ts @@ -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; + private presetsAll$: Observable; - 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) { + let presetConnectable: ConnectableObservable = 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 = 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)); } } diff --git a/src/components/keymap/header/keymap-header.component.html b/src/components/keymap/header/keymap-header.component.html new file mode 100644 index 00000000..90694f61 --- /dev/null +++ b/src/components/keymap/header/keymap-header.component.html @@ -0,0 +1,30 @@ +
+

+ + keymap + () + + + +

+
\ No newline at end of file diff --git a/src/components/keymap/header/keymap-header.component.scss b/src/components/keymap/header/keymap-header.component.scss new file mode 100644 index 00000000..5620f0bc --- /dev/null +++ b/src/components/keymap/header/keymap-header.component.scss @@ -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; + } +} diff --git a/src/components/keymap/header/keymap-header.component.ts b/src/components/keymap/header/keymap-header.component.ts new file mode 100644 index 00000000..cd734430 --- /dev/null +++ b/src/components/keymap/header/keymap-header.component.ts @@ -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 + ) { } + + 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)); + } +} diff --git a/src/components/keymap/index.ts b/src/components/keymap/index.ts index 753c64f3..8035bd43 100644 --- a/src/components/keymap/index.ts +++ b/src/components/keymap/index.ts @@ -1,3 +1,4 @@ export * from './keymap.component'; export * from './keymap.routes'; export * from './add/keymap-add.component'; +export * from './header/keymap-header.component'; diff --git a/src/components/keymap/keymap.component.html b/src/components/keymap/keymap.component.html index 36a1047c..a929aedd 100644 --- a/src/components/keymap/keymap.component.html +++ b/src/components/keymap/keymap.component.html @@ -1,13 +1,8 @@ - \ No newline at end of file + + +
+ Sorry, there is no keymap with this abbreviation. +
\ No newline at end of file diff --git a/src/components/keymap/keymap.component.scss b/src/components/keymap/keymap.component.scss index da3fcdcc..529694f2 100644 --- a/src/components/keymap/keymap.component.scss +++ b/src/components/keymap/keymap.component.scss @@ -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; - } - } - } -} diff --git a/src/components/keymap/keymap.component.ts b/src/components/keymap/keymap.component.ts index 3c010295..45c46ff9 100644 --- a/src/components/keymap/keymap.component.ts +++ b/src/components/keymap/keymap.component.ts @@ -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; constructor( - private uhkConfigurationService: UhkConfigurationService, + private store: Store, private route: ActivatedRoute ) { - } + let keymapConnectable: ConnectableObservable = route + .params + .select('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(); } } diff --git a/src/components/keymap/keymap.routes.ts b/src/components/keymap/keymap.routes.ts index 9077451d..dad0794d 100644 --- a/src/components/keymap/keymap.routes.ts +++ b/src/components/keymap/keymap.routes.ts @@ -18,7 +18,7 @@ export const keymapRoutes: Routes = [ component: KeymapAddComponent }, { - path: 'keymap/:id', + path: 'keymap/:abbr', component: KeymapComponent } ]; diff --git a/src/components/popover/popover.component.html b/src/components/popover/popover.component.html index 311904c7..66cd9378 100644 --- a/src/components/popover/popover.component.html +++ b/src/components/popover/popover.component.html @@ -46,7 +46,7 @@ - +
diff --git a/src/components/popover/popover.component.ts b/src/components/popover/popover.component.ts index 5e74e2c7..949dd6f9 100644 --- a/src/components/popover/popover.component.ts +++ b/src/components/popover/popover.component.ts @@ -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; - constructor() { } + constructor(private store: Store) { + 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; } - } diff --git a/src/components/popover/tab/keymap/keymap-tab.component.html b/src/components/popover/tab/keymap/keymap-tab.component.html index 5d60df0f..70661a74 100644 --- a/src/components/popover/tab/keymap/keymap-tab.component.html +++ b/src/components/popover/tab/keymap/keymap-tab.component.html @@ -1,12 +1,17 @@
Switch to keymap: - +
-
+
- +
\ No newline at end of file diff --git a/src/components/popover/tab/keymap/keymap-tab.component.ts b/src/components/popover/tab/keymap/keymap-tab.component.ts index a565506d..dc69c4f6 100644 --- a/src/components/popover/tab/keymap/keymap-tab.component.ts +++ b/src/components/popover/tab/keymap/keymap-tab.component.ts @@ -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; - 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 = 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; } } diff --git a/src/components/side-menu/side-menu.component.html b/src/components/side-menu/side-menu.component.html index d4a36b79..41790363 100644 --- a/src/components/side-menu/side-menu.component.html +++ b/src/components/side-menu/side-menu.component.html @@ -8,9 +8,9 @@