Add 'New update available' dialog to the electron version (#299)

* build(tsconfig): Rename root tsconfig.json -> tsconfig.base.json

* feat(auto-update): Add update dialog

When new update available than new message will visible of the top of the screen with 2 buttons 'Update' and 'Close'.
- Update button: Update the application (close and restart)
- Close button: Hide the updatePanel

* fix(auto-update): Add types to the event methods

* style: Add comma after SafeStylePipe import

I forgot add the comma when I rebased the branch

* fix(auto-update): Use electron-is-dev package to detect dev build

I removed the isDev() function from the shared util library because it is electron specific code.

* ci: Change osx_image: xcode8.3

Recommended after the last travis upgrade

* feat(auto-update): Add auto update settings page and save config save on electron platform

* ci: Fix osx image

* ci: Upgrade the electron builder -> 19.6.1

The builder now use the 2 package.json structure and build only
the necessary dependencies.
This commit is contained in:
Róbert Kiss
2017-06-22 14:22:54 +02:00
committed by László Monda
parent 2598109f8c
commit 121807a65a
49 changed files with 1028 additions and 129 deletions

View File

@@ -11,7 +11,7 @@ matrix:
fast_finish: true
include:
- os: osx
osx_image: xcode8.3
- os: linux
env: CC=clang CXX=clang++ npm_config_clang=1
compiler: clang

View File

@@ -67,6 +67,7 @@ import { SvgKeyboardWrapComponent } from './shared/components/svg/wrap';
import { appRoutingProviders, routing } from './app/app.routes';
import { AppComponent } from './app/app.component';
import { MainAppComponent } from './main-app';
import { UpdateAvailableComponent } from './components/update-available/update-available.component';
import { CancelableDirective } from './shared/directives';
import { SafeStylePipe } from './shared/pipes';
@@ -76,7 +77,8 @@ import { MapperService } from './shared/services/mapper.service';
import { SvgModuleProviderService } from './shared/services/svg-module-provider.service';
import { UhkDeviceService } from './services/uhk-device.service';
import { KeymapEffects, MacroEffects, UserConfigEffects} from './shared/store/effects';
import { AutoUpdateSettingsEffects, KeymapEffects, MacroEffects, UserConfigEffects } from './shared/store/effects';
import { ApplicationEffect, AppUpdateEffect } from './store/effects';
import { KeymapEditGuard } from './shared/components/keymap/edit';
import { MacroNotFoundGuard } from './shared/components/macro/not-found';
@@ -88,7 +90,9 @@ import { UhkDeviceUninitializedGuard } from './services/uhk-device-uninitialized
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';
import { AppUpdateRendererService } from './services/app-update-renderer.service';
import { reducer } from './store';
import { AutoUpdateSettings } from './shared/components/auto-update-settings/auto-update-settings';
@NgModule({
declarations: [
@@ -143,7 +147,9 @@ import { reducer } from '../../shared/src/store/reducers/index';
PrivilegeCheckerComponent,
UhkMessageComponent,
CancelableDirective,
SafeStylePipe
SafeStylePipe,
UpdateAvailableComponent,
AutoUpdateSettings
],
imports: [
BrowserModule,
@@ -163,7 +169,10 @@ import { reducer } from '../../shared/src/store/reducers/index';
Select2Module,
EffectsModule.runAfterBootstrap(KeymapEffects),
EffectsModule.runAfterBootstrap(MacroEffects),
EffectsModule.runAfterBootstrap(UserConfigEffects)
EffectsModule.runAfterBootstrap(UserConfigEffects),
EffectsModule.runAfterBootstrap(AutoUpdateSettingsEffects),
EffectsModule.run(ApplicationEffect),
EffectsModule.run(AppUpdateEffect)
],
providers: [
UhkDeviceConnectedGuard,
@@ -177,9 +186,11 @@ import { reducer } from '../../shared/src/store/reducers/index';
MacroNotFoundGuard,
CaptureService,
UhkDeviceService,
{provide: DATA_STORAGE_REPOSITORY, useClass: ElectronDataStorageRepositoryService},
DefaultUserConfigurationService
{ provide: DATA_STORAGE_REPOSITORY, useClass: ElectronDataStorageRepositoryService },
DefaultUserConfigurationService,
AppUpdateRendererService
],
bootstrap: [AppComponent]
})
export class AppModule { }
export class AppModule {
}

View File

@@ -1 +1,7 @@
<app-update-available
[showUpdateAvailable]="showUpdateAvailable$ | async"
(updateApp)="updateApp()"
(doNotUpdateApp)="doNotUpdateApp()">
</app-update-available>
<router-outlet></router-outlet>

View File

@@ -1,4 +1,9 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Rx';
import { AppState, getShowAppUpdateAvailable } from '../store';
import { DoNotUpdateAppAction, UpdateAppAction } from '../store/actions/app-update.action';
@Component({
selector: 'app',
@@ -6,4 +11,18 @@ import { Component, ViewEncapsulation } from '@angular/core';
styleUrls: ['app.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class AppComponent { }
export class AppComponent {
showUpdateAvailable$: Observable<boolean>;
constructor(private store: Store<AppState>) {
this.showUpdateAvailable$ = store.select(getShowAppUpdateAvailable);
}
updateApp() {
this.store.dispatch(new UpdateAppAction());
}
doNotUpdateApp() {
this.store.dispatch(new DoNotUpdateAppAction());
}
}

View File

@@ -1,3 +1,4 @@
/// <reference path="../../custom_types/sudo-prompt.d.ts"/>
import { Component } from '@angular/core';
import { Router } from '@angular/router';
@@ -13,7 +14,7 @@ import { remote } from 'electron';
import * as path from 'path';
import * as sudo from 'sudo-prompt';
import { UhkDeviceService } from './../../services/uhk-device.service';
import { UhkDeviceService } from '../../services/uhk-device.service';
@Component({
selector: 'privilege-checker',

View File

@@ -0,0 +1 @@
export { UpdateAvailableComponent } from './update-available.component';

View File

@@ -0,0 +1,5 @@
<div *ngIf="showUpdateAvailable">
New version available.
<button type="button" (click)="updateApp.emit()" class="btn btn-primary">Update</button>
<button type="button" (click)="doNotUpdateApp.emit()" class="btn btn-default">Close</button>
</div>

View File

@@ -0,0 +1,5 @@
:host {
display: flex;
justify-content: center;
margin: 0.5rem;
}

View File

@@ -0,0 +1,13 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
selector: 'app-update-available',
templateUrl: './update-available.component.html',
styleUrls: ['./update-available.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class UpdateAvailableComponent {
@Input() showUpdateAvailable: boolean = false;
@Output() updateApp = new EventEmitter<null>();
@Output() doNotUpdateApp = new EventEmitter<null>();
}

View File

@@ -0,0 +1 @@
declare module 'electron-is-dev';

View File

@@ -0,0 +1,22 @@
/// <reference path="./custom_types/electron-is-dev.d.ts"/>
/*
* Install DevTool extensions when Electron is in development mode
*/
import { app } from 'electron';
import * as isDev from 'electron-is-dev';
if (isDev) {
app.once('ready', () => {
const { default: installExtension, REDUX_DEVTOOLS } = require('electron-devtools-installer');
installExtension(REDUX_DEVTOOLS)
.then((name: string) => console.log(`Added Extension: ${name}`))
.catch((err: any) => console.log('An error occurred: ', err));
require('electron-debug')({ showDevTools: true });
});
}

View File

@@ -1,10 +1,27 @@
import { BrowserWindow, app } from 'electron';
/// <reference path="./custom_types/electron-is-dev.d.ts"/>
import { app, BrowserWindow, ipcMain } from 'electron';
import { autoUpdater } from 'electron-updater';
import * as log from 'electron-log';
import * as path from 'path';
import { ProgressInfo } from 'electron-builder-http/out/ProgressCallbackTransform';
import { VersionInfo } from 'electron-builder-http/out/publishOptions';
import * as settings from 'electron-settings';
import * as isDev from 'electron-is-dev';
import { IpcEvents } from './shared/util';
import { ElectronDataStorageRepositoryService } from './services/electron-datastorage-repository.service';
// import './dev-extension';
// require('electron-debug')({ showDevTools: true, enabled: true });
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win: Electron.BrowserWindow;
log.transports.file.level = 'debug';
autoUpdater.logger = log;
function createWindow() {
// Create the browser window.
win = new BrowserWindow({
@@ -35,6 +52,9 @@ function createWindow() {
// when you should delete the corresponding element.
win = null;
});
win.webContents.on('did-finish-load', () => {
});
}
// This method will be called when Electron has finished
@@ -51,6 +71,10 @@ app.on('window-all-closed', () => {
}
});
app.on('will-quit', () => {
saveFirtsRun();
});
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
@@ -61,3 +85,101 @@ app.on('activate', () => {
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here
// =========================================================================
// Auto update events
// =========================================================================
function checkForUpdate() {
if (isDev) {
const msg = 'Application update is not working in dev mode.';
log.info(msg);
sendIpcToWindow(IpcEvents.autoUpdater.checkForUpdateNotAvailable, msg);
return;
}
if (isFirstRun()) {
const msg = 'Application update is skipping at first run.';
log.info(msg);
sendIpcToWindow(IpcEvents.autoUpdater.checkForUpdateNotAvailable, msg);
return;
}
autoUpdater.allowPrerelease = allowPreRelease();
autoUpdater.checkForUpdates();
}
autoUpdater.on('checking-for-update', () => {
sendIpcToWindow(IpcEvents.autoUpdater.checkingForUpdate);
});
autoUpdater.on('update-available', (ev: any, info: VersionInfo) => {
autoUpdater.downloadUpdate();
sendIpcToWindow(IpcEvents.autoUpdater.updateAvailable, info);
});
autoUpdater.on('update-not-available', (ev: any, info: VersionInfo) => {
sendIpcToWindow(IpcEvents.autoUpdater.updateNotAvailable, info);
});
autoUpdater.on('error', (ev: any, err: Error) => {
sendIpcToWindow(IpcEvents.autoUpdater.autoUpdateError, err);
});
autoUpdater.on('download-progress', (progressObj: ProgressInfo) => {
sendIpcToWindow(IpcEvents.autoUpdater.autoUpdateDownloadProgress, progressObj);
});
autoUpdater.on('update-downloaded', (ev: any, info: VersionInfo) => {
sendIpcToWindow(IpcEvents.autoUpdater.autoUpdateDownloaded, info);
});
ipcMain.on(IpcEvents.autoUpdater.updateAndRestart, () => autoUpdater.quitAndInstall(true));
ipcMain.on(IpcEvents.app.appStarted, () => {
if (checkForUpdateAtStartup()) {
checkForUpdate();
}
});
ipcMain.on(IpcEvents.autoUpdater.checkForUpdate, () => checkForUpdate());
function isFirstRun() {
if (!settings.has('firstRunVersion')) {
return true;
}
const firstRunVersion = settings.get('firstRunVersion');
log.info(`firstRunVersion: ${firstRunVersion}`);
log.info(`package.version: ${app.getVersion()}`);
return firstRunVersion !== app.getVersion();
}
function saveFirtsRun() {
settings.set('firstRunVersion', app.getVersion());
}
function sendIpcToWindow(message: string, arg?: any) {
log.info('sendIpcToWindow:', message, arg);
if (!win || win.isDestroyed()) {
return;
}
win.webContents.send(message, arg);
}
function allowPreRelease() {
const settings = getAutoUpdateSettings();
return settings && settings.usePreReleaseUpdate;
}
function checkForUpdateAtStartup() {
const settings = getAutoUpdateSettings();
return settings && settings.checkForUpdateOnStartUp;
}
function getAutoUpdateSettings() {
const storageService = new ElectronDataStorageRepositoryService();
return storageService.getAutoUpdateSettings();
}

20
electron/src/package.json Normal file
View File

@@ -0,0 +1,20 @@
{
"name": "uhk-agent",
"main": "electron-main.js",
"version": "0.0.0",
"description": "Agent is the configuration application of the Ultimate Hacking Keyboard.",
"author": "Ultimate Gadget Laboratories",
"repository": {
"type": "git",
"url": "git@github.com:UltimateHackingKeyboard/agent.git"
},
"license": "GPL-3.0",
"engines": {
"node": ">=6.9.5 <7.0.0",
"npm": ">=3.10.7 <4.0.0"
},
"dependencies": {
"usb": "git+https://github.com/aktary/node-usb.git"
}
}

View File

@@ -0,0 +1,75 @@
import { Injectable, NgZone } from '@angular/core';
import { Action, Store } from '@ngrx/store';
import { ipcRenderer } from 'electron';
import { IpcEvents } from '../shared/util';
import { AppState } from '../store';
import { UpdateDownloadedAction } from '../store/actions/app-update.action';
import { CheckForUpdateFailedAction, CheckForUpdateSuccessAction } from '../shared/store/actions/auto-update-settings';
/**
* This service handle the application update events in the electron renderer process.
*
* The class contains parameters with 'any' type, because the relevant type definitions in
* import { ProgressInfo } from 'electron-builder-http/out/ProgressCallbackTransform';
* import { VersionInfo } from 'electron-builder-http/out/publishOptions';
* but, typescript allow import these if import 'electron-updater' too, but I i don't want to import
* the updater in renderer process.
*/
@Injectable()
export class AppUpdateRendererService {
constructor(private store: Store<AppState>,
private zone: NgZone) {
this.registerEvents();
}
sendAppStarted() {
ipcRenderer.send(IpcEvents.app.appStarted);
}
sendUpdateAndRestartApp() {
ipcRenderer.send(IpcEvents.autoUpdater.updateAndRestart);
}
checkForUpdate() {
ipcRenderer.send(IpcEvents.autoUpdater.checkForUpdate);
}
private registerEvents() {
ipcRenderer.on(IpcEvents.autoUpdater.updateAvailable, (event: string, arg: any) => {
this.writeUpdateState(IpcEvents.autoUpdater.updateAvailable, arg);
});
ipcRenderer.on(IpcEvents.autoUpdater.updateNotAvailable, () => {
this.writeUpdateState(IpcEvents.autoUpdater.updateNotAvailable);
this.dispachStoreAction(new CheckForUpdateSuccessAction('No update available'));
});
ipcRenderer.on(IpcEvents.autoUpdater.autoUpdateError, (event: string, arg: any) => {
this.writeUpdateState(IpcEvents.autoUpdater.autoUpdateError, arg);
this.dispachStoreAction(new CheckForUpdateFailedAction(arg));
});
ipcRenderer.on(IpcEvents.autoUpdater.autoUpdateDownloadProgress, (event: string, arg: any) => {
this.writeUpdateState(IpcEvents.autoUpdater.autoUpdateDownloadProgress, arg);
});
ipcRenderer.on(IpcEvents.autoUpdater.autoUpdateDownloaded, (event: string, arg: any) => {
this.writeUpdateState(IpcEvents.autoUpdater.autoUpdateDownloaded, arg);
this.dispachStoreAction(new UpdateDownloadedAction());
});
ipcRenderer.on(IpcEvents.autoUpdater.checkForUpdateNotAvailable, (event: string, arg: any) => {
this.writeUpdateState(IpcEvents.autoUpdater.checkForUpdateNotAvailable, arg);
this.dispachStoreAction(new CheckForUpdateFailedAction(arg));
});
}
private dispachStoreAction(action: Action) {
this.zone.run(() => this.store.dispatch(action));
}
private writeUpdateState(event: any, arg?: any) {
console.log({ event, arg });
}
}

View File

@@ -1,13 +1,36 @@
import { UserConfiguration } from '../shared/config-serializer/config-items/UserConfiguration';
import * as storage from 'electron-settings';
export class ElectronDataStorageRepositoryService {
getConfig(): UserConfiguration {
// TODO implement load logic
return;
import { UserConfiguration } from '../shared/config-serializer/config-items/UserConfiguration';
import { DataStorageRepositoryService } from '../shared/services/datastorage-repository.service';
import { AutoUpdateSettings } from '../shared/models/auto-update-settings';
export class ElectronDataStorageRepositoryService implements DataStorageRepositoryService {
static getValue(key: string): any {
const value = storage.get(key);
if (!value) {
return null;
}
return JSON.parse(<string>value);
}
static saveValue(key: string, value: any) {
storage.set(key, JSON.stringify(value));
}
getConfig(): UserConfiguration {
return ElectronDataStorageRepositoryService.getValue('user-config');
}
/* tslint:disable:no-unused-variable */
saveConfig(config: UserConfiguration): void {
// TODO implement save logic
ElectronDataStorageRepositoryService.saveValue('user-config', config.toJsonObject());
}
getAutoUpdateSettings(): AutoUpdateSettings {
return ElectronDataStorageRepositoryService.getValue('auto-update-settings');
}
saveAutoUpdateSettings(settings: AutoUpdateSettings): void {
ElectronDataStorageRepositoryService.saveValue('auto-update-settings', settings);
}
}

View File

@@ -0,0 +1,40 @@
import { Action } from '@ngrx/store';
import { type } from '../../shared/util/';
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')
};
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 type Actions
= UpdateAvailableAction
| UpdateAppAction
| DoNotUpdateAppAction
| UpdateDownloadedAction
| UpdatingAction;

View File

@@ -0,0 +1,22 @@
import { Action } from '@ngrx/store';
import { type } from '../../shared/util/';
const PREFIX = '[app] ';
// tslint:disable-next-line:variable-name
export const ActionTypes = {
APP_BOOTSRAPPED: type(PREFIX + 'bootstrapped'),
APP_STARTED: type(PREFIX + 'started')
};
export class AppBootsrappedAction implements Action {
type = ActionTypes.APP_BOOTSRAPPED;
}
export class AppStartedAction implements Action {
type = ActionTypes.APP_STARTED;
}
export type Actions
= AppStartedAction
| AppBootsrappedAction;

View File

@@ -0,0 +1,32 @@
import { Injectable } from '@angular/core';
import { Action } from '@ngrx/store';
import { Actions, Effect } from '@ngrx/effects';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/first';
import { ActionTypes } from '../actions/app-update.action';
import { ActionTypes as AutoUpdateActionTypes } from '../../shared/store/actions/auto-update-settings';
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();
});
constructor(private actions$: Actions,
private appUpdateRendererService: AppUpdateRendererService) {
}
}

View File

@@ -0,0 +1,24 @@
import { Injectable } from '@angular/core';
import { Action } from '@ngrx/store';
import { Effect, Actions } from '@ngrx/effects';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/do';
import * as app from '../actions/app.action';
import { AppUpdateRendererService } from '../../services/app-update-renderer.service';
@Injectable()
export class ApplicationEffect {
@Effect()
appStart$: Observable<Action> = this.actions$
.ofType(app.ActionTypes.APP_BOOTSRAPPED)
.startWith(new app.AppStartedAction())
.delay(3000) // wait 3 sec to mainRenderer subscribe all events
.do(() => {
this.appUpdateRendererService.sendAppStarted();
});
constructor(
private actions$: Actions,
private appUpdateRendererService: AppUpdateRendererService) { }
}

View File

@@ -0,0 +1,2 @@
export { AppUpdateEffect } from './app-update.effect';
export { ApplicationEffect } from './app.effect';

View File

@@ -0,0 +1,41 @@
/// <reference path="../custom_types/electron-is-dev.d.ts"/>
import { createSelector } from 'reselect';
import { compose } from '@ngrx/core/compose';
import { storeFreeze } from 'ngrx-store-freeze';
import { ActionReducer, combineReducers } from '@ngrx/store';
import { routerReducer } from '@ngrx/router-store';
import * as isDev from 'electron-is-dev';
import { AppState as CommonState } from '../shared/store';
import * as fromApp from './reducers/app.reducer';
import * as fromAppUpdate from './reducers/app-update.reducer';
import { autoUpdateReducer, presetReducer, userConfigurationReducer } from '../shared/store/reducers';
export interface AppState extends CommonState {
app: fromApp.State;
appUpdate: fromAppUpdate.State;
}
const reducers = {
userConfiguration: userConfigurationReducer,
presetKeymaps: presetReducer,
router: routerReducer,
app: fromApp.reducer,
appUpdate: fromAppUpdate.reducer,
autoUpdateSettings: autoUpdateReducer
};
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 appUpdateState = (state: AppState) => state.appUpdate;
export const getShowAppUpdateAvailable = createSelector(appUpdateState, fromAppUpdate.getShowAppUpdateAvailable);

View File

@@ -0,0 +1,37 @@
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;

View File

@@ -0,0 +1,21 @@
import { Actions, ActionTypes } from '../actions/app.action';
export interface State {
started: boolean;
}
const initialState: State = {
started: false
};
export function reducer(state = initialState, action: Actions) {
switch (action.type) {
case ActionTypes.APP_STARTED: {
const newState = Object.assign({}, state);
newState.started = true;
return newState;
}
default:
return state;
}
}

View File

@@ -1,5 +1,5 @@
{
"extends": "../../tsconfig.json",
"extends": "../../tsconfig.base.json",
"files": [
"electron-main.ts"
]

View File

@@ -1,7 +1,6 @@
{
"extends": "../../tsconfig.json",
"extends": "../../tsconfig.base.json",
"exclude": [
"../dist",
"electron-main.ts",
"webpack.config.*"
]

View File

@@ -80,6 +80,10 @@ module.exports = {
{
from: 'node_modules/usb',
to: 'vendor/usb'
},
{
from: 'electron/src/package.json',
to: 'package.json'
}
]
),

View File

@@ -16,17 +16,23 @@
"@ngrx/store-devtools": "3.2.4",
"@ngrx/store-log-monitor": "3.0.2",
"@types/core-js": "0.9.35",
"@types/electron-devtools-installer": "^2.0.2",
"@types/electron-settings": "^3.0.0",
"@types/file-saver": "0.0.1",
"@types/jquery": "3.2.1",
"@types/node": "^6.0.78",
"@types/usb": "^1.1.3",
"angular2-template-loader": "0.6.2",
"copy-webpack-plugin": "^4.0.1",
"devtron": "^1.4.0",
"electron": "1.6.11",
"electron-builder": "^19.4.2",
"electron-builder": "19.6.1",
"electron-debug": "^1.1.0",
"electron-devtools-installer": "^2.2.0",
"electron-rebuild": "^1.5.7",
"expose-loader": "^0.7.1",
"html-loader": "0.4.5",
"jsonfile": "3.0.0",
"node-sass": "^4.5.2",
"npm-run-all": "4.0.2",
"path": "^0.12.7",
@@ -58,12 +64,18 @@
"buffer": "^5.0.6",
"core-js": "2.4.1",
"dragula": "^3.7.2",
"electron-is-dev": "0.1.2",
"electron-log": "2.2.6",
"electron-settings": "3.0.14",
"electron-updater": "2.2.0",
"filesaver.js": "^0.2.0",
"font-awesome": "^4.6.3",
"jquery": "3.2.1",
"json-loader": "^0.5.4",
"ng2-dragula": "1.5.0",
"ng2-select2": "1.0.0-beta.10",
"ngrx-store-freeze": "^0.1.9",
"reselect": "3.0.1",
"rxjs": "^5.4.1",
"select2": "^4.0.3",
"sudo-prompt": "^7.0.0",
@@ -76,7 +88,7 @@
"postinstall": "run-p build:usb \"symlink -- -i\" ",
"test": "cd ./test-serializer && node ./test-serializer.js",
"lint": "run-s -scn lint:ts lint:style",
"lint:ts": "tslint \"electron/**/*.ts\" \"web/**/*.ts\" \"shared/**/*.ts\" \"test-serializer/**/*.ts\"",
"lint:ts": "tslint \"electron/src/**/*.ts\" \"web/src/**/*.ts\" \"shared/**/*.ts\" \"test-serializer/**/*.ts\"",
"lint:style": "stylelint \"electron/**/*.scss\" \"web/**/*.scss\" \"shared/**/*.scss\" --syntax scss",
"build": "run-p build:web build:electron",
"build:web": "webpack --config \"web/src/webpack.config.js\"",
@@ -91,6 +103,7 @@
"symlink": "node ./tools/symlinker",
"standard-version": "standard-version",
"pack": "node ./scripts/release.js",
"release": "node ./scripts/release.js"
"install:build-deps": "cd electron/dist && npm i",
"release": "npm run install:build-deps && node ./scripts/release.js"
}
}

View File

@@ -1,111 +1,129 @@
'use strict';
const jsonfile = require('jsonfile');
const TEST_BUILD = false; // set true if you would like to test on your local machince
if (!process.env.CI && !TEST_BUILD) {
console.error('Create release only on CI server')
process.exit(1)
console.error('Create release only on CI server');
process.exit(1);
}
let branchName = ''
let pullRequestNr = ''
let gitTag = ''
let repoName = ''
let branchName = '';
let pullRequestNr = '';
let gitTag = '';
let repoName = '';
if (process.env.TRAVIS) {
branchName = process.env.TRAVIS_BRANCH
pullRequestNr = process.env.TRAVIS_PULL_REQUEST
gitTag = process.env.TRAVIS_TAG
repoName = process.env.TRAVIS_REPO_SLUG
branchName = process.env.TRAVIS_BRANCH;
pullRequestNr = process.env.TRAVIS_PULL_REQUEST;
gitTag = process.env.TRAVIS_TAG;
repoName = process.env.TRAVIS_REPO_SLUG;
} else if (process.env.APPVEYOR) {
branchName = process.env.APPVEYOR_REPO_BRANCH
pullRequestNr = process.env.APPVEYOR_PULL_REQUEST_NUMBER
gitTag = process.env.APPVEYOR_REPO_TAG_NAME
repoName = process.env.APPVEYOR_REPO_NAME
branchName = process.env.APPVEYOR_REPO_BRANCH;
pullRequestNr = process.env.APPVEYOR_PULL_REQUEST_NUMBER;
gitTag = process.env.APPVEYOR_REPO_TAG_NAME;
repoName = process.env.APPVEYOR_REPO_NAME;
}
console.log({ branchName, pullRequestNr, gitTag, repoName })
console.log({branchName, pullRequestNr, gitTag, repoName});
// TODO(Robi): Remove the comment after success tests
const isReleaseCommit = TEST_BUILD || branchName === gitTag && repoName === 'UltimateHackingKeyboard/agent'
const isReleaseCommit = TEST_BUILD || branchName === gitTag && repoName === 'UltimateHackingKeyboard/agent';
if (!isReleaseCommit) {
console.log('It is not a release task. Skipping publish.')
console.log('It is not a release task. Skipping publish.');
process.exit(0)
}
const fs = require('fs-extra')
const cp = require('child_process')
const path = require('path')
const builder = require("electron-builder")
const Platform = builder.Platform
const fs = require('fs-extra');
const cp = require('child_process');
const path = require('path');
const builder = require("electron-builder");
const Platform = builder.Platform;
const electron_build_folder = path.join(__dirname, '../electron/dist');
let sha = ''
let sha = '';
if (process.env.TRAVIS) {
sha = process.env.TRAVIS_COMMIT
sha = process.env.TRAVIS_COMMIT;
} else if (process.env.APPVEYOR) {
sha = process.env.APPVEYOR_REPO_COMMIT
sha = process.env.APPVEYOR_REPO_COMMIT;
}
let target = ''
let target = '';
let artifactName = 'UHK.Agent-${version}-${os}';
if (process.platform === 'darwin') {
target = Platform.MAC.createTarget()
target = Platform.MAC.createTarget();
artifactName += '.${ext}';
} else if (process.platform === 'win32') {
target = Platform.WINDOWS.createTarget()
target = Platform.WINDOWS.createTarget();
artifactName += '-${arch}.${ext}';
} else if (process.platform === 'linux') {
target = Platform.LINUX.createTarget()
target = Platform.LINUX.createTarget();
artifactName += '.${ext}';
} else {
console.error(`I dunno how to publish a release for ${process.platform} :(`)
process.exit(1)
console.error(`I dunno how to publish a release for ${process.platform} :(`);
process.exit(1);
}
if (process.platform === 'darwin') {
// TODO: Remove comment when macOS certificates boughted and exported
//require('./setup-macos-keychain').registerKeyChain()
//require('./setup-macos-keychain').registerKeyChain();
}
let version = ''
let version = '';
if (TEST_BUILD || gitTag) {
version = gitTag
const jsonVersion = require('../package.json').version;
version = gitTag;
updateVersionNumberIn2rndPackageJson(jsonVersion);
builder.build({
dir: true,
targets: target,
appMetadata: {
main: 'electron/dist/electron-main.js',
main: 'electron-main.js',
name: 'UHK Agent',
author: {
name: 'Ultimate Gaget Laboratories'
name: 'Ultimate Gadget Laboratories'
},
version: jsonVersion
},
config: {
directories: {
app: electron_build_folder
},
appId: 'com.ultimategadgetlabs.uhk.agent',
productName: 'UHK Agent',
mac: {
category: 'public.app-category.utilities',
category: 'public.app-category.utilities'
},
publish: 'github',
artifactName,
files: [
'!**/*',
'electron/dist/**/*',
'node_modules/**/*'
'**/*'
]
},
})
.then(() => {
console.log('Packing success.')
console.log('Packing success.');
})
.catch((error) => {
console.error(`${error}`)
process.exit(1)
console.error(`${error}`);
process.exit(1);
})
}
else {
console.log('No git tag')
console.log('No git tag');
// TODO: Need it?
version = sha.substr(0, 8)
process.exit(1)
version = sha.substr(0, 8);
process.exit(1);
}
function updateVersionNumberIn2rndPackageJson(version) {
const jsonPath = path.join(__dirname,'../electron/dist/package.json');
const json = require(jsonPath);
json.version = version;
jsonfile.writeFileSync(jsonPath, json, {spaces: 2})
}

View File

@@ -0,0 +1,36 @@
<div class="row">
<div class="col-xs-12">
<div class="checkbox">
<label>
<input type="checkbox"
[checked]="settings.checkForUpdateOnStartUp"
(change)="emitCheckForUpdateOnStartUp($event.target.checked)"> Automatically check for update on
application start
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox"
[checked]="settings.usePreReleaseUpdate"
(change)="emitUsePreReleaseUpdate($event.target.checked)"> Allow alpha / pre release
</label>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Version:</label>
<div class="col-sm-10">
<p class="form-control-static">{{version}}</p>
</div>
</div>
<button class="btn btn-link" (click)="emitCheckForUpdate()">
Check for update
<span *ngIf="checkingForUpdate"
class="fa fa-spinner fa-spin"></span>
</button>
<div>
{{message}}
</div>
</div>
</div>

View File

@@ -0,0 +1,35 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { State } from '../../store/reducers/auto-update-settings';
@Component({
selector: 'auto-update-settings',
templateUrl: './auto-update-settings.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AutoUpdateSettings {
@Input() version: string;
@Input() settings: State;
@Input() checkingForUpdate: boolean;
@Input() message: string;
@Output() toggleCheckForUpdateOnStartUp = new EventEmitter<boolean>();
@Output() toggleUsePreReleaseUpdate = new EventEmitter<boolean>();
@Output() checkForUpdate = new EventEmitter();
constructor() {
}
emitCheckForUpdateOnStartUp(value: boolean) {
this.toggleCheckForUpdateOnStartUp.emit(value);
}
emitUsePreReleaseUpdate(value: boolean) {
this.toggleUsePreReleaseUpdate.emit(value);
}
emitCheckForUpdate() {
this.checkForUpdate.emit();
}
}

View File

@@ -4,4 +4,15 @@
<span class="macro__name pane-title__name">Settings</span>
</h1>
</div>
To be done...
<div *ngIf="!runInElectron">
To be done...
</div>
<auto-update-settings *ngIf="runInElectron"
[version]="version"
[settings]="autoUpdateSettings$ | async"
[checkingForUpdate]="checkingForUpdate$ | async"
[message]="autoUpdateMessage$ | async"
(toggleCheckForUpdateOnStartUp)="toogleCheckForUpdateOnStartUp($event)"
(toggleUsePreReleaseUpdate)="toogleUsePreReleaseUpdate($event)"
(checkForUpdate)="checkForUpdate()">
</auto-update-settings>

View File

@@ -1,4 +1,15 @@
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
import { runInElectron } from '../../util/index';
import { AppState, getAutoUpdateMessage, getAutoUpdateSettings, getCheckingForUpdate } from '../../store';
import {
CheckForUpdateNowAction,
ToggleCheckForUpdateOnStartupAction,
TogglePreReleaseFlagAction
} from '../../store/actions/auto-update-settings';
import { AutoUpdateSettings } from '../../models/auto-update-settings';
@Component({
selector: 'settings',
@@ -9,5 +20,28 @@ import { Component } from '@angular/core';
}
})
export class SettingsComponent {
constructor() { }
runInElectron = runInElectron();
// TODO: From where do we get the version number? The electron gives back in main process, but the web...
version = '1.0.0';
autoUpdateSettings$: Observable<AutoUpdateSettings>;
checkingForUpdate$: Observable<boolean>;
autoUpdateMessage$: Observable<string>;
constructor(private store: Store<AppState>) {
this.autoUpdateSettings$ = store.select(getAutoUpdateSettings);
this.checkingForUpdate$ = store.select(getCheckingForUpdate);
this.autoUpdateMessage$ = store.select(getAutoUpdateMessage);
}
toogleCheckForUpdateOnStartUp(value: boolean) {
this.store.dispatch(new ToggleCheckForUpdateOnStartupAction(value));
}
toogleUsePreReleaseUpdate(value: boolean) {
this.store.dispatch(new TogglePreReleaseFlagAction(value));
}
checkForUpdate() {
this.store.dispatch(new CheckForUpdateNowAction());
}
}

View File

@@ -0,0 +1,4 @@
export interface AutoUpdateSettings {
checkForUpdateOnStartUp: boolean;
usePreReleaseUpdate: boolean;
}

View File

@@ -1,12 +1,17 @@
import { InjectionToken } from '@angular/core';
import { UserConfiguration } from '../config-serializer/config-items/UserConfiguration';
import { AutoUpdateSettings } from '../models/auto-update-settings';
export interface DataStorageRepositoryService {
getConfig(): UserConfiguration;
saveConfig(config: UserConfiguration): void;
getAutoUpdateSettings(): AutoUpdateSettings;
saveAutoUpdateSettings(settings: AutoUpdateSettings): void;
}
export let DATA_STORAGE_REPOSITORY = new InjectionToken('dataStorage-repository');

View File

@@ -1,27 +1,26 @@
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';
import { State as AutoUpdateSettings } from '../store/reducers/auto-update-settings';
@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;
return JSON.parse(localStorage.getItem('config'));
}
saveConfig(config: UserConfiguration): void {
localStorage.setItem('config', JSON.stringify(config.toJsonObject()));
}
getAutoUpdateSettings(): AutoUpdateSettings {
return JSON.parse(localStorage.getItem('auto-update-settings'));
}
saveAutoUpdateSettings(settings: AutoUpdateSettings): void {
localStorage.setItem('auto-update-settings', JSON.stringify(settings));
}
}

View File

@@ -0,0 +1,74 @@
import { Action } from '@ngrx/store';
import { type } from '../../util';
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;

View File

@@ -0,0 +1,42 @@
import { Inject, Injectable } from '@angular/core';
import { Actions, Effect } from '@ngrx/effects';
import { Observable } from 'rxjs/Observable';
import { Action, Store } from '@ngrx/store';
import {
ActionTypes,
LoadAutoUpdateSettingsAction,
LoadAutoUpdateSettingsSuccessAction,
SaveAutoUpdateSettingsSuccessAction
} from '../actions/auto-update-settings';
import { DATA_STORAGE_REPOSITORY, DataStorageRepositoryService } from '../../services/datastorage-repository.service';
import { AppState, getAutoUpdateSettings } from '../index';
import { initialState, State } from '../reducers/auto-update-settings';
import { AutoUpdateSettings } from '../../models/auto-update-settings';
@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();
});
constructor(private actions$: Actions,
@Inject(DATA_STORAGE_REPOSITORY) private dataStorageRepository: DataStorageRepositoryService,
private store: Store<AppState>) {
}
}

