From f1f8a8d64ea054f2ec37c9a52cccd74d6ed3f6e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Farkas=20J=C3=B3zsef?= Date: Tue, 7 Feb 2017 23:43:34 +0100 Subject: [PATCH] Add the whole user configuration to the AppState Closes #252 Fixes #219 --- electron/src/app.module.ts | 5 +- .../keymap/edit/keymap-edit-guard.service.ts | 7 +- .../keymap/edit/keymap-edit.component.ts | 4 +- .../macro/edit/macro-edit.component.ts | 2 +- .../macro-not-found-guard.service.ts | 7 +- .../components/popover/popover.component.ts | 4 +- .../popover/tab/macro/macro-tab.component.ts | 4 +- .../side-menu/side-menu.component.ts | 9 +- .../svg-keyboard-key.component.ts | 4 +- .../svg/wrap/svg-keyboard-wrap.component.ts | 6 +- shared/src/store/effects/keymap.ts | 29 +- shared/src/store/effects/macro.ts | 19 +- shared/src/store/index.ts | 13 +- shared/src/store/reducers/index.ts | 5 +- shared/src/store/reducers/keymap.ts | 237 ------------- shared/src/store/reducers/macro.ts | 184 ---------- .../src/store/reducers/user-configuration.ts | 321 ++++++++++++++++++ shared/src/store/storage/index.ts | 34 +- web/src/app.module.ts | 5 +- 19 files changed, 374 insertions(+), 525 deletions(-) delete mode 100644 shared/src/store/reducers/keymap.ts delete mode 100644 shared/src/store/reducers/macro.ts create mode 100644 shared/src/store/reducers/user-configuration.ts diff --git a/electron/src/app.module.ts b/electron/src/app.module.ts index ac05e53e..f6134b99 100644 --- a/electron/src/app.module.ts +++ b/electron/src/app.module.ts @@ -68,7 +68,7 @@ import { MapperService } from './shared/services/mapper.service'; import { UhkDeviceService } from './services/uhk-device.service'; import { KeymapEffects, MacroEffects } from './shared/store/effects'; -import { keymapReducer, macroReducer, presetReducer } from './shared/store/reducers'; +import { userConfigurationReducer, presetReducer } from './shared/store/reducers'; import { DataStorage } from './shared/store/storage'; import { KeymapEditGuard } from './shared/components/keymap/edit'; @@ -81,8 +81,7 @@ const storageService: DataStorage = storageInjector.get(DataStorage); // All reducers that are used in application const storeConfig = { - keymaps: storageService.saveState(keymapReducer), - macros: storageService.saveState(macroReducer), + userConfiguration: storageService.saveState(userConfigurationReducer), presetKeymaps: presetReducer }; diff --git a/shared/src/components/keymap/edit/keymap-edit-guard.service.ts b/shared/src/components/keymap/edit/keymap-edit-guard.service.ts index a16e5857..bcdbadb2 100644 --- a/shared/src/components/keymap/edit/keymap-edit-guard.service.ts +++ b/shared/src/components/keymap/edit/keymap-edit-guard.service.ts @@ -10,8 +10,9 @@ import 'rxjs/add/operator/switchMap'; import { Store } from '@ngrx/store'; +import { Keymap } from './../../../config-serializer/config-items/Keymap'; import { AppState } from '../../../store/index'; -import { getKeymapEntities } from '../../../store/reducers'; +import { getKeymaps } from '../../../store/reducers/user-configuration'; @Injectable() export class KeymapEditGuard implements CanActivate { @@ -20,8 +21,8 @@ export class KeymapEditGuard implements CanActivate { canActivate(): Observable { return this.store - .let(getKeymapEntities()) - .do(keymaps => { + .let(getKeymaps()) + .do((keymaps: Keymap[]) => { const defaultKeymap = keymaps.find(keymap => keymap.isDefault); this.router.navigate(['/keymap', defaultKeymap.abbreviation]); }) diff --git a/shared/src/components/keymap/edit/keymap-edit.component.ts b/shared/src/components/keymap/edit/keymap-edit.component.ts index ac27e2c0..09a063ec 100644 --- a/shared/src/components/keymap/edit/keymap-edit.component.ts +++ b/shared/src/components/keymap/edit/keymap-edit.component.ts @@ -11,7 +11,7 @@ import { Observable } from 'rxjs/Observable'; import { Keymap } from '../../../config-serializer/config-items/Keymap'; import { AppState } from '../../../store'; -import { getKeymap, getKeymapEntities } from '../../../store/reducers/keymap'; +import { getKeymap, getKeymaps } from '../../../store/reducers/user-configuration'; @Component({ selector: 'keymap-edit', @@ -37,7 +37,7 @@ export class KeymapEditComponent { .publishReplay(1) .refCount(); - this.deletable$ = store.let(getKeymapEntities()) + this.deletable$ = store.let(getKeymaps()) .map((keymaps: Keymap[]) => keymaps.length > 1); } diff --git a/shared/src/components/macro/edit/macro-edit.component.ts b/shared/src/components/macro/edit/macro-edit.component.ts index ffbfb156..c9908c9d 100644 --- a/shared/src/components/macro/edit/macro-edit.component.ts +++ b/shared/src/components/macro/edit/macro-edit.component.ts @@ -10,7 +10,7 @@ import { MacroAction } from '../../../config-serializer/config-items/macro-actio import { MacroActions } from '../../../store/actions'; import { AppState } from '../../../store/index'; -import { getMacro } from '../../../store/reducers/macro'; +import { getMacro } from '../../../store/reducers/user-configuration'; @Component({ selector: 'macro-edit', diff --git a/shared/src/components/macro/not-found/macro-not-found-guard.service.ts b/shared/src/components/macro/not-found/macro-not-found-guard.service.ts index cf365a7c..e6d02549 100644 --- a/shared/src/components/macro/not-found/macro-not-found-guard.service.ts +++ b/shared/src/components/macro/not-found/macro-not-found-guard.service.ts @@ -9,7 +9,8 @@ import 'rxjs/add/operator/map'; import { Store } from '@ngrx/store'; import { AppState } from '../../../store/index'; -import { getMacroEntities } from '../../../store/reducers'; +import { getMacros } from '../../../store/reducers/user-configuration'; +import { Macro } from './../../../config-serializer/config-items/Macro'; @Injectable() export class MacroNotFoundGuard implements CanActivate { @@ -18,8 +19,8 @@ export class MacroNotFoundGuard implements CanActivate { canActivate(): Observable { return this.store - .let(getMacroEntities()) - .map(macros => { + .let(getMacros()) + .map((macros: Macro[]) => { const hasMacros = macros.length > 0; if (hasMacros) { this.router.navigate(['/macro', macros[0].id]); diff --git a/shared/src/components/popover/popover.component.ts b/shared/src/components/popover/popover.component.ts index f52629c5..5487a7c5 100644 --- a/shared/src/components/popover/popover.component.ts +++ b/shared/src/components/popover/popover.component.ts @@ -19,7 +19,7 @@ import { Keymap } from '../../config-serializer/config-items/Keymap'; import { Tab } from './tab/tab'; import { AppState } from '../../store'; -import { getKeymapEntities } from '../../store/reducers'; +import { getKeymaps } from '../../store/reducers/user-configuration'; enum TabName { Keypress, @@ -90,7 +90,7 @@ export class PopoverComponent implements OnChanges { constructor(private store: Store) { this.animationState = 'closed'; - this.keymaps$ = store.let(getKeymapEntities()) + this.keymaps$ = store.let(getKeymaps()) .map((keymaps: Keymap[]) => keymaps.filter((keymap: Keymap) => this.currentKeymap.abbreviation !== keymap.abbreviation) ); diff --git a/shared/src/components/popover/tab/macro/macro-tab.component.ts b/shared/src/components/popover/tab/macro/macro-tab.component.ts index dada22b4..8f0d1a34 100644 --- a/shared/src/components/popover/tab/macro/macro-tab.component.ts +++ b/shared/src/components/popover/tab/macro/macro-tab.component.ts @@ -12,7 +12,7 @@ import { Macro } from '../../../../config-serializer/config-items/Macro'; import { Tab } from '../tab'; import { AppState } from '../../../../store/index'; -import { getMacroEntities } from '../../../../store/reducers/macro'; +import { getMacros } from '../../../../store/reducers/user-configuration'; @Component({ selector: 'macro-tab', @@ -29,7 +29,7 @@ export class MacroTabComponent extends Tab implements OnInit, OnChanges, OnDestr constructor(private store: Store) { super(); - this.subscription = store.let(getMacroEntities()) + this.subscription = store.let(getMacros()) .subscribe((macros: Macro[]) => this.macros = macros); this.macroOptions = []; this.selectedMacroIndex = 0; diff --git a/shared/src/components/side-menu/side-menu.component.ts b/shared/src/components/side-menu/side-menu.component.ts index 64e315c5..ff39b84e 100644 --- a/shared/src/components/side-menu/side-menu.component.ts +++ b/shared/src/components/side-menu/side-menu.component.ts @@ -2,16 +2,17 @@ import { Component, Renderer, animate, state, style, transition, trigger } from import { Store } from '@ngrx/store'; +import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/do'; import 'rxjs/add/operator/map'; -import { Observable } from 'rxjs/Observable'; +import 'rxjs/add/operator/let'; import { Keymap } from '../../config-serializer/config-items/Keymap'; import { Macro } from '../../config-serializer/config-items/Macro'; import { AppState } from '../../store'; import { MacroActions } from '../../store/actions'; -import { getKeymapEntities, getMacroEntities } from '../../store/reducers'; +import { getKeymaps, getMacros } from '../../store/reducers/user-configuration'; @Component({ animations: [ @@ -41,13 +42,13 @@ export class SideMenuComponent { addon: 'active' }; - this.keymaps$ = store.let(getKeymapEntities()) + this.keymaps$ = store.let(getKeymaps()) .map(keymaps => keymaps.slice()) // Creating a new array reference, because the sort is working in place .do((keymaps: Keymap[]) => { keymaps.sort((first: Keymap, second: Keymap) => first.name.localeCompare(second.name)); }); - this.macros$ = store.let(getMacroEntities()) + this.macros$ = store.let(getMacros()) .map(macros => macros.slice()) // Creating a new array reference, because the sort is working in place .do((macros: Macro[]) => { macros.sort((first: Macro, second: Macro) => first.name.localeCompare(second.name)); diff --git a/shared/src/components/svg/keys/svg-keyboard-key/svg-keyboard-key.component.ts b/shared/src/components/svg/keys/svg-keyboard-key/svg-keyboard-key.component.ts index 0aa78440..83fec9e4 100644 --- a/shared/src/components/svg/keys/svg-keyboard-key/svg-keyboard-key.component.ts +++ b/shared/src/components/svg/keys/svg-keyboard-key/svg-keyboard-key.component.ts @@ -23,7 +23,7 @@ import { CaptureService } from '../../../../services/capture.service'; import { MapperService } from '../../../../services/mapper.service'; import { AppState } from '../../../../store/index'; -import { getMacroEntities } from '../../../../store/reducers/macro'; +import { getMacros } from '../../../../store/reducers/user-configuration'; enum LabelTypes { KeystrokeKey, @@ -146,7 +146,7 @@ export class SvgKeyboardKeyComponent implements OnInit, OnChanges, OnDestroy { private captureService: CaptureService, private renderer: Renderer ) { - this.subscription = store.let(getMacroEntities()) + this.subscription = store.let(getMacros()) .subscribe((macros: Macro[]) => this.macros = macros); this.reset(); diff --git a/shared/src/components/svg/wrap/svg-keyboard-wrap.component.ts b/shared/src/components/svg/wrap/svg-keyboard-wrap.component.ts index 26072231..2b18fbf3 100644 --- a/shared/src/components/svg/wrap/svg-keyboard-wrap.component.ts +++ b/shared/src/components/svg/wrap/svg-keyboard-wrap.component.ts @@ -283,8 +283,8 @@ export class SvgKeyboardWrapComponent implements OnInit, OnChanges { } else if (keyAction instanceof PlayMacroAction) { const playMacroAction: PlayMacroAction = keyAction; return this.store - .select(appState => appState.macros) - .map(macroState => macroState.entities.find(macro => { + .select(appState => appState.userConfiguration.macros) + .map(macroState => macroState.find(macro => { return macro.id === playMacroAction.macroId; }).name) .map(macroName => { @@ -303,7 +303,7 @@ export class SvgKeyboardWrapComponent implements OnInit, OnChanges { } else if (keyAction instanceof SwitchKeymapAction) { const switchKeymapAction: SwitchKeymapAction = keyAction; return this.store - .select(appState => appState.keymaps.entities) + .select(appState => appState.userConfiguration.keymaps) .map(keymaps => keymaps.find(keymap => keymap.abbreviation === switchKeymapAction.keymapAbbreviation).name) .map(keymapName => { const content: NameValuePair[] = [ diff --git a/shared/src/store/effects/keymap.ts b/shared/src/store/effects/keymap.ts index 21006380..0775759a 100644 --- a/shared/src/store/effects/keymap.ts +++ b/shared/src/store/effects/keymap.ts @@ -16,34 +16,23 @@ import { Keymap } from '../../config-serializer/config-items/Keymap'; @Injectable() export class KeymapEffects { - @Effect({ dispatch: false }) add$: any = this.actions$ - .ofType(KeymapActions.ADD) + @Effect({ dispatch: false }) addOrDuplicate$: any = this.actions$ + .ofType(KeymapActions.ADD, KeymapActions.DUPLICATE) .withLatestFrom(this.store) - .do((latest) => { - const state: AppState = latest[1]; - const entities: Keymap[] = state.keymaps.entities; - this.router.navigate(['/keymap', entities[entities.length - 1].abbreviation]); - }); - - @Effect({ dispatch: false }) duplicate$: any = this.actions$ - .ofType(KeymapActions.DUPLICATE) - .withLatestFrom(this.store) - .do((latest) => { - const state: AppState = latest[1]; - const entities: Keymap[] = state.keymaps.entities; - this.router.navigate(['/keymap', entities[entities.length - 1].abbreviation]); + .map(latest => latest[1].userConfiguration.keymaps) + .do(keymaps => { + this.router.navigate(['/keymap', keymaps[keymaps.length - 1].abbreviation]); }); @Effect({ dispatch: false }) remove$: any = this.actions$ .ofType(KeymapActions.REMOVE) .withLatestFrom(this.store) - .do((latest) => { - const state: AppState = latest[1]; - - if (state.keymaps.entities.length === 0) { + .map(latest => latest[1].userConfiguration.keymaps) + .do(keymaps => { + if (keymaps.length === 0) { this.router.navigate(['/keymap/add']); } else { - const favourite: Keymap = state.keymaps.entities.find(keymap => keymap.isDefault); + const favourite: Keymap = keymaps.find(keymap => keymap.isDefault); this.router.navigate(['/keymap', favourite.abbreviation]); } }); diff --git a/shared/src/store/effects/macro.ts b/shared/src/store/effects/macro.ts index a716e8d6..119c456f 100644 --- a/shared/src/store/effects/macro.ts +++ b/shared/src/store/effects/macro.ts @@ -20,25 +20,22 @@ export class MacroEffects { .ofType(MacroActions.REMOVE) .map(action => this.store.dispatch(KeymapActions.checkMacro(action.payload))) .withLatestFrom(this.store) - .do((latest) => { - const state: AppState = latest[1]; - const macro: Macro[] = state.macros.entities; - - if (state.macros.entities.length === 0) { + .map(latest => latest[1].userConfiguration.macros) + .do(macros => { + if (macros.length === 0) { this.router.navigate(['/macro']); } else { - this.router.navigate(['/macro', macro[0].id]); + this.router.navigate(['/macro', macros[0].id]); } }); @Effect({dispatch: false}) add$: any = this.actions$ .ofType(MacroActions.ADD) .withLatestFrom(this.store) - .do((latest) => { - const state: AppState = latest[1]; - const macro: Macro = state.macros.entities[state.macros.entities.length - 1]; - - this.router.navigate(['/macro', macro.id, 'new']); + .map(latest => latest[1].userConfiguration.macros) + .map(macros => macros[macros.length - 1]) + .do(lastMacro => { + this.router.navigate(['/macro', lastMacro.id, 'new']); }); constructor(private actions$: Actions, private router: Router, private store: Store) {} diff --git a/shared/src/store/index.ts b/shared/src/store/index.ts index 0e06626f..cd64b72c 100644 --- a/shared/src/store/index.ts +++ b/shared/src/store/index.ts @@ -1,17 +1,8 @@ import { Keymap } from '../config-serializer/config-items/Keymap'; -import { Macro } from '../config-serializer/config-items/Macro'; - -export interface KeymapState { - entities: Keymap[]; -} - -export interface MacroState { - entities: Macro[]; -} +import { UserConfiguration } from '../config-serializer/config-items/UserConfiguration'; // State interface for the application export interface AppState { - keymaps: KeymapState; - macros: MacroState; + userConfiguration: UserConfiguration; presetKeymaps: Keymap[]; } diff --git a/shared/src/store/reducers/index.ts b/shared/src/store/reducers/index.ts index 7a297701..ace5974d 100644 --- a/shared/src/store/reducers/index.ts +++ b/shared/src/store/reducers/index.ts @@ -1,5 +1,4 @@ -import keymapReducer, { getKeymapEntities } from './keymap'; -import macroReducer, { getMacroEntities } from './macro'; +import userConfigurationReducer, { getUserConfiguration } from './user-configuration'; import presetReducer from './preset'; -export { keymapReducer, macroReducer, presetReducer, getKeymapEntities, getMacroEntities }; +export { userConfigurationReducer, presetReducer, getUserConfiguration }; diff --git a/shared/src/store/reducers/keymap.ts b/shared/src/store/reducers/keymap.ts deleted file mode 100644 index cc5b9f80..00000000 --- a/shared/src/store/reducers/keymap.ts +++ /dev/null @@ -1,237 +0,0 @@ -import '@ngrx/core/add/operator/select'; -import { Action } from '@ngrx/store'; - -import 'rxjs/add/operator/map'; -import { Observable } from 'rxjs/Observable'; - -import { Helper as KeyActionHelper, KeyAction } from '../../config-serializer/config-items/key-action'; -import { Keymap } from '../../config-serializer/config-items/Keymap'; -import { Layer } from '../../config-serializer/config-items/Layer'; -import { Module } from '../../config-serializer/config-items/Module'; -import { KeymapActions } from '../actions'; -import { AppState, KeymapState } from '../index'; - -const initialState: KeymapState = { - entities: [] -}; - -export default function (state = initialState, action: Action): KeymapState { - let newState: Keymap[]; - let changedKeymap: Keymap = new Keymap(); - - switch (action.type) { - case KeymapActions.ADD: - case KeymapActions.DUPLICATE: - { - let newKeymap: Keymap = new Keymap(action.payload); - - newKeymap.abbreviation = generateAbbr(state.entities, newKeymap.abbreviation); - newKeymap.name = generateName(state.entities, newKeymap.name); - newKeymap.isDefault = (state.entities.length === 0); - - return { - entities: [...state.entities, newKeymap] - }; - } - /* tslint:disable:no-switch-case-fall-through */ - // tslint bug: https://github.com/palantir/tslint/issues/1538 - case KeymapActions.EDIT_NAME: - /* tslint:enable:no-switch-case-fall-through */ - let name: string = generateName(state.entities, action.payload.name); - - newState = state.entities.map((keymap: Keymap) => { - if (keymap.abbreviation === action.payload.abbr) { - keymap.name = name; - } - - return keymap; - }); - - return { - entities: newState - }; - - case KeymapActions.EDIT_ABBR: - let abbr: string = generateAbbr(state.entities, action.payload.newAbbr); - - newState = state.entities.map((keymap: Keymap) => { - if (keymap.abbreviation === action.payload.abbr) { - keymap.abbreviation = abbr; - } - - return keymap; - }); - - return { - entities: newState - }; - - case KeymapActions.SET_DEFAULT: - newState = state.entities.map((keymap: Keymap) => { - if (keymap.abbreviation === action.payload || keymap.isDefault) { - let newKeymap: Keymap = new Keymap(); - Object.assign(newKeymap, keymap); - keymap = newKeymap; - keymap.isDefault = keymap.abbreviation === action.payload; - } - - return keymap; - }); - - return { - entities: newState - }; - - case KeymapActions.REMOVE: - let isDefault: boolean; - - let filtered: Keymap[] = state.entities.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; - } - - // Check for the deleted keymap in other keymaps - newState = filtered.map((keymap: Keymap) => { - changedKeymap = new Keymap(); - Object.assign(changedKeymap, keymap); - changedKeymap.layers = checkExistence(changedKeymap.layers, 'keymapAbbreviation', action.payload); - - return changedKeymap; - }); - - return { - entities: newState - }; - - case KeymapActions.SAVE_KEY: - { - const keymap: Keymap = action.payload.keymap; - Object.assign(changedKeymap, keymap); - - const layerIndex: number = action.payload.layer; - const layer: Layer = changedKeymap.layers[layerIndex]; - const changedLayer: Layer = new Layer(); - Object.assign(changedLayer, layer); - changedKeymap.layers = changedKeymap.layers.slice(); - changedKeymap.layers[layerIndex] = changedLayer; - - const moduleIndex: number = action.payload.module; - const module: Module = changedLayer.modules[moduleIndex]; - const changedModule: Module = new Module(); - Object.assign(changedModule, module); - changedLayer.modules[moduleIndex] = changedModule; - - const keyIndex: number = action.payload.key; - changedModule.keyActions[keyIndex] = KeyActionHelper.createKeyAction(action.payload.keyAction); - - newState = state.entities.map((_keymap: Keymap) => { - if (_keymap.abbreviation === changedKeymap.abbreviation) { - _keymap = changedKeymap; - } - - return _keymap; - }); - - return { - entities: newState - }; - } - /* tslint:disable:no-switch-case-fall-through */ - // tslint bug: https://github.com/palantir/tslint/issues/1538 - case KeymapActions.CHECK_MACRO: - /* tslint:enable:no-switch-case-fall-through */ - newState = state.entities.map((keymap: Keymap) => { - changedKeymap = new Keymap(); - Object.assign(changedKeymap, keymap); - changedKeymap.layers = checkExistence(changedKeymap.layers, '_macroId', action.payload); - - return changedKeymap; - }); - - return { - entities: newState - }; - - default: { - return state; - } - } -} - -export function getKeymapEntities(): (state$: Observable) => Observable { - return (state$: Observable) => state$ - .select(state => state.keymaps.entities); -} - -export function getKeymap(abbr: string) { - if (abbr === undefined) { - return getDefault(); - } - - return (state$: Observable) => state$ - .select(appState => appState.keymaps.entities) - .map((keymaps: Keymap[]) => - keymaps.find((keymap: Keymap) => keymap.abbreviation === abbr) - ); -} - -export function getDefault() { - return (state$: Observable) => state$ - .select(appState => appState.keymaps.entities) - .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; -} - -function checkExistence(layers: Layer[], property: string, value: any) { - let newLayers = layers.map((layer) => { - let newLayer = new Layer(layer); - - newLayer.modules = layer.modules.map((module: Module) => { - module.keyActions.forEach((action: KeyAction, index: number) => { - if (action && action.hasOwnProperty(property) && action[property] === value) { - module.keyActions[index] = undefined; - } - }); - - return module; - }); - - return newLayer; - }); - - return newLayers; -} diff --git a/shared/src/store/reducers/macro.ts b/shared/src/store/reducers/macro.ts deleted file mode 100644 index 19405ce7..00000000 --- a/shared/src/store/reducers/macro.ts +++ /dev/null @@ -1,184 +0,0 @@ -import '@ngrx/core/add/operator/select'; -import { Action } from '@ngrx/store'; - -import 'rxjs/add/observable/of'; -import 'rxjs/add/operator/map'; -import { Observable } from 'rxjs/Observable'; - -import { Macro } from '../../config-serializer/config-items/Macro'; - -import { MacroActions } from '../actions'; -import { AppState, MacroState } from '../index'; - -const initialState: MacroState = { - entities: [] -}; - -export default function (state = initialState, action: Action): MacroState { - let newMacro: Macro; - let newState: Macro[]; - - switch (action.type) { - case MacroActions.ADD: - newMacro = new Macro(); - newMacro.id = generateId(state.entities); - newMacro.name = generateName(state.entities, 'New macro'); - newMacro.isLooped = false; - newMacro.isPrivate = true; - newMacro.macroActions = []; - - return { - entities: [...state.entities, newMacro] - }; - - case MacroActions.DUPLICATE: - - newMacro = new Macro(action.payload); - newMacro.name = generateName(state.entities, newMacro.name); - newMacro.id = generateId(state.entities); - - return { - entities: [...state.entities, newMacro] - }; - - case MacroActions.EDIT_NAME: - let name: string = generateName(state.entities, action.payload.name); - - newState = state.entities.map((macro: Macro) => { - if (macro.id === action.payload.id) { - macro.name = name; - } - - return macro; - }); - - return { - entities: newState - }; - - case MacroActions.REMOVE: - newState = state.entities.filter((macro: Macro) => macro.id !== action.payload); - - return { - entities: newState - }; - - case MacroActions.ADD_ACTION: - newState = state.entities.map((macro: Macro) => { - if (macro.id === action.payload.id) { - newMacro = new Macro(macro); - newMacro.macroActions.push(action.payload.action); - - return newMacro; - } - - return macro; - }); - - return { - entities: newState - }; - - case MacroActions.SAVE_ACTION: - newState = state.entities.map((macro: Macro) => { - if (macro.id === action.payload.id) { - newMacro = new Macro(macro); - newMacro.macroActions[action.payload.index] = action.payload.action; - - return newMacro; - } - - return macro; - }); - - return { - entities: newState - }; - - case MacroActions.DELETE_ACTION: - newState = state.entities.map((macro: Macro) => { - if (macro.id === action.payload.id) { - newMacro = new Macro(macro); - newMacro.macroActions.splice(action.payload.index, 1); - - return newMacro; - } - - return macro; - }); - - return { - entities: newState - }; - - case MacroActions.REORDER_ACTION: - newState = state.entities.map((macro: Macro) => { - if (macro.id === action.payload.id) { - let newIndex: number = action.payload.newIndex; - - // We need to reduce the new index for one when we are moving action down - if (newIndex > action.payload.oldIndex) { - --newIndex; - } - - newMacro = new Macro(macro); - newMacro.macroActions.splice( - newIndex, - 0, - newMacro.macroActions.splice(action.payload.oldIndex, 1)[0] - ); - - return newMacro; - } - - return macro; - }); - - return { - entities: newState - }; - - default: - return state; - } -} - -export function getMacroEntities(): (state$: Observable) => Observable { - return (state$: Observable) => state$ - .select(state => state.macros.entities); -} - -export function getMacro(id: number) { - if (isNaN(id)) { - return () => Observable.of(undefined); - } else { - return (state$: Observable) => state$ - .select(appState => appState.macros.entities) - .map((macros: Macro[]) => macros.find((macro: Macro) => macro.id === id)); - } -} - -function generateName(macros: Macro[], name: string) { - let suffix = 2; - const oldName: string = name; - - while (macros.some((macro: Macro) => macro.name === name)) { - name = oldName + ` (${suffix})`; - ++suffix; - } - - return name; -} - -function generateId(macros: Macro[]) { - let newId = 0; - - macros.forEach((macro: Macro) => { - if (macro.id > newId) { - newId = macro.id; - } - }); - - return ++newId; - -} diff --git a/shared/src/store/reducers/user-configuration.ts b/shared/src/store/reducers/user-configuration.ts new file mode 100644 index 00000000..4c0853b1 --- /dev/null +++ b/shared/src/store/reducers/user-configuration.ts @@ -0,0 +1,321 @@ +import '@ngrx/core/add/operator/select'; +import { Action } from '@ngrx/store'; + +import 'rxjs/add/operator/map'; +import { Observable } from 'rxjs/Observable'; + +import { Helper as KeyActionHelper, KeyAction } from '../../config-serializer/config-items/key-action'; +import { Keymap } from '../../config-serializer/config-items/Keymap'; +import { Macro } from '../../config-serializer/config-items/Macro'; +import { UserConfiguration } from '../../config-serializer/config-items/UserConfiguration'; +import { Layer } from '../../config-serializer/config-items/Layer'; +import { Module } from '../../config-serializer/config-items/Module'; +import { KeymapActions, MacroActions } from '../actions'; +import { AppState } from '../index'; + +const initialState: UserConfiguration = new UserConfiguration(); + +/* tslint:disable:no-switch-case-fall-through */ +// tslint bug: https://github.com/palantir/tslint/issues/1538 + +export default function (state = initialState, action: Action): UserConfiguration { + const changedUserConfiguration: UserConfiguration = Object.assign(new UserConfiguration(), state); + + switch (action.type) { + case KeymapActions.ADD: + case KeymapActions.DUPLICATE: + { + const newKeymap: Keymap = new Keymap(action.payload); + newKeymap.abbreviation = generateAbbr(state.keymaps, newKeymap.abbreviation); + newKeymap.name = generateName(state.keymaps, newKeymap.name); + newKeymap.isDefault = (state.keymaps.length === 0); + + changedUserConfiguration.keymaps = state.keymaps.concat(newKeymap); + break; + } + case KeymapActions.EDIT_NAME: + { + const name: string = generateName(state.keymaps, action.payload.name); + + changedUserConfiguration.keymaps = state.keymaps.map((keymap: Keymap) => { + if (keymap.abbreviation === action.payload.abbr) { + keymap = Object.assign(new Keymap(), keymap); + keymap.name = name; + } + return keymap; + }); + break; + } + case KeymapActions.EDIT_ABBR: + const abbr: string = generateAbbr(state.keymaps, action.payload.newAbbr); + + changedUserConfiguration.keymaps = state.keymaps.map((keymap: Keymap) => { + if (keymap.abbreviation === action.payload.abbr) { + keymap = Object.assign(new Keymap(), keymap); + keymap.abbreviation = abbr; + } + + return keymap; + }); + break; + case KeymapActions.SET_DEFAULT: + changedUserConfiguration.keymaps = state.keymaps.map((keymap: Keymap) => { + if (keymap.abbreviation === action.payload || keymap.isDefault) { + keymap = Object.assign(new Keymap(), keymap); + keymap.isDefault = keymap.abbreviation === action.payload; + } + + return keymap; + }); + break; + case KeymapActions.REMOVE: + let isDefault: boolean; + + let filtered: Keymap[] = state.keymaps.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; + } + + // Check for the deleted keymap in other keymaps + changedUserConfiguration.keymaps = filtered.map(keymap => { + keymap = Object.assign(new Keymap(), keymap); + keymap.layers = checkExistence(keymap.layers, 'keymapAbbreviation', action.payload); + + return keymap; + }); + break; + + case KeymapActions.SAVE_KEY: + { + const newKeymap: Keymap = Object.assign(new Keymap(), action.payload.keymap); + newKeymap.layers = newKeymap.layers.slice(); + + const layerIndex: number = action.payload.layer; + const newLayer: Layer = Object.assign(new Layer(), newKeymap.layers[layerIndex]); + newKeymap.layers[layerIndex] = newLayer; + + const moduleIndex: number = action.payload.module; + const newModule: Module = Object.assign(new Module(), newLayer.modules[moduleIndex]); + newLayer.modules[moduleIndex] = newModule; + + const keyIndex: number = action.payload.key; + newModule.keyActions[keyIndex] = KeyActionHelper.createKeyAction(action.payload.keyAction); + + changedUserConfiguration.keymaps = state.keymaps.map(keymap => { + if (keymap.abbreviation === newKeymap.abbreviation) { + keymap = newKeymap; + } + + return keymap; + }); + break; + } + case KeymapActions.CHECK_MACRO: + changedUserConfiguration.keymaps = state.keymaps.map(keymap => { + keymap = Object.assign(new Keymap(), keymap); + keymap.layers = checkExistence(keymap.layers, '_macroId', action.payload); + return keymap; + }); + break; + case MacroActions.ADD: + { + const newMacro = new Macro(); + newMacro.id = generateMacroId(state.macros); + newMacro.name = generateName(state.macros, 'New macro'); + newMacro.isLooped = false; + newMacro.isPrivate = true; + newMacro.macroActions = []; + + changedUserConfiguration.macros = state.macros.concat(newMacro); + break; + } + case MacroActions.DUPLICATE: + { + const newMacro = new Macro(action.payload); + newMacro.name = generateName(state.macros, newMacro.name); + newMacro.id = generateMacroId(state.macros); + + changedUserConfiguration.macros = state.macros.concat(newMacro); + break; + } + case MacroActions.EDIT_NAME: + { + const name: string = generateName(state.macros, action.payload.name); + + changedUserConfiguration.macros = state.macros.map((macro: Macro) => { + if (macro.id === action.payload.id) { + macro.name = name; + } + + return macro; + }); + + break; + } + case MacroActions.REMOVE: + changedUserConfiguration.macros = state.macros.filter((macro: Macro) => macro.id !== action.payload); + break; + case MacroActions.ADD_ACTION: + changedUserConfiguration.macros = state.macros.map((macro: Macro) => { + if (macro.id === action.payload.id) { + macro = new Macro(macro); + macro.macroActions.push(action.payload.action); + } + + return macro; + }); + break; + case MacroActions.SAVE_ACTION: + changedUserConfiguration.macros = state.macros.map((macro: Macro) => { + if (macro.id === action.payload.id) { + macro = new Macro(macro); + macro.macroActions[action.payload.index] = action.payload.action; + } + + return macro; + }); + break; + case MacroActions.DELETE_ACTION: + changedUserConfiguration.macros = state.macros.map((macro: Macro) => { + if (macro.id === action.payload.id) { + macro = new Macro(macro); + macro.macroActions.splice(action.payload.index, 1); + } + + return macro; + }); + break; + case MacroActions.REORDER_ACTION: + changedUserConfiguration.macros = state.macros.map((macro: Macro) => { + if (macro.id === action.payload.id) { + let newIndex: number = action.payload.newIndex; + + // We need to reduce the new index for one when we are moving action down + if (newIndex > action.payload.oldIndex) { + --newIndex; + } + + macro = new Macro(macro); + macro.macroActions.splice( + newIndex, + 0, + macro.macroActions.splice(action.payload.oldIndex, 1)[0] + ); + } + + return macro; + }); + break; + default: + break; + } + + return changedUserConfiguration; +} + +export function getUserConfiguration(): (state$: Observable) => Observable { + return (state$: Observable) => state$ + .select(state => state.userConfiguration); +} + +export function getKeymaps(): (state$: Observable) => Observable { + return (state$: Observable) => state$ + .select(state => state.userConfiguration.keymaps); +} + +export function getKeymap(abbr: string) { + if (abbr === undefined) { + return getDefaultKeymap(); + } + + return (state$: Observable) => getKeymaps()(state$) + .map((keymaps: Keymap[]) => + keymaps.find((keymap: Keymap) => keymap.abbreviation === abbr) + ); +} + +export function getDefaultKeymap() { + return (state$: Observable) => getKeymaps()(state$) + .map((keymaps: Keymap[]) => + keymaps.find((keymap: Keymap) => keymap.isDefault) + ); +} + +export function getMacros(): (state$: Observable) => Observable { + return (state$: Observable) => state$ + .select(state => state.userConfiguration.macros); +} + +export function getMacro(id: number) { + if (isNaN(id)) { + return () => Observable.of(undefined); + } else { + return (state$: Observable) => getMacros()(state$) + .map((macros: Macro[]) => macros.find((macro: Macro) => macro.id === id)); + } +} + +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(items: { name: string }[], name: string) { + let suffix = 2; + const oldName: string = name; + + while (items.some(item => item.name === name)) { + name = oldName + ` (${suffix})`; + ++suffix; + } + + return name; +} + +function generateMacroId(macros: Macro[]) { + let newId = 0; + + macros.forEach((macro: Macro) => { + if (macro.id > newId) { + newId = macro.id; + } + }); + + return newId + 1; +} + +function checkExistence(layers: Layer[], property: string, value: any) { + let newLayers = layers.map((layer) => { + let newLayer = new Layer(layer); + + newLayer.modules = layer.modules.map((module: Module) => { + module.keyActions.forEach((action: KeyAction, index: number) => { + if (action && action.hasOwnProperty(property) && action[property] === value) { + module.keyActions[index] = undefined; + } + }); + + return module; + }); + + return newLayer; + }); + + return newLayers; +} diff --git a/shared/src/store/storage/index.ts b/shared/src/store/storage/index.ts index 5e4045a9..4cb8a9e2 100644 --- a/shared/src/store/storage/index.ts +++ b/shared/src/store/storage/index.ts @@ -26,12 +26,7 @@ export class DataStorage { initialState(): AppState { const config: UserConfiguration = this.getConfiguration(); return { - keymaps: { - entities: config.keymaps - }, - macros: { - entities: config.macros - }, + userConfiguration: config, presetKeymaps: this.uhkPresets }; } @@ -51,31 +46,8 @@ export class DataStorage { // TODO: Add type for state saveState(reducer: any): (state: any, action: Action) => AppState { return (state: any, action: Action) => { - let nextState = reducer(state, action); - let config: UserConfiguration; - - // Save elements to the UhkConfiguration - if ( - action.type.startsWith(KeymapActions.PREFIX) && - ( - (nextState.entities && nextState.entities.length && nextState.entities[0] instanceof Keymap) || - (state.entities && state.entities.length && state.entities[0] instanceof Keymap) - ) - ) { - config = this.getConfiguration(); - config.keymaps = Object.values(nextState.entities); - this._environment.saveConfig(config); - } else if ( - action.type.startsWith(MacroActions.PREFIX) && - ( - (nextState.entities && nextState.entities.length && nextState.entities[0] instanceof Macro) || - (state.entities && state.entities.length && state.entities[0] instanceof Macro) - ) - ) { - config = this.getConfiguration(); - config.macros = Object.values(nextState.entities); - this._environment.saveConfig(config); - } + const nextState = reducer(state, action); + this._environment.saveConfig(nextState); return nextState; }; } diff --git a/web/src/app.module.ts b/web/src/app.module.ts index b1e1ab34..528b2a69 100644 --- a/web/src/app.module.ts +++ b/web/src/app.module.ts @@ -66,7 +66,7 @@ import { CaptureService } from './shared/services/capture.service'; import { MapperService } from './shared/services/mapper.service'; import { KeymapEffects, MacroEffects } from './shared/store/effects'; -import { keymapReducer, macroReducer, presetReducer } from './shared/store/reducers'; +import { userConfigurationReducer, presetReducer } from './shared/store/reducers'; import { DataStorage } from './shared/store/storage'; import { KeymapEditGuard } from './shared/components/keymap/edit'; @@ -79,8 +79,7 @@ const storageService: DataStorage = storageInjector.get(DataStorage); // All reducers that are used in application const storeConfig = { - keymaps: storageService.saveState(keymapReducer), - macros: storageService.saveState(macroReducer), + userConfiguration: storageService.saveState(userConfigurationReducer), presetKeymaps: presetReducer };