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:
committed by
László Monda
parent
2598109f8c
commit
121807a65a
@@ -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 {
|
||||
}
|
||||
|
||||
@@ -1 +1,7 @@
|
||||
<app-update-available
|
||||
[showUpdateAvailable]="showUpdateAvailable$ | async"
|
||||
(updateApp)="updateApp()"
|
||||
(doNotUpdateApp)="doNotUpdateApp()">
|
||||
</app-update-available>
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
1
electron/src/components/update-available/index.ts
Normal file
1
electron/src/components/update-available/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { UpdateAvailableComponent } from './update-available.component';
|
||||
@@ -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>
|
||||
@@ -0,0 +1,5 @@
|
||||
:host {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 0.5rem;
|
||||
}
|
||||
@@ -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>();
|
||||
}
|
||||
1
electron/src/custom_types/electron-is-dev.d.ts
vendored
Normal file
1
electron/src/custom_types/electron-is-dev.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
declare module 'electron-is-dev';
|
||||
22
electron/src/dev-extension.ts
Normal file
22
electron/src/dev-extension.ts
Normal 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 });
|
||||
});
|
||||
|
||||
}
|
||||
@@ -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
20
electron/src/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
|
||||
75
electron/src/services/app-update-renderer.service.ts
Normal file
75
electron/src/services/app-update-renderer.service.ts
Normal 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 });
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
40
electron/src/store/actions/app-update.action.ts
Normal file
40
electron/src/store/actions/app-update.action.ts
Normal 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;
|
||||
22
electron/src/store/actions/app.action.ts
Normal file
22
electron/src/store/actions/app.action.ts
Normal 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;
|
||||
32
electron/src/store/effects/app-update.effect.ts
Normal file
32
electron/src/store/effects/app-update.effect.ts
Normal 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) {
|
||||
}
|
||||
|
||||
}
|
||||
24
electron/src/store/effects/app.effect.ts
Normal file
24
electron/src/store/effects/app.effect.ts
Normal 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) { }
|
||||
}
|
||||
2
electron/src/store/effects/index.ts
Normal file
2
electron/src/store/effects/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { AppUpdateEffect } from './app-update.effect';
|
||||
export { ApplicationEffect } from './app.effect';
|
||||
41
electron/src/store/index.ts
Normal file
41
electron/src/store/index.ts
Normal 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);
|
||||
37
electron/src/store/reducers/app-update.reducer.ts
Normal file
37
electron/src/store/reducers/app-update.reducer.ts
Normal 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;
|
||||
21
electron/src/store/reducers/app.reducer.ts
Normal file
21
electron/src/store/reducers/app.reducer.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"files": [
|
||||
"electron-main.ts"
|
||||
]
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"exclude": [
|
||||
"../dist",
|
||||
"electron-main.ts",
|
||||
"webpack.config.*"
|
||||
]
|
||||
|
||||
@@ -80,6 +80,10 @@ module.exports = {
|
||||
{
|
||||
from: 'node_modules/usb',
|
||||
to: 'vendor/usb'
|
||||
},
|
||||
{
|
||||
from: 'electron/src/package.json',
|
||||
to: 'package.json'
|
||||
}
|
||||
]
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user