View File

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

View File

@@ -30,18 +30,28 @@ export class UserConfigEffects {
.ofType(ActionTypes.LOAD_USER_CONFIG)
.startWith(new LoadUserConfigAction())
.switchMap(() => {
let config: UserConfiguration = this.dataStorageRepository.getConfig();
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 Observable.of(new LoadUserConfigSuccessAction(config));
});
@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, KeymapActions.CHECK_MACRO,
MacroActions.ADD, MacroActions.DUPLICATE, MacroActions.EDIT_NAME, MacroActions.REMOVE, MacroActions.ADD_ACTION,
MacroActions.SAVE_ACTION, MacroActions.DELETE_ACTION, MacroActions.REORDER_ACTION)
.ofType(
KeymapActions.ADD, KeymapActions.DUPLICATE, KeymapActions.EDIT_NAME, KeymapActions.EDIT_ABBR,
KeymapActions.SET_DEFAULT, KeymapActions.REMOVE, KeymapActions.SAVE_KEY, KeymapActions.CHECK_MACRO,
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))
.map(([action, config]) => {
this.dataStorageRepository.saveConfig(config);

View File

@@ -1,10 +1,20 @@
import { createSelector } from 'reselect';
import { Keymap } from '../config-serializer/config-items/Keymap';
import { UserConfiguration } from '../config-serializer/config-items/UserConfiguration';
import * as autoUpdate from './reducers/auto-update-settings';
// State interface for the application
export interface AppState {
userConfiguration: UserConfiguration;
presetKeymaps: Keymap[];
autoUpdateSettings: autoUpdate.State;
}
export const getUserConfiguration = (state: AppState) => state.userConfiguration;
export const appUpdateState = (state: AppState) => state.autoUpdateSettings;
export const getAutoUpdateSettings = createSelector(appUpdateState, autoUpdate.getUpdateSettings);
export const getCheckingForUpdate = createSelector(appUpdateState, autoUpdate.checkingForUpdate);
export const getAutoUpdateMessage = createSelector(appUpdateState, autoUpdate.getMessage);

View File

@@ -0,0 +1,50 @@
import { Action } from '@ngrx/store';
import { ActionTypes } from '../actions/auto-update-settings';
import { AutoUpdateSettings } from '../../models/auto-update-settings';
export interface State extends AutoUpdateSettings {
checkingForUpdate: boolean;
message?: string;
}
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, message: null });
}
case ActionTypes.CHECK_FOR_UPDATE_SUCCESS:
case ActionTypes.CHECK_FOR_UPDATE_FAILED: {
return Object.assign({}, state, { checkingForUpdate: false, message: action.payload });
}
default:
return state;
}
}
export const getUpdateSettings = (state: State) => ({
checkForUpdateOnStartUp: state.checkForUpdateOnStartUp,
usePreReleaseUpdate: state.usePreReleaseUpdate
});
export const checkingForUpdate = (state: State) => state.checkingForUpdate;
export const getMessage = (state: State) => state.message;

