electron update window is implemented

This commit is contained in:
병준 박 2019-12-16 01:06:07 +09:00
parent 2baae8e81c
commit e4a42d0dcc
10 changed files with 626 additions and 20 deletions

View File

@ -1,11 +1,11 @@
import * as path from 'path';
import * as url from 'url';
import * as fse from 'fs-extra';
import path from 'path';
import url from 'url';
import fse from 'fs-extra';
import { AnimationQueue } from '../utils/animation-queue';
import {
ElectronNotificationOptions,
DefaultElectronNotificationOptions,
DefaultElectronNotificationOptions
} from '../models/electron-notification-options';
import { screen, BrowserWindow, ipcMain, IpcMainEvent, shell } from 'electron';
import { ElectronNotification } from '../models/electron-notification';
@ -43,12 +43,12 @@ export class ElectronNotificationService {
constructor(options?: ElectronNotificationOptions) {
this.customOptions = {
...DefaultElectronNotificationOptions,
...DefaultElectronNotificationOptions
};
if (!!options) {
this.customOptions = {
...this.customOptions,
...options,
...options
};
}
@ -60,7 +60,7 @@ export class ElectronNotificationService {
if (!!options) {
this.customOptions = {
...this.customOptions,
...options,
...options
};
}
this.calcDimensions();
@ -89,7 +89,7 @@ export class ElectronNotificationService {
this.animationQueue.push({
context: this,
func: this.showNotification,
args: [notification],
args: [notification]
});
return notification.id;
}
@ -124,7 +124,7 @@ export class ElectronNotificationService {
this.lowerRightCornerPosition = {
x: display.bounds.x + display.workArea.x + display.workAreaSize.width,
y: display.bounds.y + display.workArea.y + display.workAreaSize.height,
y: display.bounds.y + display.workArea.y + display.workAreaSize.height
};
this.calcDimensions();
@ -177,7 +177,7 @@ export class ElectronNotificationService {
notificationWindow[onClickElectronNotification]({
type: ElectronNotificationEventType.Click,
id: notification.id,
close: self.buildCloseNotificationSafely(onClose),
close: self.buildCloseNotificationSafely(onClose)
});
delete notificationWindow[onClickElectronNotification];
}
@ -188,17 +188,17 @@ export class ElectronNotificationService {
private calcDimensions() {
this.totalDimension = {
width: this.customOptions.width + this.customOptions.padding,
height: this.customOptions.height + this.customOptions.padding,
height: this.customOptions.height + this.customOptions.padding
};
this.firstPosition = {
x: this.lowerRightCornerPosition.x - this.totalDimension.width,
y: this.lowerRightCornerPosition.y - this.totalDimension.height,
y: this.lowerRightCornerPosition.y - this.totalDimension.height
};
this.nextInsertPosition = {
x: this.firstPosition.x,
y: this.firstPosition.y,
y: this.firstPosition.y
};
}
@ -217,7 +217,7 @@ export class ElectronNotificationService {
this.templateUrl = url.format({
pathname: this.customOptions.templatePath,
protocol: 'file:',
slashes: true,
slashes: true
});
} catch (e) {
console.log(
@ -266,7 +266,7 @@ export class ElectronNotificationService {
notification.onShow({
type: ElectronNotificationEventType.Show,
id: notification.id,
close: onCloseNotificationSafely,
close: onCloseNotificationSafely
});
}
@ -321,7 +321,7 @@ export class ElectronNotificationService {
if (!!notificationWindow[onCloseElectronNotification]) {
notificationWindow[onCloseElectronNotification]({
type: e,
id: notification.id,
id: notification.id
});
delete notificationWindow[onCloseElectronNotification];
}
@ -353,7 +353,7 @@ export class ElectronNotificationService {
self.animationQueue.push({
context: self,
func: onClose,
args: [reason],
args: [reason]
});
};
}
@ -366,7 +366,7 @@ export class ElectronNotificationService {
this.animationQueue.push({
context: this,
func: this.showNotification,
args: [this.notificationQueue.shift()],
args: [this.notificationQueue.shift()]
});
}
}

View File

@ -0,0 +1,22 @@
import { BrowserWindowConstructorOptions } from 'electron';
export interface ElectronUpdateWindowOptions
extends BrowserWindowConstructorOptions {
templatePath?: string;
onReady?: () => void;
onAcceptUpdate?: () => void;
onDenyUpdate?: () => void;
onCancelDownload?: () => void;
}
export const DefaultElectronUpdateWindowOptions: ElectronUpdateWindowOptions = {
width: 500,
height: 160,
frame: false,
skipTaskbar: true,
alwaysOnTop: true,
maximizable: false,
webPreferences: {
nodeIntegration: true
}
};

View File

@ -1 +1,122 @@
export class ElectronUpdateWindowService {}
import { BrowserWindow, ipcMain } from 'electron';
import url from 'url';
import fse from 'fs-extra';
import {
ElectronUpdateWindowOptions,
DefaultElectronUpdateWindowOptions
} from '../models/electron-update-window-options';
import { Channel } from '../types/channel.type';
export class ElectronUpdateWindowService {
private customOptions: ElectronUpdateWindowOptions;
private browserWindow: BrowserWindow;
private templateUrl: string;
constructor(options: ElectronUpdateWindowOptions) {
this.customOptions = {
...DefaultElectronUpdateWindowOptions
};
if (!!options) {
this.customOptions = {
...this.customOptions,
...options
};
}
}
set options(options: ElectronUpdateWindowOptions) {
if (!!options) {
this.customOptions = {
...this.customOptions,
...options
};
}
}
get options(): ElectronUpdateWindowOptions {
return this.customOptions;
}
set templatePath(templatePath: string) {
if (!!templatePath) {
this.customOptions.templatePath = templatePath;
this.updateTemplatePath();
}
}
get templatePath(): string {
if (!this.templateUrl) {
this.updateTemplatePath();
}
return this.templateUrl;
}
show() {
this.browserWindow = new BrowserWindow(this.customOptions);
this.browserWindow.loadURL(this.templatePath);
this.browserWindow.on('closed', () => {
this.browserWindow = null;
});
this.browserWindow.webContents.on('did-finish-load', () => {
if (process.env.NODE_ENV === 'development') {
this.browserWindow.webContents.openDevTools();
}
if (!!this.customOptions.onReady) {
this.customOptions.onReady();
}
});
ipcMain.on(Channel.acceptUpdate, this._acceptUpdateHandler.bind(this));
ipcMain.on(Channel.denyUpdate, this._denyUpdateHandler.bind(this));
ipcMain.on(Channel.cancelDownload, this._cancelDownloadHandler.bind(this));
}
setDownloadValue(value: number, total: number) {
this.browserWindow.webContents.send(Channel.downloadProcess, value, total);
}
setDownloadComplete() {
this.browserWindow.webContents.send(Channel.downloadComplete);
}
close() {
if (!this.browserWindow || this.browserWindow.isDestroyed()) {
return;
}
this.browserWindow.destroy();
}
_acceptUpdateHandler() {
if (!!this.customOptions.onAcceptUpdate) {
this.customOptions.onAcceptUpdate();
}
}
_denyUpdateHandler() {
if (!!this.customOptions.onDenyUpdate) {
this.customOptions.onDenyUpdate();
}
}
_cancelDownloadHandler() {
if (!!this.customOptions.onCancelDownload) {
this.customOptions.onCancelDownload();
}
}
private updateTemplatePath() {
try {
fse.statSync(this.customOptions.templatePath).isFile();
this.templateUrl = url.format({
pathname: this.customOptions.templatePath,
protocol: 'file:',
slashes: true
});
} catch (e) {
console.log(
'electron-update-window: Could not find template ("' +
this.customOptions.templatePath +
'").'
);
}
}
}

View File

@ -0,0 +1,8 @@
export enum Channel {
acceptUpdate = 'UCAP::ElectronUpdateWindow::acceptUpdate',
denyUpdate = 'UCAP::ElectronUpdateWindow::denyUpdate',
cancelDownload = 'UCAP::ElectronUpdateWindow::cancelDownload',
downloadProcess = 'UCAP::ElectronUpdateWindow::downloadProcess',
downloadComplete = 'UCAP::ElectronUpdateWindow::downloadComplete'
}

View File

@ -1,3 +1,7 @@
/*
* Public API Surface of ucap-webmessenger-electron-update-window
*/
export * from './lib/models/electron-update-window-options';
export * from './lib/services/electron-update-window.service';
export * from './lib/types/channel.type';

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -0,0 +1,57 @@
'use strict';
const { ipcRenderer } = require('electron');
const updateWindowContainer = document.getElementById(
'update-window-container'
);
const confirmation = document.getElementById('confirmation');
const downloading = document.getElementById('downloading');
const update = document.getElementById('update');
const confirmationOk = document.getElementById('confirmation-ok');
const confirmationCancel = document.getElementById('confirmation-cancel');
const downloadingProgressBar = document.getElementById(
'downloading-progress-bar'
);
const downloadingProgressLabel = document.getElementById(
'downloading-progress-label'
);
const downloadingCancel = document.getElementById('downloading-cancel');
confirmationOk &&
confirmationOk.addEventListener('click', e => {
console.log('UCAP::ElectronUpdateWindow::acceptUpdate');
ipcRenderer.send('UCAP::ElectronUpdateWindow::acceptUpdate');
downloadingProgressBar.style.width = `0%`;
downloadingProgressLabel.innerText = `0%`;
updateWindowContainer.style.transform = 'translateX(-500px)';
});
confirmationCancel &&
confirmationCancel.addEventListener('click', e => {
ipcRenderer.send('UCAP::ElectronUpdateWindow::denyUpdate');
});
downloadingCancel &&
downloadingCancel.addEventListener('click', e => {
ipcRenderer.send('UCAP::ElectronUpdateWindow::cancelDownload');
});
ipcRenderer.on(
'UCAP::ElectronUpdateWindow::downloadProcess',
(event, ...args) => {
const percentage = (args[0] / args[1]) * 100;
downloadingProgressBar.style.width = `${percentage}%`;
downloadingProgressLabel.innerText = `${percentage}%`;
}
);
ipcRenderer.on(
'UCAP::ElectronUpdateWindow::downloadComplete',
(event, ...args) => {
updateWindowContainer.style.transform = 'translateX(-1000px)';
}
);

View File

@ -0,0 +1,230 @@
@charset "utf-8";
html {
height: 100%;
overflow-y: hidden;
-webkit-user-select: none;
-webkit-app-region: drag;
}
body {
position: absolute;
width: 500px;
height: 160px;
padding: 0;
margin: 0;
color: #333;
font-family: '나눔고딕', Malgun Gothic, '맑은고딕', Arial, Dotum, '돋움',
Gulim, '굴림';
font-size: 12px;
line-height: 18px !important;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
div,
p,
ol,
ul,
li,
h1,
h2,
h3,
h4,
h5,
h6,
form,
iframe,
dl,
dt,
dd,
a {
margin: 0;
padding: 0;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
::-webkit-scrollbar {
display: none;
}
.no-drag {
-webkit-app-region: no-drag;
}
.popup {
border: 1px solid #666;
position: absolute;
}
.popup header {
width: 100%;
height: 36px;
background-image: none;
color: #6f6f6f;
background-color: #fff;
border-bottom: solid 1px #28abdb;
}
.popup header h1 {
width: 100%;
background: none;
text-align: center;
font-size: 16px;
line-height: 50px;
font-size: 14px;
margin: 0;
padding: 0;
}
.popup .btn_close {
position: absolute;
top: 4px;
right: 4px;
width: 20px;
height: 25px;
font-size: 0;
margin-right: 6px;
vertical-align: middle;
background: url(../images/btnimg_top_close.png) no-repeat 50% 50%;
}
.popup .btn_close:hover {
opacity: 0.8;
}
.versionup .btns {
height: 50px;
position: absolute;
bottom: 0;
width: 100%;
text-align: center;
padding: 10px;
}
.btns ul {
list-style: none;
}
.btns li {
display: inline-block;
width: 70px;
height: 30px;
margin: 0 2px;
}
.btns li a {
display: block;
width: 70px;
height: 30px;
border-radius: 3px;
text-align: center;
line-height: 28px;
text-decoration: none;
}
.btnNormal {
background-color: #eee;
color: #333;
}
.btnSpecial {
background-color: #3385bd;
color: #fff;
}
.btns li a:hover {
opacity: 0.8;
}
#update-window-container {
position: absolute;
width: 1500px;
height: 160px;
overflow: hidden;
margin-top: 0px;
margin-left: 0px;
-webkit-transition: all 500ms ease-in-out;
-moz-transition: all 500ms ease-in-out;
-o-transition: all 500ms ease-in-out;
transition: all 500ms ease-in-out;
}
/*******************************************************************************************************************************************
.versionup
*******************************************************************************************************************************************/
/* .versionup {
position: absolute;
top: 0;
left: 500px;
}
.versionup.on {
position: absolute;
top: 0;
left: 0;
} */
.versionup {
width: 500px;
height: 160px;
background-color: #fff;
}
.versionup header {
height: 50px !important;
border-bottom: none !important;
}
.versionup header h1 {
font-size: 16px;
line-height: 50px;
}
.versionup .download p {
text-align: center;
margin-bottom: 10px;
font-size: 12px;
}
.versionup .download {
position: relative;
padding: 0 20px;
}
.versionup .btns {
height: 50px;
position: absolute;
bottom: 0;
width: 100%;
text-align: center;
}
.versionup.step1 {
position: absolute;
top: 0;
left: 0;
}
.versionup.step2 {
position: absolute;
top: 0;
left: 500;
}
.versionup.step3 {
position: absolute;
top: 0;
left: 1000;
}
/*******************************************************************************************************************************************
progress
*******************************************************************************************************************************************/
.progress {
background-color: #efefef;
height: 12px;
margin: 0 !important;
}
.progress .bar {
width: 0%;
height: 12px;
position: relative;
color: #fff;
font-size: 11px;
text-align: right;
margin: 0 !important;
line-height: 12px;
}
/*theme별 색상변경*/
.versionup .download p {
color: #13b7eb;
}
.progress .bar {
background-color: #13b7eb;
}

View File

@ -0,0 +1,86 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>DS Talk Update</title>
<meta charset="UTF-8" />
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<link type="text/css" rel="stylesheet" href="styles/update-window.css" />
</head>
<body>
<div id="update-window-container">
<!--[S]버전다운로드-->
<div id="confirmation" class="versionup popup step1">
<header>
<h1>DS Talk의 새로운 버전이 존재합니다.</h1>
</header>
<!-- <a class="no-drag btn_close">
<label class="global">닫기</label>
</a> -->
<div class="download">
<p>업데이트 하시겠습니까?</p>
</div>
<div class="btns">
<ul>
<li>
<a id="confirmation-ok" href="#" class="no-drag btnSpecial">
<label class="global">확인</label>
</a>
</li>
<li>
<a id="confirmation-cancel" href="#" class="no-drag btnNormal">
<label class="global">취소</label>
</a>
</li>
</ul>
</div>
</div>
<!--[E]버전다운로드-->
<!--[S]버전다운로드-->
<div id="downloading" class="versionup popup step2">
<header>
<h1>다운로드</h1>
</header>
<!-- <a class="no-drag btn_close">
<label class="global">닫기</label>
</a> -->
<div class="download">
<p>잠시만 기다려 주십시오</p>
<div class="progress">
<div id="downloading-progress-bar" class="bar" style="width:50%;">
<label id="downloading-progress-label">50%</label>
</div>
</div>
</div>
<div class="btns">
<ul>
<li>
<a id="downloading-cancel" href="#" class="no-drag btnNormal">
<label class="global">취소</label>
</a>
</li>
</ul>
</div>
</div>
<!--[E]버전다운로드-->
<!--[S]버전다운로드-->
<div id="update" class="versionup popup step3">
<header>
<h1>다운로드 완료</h1>
</header>
<!-- <a class="no-drag btn_close">
<label class="global">닫기</label>
</a> -->
<div class="download">
<p>업데이트를 설치하고 DS Talk을 재시작합니다.</p>
</div>
<div class="btns">
<ul></ul>
</div>
</div>
<!--[E]버전다운로드-->
</div>
<script src="preload.js"></script>
</body>
</html>

View File

@ -1,6 +1,7 @@
import { app, ipcMain, IpcMainEvent, Tray, Menu, shell } from 'electron';
import path from 'path';
import fse from 'fs-extra';
import semver from 'semver';
import { AppWindow } from './app/AppWindow';
import { now } from './util/now';
@ -15,6 +16,7 @@ import {
MessengerChannel
} from '@ucap-webmessenger/native-electron';
import { ElectronNotificationService } from '@ucap-webmessenger/electron-notification';
import { ElectronUpdateWindowService } from '@ucap-webmessenger/electron-update-window';
import { root } from './util/root';
import { DefaultFolder } from './lib/default-folder';
@ -48,6 +50,7 @@ let onDidLoadFns: Array<OnDidLoadFn> | null = [];
let preventQuit = false;
let notificationService: ElectronNotificationService | null;
let updateWindowService: ElectronUpdateWindowService | null;
function handleUncaughtException(error: Error) {
preventQuit = true;
@ -242,6 +245,41 @@ app.on(ElectronAppChannel.Ready, () => {
'resources/notification/template.html'
);
updateWindowService = new ElectronUpdateWindowService({
width: 500,
height: 160,
frame: false,
skipTaskbar: true,
alwaysOnTop: true,
maximizable: false,
onReady: () => {},
onAcceptUpdate: () => {
log.info('OnAcceptUpdate');
autoUpdaterCancellationToken = new CancellationToken();
autoUpdater.downloadUpdate(autoUpdaterCancellationToken);
},
onDenyUpdate: () => {
log.info('OnDenyUpdate');
updateWindowService.close();
},
onCancelDownload: () => {
autoUpdaterCancellationToken.cancel();
updateWindowService.close();
}
});
updateWindowService.options.webPreferences.preload = path.join(
__dirname,
'resources/update-window/preload.js'
);
updateWindowService.templatePath = path.join(
__dirname,
'resources/update-window/template.html'
);
// updateWindowService.show();
ipcMain.on('uncaught-exception', (event: IpcMainEvent, error: Error) => {
handleUncaughtException(error);
});
@ -281,7 +319,13 @@ function onDidLoad(fn: OnDidLoadFn) {
}
ipcMain.on(UpdaterChannel.Check, (event: IpcMainEvent, ...args: any[]) => {
event.returnValue = false;
const ver = args[0];
if (semver.lt(app.getVersion(), ver)) {
autoUpdater.checkForUpdatesAndNotify();
return true;
} else {
return false;
}
});
ipcMain.on(
@ -443,3 +487,37 @@ ipcMain.on(
console.log('Channel.closeAllNotify', args);
}
);
autoUpdater.on('checking-for-update', () => {
log.info('Checking for update...');
});
autoUpdater.on('update-available', info => {
log.info(info);
log.info('Update available.');
updateWindowService.show();
});
autoUpdater.on('update-not-available', info => {
log.info('Update not available.');
});
autoUpdater.on('error', err => {
log.info('Error in auto-updater. ' + err);
});
autoUpdater.on('download-progress', progressObj => {
let logMessage = 'Download speed: ' + progressObj.bytesPerSecond;
logMessage = logMessage + ' - Downloaded ' + progressObj.percent + '%';
logMessage =
logMessage + ' (' + progressObj.transferred + '/' + progressObj.total + ')';
log.info(logMessage);
updateWindowService.setDownloadValue(
progressObj.transferred,
progressObj.total
);
});
autoUpdater.on('update-downloaded', info => {
log.info('Update downloaded');
updateWindowService.setDownloadComplete();
autoUpdater.quitAndInstall(true, true);
});