refactore: create feature modules (#387)

* add @angular/cli to the project

* increase nodejs version -> 8.2.1

* add lerna

* merge web and shared module

* move electron module into packages as uhk-agent

Electron agent functionality is not working

* delete symlinker

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

* revert uhk-message.component

* fix component path

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

* building web and electron module

* delete uhk-renderer package

* handle device connect disconnect state

* add privilege detection

* fix set privilege functionality

* turn back download keymap functionality

* add bootstrap, select2 js and fix null pointer exception

* turn back upload data to keyboard

* fix send keymap

* fix test-serializer

* add missing package.json

* merging

* fix appveyor build

* fix linting

* turn back electron storage service

* commit the missing electron-datastorage-repository

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

* set exact version number in appveyor build

* vertical align privilege and missing device components

* set back node version to 8 in appveyor

* move node-usb dependency from usb dir to root

maybe it is fix the appveyor build

* revert usb to root

* fix electron builder script

* fix electron builder script

* turn off electron devtools

* remove CTRL+U functionality

* fix CTRL+o

* fix lint error

* turnoff store freeze

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

* move files from root usb dir -> packages/usb
This commit is contained in:
Róbert Kiss
2017-08-19 20:02:17 +02:00
committed by László Monda
parent 97770f67c0
commit 0f558e4132
524 changed files with 25606 additions and 5036 deletions
@@ -0,0 +1,48 @@
import { Action } from '@ngrx/store';
import { type } from 'uhk-common';
const PREFIX = '[app-update] ';
// tslint:disable-next-line:variable-name
export const ActionTypes = {
UPDATE_AVAILABLE: type(PREFIX + 'update available'),
UPDATE_APP: type(PREFIX + 'update app'),
DO_NOT_UPDATE_APP: type(PREFIX + 'do not update app'),
UPDATE_DOWNLOADED: type(PREFIX + 'update downloaded'),
UPDATING: type(PREFIX + 'updating'),
UPDATE_ERROR: type(PREFIX + 'error')
};
export class UpdateAvailableAction implements Action {
type = ActionTypes.UPDATE_AVAILABLE;
}
export class UpdateAppAction implements Action {
type = ActionTypes.UPDATE_APP;
}
export class DoNotUpdateAppAction implements Action {
type = ActionTypes.DO_NOT_UPDATE_APP;
}
export class UpdateDownloadedAction implements Action {
type = ActionTypes.UPDATE_DOWNLOADED;
}
export class UpdatingAction implements Action {
type = ActionTypes.UPDATING;
}
export class UpdateErrorAction implements Action {
type = ActionTypes.UPDATE_ERROR;
constructor(public payload: any) {}
}
export type Actions
= UpdateAvailableAction
| UpdateAppAction
| DoNotUpdateAppAction
| UpdateDownloadedAction
| UpdatingAction
| UpdateErrorAction;
@@ -0,0 +1,69 @@
import { Action } from '@ngrx/store';
import { type } from 'uhk-common';
import { Notification, CommandLineArgs } from 'uhk-common';
import { AppStartInfo } from '../../../../../uhk-common/src/models/app-start-info';
const PREFIX = '[app] ';
// tslint:disable-next-line:variable-name
export const ActionTypes = {
APP_BOOTSRAPPED: type(PREFIX + 'bootstrapped'),
APP_STARTED: type(PREFIX + 'started'),
APP_SHOW_NOTIFICATION: type(PREFIX + 'show notification'),
APP_TOGGLE_ADDON_MENU: type(PREFIX + 'toggle add-on menu'),
APP_PROCESS_START_INFO: type(PREFIX + 'process start info'),
UNDO_LAST: type(PREFIX + 'undo last action'),
UNDO_LAST_SUCCESS: type(PREFIX + 'undo last action success'),
DISMISS_UNDO_NOTIFICATION: type(PREFIX + 'dismiss notification action')
};
export class AppBootsrappedAction implements Action {
type = ActionTypes.APP_BOOTSRAPPED;
}
export class AppStartedAction implements Action {
type = ActionTypes.APP_STARTED;
}
export class ShowNotificationAction implements Action {
type = ActionTypes.APP_SHOW_NOTIFICATION;
constructor(public payload: Notification) { }
}
export class ToggleAddonMenuAction implements Action {
type = ActionTypes.APP_TOGGLE_ADDON_MENU;
constructor(public payload: boolean) { }
}
export class ProcessAppStartInfoAction implements Action {
type = ActionTypes.APP_PROCESS_START_INFO;
constructor(public payload: AppStartInfo) { }
}
export class UndoLastAction implements Action {
type = ActionTypes.UNDO_LAST;
constructor(public payload: any) {}
}
export class UndoLastSuccessAction implements Action {
type = ActionTypes.UNDO_LAST_SUCCESS;
}
export class DismissUndoNotificationAction implements Action {
type = ActionTypes.DISMISS_UNDO_NOTIFICATION;
}
export type Actions
= AppStartedAction
| AppBootsrappedAction
| ShowNotificationAction
| ToggleAddonMenuAction
| ProcessAppStartInfoAction
| UndoLastAction
| UndoLastSuccessAction
| DismissUndoNotificationAction;
@@ -0,0 +1,74 @@
import { Action } from '@ngrx/store';
import { type } from 'uhk-common';
import { AutoUpdateSettings } from '../../models/auto-update-settings';
const PREFIX = '[app-update-config] ';
// tslint:disable-next-line:variable-name
export const ActionTypes = {
TOGGLE_CHECK_FOR_UPDATE_ON_STARTUP: type(PREFIX + 'Check for update on startup'),
CHECK_FOR_UPDATE_NOW: type(PREFIX + 'Check for update now'),
CHECK_FOR_UPDATE_SUCCESS: type(PREFIX + 'Check for update success'),
CHECK_FOR_UPDATE_FAILED: type(PREFIX + 'Check for update faild'),
TOGGLE_PRE_RELEASE_FLAG: type(PREFIX + 'Toggle pre release update flag'),
LOAD_AUTO_UPDATE_SETTINGS: type(PREFIX + 'Load auto update settings'),
LOAD_AUTO_UPDATE_SETTINGS_SUCCESS: type(PREFIX + 'Load auto update settings success'),
SAVE_AUTO_UPDATE_SETTINGS_SUCCESS: type(PREFIX + 'Save auto update settings success')
};
export class ToggleCheckForUpdateOnStartupAction implements Action {
type = ActionTypes.TOGGLE_CHECK_FOR_UPDATE_ON_STARTUP;
constructor(public payload: boolean) {
}
}
export class CheckForUpdateNowAction implements Action {
type = ActionTypes.CHECK_FOR_UPDATE_NOW;
}
export class CheckForUpdateSuccessAction implements Action {
type = ActionTypes.CHECK_FOR_UPDATE_SUCCESS;
constructor(public payload?: string) {
}
}
export class CheckForUpdateFailedAction implements Action {
type = ActionTypes.CHECK_FOR_UPDATE_FAILED;
constructor(public payload: any) {
}
}
export class TogglePreReleaseFlagAction implements Action {
type = ActionTypes.TOGGLE_PRE_RELEASE_FLAG;
constructor(public payload: boolean) {
}
}
export class LoadAutoUpdateSettingsAction implements Action {
type = ActionTypes.LOAD_AUTO_UPDATE_SETTINGS_SUCCESS;
}
export class LoadAutoUpdateSettingsSuccessAction implements Action {
type = ActionTypes.LOAD_AUTO_UPDATE_SETTINGS_SUCCESS;
constructor(public payload: AutoUpdateSettings) {
}
}
export class SaveAutoUpdateSettingsSuccessAction implements Action {
type = ActionTypes.SAVE_AUTO_UPDATE_SETTINGS_SUCCESS;
}
export type Actions
= ToggleCheckForUpdateOnStartupAction
| CheckForUpdateNowAction
| CheckForUpdateSuccessAction
| CheckForUpdateFailedAction
| TogglePreReleaseFlagAction
| LoadAutoUpdateSettingsAction
| LoadAutoUpdateSettingsSuccessAction
| SaveAutoUpdateSettingsSuccessAction;
@@ -0,0 +1,52 @@
import { Action } from '@ngrx/store';
import { type, IpcResponse } from 'uhk-common';
const PREFIX = '[device] ';
// tslint:disable-next-line:variable-name
export const ActionTypes = {
SET_PRIVILEGE_ON_LINUX: type(PREFIX + 'set privilege on linux'),
SET_PRIVILEGE_ON_LINUX_REPLY: type(PREFIX + 'set privilege on linux reply'),
CONNECTION_STATE_CHANGED: type(PREFIX + 'connection state changed'),
PERMISSION_STATE_CHANGED: type(PREFIX + 'permission state changed'),
SAVE_CONFIGURATION: type(PREFIX + 'save configuration'),
SAVE_CONFIGURATION_REPLY: type(PREFIX + 'save configuration reply')
};
export class SetPrivilegeOnLinuxAction implements Action {
type = ActionTypes.SET_PRIVILEGE_ON_LINUX;
}
export class SetPrivilegeOnLinuxReplyAction implements Action {
type = ActionTypes.SET_PRIVILEGE_ON_LINUX_REPLY;
constructor(public payload: IpcResponse) {}
}
export class ConnectionStateChangedAction implements Action {
type = ActionTypes.CONNECTION_STATE_CHANGED;
constructor(public payload: boolean) {}
}
export class PermissionStateChangedAction implements Action {
type = ActionTypes.PERMISSION_STATE_CHANGED;
constructor(public payload: boolean) {}
}
export class SaveConfigurationAction implements Action {
type = ActionTypes.SAVE_CONFIGURATION;
constructor(public payload: Buffer) {}
}
export class SaveConfigurationReplyAction implements Action {
type = ActionTypes.SAVE_CONFIGURATION_REPLY;
constructor(public payload: IpcResponse) {}
}
export type Actions
= SetPrivilegeOnLinuxAction
| ConnectionStateChangedAction;
@@ -0,0 +1,2 @@
export * from './keymap';
export * from './macro';
@@ -0,0 +1,102 @@
import { Action } from '@ngrx/store';
import { KeyAction } from '../../config-serializer/config-items/key-action';
import { Keymap } from '../../config-serializer/config-items/keymap';
import { Macro } from '../../config-serializer/config-items/macro';
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 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 const UNDO_LAST_ACTION = KeymapActions.PREFIX + 'Undo last action';
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 {
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(name: string, abbr: string, newAbbr: string): Action {
return {
type: KeymapActions.EDIT_ABBR,
payload: {
name,
abbr,
newAbbr
}
};
}
export function saveKey(keymap: Keymap, layer: number, module: number, key: number, keyAction: KeyAction): Action {
return {
type: KeymapActions.SAVE_KEY,
payload: {
keymap,
layer,
module,
key,
keyAction
}
};
}
export function checkMacro(macro: Macro): Action {
return {
type: KeymapActions.CHECK_MACRO,
payload: macro
};
}
}
@@ -0,0 +1,91 @@
import { Action } from '@ngrx/store';
import { Macro } from '../../config-serializer/config-items/macro';
import { MacroAction } from '../../config-serializer/config-items/macro-action';
export namespace MacroActions {
export const PREFIX = '[Macro] ';
export const DUPLICATE = MacroActions.PREFIX + 'Duplicate macro';
export const EDIT_NAME = MacroActions.PREFIX + 'Edit macro title';
export const REMOVE = MacroActions.PREFIX + 'Remove macro';
export const ADD = MacroActions.PREFIX + 'Add macro';
export const ADD_ACTION = MacroActions.PREFIX + 'Add macro action';
export const SAVE_ACTION = MacroActions.PREFIX + 'Save macro action';
export const DELETE_ACTION = MacroActions.PREFIX + 'Delete macro action';
export const REORDER_ACTION = MacroActions.PREFIX + 'Reorder macro action';
export function addMacro(): Action {
return {
type: MacroActions.ADD
};
}
export function removeMacro(macroId: number): Action {
return {
type: MacroActions.REMOVE,
payload: macroId
};
}
export function duplicateMacro(macro: Macro): Action {
return {
type: MacroActions.DUPLICATE,
payload: macro
};
}
export function editMacroName(id: number, name: string): Action {
return {
type: MacroActions.EDIT_NAME,
payload: {
id: id,
name: name
}
};
}
export function addMacroAction(id: number, action: MacroAction): Action {
return {
type: MacroActions.ADD_ACTION,
payload: {
id: id,
action: action
}
};
}
export function saveMacroAction(id: number, index: number, action: MacroAction): Action {
return {
type: MacroActions.SAVE_ACTION,
payload: {
id: id,
index: index,
action: action
}
};
}
export function deleteMacroAction(id: number, index: number, action: MacroAction): Action {
return {
type: MacroActions.DELETE_ACTION,
payload: {
id: id,
index: index,
action: action
}
};
}
export function reorderMacroAction(id: number, oldIndex: number, newIndex: number): Action {
return {
type: MacroActions.REORDER_ACTION,
payload: {
id: id,
oldIndex: oldIndex,
newIndex: newIndex
}
};
}
}
@@ -0,0 +1,34 @@
import { Action } from '@ngrx/store';
import { type } from 'uhk-common';
import { UserConfiguration } from '../../config-serializer/config-items/user-configuration';
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'),
SAVE_USER_CONFIG_SUCCESS: type(PREFIX + 'Save 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 class SaveUserConfigSuccessAction implements Action {
type = ActionTypes.SAVE_USER_CONFIG_SUCCESS;
constructor(public payload: UserConfiguration) { }
}
export type Actions
= LoadUserConfigAction
| LoadUserConfigSuccessAction
| SaveUserConfigSuccessAction;
@@ -0,0 +1,47 @@
import { Injectable } from '@angular/core';
import { Action } from '@ngrx/store';
import { Actions, Effect, toPayload } from '@ngrx/effects';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/first';
import { NotificationType } from 'uhk-common';
import { ActionTypes } from '../actions/app-update.action';
import { ActionTypes as AutoUpdateActionTypes } from '../actions/auto-update-settings';
import { ShowNotificationAction } from '../actions/app';
import { AppUpdateRendererService } from '../../services/app-update-renderer.service';
@Injectable()
export class AppUpdateEffect {
@Effect({ dispatch: false })
appStart$: Observable<Action> = this.actions$
.ofType(ActionTypes.UPDATE_APP)
.first()
.do(() => {
this.appUpdateRendererService.sendUpdateAndRestartApp();
});
@Effect({ dispatch: false }) checkForUpdate$: Observable<Action> = this.actions$
.ofType(AutoUpdateActionTypes.CHECK_FOR_UPDATE_NOW)
.do(() => {
this.appUpdateRendererService.checkForUpdate();
});
@Effect() handleError$: Observable<Action> = this.actions$
.ofType(ActionTypes.UPDATE_ERROR)
.map(toPayload)
.map((message: string) => {
return new ShowNotificationAction({
type: NotificationType.Error,
message
});
});
constructor(private actions$: Actions,
private appUpdateRendererService: AppUpdateRendererService) {
}
}
@@ -0,0 +1,67 @@
import { Injectable } from '@angular/core';
import { Action } from '@ngrx/store';
import { Actions, Effect, toPayload } from '@ngrx/effects';
import { Observable } from 'rxjs/Observable';
import { NotifierService } from 'angular-notifier';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/catch';
import { AppStartInfo, Notification, NotificationType, LogService } from 'uhk-common';
import { ActionTypes, AppStartedAction, DismissUndoNotificationAction, ToggleAddonMenuAction } from '../actions/app';
import { AppRendererService } from '../../services/app-renderer.service';
import { AppUpdateRendererService } from '../../services/app-update-renderer.service';
import { ConnectionStateChangedAction, PermissionStateChangedAction } from '../actions/device';
@Injectable()
export class ApplicationEffects {
@Effect()
appStart$: Observable<Action> = this.actions$
.ofType(ActionTypes.APP_BOOTSRAPPED)
.startWith(new AppStartedAction())
.do(() => {
this.logService.info('Renderer appStart effect start');
this.appUpdateRendererService.sendAppStarted();
this.appRendererService.getAppStartInfo();
this.logService.info('Renderer appStart effect end');
});
@Effect({ dispatch: false })
showNotification$: Observable<Action> = this.actions$
.ofType(ActionTypes.APP_SHOW_NOTIFICATION)
.map(toPayload)
.do((notification: Notification) => {
if (notification.type === NotificationType.Undoable) {
return;
}
this.notifierService.notify(notification.type, notification.message);
});
@Effect()
processStartInfo$: Observable<Action> = this.actions$
.ofType(ActionTypes.APP_PROCESS_START_INFO)
.map(toPayload)
.mergeMap((appInfo: AppStartInfo) => {
this.logService.debug('[AppEffect][processStartInfo] payload:', appInfo);
return [
new ToggleAddonMenuAction(appInfo.commandLineArgs.addons),
new ConnectionStateChangedAction(appInfo.deviceConnected),
new PermissionStateChangedAction(appInfo.hasPermission)
];
});
@Effect() undoLastNotification$: Observable<Action> = this.actions$
.ofType(ActionTypes.UNDO_LAST)
.map(toPayload)
.mergeMap((action: Action) => [action, new DismissUndoNotificationAction()]);
constructor(private actions$: Actions,
private notifierService: NotifierService,
private appUpdateRendererService: AppUpdateRendererService,
private appRendererService: AppRendererService,
private logService: LogService) { }
}
@@ -0,0 +1,61 @@
import { Injectable } from '@angular/core';
import { Actions, Effect, toPayload } from '@ngrx/effects';
import { Observable } from 'rxjs/Observable';
import { Action, Store } from '@ngrx/store';
import 'rxjs/add/operator/startWith';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/withLatestFrom';
import 'rxjs/add/operator/map';
import { NotificationType } from 'uhk-common';
import {
ActionTypes,
LoadAutoUpdateSettingsAction,
LoadAutoUpdateSettingsSuccessAction,
SaveAutoUpdateSettingsSuccessAction
} from '../actions/auto-update-settings';
import { DataStorageRepositoryService } from '../../services/datastorage-repository.service';
import { AppState, getAutoUpdateSettings } from '../index';
import { initialState } from '../reducers/auto-update-settings';
import { AutoUpdateSettings } from '../../models/auto-update-settings';
import { ShowNotificationAction } from '../actions/app';
@Injectable()
export class AutoUpdateSettingsEffects {
@Effect() loadUserConfig$: Observable<Action> = this.actions$
.ofType(ActionTypes.LOAD_AUTO_UPDATE_SETTINGS)
.startWith(new LoadAutoUpdateSettingsAction())
.switchMap(() => {
let settings: AutoUpdateSettings = this.dataStorageRepository.getAutoUpdateSettings();
if (!settings) {
settings = initialState;
}
return Observable.of(new LoadAutoUpdateSettingsSuccessAction(settings));
});
@Effect() saveAutoUpdateConfig$: Observable<Action> = this.actions$
.ofType(ActionTypes.TOGGLE_CHECK_FOR_UPDATE_ON_STARTUP, ActionTypes.TOGGLE_PRE_RELEASE_FLAG)
.withLatestFrom(this.store.select(getAutoUpdateSettings))
.map(([action, config]) => {
this.dataStorageRepository.saveAutoUpdateSettings(config);
return new SaveAutoUpdateSettingsSuccessAction();
});
@Effect() sendNotification$: Observable<Action> = this.actions$
.ofType(ActionTypes.CHECK_FOR_UPDATE_FAILED, ActionTypes.CHECK_FOR_UPDATE_SUCCESS)
.map(toPayload)
.map((message: string) => {
return new ShowNotificationAction({
type: NotificationType.Info,
message
});
});
constructor(private actions$: Actions,
private dataStorageRepository: DataStorageRepositoryService,
private store: Store<AppState>) {
}
}
@@ -0,0 +1,101 @@
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { Action } from '@ngrx/store';
import { Actions, Effect, toPayload } from '@ngrx/effects';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';
import { NotificationType, IpcResponse } from 'uhk-common';
import { ActionTypes, ConnectionStateChangedAction, PermissionStateChangedAction } from '../actions/device';
import { DeviceRendererService } from '../../services/device-renderer.service';
import { ShowNotificationAction } from '../actions/app';
@Injectable()
export class DeviceEffects {
@Effect({ dispatch: false })
deviceConnectionStateChange$: Observable<Action> = this.actions$
.ofType(ActionTypes.CONNECTION_STATE_CHANGED)
.map(toPayload)
.do((connected: boolean) => {
if (connected) {
this.router.navigate(['/']);
}
else {
this.router.navigate(['/detection']);
}
});
@Effect({ dispatch: false })
permissionStateChange$: Observable<Action> = this.actions$
.ofType(ActionTypes.PERMISSION_STATE_CHANGED)
.map(toPayload)
.do((hasPermission: boolean) => {
if (hasPermission) {
this.router.navigate(['/detection']);
}
else {
this.router.navigate(['/privilege']);
}
});
@Effect({ dispatch: false })
setPrivilegeOnLinux$: Observable<Action> = this.actions$
.ofType(ActionTypes.SET_PRIVILEGE_ON_LINUX)
.do(() => {
this.deviceRendererService.setPrivilegeOnLinux();
});
@Effect()
setPrivilegeOnLinuxReply$: Observable<Action> = this.actions$
.ofType(ActionTypes.SET_PRIVILEGE_ON_LINUX_REPLY)
.map(toPayload)
.mergeMap((response: any) => {
if (response.success) {
return [
new ConnectionStateChangedAction(true),
new PermissionStateChangedAction(true)
];
}
return [
<any>new ShowNotificationAction({
type: NotificationType.Error,
message: response.error.message
})
];
});
@Effect({ dispatch: false })
saveConfiguration$: Observable<Action> = this.actions$
.ofType(ActionTypes.SAVE_CONFIGURATION)
.map(toPayload)
.do((buffer: Buffer) => {
this.deviceRendererService.saveUserConfiguration(buffer);
});
@Effect()
saveConfigurationReply$: Observable<Action> = this.actions$
.ofType(ActionTypes.SAVE_CONFIGURATION_REPLY)
.map(toPayload)
.map((response: IpcResponse) => {
if (response.success) {
return new ShowNotificationAction({
type: NotificationType.Success,
message: 'Save configuration successful.'
});
}
return new ShowNotificationAction({
type: NotificationType.Error,
message: response.error.message
});
});
constructor(private actions$: Actions,
private router: Router,
private deviceRendererService: DeviceRendererService) {
}
}
@@ -0,0 +1,5 @@
export * from './keymap';
export * from './macro';
export * from './user-config';
export * from './auto-update-settings';
export * from './app';
@@ -0,0 +1,68 @@
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { Actions, Effect } from '@ngrx/effects';
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';
import { Keymap } from '../../config-serializer/config-items/keymap';
@Injectable()
export class KeymapEffects {
@Effect() loadKeymaps$: Observable<Action> = this.actions$
.ofType(KeymapActions.LOAD_KEYMAPS)
.startWith(KeymapActions.loadKeymaps())
.switchMap(() => {
const presetsRequireContext = (<any>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)
.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)
.map(latest => latest[1].userConfiguration.keymaps)
.do(keymaps => {
if (keymaps.length === 0) {
this.router.navigate(['/keymap/add']);
} else {
const favourite: Keymap = keymaps.find(keymap => keymap.isDefault);
this.router.navigate(['/keymap', favourite.abbreviation]);
}
});
@Effect({ dispatch: false }) editAbbr$: any = this.actions$
.ofType(KeymapActions.EDIT_ABBR)
.withLatestFrom(this.store)
.do(([action, store]) => {
for (const keymap of store.userConfiguration.keymaps) {
if (keymap.name === action.payload.name && keymap.abbreviation === action.payload.newAbbr) {
this.router.navigate(['/keymap', action.payload.newAbbr]);
return;
}
}
});
constructor(private actions$: Actions, private router: Router, private store: Store<AppState>) { }
}
@@ -0,0 +1,49 @@
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { Actions, Effect } from '@ngrx/effects';
import { Store } from '@ngrx/store';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/withLatestFrom';
import { KeymapActions, MacroActions } from '../actions';
import { AppState } from '../index';
@Injectable()
export class MacroEffects {
@Effect({ dispatch: false }) remove$: any = this.actions$
.ofType(MacroActions.REMOVE)
.map(action => this.store.dispatch(KeymapActions.checkMacro(action.payload)))
.withLatestFrom(this.store)
.map(([action, state]) => state.userConfiguration.macros)
.do(macros => {
if (macros.length === 0) {
this.router.navigate(['/macro']);
} else {
this.router.navigate(['/macro', macros[0].id]);
}
});
@Effect({ dispatch: false }) add$: any = this.actions$
.ofType(MacroActions.ADD)
.withLatestFrom(this.store)
.map(([action, state]) => state.userConfiguration.macros)
.map(macros => macros[macros.length - 1])
.do(lastMacro => {
this.router.navigate(['/macro', lastMacro.id, 'new']);
});
@Effect({ dispatch: false }) duplicate: any = this.actions$
.ofType(MacroActions.DUPLICATE)
.withLatestFrom(this.store)
.map(([action, state]) => state.userConfiguration.macros)
.map(macros => macros[macros.length - 1])
.do(lastMacro => {
this.router.navigate(['/macro', lastMacro.id]);
});
constructor(private actions$: Actions, private router: Router, private store: Store<AppState>) {}
}
@@ -0,0 +1,106 @@
import { Injectable } from '@angular/core';
import { go } from '@ngrx/router-store';
import { Actions, Effect, toPayload } from '@ngrx/effects';
import { Observable } from 'rxjs/Observable';
import { Action, Store } from '@ngrx/store';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/startWith';
import 'rxjs/add/operator/withLatestFrom';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/observable/of';
import { NotificationType } from 'uhk-common';
import {
ActionTypes,
LoadUserConfigAction,
LoadUserConfigSuccessAction,
SaveUserConfigSuccessAction
} from '../actions/user-config';
import { UserConfiguration } from '../../config-serializer/config-items/user-configuration';
import { DataStorageRepositoryService } from '../../services/datastorage-repository.service';
import { DefaultUserConfigurationService } from '../../services/default-user-configuration.service';
import { AppState, getPrevUserConfiguration, getUserConfiguration } from '../index';
import { KeymapActions } from '../actions/keymap';
import { MacroActions } from '../actions/macro';
import { UndoUserConfigData } from '../../models/undo-user-config-data';
import { ShowNotificationAction, DismissUndoNotificationAction } from '../actions/app';
@Injectable()
export class UserConfigEffects {
@Effect() loadUserConfig$: Observable<Action> = this.actions$
.ofType(ActionTypes.LOAD_USER_CONFIG)
.startWith(new LoadUserConfigAction())
.switchMap(() => Observable.of(new LoadUserConfigSuccessAction(this.getUserConfiguration())));
@Effect() saveUserConfig$: Observable<Action> = this.actions$
.ofType(
KeymapActions.ADD, KeymapActions.DUPLICATE, KeymapActions.EDIT_NAME, KeymapActions.EDIT_ABBR,
KeymapActions.SET_DEFAULT, KeymapActions.REMOVE, KeymapActions.SAVE_KEY,
MacroActions.ADD, MacroActions.DUPLICATE, MacroActions.EDIT_NAME, MacroActions.REMOVE, MacroActions.ADD_ACTION,
MacroActions.SAVE_ACTION, MacroActions.DELETE_ACTION, MacroActions.REORDER_ACTION)
.withLatestFrom(this.store.select(getUserConfiguration), this.store.select(getPrevUserConfiguration))
.mergeMap(([action, config, prevUserConfiguration]) => {
this.dataStorageRepository.saveConfig(config);
if (action.type === KeymapActions.REMOVE || action.type === MacroActions.REMOVE) {
const text = action.type === KeymapActions.REMOVE ? 'Keymap' : 'Macro';
const pathPrefix = action.type === KeymapActions.REMOVE ? 'keymap' : 'macro';
const payload: UndoUserConfigData = {
path: `/${pathPrefix}/${action.payload}`,
config: prevUserConfiguration.toJsonObject()
};
return [
new SaveUserConfigSuccessAction(config),
new ShowNotificationAction({
type: NotificationType.Undoable,
message: `${text} has been deleted`,
extra: {
payload,
type: KeymapActions.UNDO_LAST_ACTION
}
})
];
}
return [new SaveUserConfigSuccessAction(config), new DismissUndoNotificationAction()];
});
@Effect() undoUserConfig$: Observable<Action> = this.actions$
.ofType(KeymapActions.UNDO_LAST_ACTION)
.map(toPayload)
.mergeMap((payload: UndoUserConfigData) => {
const config = new UserConfiguration().fromJsonObject(payload.config);
this.dataStorageRepository.saveConfig(config);
return [new LoadUserConfigSuccessAction(config), go(payload.path)];
});
constructor(private actions$: Actions,
private dataStorageRepository: DataStorageRepositoryService,
private store: Store<AppState>,
private defaultUserConfigurationService: DefaultUserConfigurationService) {
}
private getUserConfiguration() {
const configJsonObject = this.dataStorageRepository.getConfig();
let config: UserConfiguration;
if (configJsonObject) {
if (configJsonObject.dataModelVersion === this.defaultUserConfigurationService.getDefault().dataModelVersion) {
config = new UserConfiguration().fromJsonObject(configJsonObject);
}
}
if (!config) {
config = this.defaultUserConfigurationService.getDefault();
}
return config;
}
}
+71
View File
@@ -0,0 +1,71 @@
import { createSelector } from 'reselect';
import { compose } from '@ngrx/core/compose';
import { ActionReducer, combineReducers } from '@ngrx/store';
import { RouterState, routerReducer } from '@ngrx/router-store';
import { storeFreeze } from 'ngrx-store-freeze';
import userConfigurationReducer from './reducers/user-configuration';
import presetReducer from './reducers/preset';
import { Keymap } from '../config-serializer/config-items/keymap';
import { UserConfiguration } from '../config-serializer/config-items/user-configuration';
import * as fromAppUpdate from './reducers/app-update.reducer';
import * as autoUpdateSettings from './reducers/auto-update-settings';
import * as fromApp from './reducers/app.reducer';
import * as fromDevice from './reducers/device';
export const reducers = {
userConfiguration: userConfigurationReducer,
presetKeymaps: presetReducer,
router: routerReducer,
autoUpdateSettings: autoUpdateSettings.reducer,
app: fromApp.reducer,
appUpdate: fromAppUpdate.reducer,
device: fromDevice.reducer
};
// State interface for the application
export interface AppState {
userConfiguration: UserConfiguration;
presetKeymaps: Keymap[];
autoUpdateSettings: autoUpdateSettings.State;
app: fromApp.State;
router: RouterState;
appUpdate: fromAppUpdate.State;
device: fromDevice.State;
}
const developmentReducer: ActionReducer<AppState> = compose(storeFreeze, combineReducers)(reducers);
const productionReducer: ActionReducer<AppState> = combineReducers(reducers);
export function reducer(state: any, action: any) {
// if (isDev) {
// return developmentReducer(state, action);
// } else {
return productionReducer(state, action);
// }
}
export const getUserConfiguration = (state: AppState) => state.userConfiguration;
export const appState = (state: AppState) => state.app;
export const showAddonMenu = createSelector(appState, fromApp.showAddonMenu);
export const getUndoableNotification = createSelector(appState, fromApp.getUndoableNotification);
export const getPrevUserConfiguration = createSelector(appState, fromApp.getPrevUserConfiguration);
export const runningInElectron = createSelector(appState, fromApp.runningInElectron);
export const appUpdateState = (state: AppState) => state.appUpdate;
export const getShowAppUpdateAvailable = createSelector(appUpdateState, fromAppUpdate.getShowAppUpdateAvailable);
export const appUpdateSettingsState = (state: AppState) => state.autoUpdateSettings;
export const getAutoUpdateSettings = createSelector(appUpdateSettingsState, autoUpdateSettings.getUpdateSettings);
export const getCheckingForUpdate = createSelector(appUpdateSettingsState, autoUpdateSettings.checkingForUpdate);
export const deviceState = (state: AppState) => state.device;
export const isDeviceConnected = createSelector(deviceState, fromDevice.isDeviceConnected);
export const deviceConnected = createSelector(runningInElectron, isDeviceConnected, (electron, connected) => {
return !electron ? true : connected;
});
export const devicePermission = createSelector(deviceState, fromDevice.hasDevicePermission);
export const hasDevicePermission = createSelector(runningInElectron, devicePermission, (electron, permission) => {
return !electron ? true : permission;
});
@@ -0,0 +1,39 @@
import { Actions, ActionTypes } from '../actions/app-update.action';
export interface State {
updateAvailable: boolean;
updateDownloaded: boolean;
doNotUpdateApp: boolean;
}
const initialState: State = {
updateAvailable: false,
updateDownloaded: false,
doNotUpdateApp: false
};
export function reducer(state = initialState, action: Actions) {
switch (action.type) {
case ActionTypes.UPDATE_AVAILABLE: {
const newState = Object.assign({}, state);
newState.updateAvailable = true;
return newState;
}
case ActionTypes.UPDATE_DOWNLOADED: {
const newState = Object.assign({}, state);
newState.updateDownloaded = true;
return newState;
}
case ActionTypes.DO_NOT_UPDATE_APP: {
const newState = Object.assign({}, state);
newState.doNotUpdateApp = true;
return newState;
}
default:
return state;
}
}
export const getShowAppUpdateAvailable = (state: State) => state.updateDownloaded && !state.doNotUpdateApp;
@@ -0,0 +1,91 @@
import { routerActions } from '@ngrx/router-store';
import { Action } from '@ngrx/store';
import { runInElectron, Notification, NotificationType } from 'uhk-common';
import { ActionTypes, ShowNotificationAction } from '../actions/app';
import { ActionTypes as UserConfigActionTypes } from '../actions/user-config';
import { UserConfiguration } from '../../config-serializer/config-items/user-configuration';
export interface State {
started: boolean;
showAddonMenu: boolean;
undoableNotification?: Notification;
navigationCountAfterNotification: number;
prevUserConfig?: UserConfiguration;
runningInElectron: boolean;
}
const initialState: State = {
started: false,
showAddonMenu: false,
navigationCountAfterNotification: 0,
runningInElectron: runInElectron()
};
export function reducer(state = initialState, action: Action) {
switch (action.type) {
case ActionTypes.APP_STARTED: {
return {
...state,
started: true
};
}
case ActionTypes.APP_TOGGLE_ADDON_MENU: {
return {
...state,
showAddonMenu: action.payload
};
}
case ActionTypes.APP_SHOW_NOTIFICATION: {
const currentAction = <ShowNotificationAction>action;
if (currentAction.payload.type !== NotificationType.Undoable) {
return state;
}
return {
...state,
undoableNotification: currentAction.payload,
navigationCountAfterNotification: 0
};
}
// Required to dismiss the undoNotification dialog, when user navigate in the app.
// When deleted a keymap or macro the app automaticaly navigate to other keymap, or macro, so
// so we have to count the navigations and when reach the 2nd then remove the dialog.
case routerActions.UPDATE_LOCATION: {
const newState = { ...state };
newState.navigationCountAfterNotification++;
if (newState.navigationCountAfterNotification > 1) {
newState.undoableNotification = null;
}
return newState;
}
case ActionTypes.UNDO_LAST_SUCCESS:
case ActionTypes.DISMISS_UNDO_NOTIFICATION: {
return {
...state,
undoableNotification: null
};
}
case UserConfigActionTypes.LOAD_USER_CONFIG_SUCCESS:
case UserConfigActionTypes.SAVE_USER_CONFIG_SUCCESS: {
return {
...state,
prevUserConfig: action.payload
};
}
default:
return state;
}
}
export const showAddonMenu = (state: State) => state.showAddonMenu;
export const getUndoableNotification = (state: State) => state.undoableNotification;
export const getPrevUserConfiguration = (state: State) => state.prevUserConfig;
export const runningInElectron = (state: State) => state.runningInElectron;
@@ -0,0 +1,50 @@
import { Action } from '@ngrx/store';
import { ActionTypes } from '../actions/auto-update-settings';
import { ActionTypes as UpdateActions } from '../actions/app-update.action';
import { AutoUpdateSettings } from '../../models/auto-update-settings';
export interface State extends AutoUpdateSettings {
checkingForUpdate: boolean;
}
export const initialState: State = {
checkForUpdateOnStartUp: false,
usePreReleaseUpdate: false,
checkingForUpdate: false
};
export function reducer(state = initialState, action: Action): State {
switch (action.type) {
case ActionTypes.TOGGLE_CHECK_FOR_UPDATE_ON_STARTUP: {
return Object.assign({}, state, { checkForUpdateOnStartUp: action.payload });
}
case ActionTypes.TOGGLE_PRE_RELEASE_FLAG: {
return Object.assign({}, state, { usePreReleaseUpdate: action.payload });
}
case ActionTypes.LOAD_AUTO_UPDATE_SETTINGS_SUCCESS: {
return Object.assign({}, action.payload);
}
case ActionTypes.CHECK_FOR_UPDATE_NOW: {
return Object.assign({}, state, { checkingForUpdate: true});
}
case UpdateActions.UPDATE_ERROR:
case ActionTypes.CHECK_FOR_UPDATE_SUCCESS:
case ActionTypes.CHECK_FOR_UPDATE_FAILED: {
return Object.assign({}, state, { checkingForUpdate: false });
}
default:
return state;
}
}
export const getUpdateSettings = (state: State) => ({
checkForUpdateOnStartUp: state.checkForUpdateOnStartUp,
usePreReleaseUpdate: state.usePreReleaseUpdate
});
export const checkingForUpdate = (state: State) => state.checkingForUpdate;
@@ -0,0 +1,35 @@
import { Action } from '@ngrx/store';
import { ActionTypes } from '../actions/device';
export interface State {
connected: boolean;
hasPermission: boolean;
}
const initialState: State = {
connected: true,
hasPermission: true
};
export function reducer(state = initialState, action: Action) {
switch (action.type) {
case ActionTypes.CONNECTION_STATE_CHANGED:
return {
...state,
connected: action.payload
};
case ActionTypes.PERMISSION_STATE_CHANGED:
return {
...state,
hasPermission: action.payload
};
default:
return state;
}
}
export const isDeviceConnected = (state: State) => state.connected;
export const hasDevicePermission = (state: State) => state.hasPermission;
@@ -0,0 +1,12 @@
import { routerReducer } from '@ngrx/router-store';
import userConfigurationReducer from './user-configuration';
import presetReducer from './preset';
import { reducer as autoUpdateReducer } from './auto-update-settings';
import { reducer as appReducer } from './app.reducer';
import * as fromAppUpdate from './app-update.reducer';
import * as fromDevice from './device';
export { userConfigurationReducer, presetReducer, autoUpdateReducer, appReducer };
// All reducers that are used in application
@@ -0,0 +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, action: Action): Keymap[] {
switch (action.type) {
case KeymapActions.LOAD_KEYMAPS_SUCCESS: {
return action.payload;
}
default:
return state;
}
}
@@ -0,0 +1,364 @@
import '@ngrx/core/add/operator/select';
import { Action } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/map';
import { Helper as KeyActionHelper } 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/user-configuration';
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();
export default function (state = initialState, action: Action): UserConfiguration {
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: {
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 = action.payload.name.toUpperCase();
const duplicate = state.keymaps.some((keymap: Keymap) => {
return keymap.name === name && keymap.abbreviation !== action.payload.abbr;
});
changedUserConfiguration.keymaps = state.keymaps.map((keymap: Keymap) => {
keymap = Object.assign(new Keymap(), keymap);
if (!duplicate && keymap.abbreviation === action.payload.abbr) {
keymap.name = name;
}
return keymap;
});
break;
}
case KeymapActions.EDIT_ABBR: {
const abbr: string = action.payload.newAbbr.toUpperCase();
const duplicate = state.keymaps.some((keymap: Keymap) => {
return keymap.name !== action.payload.name && keymap.abbreviation === abbr;
});
changedUserConfiguration.keymaps = state.keymaps.map((keymap: Keymap) => {
keymap = Object.assign(new Keymap(), keymap);
if (!duplicate && keymap.abbreviation === action.payload.abbr) {
keymap.abbreviation = abbr;
} else {
keymap = keymap.renameKeymap(action.payload.abbr, action.payload.newAbbr);
}
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;
const 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] = Object.assign(new Keymap(), 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 = newLayer.modules.slice();
newLayer.modules[moduleIndex] = newModule;
const keyIndex: number = action.payload.key;
newModule.keyActions = newModule.keyActions.slice();
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 = action.payload.name;
const duplicate = state.macros.some((macro: Macro) => {
return macro.id !== action.payload.id && macro.name === name;
});
changedUserConfiguration.macros = state.macros.map((macro: Macro) => {
macro = Object.assign(new Macro(), macro);
if (!duplicate && 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<AppState>) => Observable<UserConfiguration> {
return (state$: Observable<AppState>) => state$
.select(state => state.userConfiguration);
}
export function getKeymaps(): (state$: Observable<AppState>) => Observable<Keymap[]> {
return (state$: Observable<AppState>) => state$
.select(state => state.userConfiguration.keymaps);
}
export function getKeymap(abbr: string) {
if (abbr === undefined) {
return getDefaultKeymap();
}
return (state$: Observable<AppState>) => getKeymaps()(state$)
.map((keymaps: Keymap[]) =>
keymaps.find((keymap: Keymap) => keymap.abbreviation === abbr)
);
}
export function getDefaultKeymap() {
return (state$: Observable<AppState>) => getKeymaps()(state$)
.map((keymaps: Keymap[]) =>
keymaps.find((keymap: Keymap) => keymap.isDefault)
);
}
export function getMacros(): (state$: Observable<AppState>) => Observable<Macro[]> {
return (state$: Observable<AppState>) => state$
.select(state => state.userConfiguration.macros);
}
export function getMacro(id: number) {
if (isNaN(id)) {
return () => Observable.of<Macro>(undefined);
} else {
return (state$: Observable<AppState>) => 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 = 1;
const regexp = / \(\d+\)$/g;
const matchName = name.replace(regexp, '');
items.forEach(item => {
if (item.name.replace(regexp, '') === matchName) {
suffix++;
}
});
return `${matchName} (${suffix})`;
}
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): Layer[] {
const keyActionsToClear: {
layerIdx: number,
moduleIdx: number,
keyActionIdx: number
}[] = [];
for (let layerIdx = 0; layerIdx < layers.length; ++layerIdx) {
const modules = layers[layerIdx].modules;
for (let moduleIdx = 0; moduleIdx < modules.length; ++moduleIdx) {
const keyActions = modules[moduleIdx].keyActions;
for (let keyActionIdx = 0; keyActionIdx < keyActions.length; ++keyActionIdx) {
const action = keyActions[keyActionIdx];
if (action && action.hasOwnProperty(property) && action[property] === value) {
keyActionsToClear.push({
layerIdx,
moduleIdx,
keyActionIdx
});
}
}
}
}
if (keyActionsToClear.length === 0) {
return layers;
}
const newLayers = [...layers];
for (const path of keyActionsToClear) {
if (newLayers[path.layerIdx] === layers[path.layerIdx]) {
newLayers[path.layerIdx] = Object.assign(new Layer(), newLayers[path.layerIdx]);
newLayers[path.layerIdx].modules = [...newLayers[path.layerIdx].modules];
}
const newModules = newLayers[path.layerIdx].modules;
if (newModules[path.moduleIdx] === layers[path.layerIdx].modules[path.moduleIdx]) {
newModules[path.moduleIdx] = Object.assign(new Module(), newModules[path.moduleIdx]);
newModules[path.moduleIdx].keyActions = [...newModules[path.moduleIdx].keyActions];
}
newModules[path.moduleIdx].keyActions[path.keyActionIdx] = undefined;
}
return newLayers;
}