View File

@@ -2,10 +2,14 @@ import { routerReducer } from '@ngrx/router-store';
import userConfigurationReducer from './user-configuration';
import presetReducer from './preset';
import { reducer as autoUpdateReducer } from './auto-update-settings';
export { userConfigurationReducer, presetReducer, autoUpdateReducer };
// All reducers that are used in application
export const reducer = {
userConfiguration: userConfigurationReducer,
presetKeymaps: presetReducer,
router: routerReducer
router: routerReducer,
autoUpdateSettings: autoUpdateReducer
};

View File

@@ -8,7 +8,7 @@ const initialState: Keymap[] = [];
export default function(state = initialState, action: Action): Keymap[] {
switch (action.type) {
case KeymapActions.LOAD_KEYMAPS_SUCCESS: {
return Object.assign(state, action.payload);
return action.payload;
}
default:

View File

@@ -1,27 +1,3 @@
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true,
"typeRoots": [
"../../node_modules/@types"
],
"types": [
"node",
"jquery",
"core-js",
"select2",
"file-saver"
]
},
"exclude": [
"node_modules",
"config-serializer"
]
"extends": "../../tsconfig.base.json"
}

View File

@@ -20,6 +20,7 @@ export function capitalizeFirstLetter(text: string): string {
*/
const typeCache: { [label: string]: boolean } = {};
export function type<T>(label: T | ''): T {
if (typeCache[<string>label]) {
throw new Error(`Action type "${label}" is not unique"`);
@@ -29,3 +30,9 @@ export function type<T>(label: T | ''): T {
return <T>label;
}
export { IpcEvents } from './ipcEvents';
export function runInElectron() {
return window && (<any>window).process && (<any>window).process.type;
}

View File

@@ -0,0 +1,20 @@
class App {
public static readonly appStarted = 'app-started';
}
class AutoUpdate {
public static readonly checkingForUpdate = 'checking-for-update';
public static readonly updateAvailable = 'update-available';
public static readonly updateNotAvailable = 'update-not-available';
public static readonly autoUpdateError = 'auto-update-error';
public static readonly autoUpdateDownloaded = 'update-downloaded';
public static readonly autoUpdateDownloadProgress = 'auto-update-download-progress';
public static readonly updateAndRestart = 'update-and-restart';
public static readonly checkForUpdate = 'check-for-update';
public static readonly checkForUpdateNotAvailable = 'check-for-update-not-available';
}
export class IpcEvents {
public static readonly app = App;
public static readonly autoUpdater = AutoUpdate;
}

View File

@@ -11,8 +11,8 @@
"suppressImplicitAnyIndexErrors": true
},
"exclude": [
"./node_modules",
"./dist",
".electron/dist",
"./dist"
"node_modules"
]
}

View File

@@ -60,7 +60,7 @@ import {
} from './shared/components/svg/keys';
import { SvgModuleComponent } from './shared/components/svg/module';
import { SvgKeyboardWrapComponent } from './shared/components/svg/wrap';
import { MainAppComponent, appRoutingProviders, routing } from './main-app';
import { appRoutingProviders, MainAppComponent, routing } from './main-app';
import { CancelableDirective } from './shared/directives';
import { SafeStylePipe } from './shared/pipes';
@@ -69,14 +69,15 @@ import { CaptureService } from './shared/services/capture.service';
import { MapperService } from './shared/services/mapper.service';
import { SvgModuleProviderService } from './shared/services/svg-module-provider.service';
import { KeymapEffects, MacroEffects, UserConfigEffects } from './shared/store/effects';
import { AutoUpdateSettingsEffects, KeymapEffects, MacroEffects, UserConfigEffects } from './shared/store/effects';
import { KeymapEditGuard } from './shared/components/keymap/edit';
import { MacroNotFoundGuard } from './shared/components/macro/not-found';
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';
import { reducer } from './shared/store/reducers/index';
import { AutoUpdateSettings } from './shared/components/auto-update-settings/auto-update-settings';
@NgModule({
declarations: [
@@ -127,7 +128,8 @@ import { reducer } from '../../shared/src/store/reducers/index';
SettingsComponent,
KeyboardSliderComponent,
CancelableDirective,
SafeStylePipe
SafeStylePipe,
AutoUpdateSettings
],
imports: [
BrowserModule,
@@ -147,7 +149,8 @@ import { reducer } from '../../shared/src/store/reducers/index';
Select2Module,
EffectsModule.runAfterBootstrap(KeymapEffects),
EffectsModule.runAfterBootstrap(MacroEffects),
EffectsModule.runAfterBootstrap(UserConfigEffects)
EffectsModule.runAfterBootstrap(UserConfigEffects),
EffectsModule.runAfterBootstrap(AutoUpdateSettingsEffects)
],
providers: [
SvgModuleProviderService,
@@ -156,9 +159,10 @@ import { reducer } from '../../shared/src/store/reducers/index';
KeymapEditGuard,
MacroNotFoundGuard,
CaptureService,
{provide: DATA_STORAGE_REPOSITORY, useClass: LocalDataStorageRepositoryService},
{ provide: DATA_STORAGE_REPOSITORY, useClass: LocalDataStorageRepositoryService },
DefaultUserConfigurationService
],
bootstrap: [MainAppComponent]
})
export class AppModule { }
export class AppModule {
}

View File

@@ -1,5 +1,5 @@
{
"extends": "../../tsconfig.json",
"extends": "../../tsconfig.base.json",
"exclude": [
"config-serializer"
]