feat(notification): display app errors, close #319 (#333)

* refactor(log): Refactor logging service

Removed the InjectionToken and changed LogService as default logger.
Finally ElectronLogService implements LogService directly.

* refactor: Optimize imports

* fix(app-update): Add missing rxjs imports

* style: Remove extra line

* refactor(store): Move app.actions.ts to shared module

* feat(notification): Add notification panel

Add angular-notifier to the app and created the ShowNotificationAction
to manage notifications

* style(notification): Fix tslint suggestion

* fix(notification): Add missing rxjs imports
This commit is contained in:
Róbert Kiss
2017-07-05 13:41:31 +02:00
committed by László Monda
parent 6bc2bc8331
commit c9a1e9853c
21 changed files with 181 additions and 41 deletions

View File

@@ -2,6 +2,7 @@ import { ErrorHandler, NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NotifierModule } from 'angular-notifier';
import { EffectsModule } from '@ngrx/effects'; import { EffectsModule } from '@ngrx/effects';
import { StoreModule } from '@ngrx/store'; import { StoreModule } from '@ngrx/store';
@@ -79,7 +80,13 @@ import { UhkLibUsbApiService } from './services/uhk-lib-usb-api.service';
import { UhkHidApiService } from './services/uhk-hid-api.service'; import { UhkHidApiService } from './services/uhk-hid-api.service';
import { uhkDeviceProvider } from './services/uhk-device-provider'; import { uhkDeviceProvider } from './services/uhk-device-provider';
import { AutoUpdateSettingsEffects, KeymapEffects, MacroEffects, UserConfigEffects } from './shared/store/effects'; import {
ApplicationEffects,
AutoUpdateSettingsEffects,
KeymapEffects,
MacroEffects,
UserConfigEffects
} from './shared/store/effects';
import { ApplicationEffect, AppUpdateEffect } from './store/effects'; import { ApplicationEffect, AppUpdateEffect } from './store/effects';
import { KeymapEditGuard } from './shared/components/keymap/edit'; import { KeymapEditGuard } from './shared/components/keymap/edit';
@@ -93,11 +100,12 @@ import { DATA_STORAGE_REPOSITORY } from './shared/services/datastorage-repositor
import { ElectronDataStorageRepositoryService } from './services/electron-datastorage-repository.service'; import { ElectronDataStorageRepositoryService } from './services/electron-datastorage-repository.service';
import { DefaultUserConfigurationService } from './shared/services/default-user-configuration.service'; import { DefaultUserConfigurationService } from './shared/services/default-user-configuration.service';
import { ElectronLogService } from './services/electron-log.service'; import { ElectronLogService } from './services/electron-log.service';
import { LOG_SERVICE } from '../../shared/src/services/logger.service'; import { LogService } from './shared/services/logger.service';
import { ElectronErrorHandlerService } from './services/electron-error-handler.service'; import { ElectronErrorHandlerService } from './services/electron-error-handler.service';
import { AppUpdateRendererService } from './services/app-update-renderer.service'; import { AppUpdateRendererService } from './services/app-update-renderer.service';
import { reducer } from './store'; import { reducer } from './store';
import { AutoUpdateSettings } from './shared/components/auto-update-settings/auto-update-settings'; import { AutoUpdateSettings } from './shared/components/auto-update-settings/auto-update-settings';
import { angularNotifierConfig } from '../../shared/src/models/angular-notifier-config';
@NgModule({ @NgModule({
declarations: [ declarations: [
@@ -172,12 +180,14 @@ import { AutoUpdateSettings } from './shared/components/auto-update-settings/aut
}), }),
StoreLogMonitorModule, StoreLogMonitorModule,
Select2Module, Select2Module,
NotifierModule.withConfig(angularNotifierConfig),
EffectsModule.runAfterBootstrap(KeymapEffects), EffectsModule.runAfterBootstrap(KeymapEffects),
EffectsModule.runAfterBootstrap(MacroEffects), EffectsModule.runAfterBootstrap(MacroEffects),
EffectsModule.runAfterBootstrap(UserConfigEffects), EffectsModule.runAfterBootstrap(UserConfigEffects),
EffectsModule.runAfterBootstrap(AutoUpdateSettingsEffects), EffectsModule.runAfterBootstrap(AutoUpdateSettingsEffects),
EffectsModule.run(ApplicationEffect), EffectsModule.run(ApplicationEffect),
EffectsModule.run(AppUpdateEffect) EffectsModule.run(AppUpdateEffect),
EffectsModule.run(ApplicationEffects)
], ],
providers: [ providers: [
UhkDeviceConnectedGuard, UhkDeviceConnectedGuard,
@@ -192,7 +202,7 @@ import { AutoUpdateSettings } from './shared/components/auto-update-settings/aut
CaptureService, CaptureService,
{ provide: DATA_STORAGE_REPOSITORY, useClass: ElectronDataStorageRepositoryService }, { provide: DATA_STORAGE_REPOSITORY, useClass: ElectronDataStorageRepositoryService },
DefaultUserConfigurationService, DefaultUserConfigurationService,
{ provide: LOG_SERVICE, useClass: ElectronLogService }, { provide: LogService, useClass: ElectronLogService },
{ provide: ErrorHandler, useClass: ElectronErrorHandlerService }, { provide: ErrorHandler, useClass: ElectronErrorHandlerService },
AppUpdateRendererService, AppUpdateRendererService,
UhkHidApiService, UhkHidApiService,

View File

@@ -5,3 +5,4 @@
</app-update-available> </app-update-available>
<router-outlet></router-outlet> <router-outlet></router-outlet>
<notifier-container></notifier-container>

View File

@@ -15,7 +15,7 @@ import * as path from 'path';
import * as sudo from 'sudo-prompt'; import * as sudo from 'sudo-prompt';
import { UhkDeviceService } from '../../services/uhk-device.service'; import { UhkDeviceService } from '../../services/uhk-device.service';
import { ILogService, LOG_SERVICE } from '../../../../shared/src/services/logger.service'; import { LogService } from '../../shared/services/logger.service';
@Component({ @Component({
selector: 'privilege-checker', selector: 'privilege-checker',
@@ -28,7 +28,7 @@ export class PrivilegeCheckerComponent {
constructor(private router: Router, constructor(private router: Router,
private uhkDevice: UhkDeviceService, private uhkDevice: UhkDeviceService,
@Inject(LOG_SERVICE) private logService: ILogService) { private logService: LogService) {
if (isDev) { if (isDev) {
this.rootDir = path.resolve(path.join(remote.process.cwd(), remote.process.argv[1]), '..'); this.rootDir = path.resolve(path.join(remote.process.cwd(), remote.process.argv[1]), '..');
} else { } else {

View File

@@ -1,8 +1,9 @@
import { ErrorHandler, Inject } from '@angular/core'; import { ErrorHandler, Injectable } from '@angular/core';
import { ILogService, LOG_SERVICE } from '../../../shared/src/services/logger.service'; import { LogService} from '../shared/services/logger.service';
@Injectable()
export class ElectronErrorHandlerService implements ErrorHandler { export class ElectronErrorHandlerService implements ErrorHandler {
constructor(@Inject(LOG_SERVICE)private logService: ILogService) {} constructor(private logService: LogService) {}
handleError(error: any) { handleError(error: any) {
this.logService.error(error); this.logService.error(error);

View File

@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
import * as log from 'electron-log'; import * as log from 'electron-log';
import * as util from 'util'; import * as util from 'util';
import { ILogService } from '../../../shared/src/services/logger.service'; import { LogService } from '../shared/services/logger.service';
/** /**
* This service use the electron-log package to write log in file. * This service use the electron-log package to write log in file.
@@ -14,7 +14,7 @@ import { ILogService } from '../../../shared/src/services/logger.service';
* The app name: UHK Agent. The up to date value in the scripts/release.js file. * The app name: UHK Agent. The up to date value in the scripts/release.js file.
*/ */
@Injectable() @Injectable()
export class ElectronLogService implements ILogService { export class ElectronLogService implements LogService {
private static getErrorText(args: any) { private static getErrorText(args: any) {
return util.inspect(args); return util.inspect(args);
} }

View File

@@ -6,7 +6,7 @@ import { Subscriber } from 'rxjs/Subscriber';
import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Subscription } from 'rxjs/Subscription'; import { Subscription } from 'rxjs/Subscription';
import { ILogService, LOG_SERVICE } from '../shared/services/logger.service'; import { LogService } from '../shared/services/logger.service';
import { SenderMessage } from '../models/sender-message'; import { SenderMessage } from '../models/sender-message';
import { Constants } from '../shared/util/constants'; import { Constants } from '../shared/util/constants';
@@ -24,7 +24,7 @@ export abstract class UhkDeviceService {
protected messageIn$: Observable<Buffer>; protected messageIn$: Observable<Buffer>;
protected messageOut$: Subject<SenderMessage>; protected messageOut$: Subject<SenderMessage>;
constructor(@Inject(LOG_SERVICE) protected logService: ILogService) { constructor(protected logService: LogService) {
this.messageOut$ = new Subject<SenderMessage>(); this.messageOut$ = new Subject<SenderMessage>();
this.initialized$ = new BehaviorSubject(false); this.initialized$ = new BehaviorSubject(false);
this.connected$ = new BehaviorSubject(false); this.connected$ = new BehaviorSubject(false);

View File

@@ -14,7 +14,7 @@ import 'rxjs/add/operator/concatMap';
import 'rxjs/add/operator/publish'; import 'rxjs/add/operator/publish';
import 'rxjs/add/operator/do'; import 'rxjs/add/operator/do';
import { ILogService, LOG_SERVICE } from '../shared/services/logger.service'; import { LogService } from '../shared/services/logger.service';
import { Constants } from '../shared/util'; import { Constants } from '../shared/util';
import { UhkDeviceService } from './uhk-device.service'; import { UhkDeviceService } from './uhk-device.service';
@@ -24,7 +24,7 @@ export class UhkHidApiService extends UhkDeviceService implements OnDestroy {
private pollTimer$: Subscription; private pollTimer$: Subscription;
constructor(@Inject(LOG_SERVICE) protected logService: ILogService) { constructor(protected logService: LogService) {
super(logService); super(logService);
this.pollUhkDevice(); this.pollUhkDevice();

View File

@@ -14,7 +14,7 @@ import 'rxjs/add/operator/do';
import { Device, findByIds, InEndpoint, Interface, on, OutEndpoint } from 'usb'; import { Device, findByIds, InEndpoint, Interface, on, OutEndpoint } from 'usb';
import { ILogService, LOG_SERVICE } from '../shared/services/logger.service'; import { LogService} from '../shared/services/logger.service';
import { Constants } from '../shared/util'; import { Constants } from '../shared/util';
import { UhkDeviceService } from './uhk-device.service'; import { UhkDeviceService } from './uhk-device.service';
@@ -28,7 +28,7 @@ export class UhkLibUsbApiService extends UhkDeviceService implements OnDestroy {
} }
constructor(zone: NgZone, constructor(zone: NgZone,
@Inject(LOG_SERVICE) protected logService: ILogService) { protected logService: LogService) {
super(logService); super(logService);
this.initialize(); this.initialize();

View File

@@ -4,7 +4,7 @@ import { Effect, Actions } from '@ngrx/effects';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/do'; import 'rxjs/add/operator/do';
import * as app from '../actions/app.action'; import * as app from '../../shared/store/actions/app.action';
import { AppUpdateRendererService } from '../../services/app-update-renderer.service'; import { AppUpdateRendererService } from '../../services/app-update-renderer.service';
@Injectable() @Injectable()

View File

@@ -1,4 +1,4 @@
import { Actions, ActionTypes } from '../actions/app.action'; import { Actions, ActionTypes } from '../../shared/store/actions/app.action';
export interface State { export interface State {
started: boolean; started: boolean;

View File

@@ -62,6 +62,7 @@
"@ngrx/effects": "2.0.3", "@ngrx/effects": "2.0.3",
"@ngrx/router-store": "^1.2.6", "@ngrx/router-store": "^1.2.6",
"@ngrx/store": "2.2.2", "@ngrx/store": "2.2.2",
"angular-notifier": "^2.0.0",
"bootstrap": "^3.3.7", "bootstrap": "^3.3.7",
"browser-stdout": "^1.3.0", "browser-stdout": "^1.3.0",
"buffer": "^5.0.6", "buffer": "^5.0.6",

View File

@@ -1,3 +1,5 @@
@import '~angular-notifier/styles.scss';
main-app { main-app {
display: block; display: block;
overflow: hidden; overflow: hidden;

View File

@@ -0,0 +1,44 @@
import { NotifierOptions } from 'angular-notifier';
export const angularNotifierConfig: NotifierOptions = {
position: {
horizontal: {
/**
* Defines the horizontal position on the screen
* @type {'left' | 'middle' | 'right'}
*/
position: 'right',
/**
* Defines the horizontal distance to the screen edge (in px)
* @type {number}
*/
distance: 12
},
vertical: {
/**
* Defines the vertical position on the screen
* @type {'top' | 'bottom'}
*/
position: 'top',
/**
* Defines the vertical distance to the screen edge (in px)
* @type {number}
*/
distance: 12,
/**
* Defines the vertical gap, existing between multiple notifications (in px)
* @type {number}
*/
gap: 10
}
}
};

View File

@@ -0,0 +1,14 @@
export enum NotificationType {
Default,
Success,
Error,
Warning,
Info
}
export interface Notification {
type: NotificationType;
title?: string;
message: string;
extra?: any;
}

View File

@@ -1,15 +1,7 @@
import {Injectable, InjectionToken} from '@angular/core'; import {Injectable} from '@angular/core';
export interface ILogService {
error(...args: any[]): void;
info(...args: any[]): void;
}
export let LOG_SERVICE = new InjectionToken('logger-service');
@Injectable() @Injectable()
export class ConsoleLogService implements ILogService { export class LogService {
error(...args: any[]): void { error(...args: any[]): void {
console.error(args); console.error(args);
} }

View File

@@ -1,12 +1,15 @@
import { Action } from '@ngrx/store'; import { Action } from '@ngrx/store';
import { type } from '../../shared/util/';
import { type } from '../../util';
import {Notification} from '../../models/notification';
const PREFIX = '[app] '; const PREFIX = '[app] ';
// tslint:disable-next-line:variable-name // tslint:disable-next-line:variable-name
export const ActionTypes = { export const ActionTypes = {
APP_BOOTSRAPPED: type(PREFIX + 'bootstrapped'), APP_BOOTSRAPPED: type(PREFIX + 'bootstrapped'),
APP_STARTED: type(PREFIX + 'started') APP_STARTED: type(PREFIX + 'started'),
APP_SHOW_NOTIFICATION: type(PREFIX + 'show notification')
}; };
export class AppBootsrappedAction implements Action { export class AppBootsrappedAction implements Action {
@@ -17,6 +20,13 @@ export class AppStartedAction implements Action {
type = ActionTypes.APP_STARTED; type = ActionTypes.APP_STARTED;
} }
export class ShowNotificationAction implements Action {
type = ActionTypes.APP_SHOW_NOTIFICATION;
constructor(public payload: Notification) {}
}
export type Actions export type Actions
= AppStartedAction = AppStartedAction
| AppBootsrappedAction; | AppBootsrappedAction
| ShowNotificationAction;

View File

@@ -0,0 +1,48 @@
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/operator/do';
import 'rxjs/add/operator/map';
import { ActionTypes } from '../actions/app.action';
import { Notification, NotificationType } from '../../models/notification';
@Injectable()
export class ApplicationEffects {
@Effect({ dispatch: false })
appStart$: Observable<Action> = this.actions$
.ofType(ActionTypes.APP_SHOW_NOTIFICATION)
.map(toPayload)
.do((notification: Notification) => {
const type = ApplicationEffects.mapNotificationType(notification.type);
this.notifierService.notify(type, notification.message);
});
// TODO: Change typescript -> 2.4 and use string enum.
// Corrently ngrx store is not compatible witn typescript 2.4
private static mapNotificationType(type: NotificationType): string {
switch (type) {
case NotificationType.Success:
return 'success';
case NotificationType.Error:
return 'error';
case NotificationType.Info:
return 'info';
case NotificationType.Warning:
return 'warning';
default:
return 'default';
}
}
constructor(private actions$: Actions,
private notifierService: NotifierService) { }
}

View File

@@ -3,6 +3,11 @@ import { Actions, Effect } from '@ngrx/effects';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { Action, Store } from '@ngrx/store'; 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 { import {
ActionTypes, ActionTypes,
LoadAutoUpdateSettingsAction, LoadAutoUpdateSettingsAction,
@@ -11,7 +16,7 @@ import {
} from '../actions/auto-update-settings'; } from '../actions/auto-update-settings';
import { DATA_STORAGE_REPOSITORY, DataStorageRepositoryService } from '../../services/datastorage-repository.service'; import { DATA_STORAGE_REPOSITORY, DataStorageRepositoryService } from '../../services/datastorage-repository.service';
import { AppState, getAutoUpdateSettings } from '../index'; import { AppState, getAutoUpdateSettings } from '../index';
import { initialState, State } from '../reducers/auto-update-settings'; import { initialState } from '../reducers/auto-update-settings';
import { AutoUpdateSettings } from '../../models/auto-update-settings'; import { AutoUpdateSettings } from '../../models/auto-update-settings';
@Injectable() @Injectable()

View File

@@ -2,3 +2,4 @@ export * from './keymap';
export * from './macro'; export * from './macro';
export * from './user-config'; export * from './user-config';
export * from './auto-update-settings'; export * from './auto-update-settings';
export * from './app';

View File

@@ -2,6 +2,7 @@ import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NotifierModule } from 'angular-notifier';
import { EffectsModule } from '@ngrx/effects'; import { EffectsModule } from '@ngrx/effects';
import { StoreModule } from '@ngrx/store'; import { StoreModule } from '@ngrx/store';
@@ -69,16 +70,23 @@ import { CaptureService } from './shared/services/capture.service';
import { MapperService } from './shared/services/mapper.service'; import { MapperService } from './shared/services/mapper.service';
import { SvgModuleProviderService } from './shared/services/svg-module-provider.service'; import { SvgModuleProviderService } from './shared/services/svg-module-provider.service';
import { AutoUpdateSettingsEffects, KeymapEffects, MacroEffects, UserConfigEffects } from './shared/store/effects'; import {
ApplicationEffects,
AutoUpdateSettingsEffects,
KeymapEffects,
MacroEffects,
UserConfigEffects
} from './shared/store/effects';
import { KeymapEditGuard } from './shared/components/keymap/edit'; import { KeymapEditGuard } from './shared/components/keymap/edit';
import { MacroNotFoundGuard } from './shared/components/macro/not-found'; import { MacroNotFoundGuard } from './shared/components/macro/not-found';
import { DATA_STORAGE_REPOSITORY } from './shared/services/datastorage-repository.service'; import { DATA_STORAGE_REPOSITORY } from './shared/services/datastorage-repository.service';
import { LocalDataStorageRepositoryService } from './shared/services/local-datastorage-repository.service'; import { LocalDataStorageRepositoryService } from './shared/services/local-datastorage-repository.service';
import { DefaultUserConfigurationService } from './shared/services/default-user-configuration.service'; import { DefaultUserConfigurationService } from './shared/services/default-user-configuration.service';
import { reducer } from '../../shared/src/store/reducers/index'; import { reducer } from './shared/store/reducers/index';
import { ConsoleLogService, LOG_SERVICE } from '../../shared/src/services/logger.service'; import { LogService } from './shared/services/logger.service';
import { AutoUpdateSettings } from './shared/components/auto-update-settings/auto-update-settings'; import { AutoUpdateSettings } from './shared/components/auto-update-settings/auto-update-settings';
import { angularNotifierConfig } from '../../shared/src/models/angular-notifier-config';
@NgModule({ @NgModule({
declarations: [ declarations: [
@@ -148,10 +156,12 @@ import { AutoUpdateSettings } from './shared/components/auto-update-settings/aut
}), }),
StoreLogMonitorModule, StoreLogMonitorModule,
Select2Module, Select2Module,
NotifierModule.withConfig(angularNotifierConfig),
EffectsModule.runAfterBootstrap(KeymapEffects), EffectsModule.runAfterBootstrap(KeymapEffects),
EffectsModule.runAfterBootstrap(MacroEffects), EffectsModule.runAfterBootstrap(MacroEffects),
EffectsModule.runAfterBootstrap(UserConfigEffects), EffectsModule.runAfterBootstrap(UserConfigEffects),
EffectsModule.runAfterBootstrap(AutoUpdateSettingsEffects) EffectsModule.runAfterBootstrap(AutoUpdateSettingsEffects),
EffectsModule.runAfterBootstrap(ApplicationEffects)
], ],
providers: [ providers: [
SvgModuleProviderService, SvgModuleProviderService,
@@ -160,11 +170,12 @@ import { AutoUpdateSettings } from './shared/components/auto-update-settings/aut
KeymapEditGuard, KeymapEditGuard,
MacroNotFoundGuard, MacroNotFoundGuard,
CaptureService, CaptureService,
{provide: DATA_STORAGE_REPOSITORY, useClass: LocalDataStorageRepositoryService}, { provide: DATA_STORAGE_REPOSITORY, useClass: LocalDataStorageRepositoryService },
DefaultUserConfigurationService, DefaultUserConfigurationService,
{ provide: LOG_SERVICE, useClass: ConsoleLogService }, LogService,
DefaultUserConfigurationService DefaultUserConfigurationService
], ],
bootstrap: [MainAppComponent] bootstrap: [MainAppComponent]
}) })
export class AppModule { } export class AppModule {
}

View File

@@ -1,4 +1,3 @@
<notification></notification>
<side-menu></side-menu> <side-menu></side-menu>
<div id="main-content" class="split split-horizontal main-content"> <div id="main-content" class="split split-horizontal main-content">
<router-outlet></router-outlet> <router-outlet></router-outlet>
@@ -7,3 +6,4 @@
<a class="" href="https://github.com/UltimateHackingKeyboard/agent" title="Fork me on GitHub">Fork me on GitHub</a> <a class="" href="https://github.com/UltimateHackingKeyboard/agent" title="Fork me on GitHub">Fork me on GitHub</a>
</div> </div>
<ngrx-store-log-monitor toggleCommand="alt-t"></ngrx-store-log-monitor> <ngrx-store-log-monitor toggleCommand="alt-t"></ngrx-store-log-monitor>
<notifier-container></notifier-container>