feat: redesign auto-update component (#910)

This commit is contained in:
Róbert Kiss
2019-02-02 02:11:54 +01:00
committed by László Monda
parent 12d361eb2e
commit 9a0b0f9df1
28 changed files with 178 additions and 105 deletions

View File

@@ -5,7 +5,7 @@ import * as settings from 'electron-settings';
import * as isDev from 'electron-is-dev';
import * as storage from 'electron-settings';
import { IpcEvents, LogService } from 'uhk-common';
import { AutoUpdateSettings, IpcEvents, LogService } from 'uhk-common';
import { MainServiceBase } from './main-service-base';
export class AppUpdateService extends MainServiceBase {
@@ -76,14 +76,17 @@ export class AppUpdateService extends MainServiceBase {
}
});
ipcMain.on(IpcEvents.autoUpdater.checkForUpdate, () => {
this.logService.debug('[AppUpdateService] checkForUpdate request from renderer process');
ipcMain.on(IpcEvents.autoUpdater.checkForUpdate, (event: Electron.Event, args: any[]) => {
const allowPrerelease: boolean = args[0];
// tslint:disable-next-line:max-line-length
const logMsg = `[AppUpdateService] checkForUpdate request from renderer process. Allow prerelease: ${allowPrerelease}`;
this.logService.debug(logMsg);
this.sendAutoUpdateNotification = true;
this.checkForUpdate();
this.checkForUpdate(allowPrerelease);
});
}
private checkForUpdate() {
private checkForUpdate(allowPrerelease = false): void {
if (isDev) {
const msg = '[AppUpdateService] Application update is not working in dev mode.';
this.logService.info(msg);
@@ -106,7 +109,7 @@ export class AppUpdateService extends MainServiceBase {
return;
}
autoUpdater.allowPrerelease = this.allowPreRelease();
autoUpdater.allowPrerelease = allowPrerelease;
autoUpdater.checkForUpdates()
.then(() => {
this.logService.debug('[AppUpdateService] checkForUpdate success');
@@ -127,12 +130,6 @@ export class AppUpdateService extends MainServiceBase {
return firstRunVersion !== this.app.getVersion();
}
private allowPreRelease() {
const autoUpdateSettings = this.getAutoUpdateSettings();
return autoUpdateSettings && autoUpdateSettings.usePreReleaseUpdate;
}
private checkForUpdateAtStartup() {
const autoUpdateSettings = this.getAutoUpdateSettings();
const checkForUpdate = autoUpdateSettings && autoUpdateSettings.checkForUpdateOnStartUp;
@@ -142,10 +139,10 @@ export class AppUpdateService extends MainServiceBase {
return checkForUpdate;
}
private getAutoUpdateSettings() {
private getAutoUpdateSettings(): AutoUpdateSettings {
const value = storage.get('auto-update-settings');
if (!value) {
return {checkForUpdateOnStartUp: false, usePreReleaseUpdate: false};
return {checkForUpdateOnStartUp: false};
}
return JSON.parse(<string>value);

View File

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

View File

@@ -1,3 +1,4 @@
export * from './auto-update-settings';
export * from './command-line-args';
export * from './notification';
export * from './ipc-response';

View File

@@ -1,10 +1,15 @@
<app-update-available *ngIf="showUpdateAvailable$ | async"
<app-update-available *ngIf="showUpdateAvailable"
[@updateAvailable]
[updateInfo]="updateInfo$ | async"
(updateApp)="updateApp()"
(doNotUpdateApp)="doNotUpdateApp()">
</app-update-available>
<side-menu *ngIf="deviceConfigurationLoaded$ | async"></side-menu>
<div id="main-content" class="main-content">
<side-menu *ngIf="deviceConfigurationLoaded$ | async"
[class.update-panel-visible]="showUpdateAvailable"></side-menu>
<div id="main-content"
class="main-content"
[class.update-panel-visible]="showUpdateAvailable">
<router-outlet></router-outlet>
</div>
<notifier-container></notifier-container>

View File

@@ -12,9 +12,11 @@ import {
deviceConfigurationLoaded,
runningInElectron,
saveToKeyboardState,
keypressCapturing
keypressCapturing,
getUpdateInfo
} from './store';
import { ProgressButtonState } from './store/reducers/progress-button-state';
import { UpdateInfo } from './models/update-info';
@Component({
selector: 'main-app',
@@ -32,11 +34,22 @@ import { ProgressButtonState } from './store/reducers/progress-button-state';
style({transform: 'translateY(0)'}),
animate('400ms ease-in-out', style({transform: 'translateY(100%)'}))
])
]),
trigger('updateAvailable', [
transition(':enter', [
style({transform: 'translateY(-45px)'}),
animate('500ms ease-out', style({transform: 'translateY(0)'}))
]),
transition(':leave', [
style({transform: 'translateY(0)'}),
animate('500ms ease-out', style({transform: 'translateY(-45px)'}))
])
])
]
})
export class MainAppComponent implements OnDestroy {
showUpdateAvailable$: Observable<boolean>;
showUpdateAvailable: boolean;
updateInfo$: Observable<UpdateInfo>;
deviceConfigurationLoaded$: Observable<boolean>;
runningInElectron$: Observable<boolean>;
saveToKeyboardState: ProgressButtonState;
@@ -44,9 +57,12 @@ export class MainAppComponent implements OnDestroy {
private keypressCapturing: boolean;
private saveToKeyboardStateSubscription: Subscription;
private keypressCapturingSubscription: Subscription;
private showUpdateAvailableSubscription: Subscription;
constructor(private store: Store<AppState>) {
this.showUpdateAvailable$ = store.select(getShowAppUpdateAvailable);
this.showUpdateAvailableSubscription = store.select(getShowAppUpdateAvailable)
.subscribe(data => this.showUpdateAvailable = data);
this.updateInfo$ = store.select(getUpdateInfo);
this.deviceConfigurationLoaded$ = store.select(deviceConfigurationLoaded);
this.runningInElectron$ = store.select(runningInElectron);
this.saveToKeyboardStateSubscription = store.select(saveToKeyboardState)
@@ -58,6 +74,7 @@ export class MainAppComponent implements OnDestroy {
ngOnDestroy(): void {
this.saveToKeyboardStateSubscription.unsubscribe();
this.keypressCapturingSubscription.unsubscribe();
this.showUpdateAvailableSubscription.unsubscribe();
}
@HostListener('document:keydown', ['$event'])

View File

@@ -4,10 +4,8 @@
<span class="macro__name pane-title__name">Settings</span>
</h1>
</div>
<auto-update-settings [version]="version"
[settings]="autoUpdateSettings$ | async"
<auto-update-settings [settings]="autoUpdateSettings$ | async"
[checkingForUpdate]="checkingForUpdate$ | async"
(toggleCheckForUpdateOnStartUp)="toogleCheckForUpdateOnStartUp($event)"
(toggleUsePreReleaseUpdate)="toogleUsePreReleaseUpdate($event)"
(checkForUpdate)="checkForUpdate()">
(checkForUpdate)="checkForUpdate($event)">
</auto-update-settings>

View File

@@ -1,15 +1,13 @@
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
import { AutoUpdateSettings } from 'uhk-common';
import { AppState, getAutoUpdateSettings, getCheckingForUpdate } from '../../../store';
import {
CheckForUpdateNowAction,
ToggleCheckForUpdateOnStartupAction,
TogglePreReleaseFlagAction
ToggleCheckForUpdateOnStartupAction
} from '../../../store/actions/auto-update-settings';
import { AutoUpdateSettings } from '../../../models/auto-update-settings';
import { getVersions } from '../../../util';
@Component({
selector: 'settings',
@@ -20,7 +18,6 @@ import { getVersions } from '../../../util';
}
})
export class SettingsComponent {
version: string = getVersions().version;
autoUpdateSettings$: Observable<AutoUpdateSettings>;
checkingForUpdate$: Observable<boolean>;
@@ -33,11 +30,7 @@ export class SettingsComponent {
this.store.dispatch(new ToggleCheckForUpdateOnStartupAction(value));
}
toogleUsePreReleaseUpdate(value: boolean) {
this.store.dispatch(new TogglePreReleaseFlagAction(value));
}
checkForUpdate() {
this.store.dispatch(new CheckForUpdateNowAction());
checkForUpdate(allowPrerelease: boolean): void {
this.store.dispatch(new CheckForUpdateNowAction(allowPrerelease));
}
}

View File

@@ -9,21 +9,7 @@
</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>{{version}}</p>
</div>
</div>
<button class="btn btn-link" (click)="emitCheckForUpdate()">
<button class="btn btn-link" (click)="emitCheckForUpdate($event)">
Check for update
<span *ngIf="checkingForUpdate"
class="fa fa-spinner fa-spin"></span>

View File

@@ -9,13 +9,11 @@ import { State } from '../../store/reducers/auto-update-settings';
})
export class AutoUpdateSettings {
@Input() version: string;
@Input() settings: State | undefined;
@Input() checkingForUpdate: boolean;
@Output() toggleCheckForUpdateOnStartUp = new EventEmitter<boolean>();
@Output() toggleUsePreReleaseUpdate = new EventEmitter<boolean>();
@Output() checkForUpdate = new EventEmitter();
@Output() checkForUpdate = new EventEmitter<boolean>();
constructor() {
}
@@ -24,11 +22,7 @@ export class AutoUpdateSettings {
this.toggleCheckForUpdateOnStartUp.emit(value);
}
emitUsePreReleaseUpdate(value: boolean) {
this.toggleUsePreReleaseUpdate.emit(value);
}
emitCheckForUpdate() {
this.checkForUpdate.emit();
emitCheckForUpdate($event: MouseEvent) {
this.checkForUpdate.emit($event.shiftKey);
}
}

View File

@@ -136,12 +136,12 @@
(click)="toggleHide($event, 'agent')"></i>
</div>
<ul [@toggler]="animation['agent']">
<!--li class="sidebar__level-2--item">
<li class="sidebar__level-2--item">
<div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/settings']"
[class.disabled]="state.updatingFirmware">Settings</a>
</div>
</li-->
</li>
<li class="sidebar__level-2--item">
<div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/help']"

View File

@@ -1,10 +1,15 @@
@import '../../../styles/common';
:host {
background-color: #f5f5f5;
border-right: 1px solid #ccc;
position: fixed;
overflow-y: auto;
top: 0;
width: 250px;
height: 100%;
@include updatePanelVisible();
}
a {
@@ -34,6 +39,7 @@ ul {
&__level-0 {
padding: 0.5rem 1rem 0;
}
&__level-1 {
padding: 0.5rem 1rem 0.5rem 2rem;
}

View File

@@ -1,5 +1,15 @@
<div class="app-update-available-wrapper">
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 class="app-update-available-wrapper" role="alert">
Agent {{ updateInfo.version }} has been <span *ngIf="updateInfo.isPrerelease">pre-</span>released.
<button type="button"
class="btn btn-link underline"
(click)="updateApp.emit()" >Update now!</button>
<button type="button"
class="btn btn-link pull-right"
data-dismiss="alert"
aria-label="Close"
(click)="doNotUpdateApp.emit()">
<span aria-hidden="true">&times;</span>
</button>
</div>

View File

@@ -1,5 +1,19 @@
.app-update-available-wrapper {
display: flex;
justify-content: center;
margin: 0.5rem;
margin: 0;
padding-top: 5px;
padding-bottom: 5px;
background-color: #428bca;
color: white;
text-align: center;
border-radius: 0;
border-width: 0;
.btn-link {
color: white;
&.underline {
text-decoration: underline;
}
}
}

View File

@@ -1,4 +1,6 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Output } from '@angular/core';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { UpdateInfo } from '../../models/update-info';
@Component({
selector: 'app-update-available',
@@ -7,6 +9,8 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Output } from '@angul
changeDetection: ChangeDetectionStrategy.OnPush
})
export class UpdateAvailableComponent {
@Input() updateInfo: UpdateInfo;
@Output() updateApp = new EventEmitter<null>();
@Output() doNotUpdateApp = new EventEmitter<null>();
}

View File

@@ -0,0 +1,4 @@
export interface UpdateInfo {
version: string;
isPrerelease: boolean;
}

View File

@@ -32,8 +32,8 @@ export class AppUpdateRendererService {
this.ipcRenderer.send(IpcEvents.autoUpdater.updateAndRestart);
}
checkForUpdate() {
this.ipcRenderer.send(IpcEvents.autoUpdater.checkForUpdate);
checkForUpdate(allowPrerelease: boolean): void {
this.ipcRenderer.send(IpcEvents.autoUpdater.checkForUpdate, allowPrerelease);
}
private registerEvents() {
@@ -57,7 +57,7 @@ export class AppUpdateRendererService {
this.ipcRenderer.on(IpcEvents.autoUpdater.autoUpdateDownloaded, (event: string, arg: any) => {
this.writeUpdateState(IpcEvents.autoUpdater.autoUpdateDownloaded, arg);
this.dispachStoreAction(new UpdateDownloadedAction());
this.dispachStoreAction(new UpdateDownloadedAction(arg));
});
this.ipcRenderer.on(IpcEvents.autoUpdater.checkForUpdateNotAvailable, (event: string, arg: any) => {

View File

@@ -1,7 +1,5 @@
import { Injectable } from '@angular/core';
import { UserConfiguration } from 'uhk-common';
import { AutoUpdateSettings } from '../models/auto-update-settings';
import { AutoUpdateSettings, UserConfiguration } from 'uhk-common';
@Injectable()
export class DataStorageRepositoryService {

View File

@@ -1,5 +1,6 @@
import { Action } from '@ngrx/store';
import { type } from 'uhk-common';
import { UpdateInfo } from '../../models/update-info';
const PREFIX = '[app-update] ';
@@ -27,6 +28,9 @@ export class DoNotUpdateAppAction implements Action {
export class UpdateDownloadedAction implements Action {
type = ActionTypes.UPDATE_DOWNLOADED;
constructor(public payload: UpdateInfo) {
}
}
export class UpdatingAction implements Action {

View File

@@ -1,7 +1,6 @@
import { Action } from '@ngrx/store';
import { type } from 'uhk-common';
import { AutoUpdateSettings } from '../../models/auto-update-settings';
import { AutoUpdateSettings, type } from 'uhk-common';
const PREFIX = '[app-update-config] ';
@@ -26,6 +25,9 @@ export class ToggleCheckForUpdateOnStartupAction implements Action {
export class CheckForUpdateNowAction implements Action {
type = ActionTypes.CHECK_FOR_UPDATE_NOW;
constructor(public payload?: boolean) {
}
}
export class CheckForUpdateSuccessAction implements Action {

View File

@@ -7,7 +7,7 @@ import { first, map, tap } from 'rxjs/operators';
import { LogService, NotificationType } from 'uhk-common';
import { ActionTypes, UpdateErrorAction } from '../actions/app-update.action';
import { ActionTypes as AutoUpdateActionTypes } from '../actions/auto-update-settings';
import { ActionTypes as AutoUpdateActionTypes, CheckForUpdateNowAction } from '../actions/auto-update-settings';
import { ShowNotificationAction } from '../actions/app';
import { AppUpdateRendererService } from '../../services/app-update-renderer.service';
@@ -23,12 +23,13 @@ export class AppUpdateEffect {
})
);
@Effect({ dispatch: false }) checkForUpdate$: Observable<Action> = this.actions$
.ofType(AutoUpdateActionTypes.CHECK_FOR_UPDATE_NOW)
@Effect({ dispatch: false }) checkForUpdate$ = this.actions$
.ofType<CheckForUpdateNowAction>(AutoUpdateActionTypes.CHECK_FOR_UPDATE_NOW)
.pipe(
tap(() => {
map(action => action.payload),
tap((allowPrerelease: boolean) => {
this.logService.debug('[AppUpdateEffect] call checkForUpdate');
this.appUpdateRendererService.checkForUpdate();
this.appUpdateRendererService.checkForUpdate(allowPrerelease);
})
);

View File

@@ -4,7 +4,7 @@ import { Observable } from 'rxjs/Observable';
import { Action, Store } from '@ngrx/store';
import { map, startWith, switchMap, withLatestFrom } from 'rxjs/operators';
import { NotificationType } from 'uhk-common';
import { AutoUpdateSettings, NotificationType } from 'uhk-common';
import {
ActionTypes,
@@ -16,7 +16,6 @@ import {
import { DataStorageRepositoryService } from '../../services/datastorage-repository.service';
import { AppState, getAutoUpdateSettings } from '../index';
import { initialState } from '../reducers/auto-update-settings';
import { AutoUpdateSettings } from '../../models/auto-update-settings';
import { ShowNotificationAction } from '../actions/app';
@Injectable()

View File

@@ -64,6 +64,7 @@ export const firmwareUpgradeAllowed = createSelector(runningOnNotSupportedWindow
export const appUpdateState = (state: AppState) => state.appUpdate;
export const getShowAppUpdateAvailable = createSelector(appUpdateState, fromAppUpdate.getShowAppUpdateAvailable);
export const getUpdateInfo = createSelector(appUpdateState, fromAppUpdate.getUpdateInfo);
export const appUpdateSettingsState = (state: AppState) => state.autoUpdateSettings;
export const getAutoUpdateSettings = createSelector(appUpdateSettingsState, autoUpdateSettings.getUpdateSettings);

View File

@@ -1,39 +1,48 @@
import { Actions, ActionTypes } from '../actions/app-update.action';
import { Actions, ActionTypes, UpdateDownloadedAction } from '../actions/app-update.action';
import { UpdateInfo } from '../../models/update-info';
export interface State {
updateAvailable: boolean;
updateDownloaded: boolean;
doNotUpdateApp: boolean;
updateInfo: UpdateInfo;
}
export const initialState: State = {
updateAvailable: false,
updateDownloaded: false,
doNotUpdateApp: false
doNotUpdateApp: false,
updateInfo: {
isPrerelease: false,
version: ''
}
};
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_AVAILABLE:
return {
...state,
updateAvailable: true
};
case ActionTypes.UPDATE_DOWNLOADED: {
const newState = Object.assign({}, state);
newState.updateDownloaded = true;
return newState;
}
case ActionTypes.UPDATE_DOWNLOADED:
return {
...state,
updateDownloaded: true,
updateInfo: (action as UpdateDownloadedAction).payload
};
case ActionTypes.DO_NOT_UPDATE_APP:
return {
...state,
doNotUpdateApp: true
};
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;
export const getUpdateInfo = (state: State) => state.updateInfo;

View File

@@ -1,7 +1,8 @@
import { Action } from '@ngrx/store';
import { AutoUpdateSettings } from 'uhk-common';
import { ActionTypes } from '../actions/auto-update-settings';
import { ActionTypes as UpdateActions } from '../actions/app-update.action';
import { AutoUpdateSettings } from '../../models/auto-update-settings';
export interface State extends AutoUpdateSettings {
checkingForUpdate: boolean;

View File

@@ -1,8 +1,7 @@
import * as storage from 'electron-settings';
import { UserConfiguration } from 'uhk-common';
import { AutoUpdateSettings, UserConfiguration } from 'uhk-common';
import { DataStorageRepositoryService } from '../../app/services/datastorage-repository.service';
import { AutoUpdateSettings } from '../../app/models/auto-update-settings';
export class ElectronDataStorageRepositoryService implements DataStorageRepositoryService {
static getValue(key: string): any {

View File

@@ -4,6 +4,7 @@ $zindex-navbar-fixed: 1030;
@import '~spacing-bootstrap-3/spacing';
@import './styles/variables';
@import './styles/common';
@import '~angular-notifier/styles';
@import '~font-awesome/scss/font-awesome';
@import './styles/tooltip';
@@ -44,6 +45,9 @@ html, body {
.main-content {
height: 100%;
top: 0;
@include updatePanelVisible();
}
.main-page-content {
@@ -52,6 +56,15 @@ html, body {
min-height: 100%;
}
app-update-available {
position: fixed;
display: block;
left: 0;
top: 0;
width: 100%;
z-index: 200;
}
.nav-pills > li > a {
cursor: pointer;
}

View File

@@ -12,3 +12,6 @@ $tooltip-border-color: $dark-grey;
$tooltip-background-color: #fff;
$tooltip-inner-color: #000;
$tooltip-arrow-border-width: $tooltip-arrow-width + $tooltip-border-width;
$update-notification-height: 35px;
$main-content-top-margin-on-update: 35px + 10px;

View File

@@ -1,3 +1,5 @@
@import './variables';
%btn-link {
padding: 7px 0;
text-decoration: none;
@@ -8,3 +10,15 @@
outline: none;
}
}
@mixin updatePanelVisible() {
transition-timing-function: ease-out;
transition: 0.5s;
margin-top: 0;
&.update-panel-visible {
transition-timing-function: ease-in;
transition: 0.5s;
margin-top: $main-content-top-margin-on-update;
}
}