diff --git a/electron/src/app.module.ts b/electron/src/app.module.ts index d59e24d3..5d3958e5 100644 --- a/electron/src/app.module.ts +++ b/electron/src/app.module.ts @@ -1,4 +1,4 @@ -import { NgModule, ReflectiveInjector } from '@angular/core'; +import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; @@ -7,6 +7,7 @@ import { EffectsModule } from '@ngrx/effects'; import { StoreModule } from '@ngrx/store'; import { StoreDevtoolsModule } from '@ngrx/store-devtools'; import { StoreLogMonitorModule, useLogMonitor } from '@ngrx/store-log-monitor'; +import { RouterStoreModule } from '@ngrx/router-store'; import { DragulaModule } from 'ng2-dragula/ng2-dragula'; import { Select2Module } from 'ng2-select2/ng2-select2'; @@ -73,9 +74,7 @@ import { CaptureService } from './shared/services/capture.service'; import { MapperService } from './shared/services/mapper.service'; import { UhkDeviceService } from './services/uhk-device.service'; -import { KeymapEffects, MacroEffects } from './shared/store/effects'; -import { userConfigurationReducer, presetReducer } from './shared/store/reducers'; -import { DataStorage } from './shared/store/storage'; +import { KeymapEffects, MacroEffects, UserConfigEffects} from './shared/store/effects'; import { KeymapEditGuard } from './shared/components/keymap/edit'; import { MacroNotFoundGuard } from './shared/components/macro/not-found'; @@ -84,17 +83,10 @@ import { UhkDeviceConnectedGuard } from './services/uhk-device-connected.guard'; import { UhkDeviceDisconnectedGuard } from './services/uhk-device-disconnected.guard'; import { UhkDeviceInitializedGuard } from './services/uhk-device-initialized.guard'; import { UhkDeviceUninitializedGuard } from './services/uhk-device-uninitialized.guard'; - -// 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 = { - userConfiguration: storageService.saveState(userConfigurationReducer), - presetKeymaps: presetReducer -}; +import { DATA_STORAGE_REPOSITORY } from './shared/services/datastorage-repository.service'; +import { ElectronDataStorageRepositoryService } from './services/electron-datastorage-repository.service'; +import { DefaultUserConfigurationService } from './shared/services/default-user-configuration.service'; +import { reducer } from '../../shared/src/store/reducers/index'; @NgModule({ declarations: [ @@ -156,7 +148,8 @@ const storeConfig = { FormsModule, DragulaModule, routing, - StoreModule.provideStore(storeConfig, storageService.initialState()), + StoreModule.provideStore(reducer), + RouterStoreModule.connectRouter(), StoreDevtoolsModule.instrumentStore({ monitor: useLogMonitor({ visible: false, @@ -166,7 +159,8 @@ const storeConfig = { StoreLogMonitorModule, Select2Module, EffectsModule.runAfterBootstrap(KeymapEffects), - EffectsModule.runAfterBootstrap(MacroEffects) + EffectsModule.runAfterBootstrap(MacroEffects), + EffectsModule.runAfterBootstrap(UserConfigEffects) ], providers: [ UhkDeviceConnectedGuard, @@ -178,7 +172,9 @@ const storeConfig = { KeymapEditGuard, MacroNotFoundGuard, CaptureService, - UhkDeviceService + UhkDeviceService, + {provide: DATA_STORAGE_REPOSITORY, useClass: ElectronDataStorageRepositoryService}, + DefaultUserConfigurationService ], bootstrap: [AppComponent] }) diff --git a/shared/src/store/storage/electron.ts b/electron/src/services/electron-datastorage-repository.service.ts similarity index 61% rename from shared/src/store/storage/electron.ts rename to electron/src/services/electron-datastorage-repository.service.ts index 0c9f174a..9cd0f066 100644 --- a/shared/src/store/storage/electron.ts +++ b/electron/src/services/electron-datastorage-repository.service.ts @@ -1,6 +1,6 @@ -import { UserConfiguration } from '../../config-serializer/config-items/UserConfiguration'; +import { UserConfiguration } from '../shared/config-serializer/config-items/UserConfiguration'; -export class Electron { +export class ElectronDataStorageRepositoryService { getConfig(): UserConfiguration { // TODO implement load logic return; diff --git a/package.json b/package.json index 94ce2f90..8ff72318 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "@angular/router": "4.0.3", "@ngrx/core": "1.2.0", "@ngrx/effects": "2.0.3", + "@ngrx/router-store": "^1.2.6", "@ngrx/store": "2.2.2", "bootstrap": "^3.3.7", "browser-stdout": "^1.3.0", @@ -66,7 +67,7 @@ "rxjs": "5.3.0", "select2": "^4.0.3", "sudo-prompt": "^7.0.0", - "typescript": "2.2.2", + "typescript": "2.3.4", "usb": "git+https://github.com/aktary/node-usb.git", "xml-loader": "1.2.1", "zone.js": "0.8.5" 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 bcdbadb2..172d8fd5 100644 --- a/shared/src/components/keymap/edit/keymap-edit-guard.service.ts +++ b/shared/src/components/keymap/edit/keymap-edit-guard.service.ts @@ -24,7 +24,9 @@ export class KeymapEditGuard implements CanActivate { .let(getKeymaps()) .do((keymaps: Keymap[]) => { const defaultKeymap = keymaps.find(keymap => keymap.isDefault); - this.router.navigate(['/keymap', defaultKeymap.abbreviation]); + if (defaultKeymap) { + this.router.navigate(['/keymap', defaultKeymap.abbreviation]); + } }) .switchMap(() => Observable.of(false)); } diff --git a/shared/src/config-serializer/config-items/UserConfiguration.ts b/shared/src/config-serializer/config-items/UserConfiguration.ts index 676f696b..99c41ccf 100644 --- a/shared/src/config-serializer/config-items/UserConfiguration.ts +++ b/shared/src/config-serializer/config-items/UserConfiguration.ts @@ -9,11 +9,11 @@ export class UserConfiguration { @assertUInt16 dataModelVersion: number; - moduleConfigurations: ModuleConfiguration[]; + moduleConfigurations: ModuleConfiguration[] = []; - keymaps: Keymap[]; + keymaps: Keymap[] = []; - macros: Macro[]; + macros: Macro[] = []; fromJsonObject(jsonObject: any): UserConfiguration { this.dataModelVersion = jsonObject.dataModelVersion; diff --git a/shared/src/services/datastorage-repository.service.ts b/shared/src/services/datastorage-repository.service.ts new file mode 100644 index 00000000..8f5b61ec --- /dev/null +++ b/shared/src/services/datastorage-repository.service.ts @@ -0,0 +1,12 @@ +import { InjectionToken } from '@angular/core'; + +import { UserConfiguration } from '../config-serializer/config-items/UserConfiguration'; + +export interface DataStorageRepositoryService { + + getConfig(): UserConfiguration; + + saveConfig(config: UserConfiguration): void; +} + +export let DATA_STORAGE_REPOSITORY = new InjectionToken('dataStorage-repository'); diff --git a/shared/src/services/default-user-configuration.service.ts b/shared/src/services/default-user-configuration.service.ts new file mode 100644 index 00000000..2ba5d764 --- /dev/null +++ b/shared/src/services/default-user-configuration.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@angular/core'; +import { UserConfiguration } from '../config-serializer/config-items/UserConfiguration'; + +@Injectable() +export class DefaultUserConfigurationService { + private _defaultConfig: UserConfiguration; + + constructor() { + this._defaultConfig = new UserConfiguration() + .fromJsonObject(require('json-loader!../config-serializer/user-config.json')); + } + + getDefault(): UserConfiguration { + return this._defaultConfig; + } +} diff --git a/shared/src/services/local-datastorage-repository.service.ts b/shared/src/services/local-datastorage-repository.service.ts new file mode 100644 index 00000000..f0b27c80 --- /dev/null +++ b/shared/src/services/local-datastorage-repository.service.ts @@ -0,0 +1,27 @@ +import { Injectable } from '@angular/core'; +import { UserConfiguration } from '../config-serializer/config-items/UserConfiguration'; +import { DataStorageRepositoryService } from './datastorage-repository.service'; +import { DefaultUserConfigurationService } from './default-user-configuration.service'; + +@Injectable() +export class LocalDataStorageRepositoryService implements DataStorageRepositoryService { + constructor(private defaultUserConfigurationService: DefaultUserConfigurationService) { } + + getConfig(): UserConfiguration { + const configJsonString = localStorage.getItem('config'); + let config: UserConfiguration; + + if (configJsonString) { + const configJsonObject = JSON.parse(configJsonString); + if (configJsonObject.dataModelVersion === this.defaultUserConfigurationService.getDefault().dataModelVersion) { + config = new UserConfiguration().fromJsonObject(configJsonObject); + } + } + + return config; + } + + saveConfig(config: UserConfiguration): void { + localStorage.setItem('config', JSON.stringify(config.toJsonObject())); + } +} diff --git a/shared/src/store/actions/keymap.ts b/shared/src/store/actions/keymap.ts index 356a4bdd..ca9e0109 100644 --- a/shared/src/store/actions/keymap.ts +++ b/shared/src/store/actions/keymap.ts @@ -14,6 +14,21 @@ export namespace KeymapActions { export const SET_DEFAULT = KeymapActions.PREFIX + 'Set default option'; export const REMOVE = KeymapActions.PREFIX + 'Remove keymap'; export const CHECK_MACRO = KeymapActions.PREFIX + 'Check deleted macro'; + export const LOAD_KEYMAPS = KeymapActions.PREFIX + 'Load keymaps'; + export const LOAD_KEYMAPS_SUCCESS = KeymapActions.PREFIX + 'Load keymaps success'; + + export function loadKeymaps(): Action { + return { + type: KeymapActions.LOAD_KEYMAPS + }; + } + + export function loadKeymapsSuccess(keymaps: Keymap[]): Action { + return { + type: KeymapActions.LOAD_KEYMAPS_SUCCESS, + payload: keymaps + }; + } export function addKeymap(item: Keymap): Action { return { diff --git a/shared/src/store/actions/user-config.ts b/shared/src/store/actions/user-config.ts new file mode 100644 index 00000000..c44ef726 --- /dev/null +++ b/shared/src/store/actions/user-config.ts @@ -0,0 +1,28 @@ +import { Action } from '@ngrx/store'; + +import { type } from '../../util'; +import { UserConfiguration } from '../../config-serializer/config-items/UserConfiguration'; + +const PREFIX = '[user-config] '; + +// tslint:disable-next-line:variable-name +export const ActionTypes = { + LOAD_USER_CONFIG: type(PREFIX + 'Load User Config'), + LOAD_USER_CONFIG_SUCCESS: type(PREFIX + 'Load User Config Success') +}; + +export class LoadUserConfigAction implements Action { + type = ActionTypes.LOAD_USER_CONFIG; +} + +export class LoadUserConfigSuccessAction implements Action { + type = ActionTypes.LOAD_USER_CONFIG_SUCCESS; + + constructor(public payload: UserConfiguration) { + + } +} + +export type Actions + = LoadUserConfigAction + | LoadUserConfigSuccessAction; diff --git a/shared/src/store/effects/index.ts b/shared/src/store/effects/index.ts index 0da8f3ab..f1b3e30c 100644 --- a/shared/src/store/effects/index.ts +++ b/shared/src/store/effects/index.ts @@ -1,2 +1,3 @@ export * from './keymap'; export * from './macro'; +export * from './user-config'; diff --git a/shared/src/store/effects/keymap.ts b/shared/src/store/effects/keymap.ts index 0775759a..ba8c430c 100644 --- a/shared/src/store/effects/keymap.ts +++ b/shared/src/store/effects/keymap.ts @@ -2,11 +2,15 @@ import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; import { Actions, Effect } from '@ngrx/effects'; -import { Store } from '@ngrx/store'; +import { Action, Store } from '@ngrx/store'; +import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/do'; import 'rxjs/add/operator/map'; +import 'rxjs/add/operator/startWith'; +import 'rxjs/add/operator/switchMap'; import 'rxjs/add/operator/withLatestFrom'; +import 'rxjs/add/observable/of'; import { KeymapActions } from '../actions'; import { AppState } from '../index'; @@ -16,6 +20,17 @@ import { Keymap } from '../../config-serializer/config-items/Keymap'; @Injectable() export class KeymapEffects { + @Effect() loadKeymaps$: Observable = this.actions$ + .ofType(KeymapActions.LOAD_KEYMAPS) + .startWith(KeymapActions.loadKeymaps()) + .switchMap(() => { + const presetsRequireContext = (require).context('../../../res/presets', false, /.json$/); + const uhkPresets = presetsRequireContext.keys().map(presetsRequireContext) // load the presets into an array + .map((keymap: any) => new Keymap().fromJsonObject(keymap)); + + return Observable.of(KeymapActions.loadKeymapsSuccess(uhkPresets)); + }); + @Effect({ dispatch: false }) addOrDuplicate$: any = this.actions$ .ofType(KeymapActions.ADD, KeymapActions.DUPLICATE) .withLatestFrom(this.store) diff --git a/shared/src/store/effects/user-config.ts b/shared/src/store/effects/user-config.ts new file mode 100644 index 00000000..67183e10 --- /dev/null +++ b/shared/src/store/effects/user-config.ts @@ -0,0 +1,32 @@ +import { Injectable, Inject } from '@angular/core'; +import { Effect, Actions } from '@ngrx/effects'; +import { Observable } from 'rxjs/Observable'; +import { Action } from '@ngrx/store'; + +import 'rxjs/add/operator/switchMap'; +import 'rxjs/add/operator/startWith'; +import 'rxjs/add/observable/of'; + +import { ActionTypes, LoadUserConfigAction, LoadUserConfigSuccessAction } from '../actions/user-config'; +import { UserConfiguration } from '../../config-serializer/config-items/UserConfiguration'; +import { DataStorageRepositoryService, DATA_STORAGE_REPOSITORY } from '../../services/datastorage-repository.service'; +import { DefaultUserConfigurationService } from '../../services/default-user-configuration.service'; + +@Injectable() +export class UserConfigEffects { + + @Effect() loadUserConfig$: Observable = this.actions$ + .ofType(ActionTypes.LOAD_USER_CONFIG) + .startWith(new LoadUserConfigAction()) + .switchMap(() => { + let config: UserConfiguration = this.dataStorageRepository.getConfig(); + if (!config) { + config = this.defaultUserConfigurationService.getDefault(); + } + return Observable.of(new LoadUserConfigSuccessAction(config)); + }); + + constructor(private actions$: Actions, + @Inject(DATA_STORAGE_REPOSITORY)private dataStorageRepository: DataStorageRepositoryService, + private defaultUserConfigurationService: DefaultUserConfigurationService) { } +} diff --git a/shared/src/store/reducers/index.ts b/shared/src/store/reducers/index.ts index ace5974d..d4be10de 100644 --- a/shared/src/store/reducers/index.ts +++ b/shared/src/store/reducers/index.ts @@ -1,4 +1,11 @@ -import userConfigurationReducer, { getUserConfiguration } from './user-configuration'; +import { routerReducer } from '@ngrx/router-store'; + +import userConfigurationReducer from './user-configuration'; import presetReducer from './preset'; -export { userConfigurationReducer, presetReducer, getUserConfiguration }; +// All reducers that are used in application +export const reducer = { + userConfiguration: userConfigurationReducer, + presetKeymaps: presetReducer, + router: routerReducer +}; diff --git a/shared/src/store/reducers/preset.ts b/shared/src/store/reducers/preset.ts index 22bcd999..f19044f7 100644 --- a/shared/src/store/reducers/preset.ts +++ b/shared/src/store/reducers/preset.ts @@ -1,7 +1,17 @@ +import { Action } from '@ngrx/store'; + import { Keymap } from '../../config-serializer/config-items/Keymap'; +import { KeymapActions } from '../actions/keymap'; const initialState: Keymap[] = []; -export default function(state = initialState): Keymap[] { - return state; +export default function(state = initialState, action: Action): Keymap[] { + switch (action.type) { + case KeymapActions.LOAD_KEYMAPS_SUCCESS: { + return Object.assign(state, action.payload); + } + + default: + return state; + } } diff --git a/shared/src/store/reducers/user-configuration.ts b/shared/src/store/reducers/user-configuration.ts index 437231b1..e6aee691 100644 --- a/shared/src/store/reducers/user-configuration.ts +++ b/shared/src/store/reducers/user-configuration.ts @@ -13,6 +13,7 @@ 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'; +import { ActionTypes } from '../actions/user-config'; const initialState: UserConfiguration = new UserConfiguration(); @@ -23,6 +24,10 @@ export default function (state = initialState, action: Action): UserConfiguratio const changedUserConfiguration: UserConfiguration = Object.assign(new UserConfiguration(), state); switch (action.type) { + case ActionTypes.LOAD_USER_CONFIG_SUCCESS: { + return Object.assign(changedUserConfiguration, action.payload); + } + case KeymapActions.ADD: case KeymapActions.DUPLICATE: { diff --git a/shared/src/store/storage/index.ts b/shared/src/store/storage/index.ts deleted file mode 100644 index 653b2cf8..00000000 --- a/shared/src/store/storage/index.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { Injectable } from '@angular/core'; - -import { Action } from '@ngrx/store'; - -import { Keymap } from '../../config-serializer/config-items/Keymap'; -import { UserConfiguration } from '../../config-serializer/config-items/UserConfiguration'; - -import { AppState } from '../index'; -import { Electron } from './electron'; -import { Local } from './local'; - -@Injectable() -export class DataStorage { - - private _environment: Local | Electron; - private defaultUserConfiguration: UserConfiguration; - private uhkPresets: Keymap[]; - - constructor() { - this.initUHKJson(); - this.detectEnvironment(); - } - - initialState(): AppState { - const config: UserConfiguration = this.getConfiguration(); - return { - userConfiguration: config, - presetKeymaps: this.uhkPresets - }; - } - - detectEnvironment(): void { - // Electron - // TODO check if we can remove when electron will be implemented (maybe use process.versions['electron']) - if (typeof window !== 'undefined' && (window).process && (window).process.type === 'renderer') { - this._environment = new Electron(); - } - // Local storage - else { - this._environment = new Local(this.defaultUserConfiguration.dataModelVersion); - } - } - - // TODO: Add type for state - saveState(reducer: any): (state: any, action: Action) => AppState { - return (state: any, action: Action) => { - const nextState = reducer(state, action); - this._environment.saveConfig(nextState); - return nextState; - }; - } - - initUHKJson() { - this.defaultUserConfiguration = new UserConfiguration() - .fromJsonObject(require('json-loader!../../config-serializer/user-config.json')); - - const presetsRequireContext = (require).context('../../../res/presets', false, /.json$/); - this.uhkPresets = presetsRequireContext.keys().map(presetsRequireContext) // load the presets into an array - .map((keymap: any) => new Keymap().fromJsonObject(keymap)); - } - - getConfiguration(): UserConfiguration { - let config: UserConfiguration = this._environment.getConfig(); - if (!config) { - config = this.defaultUserConfiguration; - } - return config; - } -} diff --git a/shared/src/store/storage/local.ts b/shared/src/store/storage/local.ts deleted file mode 100644 index 1e9873c9..00000000 --- a/shared/src/store/storage/local.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { UserConfiguration } from '../../config-serializer/config-items/UserConfiguration'; - -export class Local { - - constructor(private dataModelVersion: number) { } - - getConfig(): UserConfiguration { - const configJsonString = localStorage.getItem('config'); - let config: UserConfiguration; - - if (configJsonString) { - const configJsonObject = JSON.parse(configJsonString); - if (configJsonObject.dataModelVersion === this.dataModelVersion) { - config = new UserConfiguration().fromJsonObject(configJsonObject); - } - } - - return config; - } - - saveConfig(config: UserConfiguration): void { - localStorage.setItem('config', JSON.stringify(config.toJsonObject())); - } -} diff --git a/shared/src/util/index.ts b/shared/src/util/index.ts index 6172fb21..4496def2 100644 --- a/shared/src/util/index.ts +++ b/shared/src/util/index.ts @@ -8,3 +8,24 @@ export function camelCaseToSentence(camelCasedText: string): string { export function capitalizeFirstLetter(text: string): string { return text.charAt(0).toUpperCase() + text.slice(1); } + +/** + * This function coerces a string into a string literal type. + * Using tagged union types in TypeScript 2.0, this enables + * powerful typechecking of our reducers. + * + * Since every action label passes through this function it + * is a good place to ensure all of our action labels + * are unique. + */ + +const typeCache: { [label: string]: boolean } = {}; +export function type(label: T | ''): T { + if (typeCache[label]) { + throw new Error(`Action type "${label}" is not unique"`); + } + + typeCache[label] = true; + + return label; +} diff --git a/web/src/app.module.ts b/web/src/app.module.ts index 95da9dc5..3b7c2d07 100644 --- a/web/src/app.module.ts +++ b/web/src/app.module.ts @@ -1,4 +1,4 @@ -import { NgModule, ReflectiveInjector } from '@angular/core'; +import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; @@ -7,6 +7,7 @@ import { EffectsModule } from '@ngrx/effects'; import { StoreModule } from '@ngrx/store'; import { StoreDevtoolsModule } from '@ngrx/store-devtools'; import { StoreLogMonitorModule, useLogMonitor } from '@ngrx/store-log-monitor'; +import { RouterStoreModule } from '@ngrx/router-store'; import { DragulaModule } from 'ng2-dragula/ng2-dragula'; import { Select2Module } from 'ng2-select2/ng2-select2'; @@ -66,23 +67,14 @@ import { CancelableDirective } from './shared/directives'; import { CaptureService } from './shared/services/capture.service'; import { MapperService } from './shared/services/mapper.service'; -import { KeymapEffects, MacroEffects } from './shared/store/effects'; -import { userConfigurationReducer, presetReducer } from './shared/store/reducers'; -import { DataStorage } from './shared/store/storage'; +import { KeymapEffects, MacroEffects, UserConfigEffects } from './shared/store/effects'; import { KeymapEditGuard } from './shared/components/keymap/edit'; import { MacroNotFoundGuard } from './shared/components/macro/not-found'; - -// 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 = { - userConfiguration: storageService.saveState(userConfigurationReducer), - presetKeymaps: presetReducer -}; +import { DATA_STORAGE_REPOSITORY } from './shared/services/datastorage-repository.service'; +import { LocalDataStorageRepositoryService } from './shared/services/local-datastorage-repository.service'; +import { DefaultUserConfigurationService } from './shared/services/default-user-configuration.service'; +import { reducer } from '../../shared/src/store/reducers/index'; @NgModule({ declarations: [ @@ -140,7 +132,8 @@ const storeConfig = { FormsModule, DragulaModule, routing, - StoreModule.provideStore(storeConfig, storageService.initialState()), + StoreModule.provideStore(reducer), + RouterStoreModule.connectRouter(), StoreDevtoolsModule.instrumentStore({ monitor: useLogMonitor({ visible: false, @@ -150,14 +143,17 @@ const storeConfig = { StoreLogMonitorModule, Select2Module, EffectsModule.runAfterBootstrap(KeymapEffects), - EffectsModule.runAfterBootstrap(MacroEffects) + EffectsModule.runAfterBootstrap(MacroEffects), + EffectsModule.runAfterBootstrap(UserConfigEffects) ], providers: [ MapperService, appRoutingProviders, KeymapEditGuard, MacroNotFoundGuard, - CaptureService + CaptureService, + {provide: DATA_STORAGE_REPOSITORY, useClass: LocalDataStorageRepositoryService}, + DefaultUserConfigurationService ], bootstrap: [MainAppComponent] })