Merge branch 'master' of http://10.81.13.221:6990/Web/next-ucap-messenger
70
angular.json
|
@ -1445,6 +1445,76 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"ucap-webmessenger-electron-notification": {
|
||||||
|
"projectType": "library",
|
||||||
|
"root": "projects/ucap-webmessenger-electron-notification",
|
||||||
|
"sourceRoot": "projects/ucap-webmessenger-electron-notification/src",
|
||||||
|
"prefix": "ucap-electron-notification",
|
||||||
|
"architect": {
|
||||||
|
"build": {
|
||||||
|
"builder": "@angular-devkit/build-ng-packagr:build",
|
||||||
|
"options": {
|
||||||
|
"tsConfig": "projects/ucap-webmessenger-electron-notification/tsconfig.lib.json",
|
||||||
|
"project": "projects/ucap-webmessenger-electron-notification/ng-package.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"builder": "@angular-devkit/build-angular:karma",
|
||||||
|
"options": {
|
||||||
|
"main": "projects/ucap-webmessenger-electron-notification/src/test.ts",
|
||||||
|
"tsConfig": "projects/ucap-webmessenger-electron-notification/tsconfig.spec.json",
|
||||||
|
"karmaConfig": "projects/ucap-webmessenger-electron-notification/karma.conf.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"builder": "@angular-devkit/build-angular:tslint",
|
||||||
|
"options": {
|
||||||
|
"tsConfig": [
|
||||||
|
"projects/ucap-webmessenger-electron-notification/tsconfig.lib.json",
|
||||||
|
"projects/ucap-webmessenger-electron-notification/tsconfig.spec.json"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"**/node_modules/**"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ucap-webmessenger-electron-core": {
|
||||||
|
"projectType": "library",
|
||||||
|
"root": "projects/ucap-webmessenger-electron-core",
|
||||||
|
"sourceRoot": "projects/ucap-webmessenger-electron-core/src",
|
||||||
|
"prefix": "ucap-electron-core",
|
||||||
|
"architect": {
|
||||||
|
"build": {
|
||||||
|
"builder": "@angular-devkit/build-ng-packagr:build",
|
||||||
|
"options": {
|
||||||
|
"tsConfig": "projects/ucap-webmessenger-electron-core/tsconfig.lib.json",
|
||||||
|
"project": "projects/ucap-webmessenger-electron-core/ng-package.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"builder": "@angular-devkit/build-angular:karma",
|
||||||
|
"options": {
|
||||||
|
"main": "projects/ucap-webmessenger-electron-core/src/test.ts",
|
||||||
|
"tsConfig": "projects/ucap-webmessenger-electron-core/tsconfig.spec.json",
|
||||||
|
"karmaConfig": "projects/ucap-webmessenger-electron-core/karma.conf.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"builder": "@angular-devkit/build-angular:tslint",
|
||||||
|
"options": {
|
||||||
|
"tsConfig": [
|
||||||
|
"projects/ucap-webmessenger-electron-core/tsconfig.lib.json",
|
||||||
|
"projects/ucap-webmessenger-electron-core/tsconfig.spec.json"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"**/node_modules/**"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"defaultProject": "ucap-webmessenger-app"
|
"defaultProject": "ucap-webmessenger-app"
|
||||||
|
|
|
@ -79,6 +79,16 @@ const mainConfig: webpack.Configuration = {
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.js', '.ts'],
|
extensions: ['.js', '.ts'],
|
||||||
alias: {
|
alias: {
|
||||||
|
'@ucap-webmessenger/electron-core': path.resolve(
|
||||||
|
__dirname,
|
||||||
|
'..',
|
||||||
|
'projects/ucap-webmessenger-electron-core/src/public-api'
|
||||||
|
),
|
||||||
|
'@ucap-webmessenger/electron-notification': path.resolve(
|
||||||
|
__dirname,
|
||||||
|
'..',
|
||||||
|
'projects/ucap-webmessenger-electron-notification/src/public-api'
|
||||||
|
),
|
||||||
'@ucap-webmessenger/native': path.resolve(
|
'@ucap-webmessenger/native': path.resolve(
|
||||||
__dirname,
|
__dirname,
|
||||||
'..',
|
'..',
|
||||||
|
|
BIN
main/resources/notification/image/btn_call_message.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
main/resources/notification/image/btn_call_receive.png
Normal file
After Width: | Height: | Size: 842 B |
BIN
main/resources/notification/image/btn_call_refuse.png
Normal file
After Width: | Height: | Size: 643 B |
BIN
main/resources/notification/image/btn_call_transfer.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
main/resources/notification/image/btn_close.png
Normal file
After Width: | Height: | Size: 226 B |
BIN
main/resources/notification/image/btn_close_gray.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
main/resources/notification/image/btn_noti_call.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
main/resources/notification/image/img_nophoto_50.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
143
main/resources/notification/preload.js
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const electron = require('electron');
|
||||||
|
const ipc = electron.ipcRenderer;
|
||||||
|
const winId = electron.remote.getCurrentWindow().id;
|
||||||
|
|
||||||
|
function setStyle(config) {
|
||||||
|
// Style it
|
||||||
|
let notiDoc = global.window.document;
|
||||||
|
let container = notiDoc.getElementById('container');
|
||||||
|
let appIcon = notiDoc.getElementById('appIcon');
|
||||||
|
let image = notiDoc.getElementById('image');
|
||||||
|
let close = notiDoc.getElementById('close');
|
||||||
|
let message = notiDoc.getElementById('message');
|
||||||
|
// Default style
|
||||||
|
setStyleOnDomElement(config.defaultStyleContainer, container);
|
||||||
|
// Size and radius
|
||||||
|
let style = {
|
||||||
|
height:
|
||||||
|
config.height -
|
||||||
|
2 * config.borderRadius -
|
||||||
|
2 * config.defaultStyleContainer.padding,
|
||||||
|
width:
|
||||||
|
config.width -
|
||||||
|
2 * config.borderRadius -
|
||||||
|
2 * config.defaultStyleContainer.padding,
|
||||||
|
borderRadius: config.borderRadius + 'px'
|
||||||
|
};
|
||||||
|
setStyleOnDomElement(style, container);
|
||||||
|
// Style appIcon or hide
|
||||||
|
if (config.appIcon) {
|
||||||
|
setStyleOnDomElement(config.defaultStyleAppIcon, appIcon);
|
||||||
|
appIcon.src = config.appIcon;
|
||||||
|
} else {
|
||||||
|
setStyleOnDomElement(
|
||||||
|
{
|
||||||
|
display: 'none'
|
||||||
|
},
|
||||||
|
appIcon
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Style image
|
||||||
|
setStyleOnDomElement(config.defaultStyleImage, image);
|
||||||
|
// Style close button
|
||||||
|
setStyleOnDomElement(config.defaultStyleClose, close);
|
||||||
|
// Remove margin from text p
|
||||||
|
setStyleOnDomElement(config.defaultStyleText, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setContents(event, notificationObj) {
|
||||||
|
// sound
|
||||||
|
if (notificationObj.sound) {
|
||||||
|
// Check if file is accessible
|
||||||
|
try {
|
||||||
|
// If it's a local file, check it's existence
|
||||||
|
// Won't check remote files e.g. http://
|
||||||
|
if (
|
||||||
|
notificationObj.sound.match(/^file\:/) !== null ||
|
||||||
|
notificationObj.sound.match(/^\//) !== null
|
||||||
|
) {
|
||||||
|
let audio = new global.window.Audio(notificationObj.sound);
|
||||||
|
audio.play();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log(
|
||||||
|
'electron-notify: ERROR could not find sound file: ' +
|
||||||
|
notificationObj.sound.replace('file://', ''),
|
||||||
|
e,
|
||||||
|
e.stack
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let notiDoc = global.window.document;
|
||||||
|
// Title
|
||||||
|
let titleDoc = notiDoc.getElementById('title');
|
||||||
|
titleDoc.innerHTML = notificationObj.title || '';
|
||||||
|
// message
|
||||||
|
let messageDoc = notiDoc.getElementById('message');
|
||||||
|
messageDoc.innerHTML = notificationObj.text || '';
|
||||||
|
// Image
|
||||||
|
let imageDoc = notiDoc.getElementById('image');
|
||||||
|
if (notificationObj.image) {
|
||||||
|
imageDoc.src = notificationObj.image;
|
||||||
|
} else {
|
||||||
|
setStyleOnDomElement({ display: 'none' }, imageDoc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close button
|
||||||
|
let closeButton = notiDoc.getElementById('close');
|
||||||
|
closeButton.addEventListener('click', function(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
ipc.send('UCAP::ElectronNotification::close', winId, notificationObj);
|
||||||
|
});
|
||||||
|
|
||||||
|
// URL
|
||||||
|
let container = notiDoc.getElementById('container');
|
||||||
|
container.addEventListener('click', function() {
|
||||||
|
ipc.send('UCAP::ElectronNotification::click', winId, notificationObj);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setStyleOnDomElement(styleObj, domElement) {
|
||||||
|
try {
|
||||||
|
for (let styleAttr in styleObj) {
|
||||||
|
domElement.style[styleAttr] = styleObj[styleAttr];
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(
|
||||||
|
'electron-notify: Could not set style on domElement',
|
||||||
|
styleObj,
|
||||||
|
domElement
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadConfig(event, conf) {
|
||||||
|
setStyle(conf || {});
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
let notiDoc = global.window.document;
|
||||||
|
let container = notiDoc.getElementById('container');
|
||||||
|
let closeButton = notiDoc.getElementById('close');
|
||||||
|
|
||||||
|
// Remove event listener
|
||||||
|
let newContainer = container.cloneNode(true);
|
||||||
|
container.parentNode.replaceChild(newContainer, container);
|
||||||
|
let newCloseButton = closeButton.cloneNode(true);
|
||||||
|
closeButton.parentNode.replaceChild(newCloseButton, closeButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
ipc.on('UCAP::ElectronNotification::BrowserWindowSetContents', setContents);
|
||||||
|
ipc.on('UCAP::ElectronNotification::loadConfig', loadConfig);
|
||||||
|
ipc.on('UCAP::ElectronNotification::reset', reset);
|
||||||
|
|
||||||
|
function log() {
|
||||||
|
console.log.apply(console, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete global.require;
|
||||||
|
delete global.exports;
|
||||||
|
delete global.module;
|
BIN
main/resources/notification/sound/messageAlarm.mp3
Normal file
130
main/resources/notification/styles/noti_messege.css
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
html {
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
body * {
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
ul,
|
||||||
|
ol {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
a:link,
|
||||||
|
a:visited,
|
||||||
|
a:hover,
|
||||||
|
a:active {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noti_messege {
|
||||||
|
width: 340px;
|
||||||
|
height: 100px;
|
||||||
|
border: 1px solid #666;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0px 0px 3px 0px #e7e7e7;
|
||||||
|
}
|
||||||
|
.info {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 16px 14px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.btn_close {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
right: 6px;
|
||||||
|
top: 6px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background: url(../image/btn_close_gray.png) no-repeat 50% 50%;
|
||||||
|
}
|
||||||
|
.btn_close:hover {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
.photo {
|
||||||
|
position: relative;
|
||||||
|
top: 0px;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
margin: 4px 0;
|
||||||
|
width: 54px;
|
||||||
|
height: 54px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #5bc1ff url(../image/img_nophoto_50.png) no-repeat 50% 50%;
|
||||||
|
border: 2px solid #ddd;
|
||||||
|
}
|
||||||
|
.info .profile {
|
||||||
|
position: absolute;
|
||||||
|
width: 60px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.photo img {
|
||||||
|
overflow: hidden;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 50px;
|
||||||
|
}
|
||||||
|
.noti_messege .info .profile + div {
|
||||||
|
padding-left: 70px;
|
||||||
|
position: relative;
|
||||||
|
line-height: 180%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.sender {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
color: #333;
|
||||||
|
width: 94%;
|
||||||
|
}
|
||||||
|
.sender .name {
|
||||||
|
color: #2e7fb5;
|
||||||
|
}
|
||||||
|
.message {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.ellipsis {
|
||||||
|
display: block;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
word-wrap: normal;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.ellipsis_row2 {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
word-wrap: break-word;
|
||||||
|
line-height: 1.6em;
|
||||||
|
height: 3.2em;
|
||||||
|
}
|
50
main/resources/notification/template.html
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
|
||||||
|
<HTML>
|
||||||
|
<HEAD>
|
||||||
|
<title>[개발]M Messenger - 메시지 알림</title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link
|
||||||
|
type="text/css"
|
||||||
|
rel="stylesheet"
|
||||||
|
href="styles/noti_messege.css"
|
||||||
|
/>
|
||||||
|
</HEAD>
|
||||||
|
<style>
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
overflow-y: hidden;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<BODY>
|
||||||
|
<div class="noti_messege" id="container">
|
||||||
|
<div class="info">
|
||||||
|
<a class="btn_close" id="close"></a>
|
||||||
|
<div class="profile">
|
||||||
|
<div class="photo">
|
||||||
|
<img src="" id="appIcon" />
|
||||||
|
<img
|
||||||
|
src=""
|
||||||
|
id="image"
|
||||||
|
onerror="this.src='image/img_nophoto_50.png';"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<ul id="text">
|
||||||
|
<li class="sender ellipsis" id="title">
|
||||||
|
<span class="name">김 수안무 거북이와 두루미</span>님이 메시지를
|
||||||
|
보냈습니다.
|
||||||
|
</li>
|
||||||
|
<li class="message ellipsis_row2" id="message">
|
||||||
|
홍길동 대리(솔루션사업팀)홍길동 대리(솔루션사업팀)홍길동
|
||||||
|
대리(솔루션사업팀)홍길동 대리(솔루션사업팀)
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</BODY>
|
||||||
|
</HTML>
|
|
@ -7,6 +7,11 @@ import { EventEmitter } from 'events';
|
||||||
|
|
||||||
import { now } from '../util/now';
|
import { now } from '../util/now';
|
||||||
import { registerWindowStateChangedEvents } from '../lib/window-state';
|
import { registerWindowStateChangedEvents } from '../lib/window-state';
|
||||||
|
import {
|
||||||
|
ElectronAppChannel,
|
||||||
|
ElectronBrowserWindowChannel,
|
||||||
|
ElectronWebContentsChannel
|
||||||
|
} from '@ucap-webmessenger/electron-core';
|
||||||
|
|
||||||
export class AppWindow {
|
export class AppWindow {
|
||||||
private window: BrowserWindow | null = null;
|
private window: BrowserWindow | null = null;
|
||||||
|
@ -62,11 +67,11 @@ export class AppWindow {
|
||||||
savedWindowState.manage(this.window);
|
savedWindowState.manage(this.window);
|
||||||
|
|
||||||
let quitting = false;
|
let quitting = false;
|
||||||
app.on('before-quit', () => {
|
app.on(ElectronAppChannel.BeforeQuit, () => {
|
||||||
quitting = true;
|
quitting = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('will-quit', (event: IpcMainEvent) => {
|
ipcMain.on(ElectronAppChannel.WillQuit, (event: IpcMainEvent) => {
|
||||||
quitting = true;
|
quitting = true;
|
||||||
event.returnValue = true;
|
event.returnValue = true;
|
||||||
});
|
});
|
||||||
|
@ -75,7 +80,7 @@ export class AppWindow {
|
||||||
// lets us activate quickly and keep all our interesting logic in the
|
// lets us activate quickly and keep all our interesting logic in the
|
||||||
// renderer.
|
// renderer.
|
||||||
if (__DARWIN__) {
|
if (__DARWIN__) {
|
||||||
this.window.on('close', e => {
|
this.window.on(ElectronBrowserWindowChannel.Close, e => {
|
||||||
if (!quitting) {
|
if (!quitting) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
|
@ -92,8 +97,8 @@ export class AppWindow {
|
||||||
//
|
//
|
||||||
// can be tidied up once https://github.com/electron/electron/issues/12971
|
// can be tidied up once https://github.com/electron/electron/issues/12971
|
||||||
// has been confirmed as resolved
|
// has been confirmed as resolved
|
||||||
this.window.once('ready-to-show', () => {
|
this.window.once(ElectronBrowserWindowChannel.ReadyToShow, () => {
|
||||||
this.window.on('unmaximize', () => {
|
this.window.on(ElectronBrowserWindowChannel.Unmaximize, () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const bounds = this.window.getBounds();
|
const bounds = this.window.getBounds();
|
||||||
bounds.width += 1;
|
bounds.width += 1;
|
||||||
|
@ -109,26 +114,30 @@ export class AppWindow {
|
||||||
public load(): void {
|
public load(): void {
|
||||||
let startLoad = 0;
|
let startLoad = 0;
|
||||||
|
|
||||||
this.window.webContents.once('did-start-loading', () => {
|
this.window.webContents.once(
|
||||||
|
ElectronWebContentsChannel.DidStartLoading,
|
||||||
|
() => {
|
||||||
this._rendererReadyTime = null;
|
this._rendererReadyTime = null;
|
||||||
this._loadTime = null;
|
this._loadTime = null;
|
||||||
|
|
||||||
startLoad = now();
|
startLoad = now();
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.window.webContents.once(
|
||||||
|
ElectronWebContentsChannel.DidFinishLoad,
|
||||||
|
() => {
|
||||||
|
this.window.webContents.setVisualZoomLevelLimits(1, 1);
|
||||||
|
|
||||||
this.window.webContents.once('did-finish-load', () => {
|
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
this.window.webContents.openDevTools();
|
this.window.webContents.openDevTools();
|
||||||
}
|
}
|
||||||
|
|
||||||
this._loadTime = now() - startLoad;
|
this._loadTime = now() - startLoad;
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
this.window.webContents.on('did-finish-load', () => {
|
this.window.webContents.on(ElectronWebContentsChannel.DidFailLoad, () => {
|
||||||
this.window.webContents.setVisualZoomLevelLimits(1, 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.window.webContents.on('did-fail-load', () => {
|
|
||||||
this.window.webContents.openDevTools();
|
this.window.webContents.openDevTools();
|
||||||
this.window.show();
|
this.window.show();
|
||||||
});
|
});
|
||||||
|
@ -158,7 +167,7 @@ export class AppWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
public onClose(fn: () => void) {
|
public onClose(fn: () => void) {
|
||||||
this.window.on('closed', fn);
|
this.window.on(ElectronBrowserWindowChannel.Closed, fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -7,12 +7,22 @@ import * as fs from 'fs';
|
||||||
import { AppWindow } from './app/AppWindow';
|
import { AppWindow } from './app/AppWindow';
|
||||||
import { now } from './util/now';
|
import { now } from './util/now';
|
||||||
import { showUncaughtException } from './crash/show-uncaught-exception';
|
import { showUncaughtException } from './crash/show-uncaught-exception';
|
||||||
import { Channel } from '@ucap-webmessenger/native-electron';
|
|
||||||
|
import {
|
||||||
|
UpdaterChannel,
|
||||||
|
FileChannel,
|
||||||
|
IdleStateChannel,
|
||||||
|
NotificationChannel
|
||||||
|
} from '@ucap-webmessenger/native-electron';
|
||||||
|
import { ElectronNotificationService } from '@ucap-webmessenger/electron-notification';
|
||||||
|
|
||||||
import { root } from './util/root';
|
import { root } from './util/root';
|
||||||
import { DefaultFolder } from './lib/default-folder';
|
import { DefaultFolder } from './lib/default-folder';
|
||||||
import { FileUtil } from './lib/file-util';
|
import { FileUtil } from './lib/file-util';
|
||||||
|
|
||||||
import { IdleChecker } from './lib/idle-checker';
|
import { IdleChecker } from './lib/idle-checker';
|
||||||
|
import { NotificationRequest } from '@ucap-webmessenger/native';
|
||||||
|
import { ElectronAppChannel } from '@ucap-webmessenger/electron-core';
|
||||||
|
|
||||||
let appWindow: AppWindow | null = null;
|
let appWindow: AppWindow | null = null;
|
||||||
|
|
||||||
|
@ -23,6 +33,9 @@ type OnDidLoadFn = (window: AppWindow) => void;
|
||||||
let onDidLoadFns: Array<OnDidLoadFn> | null = [];
|
let onDidLoadFns: Array<OnDidLoadFn> | null = [];
|
||||||
|
|
||||||
let preventQuit = false;
|
let preventQuit = false;
|
||||||
|
|
||||||
|
let notificationService: ElectronNotificationService | null;
|
||||||
|
|
||||||
function handleUncaughtException(error: Error) {
|
function handleUncaughtException(error: Error) {
|
||||||
preventQuit = true;
|
preventQuit = true;
|
||||||
|
|
||||||
|
@ -57,7 +70,7 @@ const gotSingleInstanceLock = app.requestSingleInstanceLock();
|
||||||
isDuplicateInstance = !gotSingleInstanceLock;
|
isDuplicateInstance = !gotSingleInstanceLock;
|
||||||
let idle: IdleChecker | null;
|
let idle: IdleChecker | null;
|
||||||
|
|
||||||
app.on('second-instance', (event, args, workingDirectory) => {
|
app.on(ElectronAppChannel.SecondInstance, (event, args, workingDirectory) => {
|
||||||
// Someone tried to run a second instance, we should focus our window.
|
// Someone tried to run a second instance, we should focus our window.
|
||||||
if (appWindow) {
|
if (appWindow) {
|
||||||
if (appWindow.isMinimized()) {
|
if (appWindow.isMinimized()) {
|
||||||
|
@ -132,7 +145,7 @@ function createWindow() {
|
||||||
// This method will be called when Electron has finished
|
// This method will be called when Electron has finished
|
||||||
// initialization and is ready to create browser windows.
|
// initialization and is ready to create browser windows.
|
||||||
// Some APIs can only be used after this event occurs.
|
// Some APIs can only be used after this event occurs.
|
||||||
app.on('ready', () => {
|
app.on(ElectronAppChannel.Ready, () => {
|
||||||
if (isDuplicateInstance) {
|
if (isDuplicateInstance) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -141,6 +154,30 @@ app.on('ready', () => {
|
||||||
|
|
||||||
createWindow();
|
createWindow();
|
||||||
|
|
||||||
|
notificationService = new ElectronNotificationService({
|
||||||
|
width: 340,
|
||||||
|
height: 100,
|
||||||
|
padding: 0,
|
||||||
|
borderRadius: 0,
|
||||||
|
// appIcon: iconPath,
|
||||||
|
displayTime: 5000,
|
||||||
|
defaultStyleContainer: {},
|
||||||
|
defaultStyleAppIcon: { display: 'none' },
|
||||||
|
defaultStyleImage: {},
|
||||||
|
defaultStyleClose: {},
|
||||||
|
defaultStyleText: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
notificationService.options.defaultWindow.webPreferences.preload = path.join(
|
||||||
|
__dirname,
|
||||||
|
'resources/notification/preload.js'
|
||||||
|
);
|
||||||
|
|
||||||
|
notificationService.templatePath = path.join(
|
||||||
|
__dirname,
|
||||||
|
'resources/notification/template.html'
|
||||||
|
);
|
||||||
|
|
||||||
ipcMain.on('uncaught-exception', (event: IpcMainEvent, error: Error) => {
|
ipcMain.on('uncaught-exception', (event: IpcMainEvent, error: Error) => {
|
||||||
handleUncaughtException(error);
|
handleUncaughtException(error);
|
||||||
});
|
});
|
||||||
|
@ -155,7 +192,7 @@ app.on('ready', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Quit when all windows are closed.
|
// Quit when all windows are closed.
|
||||||
app.on('window-all-closed', () => {
|
app.on(ElectronAppChannel.WindowAllClosed, () => {
|
||||||
// On OS X it is common for applications and their menu bar
|
// On OS X it is common for applications and their menu bar
|
||||||
// to stay active until the user quits explicitly with Cmd + Q
|
// to stay active until the user quits explicitly with Cmd + Q
|
||||||
if (process.platform !== 'darwin') {
|
if (process.platform !== 'darwin') {
|
||||||
|
@ -163,7 +200,7 @@ app.on('window-all-closed', () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on('activate', () => {
|
app.on(ElectronAppChannel.Activate, () => {
|
||||||
onDidLoad(window => {
|
onDidLoad(window => {
|
||||||
window.show();
|
window.show();
|
||||||
});
|
});
|
||||||
|
@ -179,11 +216,11 @@ function onDidLoad(fn: OnDidLoadFn) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcMain.on(Channel.checkForUpdates, (event: IpcMainEvent, ...args: any[]) => {
|
ipcMain.on(UpdaterChannel.Check, (event: IpcMainEvent, ...args: any[]) => {
|
||||||
event.returnValue = false;
|
event.returnValue = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on(Channel.readFile, (event: IpcMainEvent, ...args: any[]) => {
|
ipcMain.on(FileChannel.ReadFile, (event: IpcMainEvent, ...args: any[]) => {
|
||||||
try {
|
try {
|
||||||
fse.readFile(root(args[0]), (err, data) => {
|
fse.readFile(root(args[0]), (err, data) => {
|
||||||
if (!!err) {
|
if (!!err) {
|
||||||
|
@ -197,7 +234,9 @@ ipcMain.on(Channel.readFile, (event: IpcMainEvent, ...args: any[]) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on(Channel.saveFile, async (event: IpcMainEvent, ...args: any[]) => {
|
ipcMain.on(
|
||||||
|
FileChannel.SaveFile,
|
||||||
|
async (event: IpcMainEvent, ...args: any[]) => {
|
||||||
try {
|
try {
|
||||||
const buffer: Buffer = args[0];
|
const buffer: Buffer = args[0];
|
||||||
const fileName: string = args[1];
|
const fileName: string = args[1];
|
||||||
|
@ -217,17 +256,51 @@ ipcMain.on(Channel.saveFile, async (event: IpcMainEvent, ...args: any[]) => {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
event.returnValue = undefined;
|
event.returnValue = undefined;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
ipcMain.on(Channel.idleStateStart, (event: IpcMainEvent, ...args: any[]) => {
|
ipcMain.on(
|
||||||
|
IdleStateChannel.StartCheck,
|
||||||
|
(event: IpcMainEvent, ...args: any[]) => {
|
||||||
if (!!idle) {
|
if (!!idle) {
|
||||||
idle.destoryChecker();
|
idle.destoryChecker();
|
||||||
idle = null;
|
idle = null;
|
||||||
}
|
}
|
||||||
idle = new IdleChecker(appWindow.browserWindow); // default 10min
|
idle = new IdleChecker(appWindow.browserWindow); // default 10min
|
||||||
idle.startChecker();
|
idle.startChecker();
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
ipcMain.on(Channel.showNotify, (event: IpcMainEvent, ...args: any[]) => {
|
ipcMain.on(
|
||||||
console.log('Channel.showNotify', args);
|
NotificationChannel.Notify,
|
||||||
});
|
(event: IpcMainEvent, ...args: any[]) => {
|
||||||
|
const noti: NotificationRequest = args[0];
|
||||||
|
|
||||||
|
notificationService.notify({
|
||||||
|
title: noti.title,
|
||||||
|
text: noti.contents,
|
||||||
|
image:
|
||||||
|
noti.image ||
|
||||||
|
path.join(__dirname, 'resources/notification/image/img_nophoto_50.png'),
|
||||||
|
sound: noti.useSound
|
||||||
|
? path.join(
|
||||||
|
'file://',
|
||||||
|
__dirname,
|
||||||
|
'resources/notification/sound/messageAlarm.mp3'
|
||||||
|
)
|
||||||
|
: '',
|
||||||
|
onClick: () => {
|
||||||
|
console.log('onClick');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Channel.notify', noti);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
ipcMain.on(
|
||||||
|
NotificationChannel.CloseAllNotify,
|
||||||
|
(event: IpcMainEvent, ...args: any[]) => {
|
||||||
|
console.log('Channel.closeAllNotify', args);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { powerMonitor, BrowserWindow } from 'electron';
|
import { powerMonitor, BrowserWindow } from 'electron';
|
||||||
import { Channel } from '@ucap-webmessenger/native-electron';
|
import { IdleStateChannel } from '@ucap-webmessenger/native-electron';
|
||||||
import { setInterval } from 'timers';
|
import { setInterval } from 'timers';
|
||||||
|
|
||||||
export enum IdleType {
|
export enum IdleType {
|
||||||
|
@ -28,13 +28,13 @@ export class IdleChecker {
|
||||||
if (this.status === IdleType.ACTIVE) {
|
if (this.status === IdleType.ACTIVE) {
|
||||||
this.status = IdleType.IDLE;
|
this.status = IdleType.IDLE;
|
||||||
// TODO :: USER_STATUS change away
|
// TODO :: USER_STATUS change away
|
||||||
this.window.webContents.send(Channel.idleStateChanged, this.status);
|
this.window.webContents.send(IdleStateChannel.Changed, this.status);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (this.status === IdleType.IDLE) {
|
if (this.status === IdleType.IDLE) {
|
||||||
this.status = IdleType.ACTIVE;
|
this.status = IdleType.ACTIVE;
|
||||||
// TODO :: USER_STATUS chage online
|
// TODO :: USER_STATUS chage online
|
||||||
this.window.webContents.send(Channel.idleStateChanged, this.status);
|
this.window.webContents.send(IdleStateChannel.Changed, this.status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { BrowserWindow } from 'electron';
|
import { BrowserWindow } from 'electron';
|
||||||
import { WindowState } from '@ucap-webmessenger/native';
|
import { WindowState } from '@ucap-webmessenger/native';
|
||||||
import { Channel } from '@ucap-webmessenger/native-electron';
|
import { WindowStateChannel } from '@ucap-webmessenger/native-electron';
|
||||||
|
import { ElectronBrowserWindowChannel } from '@ucap-webmessenger/electron-core';
|
||||||
|
|
||||||
export function getWindowState(window: Electron.BrowserWindow): WindowState {
|
export function getWindowState(window: Electron.BrowserWindow): WindowState {
|
||||||
if (window.isFullScreen()) {
|
if (window.isFullScreen()) {
|
||||||
|
@ -17,26 +18,30 @@ export function getWindowState(window: Electron.BrowserWindow): WindowState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerWindowStateChangedEvents(window: BrowserWindow) {
|
export function registerWindowStateChangedEvents(window: BrowserWindow) {
|
||||||
window.on('enter-full-screen', () =>
|
window.on(ElectronBrowserWindowChannel.EnterFullScreen, () =>
|
||||||
sendWindowStateEvent(window, WindowState.FullScreen)
|
sendWindowStateEvent(window, WindowState.FullScreen)
|
||||||
);
|
);
|
||||||
|
|
||||||
window.on('leave-full-screen', () =>
|
window.on(ElectronBrowserWindowChannel.LeaveFullScreen, () =>
|
||||||
sendWindowStateEvent(window, WindowState.Normal)
|
sendWindowStateEvent(window, WindowState.Normal)
|
||||||
);
|
);
|
||||||
|
|
||||||
window.on('maximize', () =>
|
window.on(ElectronBrowserWindowChannel.Maximize, () =>
|
||||||
sendWindowStateEvent(window, WindowState.Maximized)
|
sendWindowStateEvent(window, WindowState.Maximized)
|
||||||
);
|
);
|
||||||
window.on('minimize', () =>
|
window.on(ElectronBrowserWindowChannel.Minimize, () =>
|
||||||
sendWindowStateEvent(window, WindowState.Minimized)
|
sendWindowStateEvent(window, WindowState.Minimized)
|
||||||
);
|
);
|
||||||
window.on('unmaximize', () =>
|
window.on(ElectronBrowserWindowChannel.Unmaximize, () =>
|
||||||
sendWindowStateEvent(window, WindowState.Normal)
|
sendWindowStateEvent(window, WindowState.Normal)
|
||||||
);
|
);
|
||||||
window.on('restore', () => sendWindowStateEvent(window, WindowState.Normal));
|
window.on(ElectronBrowserWindowChannel.Restore, () =>
|
||||||
window.on('hide', () => sendWindowStateEvent(window, WindowState.Hidden));
|
sendWindowStateEvent(window, WindowState.Normal)
|
||||||
window.on('show', () => {
|
);
|
||||||
|
window.on(ElectronBrowserWindowChannel.Hide, () =>
|
||||||
|
sendWindowStateEvent(window, WindowState.Hidden)
|
||||||
|
);
|
||||||
|
window.on(ElectronBrowserWindowChannel.Show, () => {
|
||||||
// because the app can be maximized before being closed - which will restore it
|
// because the app can be maximized before being closed - which will restore it
|
||||||
// maximized on the next launch - this function should inspect the current state
|
// maximized on the next launch - this function should inspect the current state
|
||||||
// rather than always assume it is a 'normal' launch
|
// rather than always assume it is a 'normal' launch
|
||||||
|
@ -45,5 +50,5 @@ export function registerWindowStateChangedEvents(window: BrowserWindow) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendWindowStateEvent(window: BrowserWindow, windowState: WindowState) {
|
function sendWindowStateEvent(window: BrowserWindow, windowState: WindowState) {
|
||||||
window.webContents.send(Channel.windowStateChanged, windowState);
|
window.webContents.send(WindowStateChannel.Changed, windowState);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,12 @@
|
||||||
"types": ["node"],
|
"types": ["node"],
|
||||||
"lib": ["es2017", "es2016", "es2015", "dom"],
|
"lib": ["es2017", "es2016", "es2015", "dom"],
|
||||||
"paths": {
|
"paths": {
|
||||||
|
"@ucap-webmessenger/electron-core": [
|
||||||
|
"../projects/ucap-webmessenger-electron-core/src/public-api"
|
||||||
|
],
|
||||||
|
"@ucap-webmessenger/electron-notification": [
|
||||||
|
"../projects/ucap-webmessenger-electron-notification/src/public-api"
|
||||||
|
],
|
||||||
"@ucap-webmessenger/native": [
|
"@ucap-webmessenger/native": [
|
||||||
"../projects/ucap-webmessenger-native/src/public-api"
|
"../projects/ucap-webmessenger-native/src/public-api"
|
||||||
],
|
],
|
||||||
|
|
50
package-lock.json
generated
|
@ -3101,15 +3101,6 @@
|
||||||
"integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=",
|
"integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"async": {
|
|
||||||
"version": "2.6.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
|
|
||||||
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"lodash": "^4.17.14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"async-each": {
|
"async-each": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz",
|
||||||
|
@ -8061,6 +8052,15 @@
|
||||||
"once": "^1.4.0"
|
"once": "^1.4.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"async": {
|
||||||
|
"version": "2.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
|
||||||
|
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"lodash": "^4.17.14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"istanbul-lib-coverage": {
|
"istanbul-lib-coverage": {
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz",
|
||||||
|
@ -11239,6 +11239,15 @@
|
||||||
"mkdirp": "^0.5.1"
|
"mkdirp": "^0.5.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"async": {
|
||||||
|
"version": "2.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
|
||||||
|
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"lodash": "^4.17.14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "3.2.6",
|
"version": "3.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||||
|
@ -13255,6 +13264,17 @@
|
||||||
"requires": {
|
"requires": {
|
||||||
"async": "^2.5.0",
|
"async": "^2.5.0",
|
||||||
"loader-utils": "^1.1.0"
|
"loader-utils": "^1.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"async": {
|
||||||
|
"version": "2.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
|
||||||
|
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"lodash": "^4.17.14"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"source-map-resolve": {
|
"source-map-resolve": {
|
||||||
|
@ -13519,6 +13539,15 @@
|
||||||
"lodash": "^4.17.14"
|
"lodash": "^4.17.14"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"async": {
|
||||||
|
"version": "2.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
|
||||||
|
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"lodash": "^4.17.14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "3.2.6",
|
"version": "3.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||||
|
@ -14116,8 +14145,7 @@
|
||||||
"tslib": {
|
"tslib": {
|
||||||
"version": "1.10.0",
|
"version": "1.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
|
||||||
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==",
|
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"tslint": {
|
"tslint": {
|
||||||
"version": "5.15.0",
|
"version": "5.15.0",
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
"start": "npm-run-all -p start:renderer start:main",
|
"start": "npm-run-all -p start:renderer start:main",
|
||||||
"start:main": "wait-on http-get://localhost:4200/ && npm run build:main:dev && electron --nolazy --inspect-brk=9229 .",
|
"start:main": "wait-on http-get://localhost:4200/ && npm run build:main:dev && electron --nolazy --inspect-brk=9229 .",
|
||||||
"start:renderer": "ng serve",
|
"start:renderer": "ng serve",
|
||||||
|
"start:web": "cross-env UCAP_ENV=WEB ng serve",
|
||||||
"start:production": "npm run build:renderer && npm run build:main:prod && electron --nolazy --inspect-brk=9229 .",
|
"start:production": "npm run build:renderer && npm run build:main:prod && electron --nolazy --inspect-brk=9229 .",
|
||||||
"build:renderer": "cross-env NODE_ENV=production ng build --base-href ./",
|
"build:renderer": "cross-env NODE_ENV=production ng build --base-href ./",
|
||||||
"build:main:dev": "cross-env NODE_ENV=development TS_NODE_PROJECT='./config/tsconfig.webpack.json' parallel-webpack --config=config/main.webpack.config.ts",
|
"build:main:dev": "cross-env NODE_ENV=development TS_NODE_PROJECT='./config/tsconfig.webpack.json' parallel-webpack --config=config/main.webpack.config.ts",
|
||||||
|
@ -15,7 +16,9 @@
|
||||||
"e2e": "ng e2e"
|
"e2e": "ng e2e"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {},
|
"dependencies": {
|
||||||
|
"tslib": "^1.10.0"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-builders/custom-webpack": "^8.2.0",
|
"@angular-builders/custom-webpack": "^8.2.0",
|
||||||
"@angular-devkit/build-angular": "~0.803.14",
|
"@angular-devkit/build-angular": "~0.803.14",
|
||||||
|
|
|
@ -81,6 +81,9 @@
|
||||||
<button mat-menu-item (click)="onClickContextMenu('ADD_MEMBER')">
|
<button mat-menu-item (click)="onClickContextMenu('ADD_MEMBER')">
|
||||||
대화상대추가
|
대화상대추가
|
||||||
</button>
|
</button>
|
||||||
|
<button mat-menu-item (click)="onClickContextMenu('ADD_GROUP')">
|
||||||
|
그룹멤버로추가
|
||||||
|
</button>
|
||||||
<button mat-menu-item (click)="onClickContextMenu('EDIT_ROOM')">
|
<button mat-menu-item (click)="onClickContextMenu('EDIT_ROOM')">
|
||||||
대화방설정
|
대화방설정
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -19,7 +19,8 @@ import {
|
||||||
AlertDialogComponent,
|
AlertDialogComponent,
|
||||||
AlertDialogData,
|
AlertDialogData,
|
||||||
AlertDialogResult,
|
AlertDialogResult,
|
||||||
FileUploadQueueComponent
|
FileUploadQueueComponent,
|
||||||
|
StringUtil
|
||||||
} from '@ucap-webmessenger/ui';
|
} from '@ucap-webmessenger/ui';
|
||||||
import { Store, select } from '@ngrx/store';
|
import { Store, select } from '@ngrx/store';
|
||||||
import { NGXLogger } from 'ngx-logger';
|
import { NGXLogger } from 'ngx-logger';
|
||||||
|
@ -39,6 +40,7 @@ import * as AppStore from '@app/store';
|
||||||
import * as EventStore from '@app/store/messenger/event';
|
import * as EventStore from '@app/store/messenger/event';
|
||||||
import * as ChatStore from '@app/store/messenger/chat';
|
import * as ChatStore from '@app/store/messenger/chat';
|
||||||
import * as RoomStore from '@app/store/messenger/room';
|
import * as RoomStore from '@app/store/messenger/room';
|
||||||
|
import * as SyncStore from '@app/store/messenger/sync';
|
||||||
import { LoginResponse } from '@ucap-webmessenger/protocol-authentication';
|
import { LoginResponse } from '@ucap-webmessenger/protocol-authentication';
|
||||||
import { SessionStorageService } from '@ucap-webmessenger/web-storage';
|
import { SessionStorageService } from '@ucap-webmessenger/web-storage';
|
||||||
import {
|
import {
|
||||||
|
@ -75,7 +77,7 @@ import {
|
||||||
FileViewerDialogData,
|
FileViewerDialogData,
|
||||||
FileViewerDialogResult
|
FileViewerDialogResult
|
||||||
} from '@app/layouts/common/dialogs/file-viewer.dialog.component';
|
} from '@app/layouts/common/dialogs/file-viewer.dialog.component';
|
||||||
import { CONST, StringUtil, FileUtil } from '@ucap-webmessenger/core';
|
import { CONST, FileUtil } from '@ucap-webmessenger/core';
|
||||||
import { PerfectScrollbarComponent } from 'ngx-perfect-scrollbar';
|
import { PerfectScrollbarComponent } from 'ngx-perfect-scrollbar';
|
||||||
import { StatusCode } from '@ucap-webmessenger/api';
|
import { StatusCode } from '@ucap-webmessenger/api';
|
||||||
import {
|
import {
|
||||||
|
@ -83,6 +85,8 @@ import {
|
||||||
EditChatRoomDialogResult,
|
EditChatRoomDialogResult,
|
||||||
EditChatRoomDialogData
|
EditChatRoomDialogData
|
||||||
} from '../dialogs/chat/edit-chat-room.dialog.component';
|
} from '../dialogs/chat/edit-chat-room.dialog.component';
|
||||||
|
import { SelectGroupDialogComponent, SelectGroupDialogResult, SelectGroupDialogData } from '../dialogs/group/select-group.dialog.component';
|
||||||
|
import { GroupDetailData } from '@ucap-webmessenger/protocol-sync';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-layout-messenger-messages',
|
selector: 'app-layout-messenger-messages',
|
||||||
|
@ -837,6 +841,40 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'ADD_GROUP':
|
||||||
|
{
|
||||||
|
const result = await this.dialogService.open<
|
||||||
|
SelectGroupDialogComponent,
|
||||||
|
SelectGroupDialogData,
|
||||||
|
SelectGroupDialogResult
|
||||||
|
>(SelectGroupDialogComponent, {
|
||||||
|
width: '600px',
|
||||||
|
data: {
|
||||||
|
title: 'Group Select'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!!result && !!result.choice && result.choice) {
|
||||||
|
if (!!result.group) {
|
||||||
|
const oldGroup: GroupDetailData = result.group;
|
||||||
|
const trgtUserSeq: number[] = [];
|
||||||
|
result.group.userSeqs.map(seq => trgtUserSeq.push(seq));
|
||||||
|
this.userInfoList
|
||||||
|
.filter(v => result.group.userSeqs.indexOf(v.seq) < 0)
|
||||||
|
.forEach(user => {
|
||||||
|
trgtUserSeq.push(user.seq);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.store.dispatch(
|
||||||
|
SyncStore.updateGroupMember({
|
||||||
|
oldGroup,
|
||||||
|
trgtUserSeq
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'EDIT_ROOM':
|
case 'EDIT_ROOM':
|
||||||
{
|
{
|
||||||
const result = await this.dialogService.open<
|
const result = await this.dialogService.open<
|
||||||
|
|
|
@ -80,11 +80,11 @@ import * as SyncStore from '@app/store/messenger/sync';
|
||||||
import * as RoomStore from '@app/store/messenger/room';
|
import * as RoomStore from '@app/store/messenger/room';
|
||||||
import * as StatusStore from '@app/store/messenger/status';
|
import * as StatusStore from '@app/store/messenger/status';
|
||||||
import {
|
import {
|
||||||
NotiRequest,
|
NotificationRequest,
|
||||||
NativeService,
|
NativeService,
|
||||||
UCAP_NATIVE_SERVICE
|
UCAP_NATIVE_SERVICE
|
||||||
} from '@ucap-webmessenger/native';
|
} from '@ucap-webmessenger/native';
|
||||||
import { StringUtil } from '@ucap-webmessenger/core';
|
import { StringUtil } from '@ucap-webmessenger/ui';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AppNotificationService {
|
export class AppNotificationService {
|
||||||
|
@ -153,7 +153,7 @@ export class AppNotificationService {
|
||||||
|
|
||||||
// notification..
|
// notification..
|
||||||
if (notiOrRes.SSVC_TYPE === SSVC_TYPE_EVENT_SEND_NOTI) {
|
if (notiOrRes.SSVC_TYPE === SSVC_TYPE_EVENT_SEND_NOTI) {
|
||||||
const notiReq: NotiRequest = {
|
const notiReq: NotificationRequest = {
|
||||||
roomSeq: noti.roomSeq,
|
roomSeq: noti.roomSeq,
|
||||||
title: '메세지가 도착했습니다.',
|
title: '메세지가 도착했습니다.',
|
||||||
contents: StringUtil.convertFinalEventMessage(
|
contents: StringUtil.convertFinalEventMessage(
|
||||||
|
@ -164,7 +164,7 @@ export class AppNotificationService {
|
||||||
useSound: true,
|
useSound: true,
|
||||||
interval: 0
|
interval: 0
|
||||||
};
|
};
|
||||||
this.nativeService.showNotify(notiReq);
|
this.nativeService.notify(notiReq);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -28,7 +28,7 @@ import {
|
||||||
|
|
||||||
import * as RoomStore from '@app/store/messenger/room';
|
import * as RoomStore from '@app/store/messenger/room';
|
||||||
import { RoomInfo } from '@ucap-webmessenger/protocol-room';
|
import { RoomInfo } from '@ucap-webmessenger/protocol-room';
|
||||||
import { StringUtil } from '@ucap-webmessenger/core';
|
import { StringUtil } from '@ucap-webmessenger/ui';
|
||||||
|
|
||||||
export const reducer = createReducer(
|
export const reducer = createReducer(
|
||||||
initialState,
|
initialState,
|
||||||
|
|
|
@ -1,63 +1 @@
|
||||||
import {
|
export class StringUtil {}
|
||||||
EventType,
|
|
||||||
EventJson,
|
|
||||||
FileEventJson,
|
|
||||||
MassTextEventJson
|
|
||||||
} from '@ucap-webmessenger/protocol-event';
|
|
||||||
import { FileType } from '@ucap-webmessenger/protocol-file';
|
|
||||||
|
|
||||||
export class StringUtil {
|
|
||||||
public static convertFinalEventMessage(
|
|
||||||
eventType: EventType,
|
|
||||||
finalEventMessage: EventJson
|
|
||||||
): string | null {
|
|
||||||
let eventMessage: string = null;
|
|
||||||
|
|
||||||
switch (eventType) {
|
|
||||||
case EventType.Join:
|
|
||||||
case EventType.Exit:
|
|
||||||
case EventType.RenameRoom:
|
|
||||||
case EventType.NotificationForTimerRoom:
|
|
||||||
case EventType.GuideForRoomTimerChanged:
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* 해당 타입은 메시지를 갱신하지 않는다.
|
|
||||||
* @description Edit with ui-chat > messages.component.ts
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case EventType.Sticker:
|
|
||||||
eventMessage = '스티커';
|
|
||||||
break;
|
|
||||||
case EventType.File:
|
|
||||||
{
|
|
||||||
const m = finalEventMessage as FileEventJson;
|
|
||||||
|
|
||||||
if (FileType.Image === m.fileType) {
|
|
||||||
eventMessage = '이미지';
|
|
||||||
} else {
|
|
||||||
eventMessage = '첨부파일';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case EventType.VideoConference:
|
|
||||||
eventMessage = '화상회의';
|
|
||||||
break;
|
|
||||||
case EventType.MassText:
|
|
||||||
{
|
|
||||||
const m = finalEventMessage as MassTextEventJson;
|
|
||||||
eventMessage = m.content;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
const m = finalEventMessage as string;
|
|
||||||
eventMessage = m;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return eventMessage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
24
projects/ucap-webmessenger-electron-core/README.md
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# UcapWebmessengerElectronCore
|
||||||
|
|
||||||
|
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.2.11.
|
||||||
|
|
||||||
|
## Code scaffolding
|
||||||
|
|
||||||
|
Run `ng generate component component-name --project ucap-webmessenger-electron-core` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project ucap-webmessenger-electron-core`.
|
||||||
|
> Note: Don't forget to add `--project ucap-webmessenger-electron-core` or else it will be added to the default project in your `angular.json` file.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Run `ng build ucap-webmessenger-electron-core` to build the project. The build artifacts will be stored in the `dist/` directory.
|
||||||
|
|
||||||
|
## Publishing
|
||||||
|
|
||||||
|
After building your library with `ng build ucap-webmessenger-electron-core`, go to the dist folder `cd dist/ucap-webmessenger-electron-core` and run `npm publish`.
|
||||||
|
|
||||||
|
## Running unit tests
|
||||||
|
|
||||||
|
Run `ng test ucap-webmessenger-electron-core` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||||
|
|
||||||
|
## Further help
|
||||||
|
|
||||||
|
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
|
32
projects/ucap-webmessenger-electron-core/karma.conf.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// Karma configuration file, see link for more information
|
||||||
|
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||||
|
|
||||||
|
module.exports = function (config) {
|
||||||
|
config.set({
|
||||||
|
basePath: '',
|
||||||
|
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||||
|
plugins: [
|
||||||
|
require('karma-jasmine'),
|
||||||
|
require('karma-chrome-launcher'),
|
||||||
|
require('karma-jasmine-html-reporter'),
|
||||||
|
require('karma-coverage-istanbul-reporter'),
|
||||||
|
require('@angular-devkit/build-angular/plugins/karma')
|
||||||
|
],
|
||||||
|
client: {
|
||||||
|
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||||
|
},
|
||||||
|
coverageIstanbulReporter: {
|
||||||
|
dir: require('path').join(__dirname, '../../coverage/ucap-webmessenger-electron-core'),
|
||||||
|
reports: ['html', 'lcovonly', 'text-summary'],
|
||||||
|
fixWebpackSourcePaths: true
|
||||||
|
},
|
||||||
|
reporters: ['progress', 'kjhtml'],
|
||||||
|
port: 9876,
|
||||||
|
colors: true,
|
||||||
|
logLevel: config.LOG_INFO,
|
||||||
|
autoWatch: true,
|
||||||
|
browsers: ['Chrome'],
|
||||||
|
singleRun: false,
|
||||||
|
restartOnFileChange: true
|
||||||
|
});
|
||||||
|
};
|
7
projects/ucap-webmessenger-electron-core/ng-package.json
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
|
||||||
|
"dest": "../../dist/ucap-webmessenger-electron-core",
|
||||||
|
"lib": {
|
||||||
|
"entryFile": "src/public-api.ts"
|
||||||
|
}
|
||||||
|
}
|
8
projects/ucap-webmessenger-electron-core/package.json
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"name": "@ucap-webmessenger/electron-core",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@angular/common": "^8.2.11",
|
||||||
|
"@angular/core": "^8.2.11"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
export enum ElectronAppChannel {
|
||||||
|
WillFinishLaunching = 'will-finish-launching',
|
||||||
|
Ready = 'ready',
|
||||||
|
WindowAllClosed = 'window-all-closed',
|
||||||
|
BeforeQuit = 'before-quit',
|
||||||
|
WillQuit = 'will-quit',
|
||||||
|
Quit = 'quit',
|
||||||
|
OpenFile = 'open-file',
|
||||||
|
OpenUrl = 'open-url',
|
||||||
|
Activate = 'activate',
|
||||||
|
ContinueActivity = 'continue-activity',
|
||||||
|
WillContinueActivity = 'will-continue-activity',
|
||||||
|
ContinueActivityError = 'continue-activity-error',
|
||||||
|
ActivityWasContinued = 'activity-was-continued',
|
||||||
|
SecondInstance = 'second-instance'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ElectronBrowserWindowChannel {
|
||||||
|
EnterFullScreen = 'enter-full-screen',
|
||||||
|
LeaveFullScreen = 'leave-full-screen',
|
||||||
|
Maximize = 'maximize',
|
||||||
|
Minimize = 'minimize',
|
||||||
|
Unmaximize = 'unmaximize',
|
||||||
|
Restore = 'restore',
|
||||||
|
Hide = 'hide',
|
||||||
|
Show = 'show',
|
||||||
|
Close = 'close',
|
||||||
|
Closed = 'closed',
|
||||||
|
ReadyToShow = 'ready-to-show'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ElectronWebContentsChannel {
|
||||||
|
DevtoolsOpened = 'devtools-opened',
|
||||||
|
DidStartLoading = 'did-start-loading',
|
||||||
|
DidFinishLoad = 'did-finish-load',
|
||||||
|
DidFailLoad = 'did-fail-load'
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
/*
|
||||||
|
* Public API Surface of ucap-webmessenger-electron-core
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from './lib/types/channel.type';
|
21
projects/ucap-webmessenger-electron-core/src/test.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
||||||
|
|
||||||
|
import 'zone.js/dist/zone';
|
||||||
|
import 'zone.js/dist/zone-testing';
|
||||||
|
import { getTestBed } from '@angular/core/testing';
|
||||||
|
import {
|
||||||
|
BrowserDynamicTestingModule,
|
||||||
|
platformBrowserDynamicTesting
|
||||||
|
} from '@angular/platform-browser-dynamic/testing';
|
||||||
|
|
||||||
|
declare const require: any;
|
||||||
|
|
||||||
|
// First, initialize the Angular testing environment.
|
||||||
|
getTestBed().initTestEnvironment(
|
||||||
|
BrowserDynamicTestingModule,
|
||||||
|
platformBrowserDynamicTesting()
|
||||||
|
);
|
||||||
|
// Then we find all the tests.
|
||||||
|
const context = require.context('./', true, /\.spec\.ts$/);
|
||||||
|
// And load the modules.
|
||||||
|
context.keys().map(context);
|
26
projects/ucap-webmessenger-electron-core/tsconfig.lib.json
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../out-tsc/lib",
|
||||||
|
"target": "es2015",
|
||||||
|
"declaration": true,
|
||||||
|
"inlineSources": true,
|
||||||
|
"types": [],
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"es2018"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"annotateForClosureCompiler": true,
|
||||||
|
"skipTemplateCodegen": true,
|
||||||
|
"strictMetadataEmit": true,
|
||||||
|
"fullTemplateTypeCheck": true,
|
||||||
|
"strictInjectionParameters": true,
|
||||||
|
"enableResourceInlining": true
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"src/test.ts",
|
||||||
|
"**/*.spec.ts"
|
||||||
|
]
|
||||||
|
}
|
17
projects/ucap-webmessenger-electron-core/tsconfig.spec.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../out-tsc/spec",
|
||||||
|
"types": [
|
||||||
|
"jasmine",
|
||||||
|
"node"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"src/test.ts"
|
||||||
|
],
|
||||||
|
"include": [
|
||||||
|
"**/*.spec.ts",
|
||||||
|
"**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
17
projects/ucap-webmessenger-electron-core/tslint.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tslint.json",
|
||||||
|
"rules": {
|
||||||
|
"directive-selector": [
|
||||||
|
true,
|
||||||
|
"attribute",
|
||||||
|
"ucapElectronCore",
|
||||||
|
"camelCase"
|
||||||
|
],
|
||||||
|
"component-selector": [
|
||||||
|
true,
|
||||||
|
"element",
|
||||||
|
"ucap-electron-core",
|
||||||
|
"kebab-case"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
24
projects/ucap-webmessenger-electron-notification/README.md
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# UcapWebmessengerElectronNotification
|
||||||
|
|
||||||
|
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.2.11.
|
||||||
|
|
||||||
|
## Code scaffolding
|
||||||
|
|
||||||
|
Run `ng generate component component-name --project ucap-webmessenger-electron-notification` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project ucap-webmessenger-electron-notification`.
|
||||||
|
> Note: Don't forget to add `--project ucap-webmessenger-electron-notification` or else it will be added to the default project in your `angular.json` file.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Run `ng build ucap-webmessenger-electron-notification` to build the project. The build artifacts will be stored in the `dist/` directory.
|
||||||
|
|
||||||
|
## Publishing
|
||||||
|
|
||||||
|
After building your library with `ng build ucap-webmessenger-electron-notification`, go to the dist folder `cd dist/ucap-webmessenger-electron-notification` and run `npm publish`.
|
||||||
|
|
||||||
|
## Running unit tests
|
||||||
|
|
||||||
|
Run `ng test ucap-webmessenger-electron-notification` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||||
|
|
||||||
|
## Further help
|
||||||
|
|
||||||
|
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
|
|
@ -0,0 +1,32 @@
|
||||||
|
// Karma configuration file, see link for more information
|
||||||
|
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||||
|
|
||||||
|
module.exports = function (config) {
|
||||||
|
config.set({
|
||||||
|
basePath: '',
|
||||||
|
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||||
|
plugins: [
|
||||||
|
require('karma-jasmine'),
|
||||||
|
require('karma-chrome-launcher'),
|
||||||
|
require('karma-jasmine-html-reporter'),
|
||||||
|
require('karma-coverage-istanbul-reporter'),
|
||||||
|
require('@angular-devkit/build-angular/plugins/karma')
|
||||||
|
],
|
||||||
|
client: {
|
||||||
|
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||||
|
},
|
||||||
|
coverageIstanbulReporter: {
|
||||||
|
dir: require('path').join(__dirname, '../../coverage/ucap-webmessenger-electron-notification'),
|
||||||
|
reports: ['html', 'lcovonly', 'text-summary'],
|
||||||
|
fixWebpackSourcePaths: true
|
||||||
|
},
|
||||||
|
reporters: ['progress', 'kjhtml'],
|
||||||
|
port: 9876,
|
||||||
|
colors: true,
|
||||||
|
logLevel: config.LOG_INFO,
|
||||||
|
autoWatch: true,
|
||||||
|
browsers: ['Chrome'],
|
||||||
|
singleRun: false,
|
||||||
|
restartOnFileChange: true
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
|
||||||
|
"dest": "../../dist/ucap-webmessenger-electron-notification",
|
||||||
|
"lib": {
|
||||||
|
"entryFile": "src/public-api.ts"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"name": "@ucap-webmessenger/electron-notification",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"peerDependencies": {}
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
import * as path from 'path';
|
||||||
|
import { BrowserWindowConstructorOptions } from 'electron';
|
||||||
|
|
||||||
|
export interface ElectronNotificationOptions {
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
padding?: number;
|
||||||
|
borderRadius?: number;
|
||||||
|
displayTime?: number;
|
||||||
|
animationSteps?: number;
|
||||||
|
animationStepMs?: number;
|
||||||
|
animateInParallel?: boolean;
|
||||||
|
appIcon?: string;
|
||||||
|
pathToModule?: string;
|
||||||
|
logging?: boolean;
|
||||||
|
defaultStyleContainer?: {
|
||||||
|
[attribute: string]: any;
|
||||||
|
};
|
||||||
|
defaultStyleAppIcon?: {
|
||||||
|
[attribute: string]: any;
|
||||||
|
};
|
||||||
|
defaultStyleImage?: {
|
||||||
|
[attribute: string]: any;
|
||||||
|
};
|
||||||
|
defaultStyleClose?: {
|
||||||
|
[attribute: string]: any;
|
||||||
|
};
|
||||||
|
defaultStyleText?: {
|
||||||
|
[attribute: string]: any;
|
||||||
|
};
|
||||||
|
defaultWindow?: BrowserWindowConstructorOptions;
|
||||||
|
templatePath?: string;
|
||||||
|
htmlTemplate?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DefaultElectronNotificationOptions: ElectronNotificationOptions = {
|
||||||
|
width: 300,
|
||||||
|
height: 65,
|
||||||
|
padding: 10,
|
||||||
|
borderRadius: 5,
|
||||||
|
displayTime: 5000,
|
||||||
|
animationSteps: 5,
|
||||||
|
animationStepMs: 20,
|
||||||
|
appIcon: null,
|
||||||
|
pathToModule: '',
|
||||||
|
logging: true,
|
||||||
|
|
||||||
|
defaultStyleContainer: {
|
||||||
|
backgroundColor: '#f0f0f0',
|
||||||
|
overflow: 'hidden',
|
||||||
|
padding: 8,
|
||||||
|
border: '1px solid #CCC',
|
||||||
|
fontFamily: 'Arial',
|
||||||
|
fontSize: 12,
|
||||||
|
position: 'relative',
|
||||||
|
lineHeight: '15px'
|
||||||
|
},
|
||||||
|
defaultStyleAppIcon: {
|
||||||
|
overflow: 'hidden',
|
||||||
|
float: 'left',
|
||||||
|
height: 40,
|
||||||
|
width: 40,
|
||||||
|
marginRight: 10
|
||||||
|
},
|
||||||
|
defaultStyleImage: {
|
||||||
|
overflow: 'hidden',
|
||||||
|
float: 'right',
|
||||||
|
height: 40,
|
||||||
|
width: 40,
|
||||||
|
marginLeft: 10
|
||||||
|
},
|
||||||
|
defaultStyleClose: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 1,
|
||||||
|
right: 3,
|
||||||
|
fontSize: 11,
|
||||||
|
color: '#CCC'
|
||||||
|
},
|
||||||
|
defaultStyleText: {
|
||||||
|
margin: 0,
|
||||||
|
overflow: 'hidden',
|
||||||
|
cursor: 'default'
|
||||||
|
},
|
||||||
|
defaultWindow: {
|
||||||
|
alwaysOnTop: true,
|
||||||
|
skipTaskbar: true,
|
||||||
|
resizable: false,
|
||||||
|
show: false,
|
||||||
|
frame: false,
|
||||||
|
transparent: true,
|
||||||
|
acceptFirstMouse: true,
|
||||||
|
webPreferences: {
|
||||||
|
preload: path.join(__dirname, 'preload.js'),
|
||||||
|
webSecurity: false,
|
||||||
|
allowRunningInsecureContent: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
htmlTemplate:
|
||||||
|
'<html>\n' +
|
||||||
|
'<head></head>\n' +
|
||||||
|
'<body style="overflow: hidden; -webkit-user-select: none;">\n' +
|
||||||
|
'<div id="container">\n' +
|
||||||
|
' <img src="" id="appIcon" />\n' +
|
||||||
|
' <img src="" id="image" />\n' +
|
||||||
|
' <div id="text">\n' +
|
||||||
|
' <b id="title"></b>\n' +
|
||||||
|
' <p id="message"></p>\n' +
|
||||||
|
' </div>\n' +
|
||||||
|
' <div id="close">X</div>\n' +
|
||||||
|
'</div>\n' +
|
||||||
|
'</body>\n' +
|
||||||
|
'</html>'
|
||||||
|
};
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { ElectronNotificationEventType } from '../types/event.type';
|
||||||
|
|
||||||
|
export interface ElectronNotificationEvent {
|
||||||
|
type: ElectronNotificationEventType;
|
||||||
|
id: number;
|
||||||
|
close?: (reason: any) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ElectronNotification {
|
||||||
|
id?: number;
|
||||||
|
displayTime?: number;
|
||||||
|
title?: string;
|
||||||
|
text?: string;
|
||||||
|
image?: string;
|
||||||
|
url?: string;
|
||||||
|
sound?: string;
|
||||||
|
onClick?: (e: ElectronNotificationEvent) => void;
|
||||||
|
onShow?: (e: ElectronNotificationEvent) => void;
|
||||||
|
onClose?: (e: ElectronNotificationEvent) => void;
|
||||||
|
}
|
|
@ -0,0 +1,461 @@
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as url from 'url';
|
||||||
|
|
||||||
|
import { AnimationQueue } from '../utils/animation-queue';
|
||||||
|
import {
|
||||||
|
ElectronNotificationOptions,
|
||||||
|
DefaultElectronNotificationOptions
|
||||||
|
} from '../models/electron-notification-options';
|
||||||
|
import { screen, BrowserWindow, ipcMain, IpcMainEvent, shell } from 'electron';
|
||||||
|
import { ElectronNotification } from '../models/electron-notification';
|
||||||
|
import { ElectronNotificationEventType } from '../types/event.type';
|
||||||
|
import { Channel } from '../types/channel.type';
|
||||||
|
import { ElectronWebContentsChannel } from '@ucap-webmessenger/electron-core';
|
||||||
|
|
||||||
|
const onClickElectronNotification = 'onClickElectronNotification';
|
||||||
|
const onCloseElectronNotification = 'onCloseElectronNotification';
|
||||||
|
|
||||||
|
interface ENPoint {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ENDimension {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ElectronNotificationService {
|
||||||
|
private animationQueue: AnimationQueue;
|
||||||
|
private customOptions: ElectronNotificationOptions;
|
||||||
|
private nextInsertPosition: ENPoint;
|
||||||
|
private totalDimension: ENDimension;
|
||||||
|
private firstPosition: ENPoint;
|
||||||
|
private lowerRightCornerPosition: ENPoint;
|
||||||
|
private maxVisibleNotifications: number;
|
||||||
|
private activeNotifications: BrowserWindow[];
|
||||||
|
private inactiveWindows: BrowserWindow[];
|
||||||
|
private notificationQueue: ElectronNotification[];
|
||||||
|
private closedNotifications: Map<number, boolean>;
|
||||||
|
private latestId: number;
|
||||||
|
private templateUrl: string;
|
||||||
|
|
||||||
|
constructor(options?: ElectronNotificationOptions) {
|
||||||
|
this.customOptions = {
|
||||||
|
...DefaultElectronNotificationOptions
|
||||||
|
};
|
||||||
|
if (!!options) {
|
||||||
|
this.customOptions = {
|
||||||
|
...this.customOptions,
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setup();
|
||||||
|
this.setupEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
set options(options: ElectronNotificationOptions) {
|
||||||
|
if (!!options) {
|
||||||
|
this.customOptions = {
|
||||||
|
...this.customOptions,
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.calcDimensions();
|
||||||
|
}
|
||||||
|
|
||||||
|
get options(): ElectronNotificationOptions {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
notify(notification: ElectronNotification): number {
|
||||||
|
notification.id = this.latestId++;
|
||||||
|
this.animationQueue.push({
|
||||||
|
context: this,
|
||||||
|
func: this.showNotification,
|
||||||
|
args: [notification]
|
||||||
|
});
|
||||||
|
return notification.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
this.animationQueue.clear();
|
||||||
|
this.activeNotifications.forEach(window => window.close());
|
||||||
|
this.inactiveWindows.forEach(window => window.close());
|
||||||
|
}
|
||||||
|
|
||||||
|
closeAll(): void {
|
||||||
|
this.animationQueue.clear();
|
||||||
|
this.activeNotifications.forEach(window => window.close());
|
||||||
|
this.inactiveWindows.forEach(window => window.close());
|
||||||
|
|
||||||
|
this.setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setup(): void {
|
||||||
|
this.nextInsertPosition = { x: 0, y: 0 };
|
||||||
|
this.totalDimension = { width: 0, height: 0 };
|
||||||
|
this.firstPosition = { x: 0, y: 0 };
|
||||||
|
this.activeNotifications = [];
|
||||||
|
this.inactiveWindows = [];
|
||||||
|
this.notificationQueue = [];
|
||||||
|
this.closedNotifications = new Map();
|
||||||
|
this.latestId = 0;
|
||||||
|
|
||||||
|
this.animationQueue = new AnimationQueue();
|
||||||
|
|
||||||
|
const display = screen.getPrimaryDisplay();
|
||||||
|
|
||||||
|
this.lowerRightCornerPosition = {
|
||||||
|
x: display.bounds.x + display.workArea.x + display.workAreaSize.width,
|
||||||
|
y: display.bounds.y + display.workArea.y + display.workAreaSize.height
|
||||||
|
};
|
||||||
|
|
||||||
|
this.calcDimensions();
|
||||||
|
|
||||||
|
this.maxVisibleNotifications = Math.floor(
|
||||||
|
display.workAreaSize.height / this.totalDimension.height
|
||||||
|
);
|
||||||
|
|
||||||
|
this.maxVisibleNotifications =
|
||||||
|
7 < this.maxVisibleNotifications ? 7 : this.maxVisibleNotifications;
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupEvents(): void {
|
||||||
|
const self = this;
|
||||||
|
ipcMain.on(
|
||||||
|
Channel.close,
|
||||||
|
(
|
||||||
|
event: IpcMainEvent,
|
||||||
|
windowId: number,
|
||||||
|
notification: ElectronNotification
|
||||||
|
) => {
|
||||||
|
const onClose = self.buildCloseNotification(
|
||||||
|
BrowserWindow.fromId(windowId),
|
||||||
|
notification
|
||||||
|
);
|
||||||
|
self.buildCloseNotificationSafely(onClose)('close');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
ipcMain.on(
|
||||||
|
Channel.click,
|
||||||
|
(
|
||||||
|
event: IpcMainEvent,
|
||||||
|
windowId: number,
|
||||||
|
notification: ElectronNotification
|
||||||
|
) => {
|
||||||
|
if (!!notification.url) {
|
||||||
|
shell.openExternal(notification.url);
|
||||||
|
}
|
||||||
|
const notificationWindow = BrowserWindow.fromId(windowId);
|
||||||
|
|
||||||
|
if (
|
||||||
|
notificationWindow &&
|
||||||
|
notificationWindow[onClickElectronNotification]
|
||||||
|
) {
|
||||||
|
const onClose = self.buildCloseNotification(
|
||||||
|
BrowserWindow.fromId(windowId),
|
||||||
|
notification
|
||||||
|
);
|
||||||
|
notificationWindow[onClickElectronNotification]({
|
||||||
|
type: ElectronNotificationEventType.Click,
|
||||||
|
id: notification.id,
|
||||||
|
close: self.buildCloseNotificationSafely(onClose)
|
||||||
|
});
|
||||||
|
delete notificationWindow[onClickElectronNotification];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private calcDimensions() {
|
||||||
|
this.totalDimension = {
|
||||||
|
width: this.customOptions.width + 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
|
||||||
|
};
|
||||||
|
|
||||||
|
this.nextInsertPosition = {
|
||||||
|
x: this.firstPosition.x,
|
||||||
|
y: this.firstPosition.y
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private calcInsertPosition() {
|
||||||
|
if (this.activeNotifications.length < this.maxVisibleNotifications) {
|
||||||
|
this.nextInsertPosition.y =
|
||||||
|
this.lowerRightCornerPosition.y -
|
||||||
|
this.totalDimension.height * (this.activeNotifications.length + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateTemplatePath() {
|
||||||
|
try {
|
||||||
|
import('fs')
|
||||||
|
.then(fs => {
|
||||||
|
fs.statSync(this.customOptions.templatePath).isFile();
|
||||||
|
|
||||||
|
this.templateUrl = url.format({
|
||||||
|
pathname: this.customOptions.templatePath,
|
||||||
|
protocol: 'file:',
|
||||||
|
slashes: true
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(reason => {
|
||||||
|
throw reason;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.log(
|
||||||
|
'electron-notify: Could not find template ("' +
|
||||||
|
this.customOptions.templatePath +
|
||||||
|
'").'
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
'electron-notify: To use a different template you need to correct the config.templatePath or simply adapt config.htmlTemplate'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private showNotification(notification: ElectronNotification): Promise<any> {
|
||||||
|
const self = this;
|
||||||
|
return new Promise<any>((resolve, reject) => {
|
||||||
|
if (this.activeNotifications.length < this.maxVisibleNotifications) {
|
||||||
|
self.getWindow().then(notificationWindow => {
|
||||||
|
self.calcInsertPosition();
|
||||||
|
notificationWindow.setPosition(
|
||||||
|
self.nextInsertPosition.x,
|
||||||
|
self.nextInsertPosition.y
|
||||||
|
);
|
||||||
|
self.activeNotifications.push(notificationWindow);
|
||||||
|
|
||||||
|
const displayTime = !!notification.displayTime
|
||||||
|
? notification.displayTime
|
||||||
|
: self.customOptions.displayTime;
|
||||||
|
let timeoutId: any;
|
||||||
|
const onClose = self.buildCloseNotification(
|
||||||
|
notificationWindow,
|
||||||
|
notification,
|
||||||
|
() => timeoutId
|
||||||
|
);
|
||||||
|
const onCloseNotificationSafely = self.buildCloseNotificationSafely(
|
||||||
|
onClose
|
||||||
|
);
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
if (notificationWindow.isDestroyed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onCloseNotificationSafely('timeout');
|
||||||
|
}, displayTime);
|
||||||
|
|
||||||
|
if (!!notification.onShow) {
|
||||||
|
notification.onShow({
|
||||||
|
type: ElectronNotificationEventType.Show,
|
||||||
|
id: notification.id,
|
||||||
|
close: onCloseNotificationSafely
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!!notification.onClose) {
|
||||||
|
notificationWindow[onClickElectronNotification] =
|
||||||
|
notification.onClick;
|
||||||
|
} else {
|
||||||
|
delete notificationWindow[onClickElectronNotification];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!!notification.onClose) {
|
||||||
|
notificationWindow[onCloseElectronNotification] =
|
||||||
|
notification.onClose;
|
||||||
|
} else {
|
||||||
|
delete notificationWindow[onCloseElectronNotification];
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationWindow.webContents.send(
|
||||||
|
Channel.browserWindowSetContents,
|
||||||
|
notification
|
||||||
|
);
|
||||||
|
notificationWindow.showInactive();
|
||||||
|
resolve(notificationWindow);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self.notificationQueue.push(notification);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildCloseNotification(
|
||||||
|
notificationWindow: BrowserWindow,
|
||||||
|
notification: ElectronNotification,
|
||||||
|
timeoutIdFunc?: () => number
|
||||||
|
) {
|
||||||
|
const self = this;
|
||||||
|
return (e: ElectronNotificationEventType): Promise<void> => {
|
||||||
|
if (notificationWindow.isDestroyed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.closedNotifications.has(notification.id)) {
|
||||||
|
self.closedNotifications.delete(notification.id);
|
||||||
|
return new Promise<void>(resolve => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self.closedNotifications.set(notification.id, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!!notificationWindow[onCloseElectronNotification]) {
|
||||||
|
notificationWindow[onCloseElectronNotification]({
|
||||||
|
type: e,
|
||||||
|
id: notification.id
|
||||||
|
});
|
||||||
|
delete notificationWindow[onCloseElectronNotification];
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationWindow.webContents.send(Channel.reset);
|
||||||
|
|
||||||
|
if (!!timeoutIdFunc) {
|
||||||
|
clearTimeout(timeoutIdFunc());
|
||||||
|
}
|
||||||
|
const i = self.activeNotifications.indexOf(notificationWindow);
|
||||||
|
self.activeNotifications.splice(i, 1);
|
||||||
|
self.inactiveWindows.push(notificationWindow);
|
||||||
|
|
||||||
|
notificationWindow.hide();
|
||||||
|
self.checkForQueuedNotifications();
|
||||||
|
|
||||||
|
return self.moveOneDown(i);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildCloseNotificationSafely(
|
||||||
|
onClose: (e: ElectronNotificationEventType) => any
|
||||||
|
) {
|
||||||
|
const self = this;
|
||||||
|
return (reason: any) => {
|
||||||
|
if (!reason) {
|
||||||
|
reason = 'closedByAPI';
|
||||||
|
}
|
||||||
|
self.animationQueue.push({
|
||||||
|
context: self,
|
||||||
|
func: onClose,
|
||||||
|
args: [reason]
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkForQueuedNotifications(): void {
|
||||||
|
if (
|
||||||
|
0 < this.notificationQueue.length &&
|
||||||
|
this.activeNotifications.length < this.maxVisibleNotifications
|
||||||
|
) {
|
||||||
|
this.animationQueue.push({
|
||||||
|
context: this,
|
||||||
|
func: this.showNotification,
|
||||||
|
args: [this.notificationQueue.shift()]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getWindow(): Promise<BrowserWindow> {
|
||||||
|
const slef = this;
|
||||||
|
return new Promise<BrowserWindow>((resolve, reject) => {
|
||||||
|
if (0 < slef.inactiveWindows.length) {
|
||||||
|
resolve(slef.inactiveWindows.pop());
|
||||||
|
} else {
|
||||||
|
const windowProperties = slef.customOptions.defaultWindow;
|
||||||
|
windowProperties.width = slef.customOptions.width;
|
||||||
|
windowProperties.height = slef.customOptions.height;
|
||||||
|
|
||||||
|
const notificationWindow = new BrowserWindow(windowProperties);
|
||||||
|
notificationWindow.setVisibleOnAllWorkspaces(true);
|
||||||
|
notificationWindow.loadURL(slef.templatePath);
|
||||||
|
notificationWindow.webContents.on(
|
||||||
|
ElectronWebContentsChannel.DidFinishLoad,
|
||||||
|
() => {
|
||||||
|
// Done
|
||||||
|
notificationWindow.webContents.send(
|
||||||
|
Channel.loadConfig,
|
||||||
|
slef.customOptions
|
||||||
|
);
|
||||||
|
resolve(notificationWindow);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
notificationWindow.webContents.on(
|
||||||
|
ElectronWebContentsChannel.DevtoolsOpened,
|
||||||
|
() => {
|
||||||
|
notificationWindow.webContents.closeDevTools();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private moveOneDown(startPos: number): Promise<void> {
|
||||||
|
const self = this;
|
||||||
|
return new Promise<void>(async (resolve, reject) => {
|
||||||
|
if (startPos >= self.activeNotifications.length || -1 === startPos) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const aryNotificationPos: number[] = [];
|
||||||
|
for (let i = startPos; i < self.activeNotifications.length; i++) {
|
||||||
|
aryNotificationPos.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
aryNotificationPos.map(async index => {
|
||||||
|
await self.moveNotificationAnimation(index);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private moveNotificationAnimation(index: number): Promise<void> {
|
||||||
|
const self = this;
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
const notificationWindow = self.activeNotifications[index];
|
||||||
|
const newY =
|
||||||
|
self.lowerRightCornerPosition.y -
|
||||||
|
self.totalDimension.height * (index + 1);
|
||||||
|
const startY = notificationWindow.getPosition()[1];
|
||||||
|
const step = (newY - startY) / self.customOptions.animationSteps;
|
||||||
|
let curStep = 1;
|
||||||
|
const animationInterval = setInterval(() => {
|
||||||
|
// Abort condition
|
||||||
|
if (curStep === self.customOptions.animationSteps) {
|
||||||
|
notificationWindow.setPosition(self.firstPosition.x, newY);
|
||||||
|
clearInterval(animationInterval);
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
// Move one step down
|
||||||
|
notificationWindow.setPosition(
|
||||||
|
self.firstPosition.x,
|
||||||
|
Math.trunc(startY + curStep * step)
|
||||||
|
);
|
||||||
|
curStep++;
|
||||||
|
}, self.customOptions.animationStepMs);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
export enum Channel {
|
||||||
|
close = 'UCAP::ElectronNotification::close',
|
||||||
|
click = 'UCAP::ElectronNotification::click',
|
||||||
|
loadConfig = 'UCAP::ElectronNotification::loadConfig',
|
||||||
|
reset = 'UCAP::ElectronNotification::reset',
|
||||||
|
browserWindowSetContents = 'UCAP::ElectronNotification::BrowserWindowSetContents'
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
export enum ElectronNotificationEventType {
|
||||||
|
Show = 'Show',
|
||||||
|
Click = 'Click',
|
||||||
|
Close = 'Close'
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
export interface AnimationQueueObject {
|
||||||
|
context: any;
|
||||||
|
func: (...args: any[]) => Promise<any>;
|
||||||
|
args: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AnimationQueue {
|
||||||
|
private running = false;
|
||||||
|
private queue: AnimationQueueObject[] = [];
|
||||||
|
|
||||||
|
push(o: AnimationQueueObject): void {
|
||||||
|
if (this.running) {
|
||||||
|
this.queue.push(o);
|
||||||
|
} else {
|
||||||
|
this.running = true;
|
||||||
|
this.animate(o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
animate(o: AnimationQueueObject): void {
|
||||||
|
const self = this;
|
||||||
|
try {
|
||||||
|
(o.func.apply(o.context, o.args) as Promise<any>)
|
||||||
|
.then(() => {
|
||||||
|
if (self.queue.length > 0) {
|
||||||
|
self.animate.call(self, self.queue.shift());
|
||||||
|
} else {
|
||||||
|
self.running = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(reason => {
|
||||||
|
console.log(reason);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clear(): void {
|
||||||
|
this.queue = [];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
/*
|
||||||
|
* Public API Surface of ucap-webmessenger-electron-notification
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from './lib/models/electron-notification-options';
|
||||||
|
export * from './lib/models/electron-notification';
|
||||||
|
|
||||||
|
export * from './lib/services/electron-notification.service';
|
||||||
|
|
||||||
|
export * from './lib/types/channel.type';
|
||||||
|
export * from './lib/types/event.type';
|
||||||
|
|
||||||
|
export * from './lib/utils/animation-queue';
|
21
projects/ucap-webmessenger-electron-notification/src/test.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
||||||
|
|
||||||
|
import 'zone.js/dist/zone';
|
||||||
|
import 'zone.js/dist/zone-testing';
|
||||||
|
import { getTestBed } from '@angular/core/testing';
|
||||||
|
import {
|
||||||
|
BrowserDynamicTestingModule,
|
||||||
|
platformBrowserDynamicTesting
|
||||||
|
} from '@angular/platform-browser-dynamic/testing';
|
||||||
|
|
||||||
|
declare const require: any;
|
||||||
|
|
||||||
|
// First, initialize the Angular testing environment.
|
||||||
|
getTestBed().initTestEnvironment(
|
||||||
|
BrowserDynamicTestingModule,
|
||||||
|
platformBrowserDynamicTesting()
|
||||||
|
);
|
||||||
|
// Then we find all the tests.
|
||||||
|
const context = require.context('./', true, /\.spec\.ts$/);
|
||||||
|
// And load the modules.
|
||||||
|
context.keys().map(context);
|
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../out-tsc/lib",
|
||||||
|
"target": "es2015",
|
||||||
|
"declaration": true,
|
||||||
|
"inlineSources": true,
|
||||||
|
"types": [],
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"es2018"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"annotateForClosureCompiler": true,
|
||||||
|
"skipTemplateCodegen": true,
|
||||||
|
"strictMetadataEmit": true,
|
||||||
|
"fullTemplateTypeCheck": true,
|
||||||
|
"strictInjectionParameters": true,
|
||||||
|
"enableResourceInlining": true
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"src/test.ts",
|
||||||
|
"**/*.spec.ts"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../out-tsc/spec",
|
||||||
|
"types": [
|
||||||
|
"jasmine",
|
||||||
|
"node"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"src/test.ts"
|
||||||
|
],
|
||||||
|
"include": [
|
||||||
|
"**/*.spec.ts",
|
||||||
|
"**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
17
projects/ucap-webmessenger-electron-notification/tslint.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tslint.json",
|
||||||
|
"rules": {
|
||||||
|
"directive-selector": [
|
||||||
|
true,
|
||||||
|
"attribute",
|
||||||
|
"ucapElectronNotification",
|
||||||
|
"camelCase"
|
||||||
|
],
|
||||||
|
"component-selector": [
|
||||||
|
true,
|
||||||
|
"element",
|
||||||
|
"ucap-electron-notification",
|
||||||
|
"kebab-case"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,14 +3,15 @@ import { Observable } from 'rxjs';
|
||||||
import {
|
import {
|
||||||
NativeService,
|
NativeService,
|
||||||
WindowState,
|
WindowState,
|
||||||
NotiRequest,
|
NotificationRequest,
|
||||||
WindowIdle
|
WindowIdle
|
||||||
} from '@ucap-webmessenger/native';
|
} from '@ucap-webmessenger/native';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
export class BrowserNativeService implements NativeService {
|
export class BrowserNativeService implements NativeService {
|
||||||
showNotify(noti: NotiRequest): void {}
|
notify(noti: NotificationRequest): void {}
|
||||||
|
closeAllNotify(): void {}
|
||||||
|
|
||||||
checkForUpdates(): Observable<boolean> {
|
checkForUpdates(): Observable<boolean> {
|
||||||
return new Observable<boolean>(subscriber => {
|
return new Observable<boolean>(subscriber => {
|
||||||
|
|
|
@ -5,11 +5,17 @@ import { Observable, Subject } from 'rxjs';
|
||||||
import {
|
import {
|
||||||
NativeService,
|
NativeService,
|
||||||
WindowState,
|
WindowState,
|
||||||
NotiRequest,
|
NotificationRequest,
|
||||||
WindowIdle
|
WindowIdle
|
||||||
} from '@ucap-webmessenger/native';
|
} from '@ucap-webmessenger/native';
|
||||||
import { Channel } from '../types/channel.type';
|
|
||||||
import { share } from 'rxjs/operators';
|
import { share } from 'rxjs/operators';
|
||||||
|
import {
|
||||||
|
NotificationChannel,
|
||||||
|
UpdaterChannel,
|
||||||
|
FileChannel,
|
||||||
|
WindowStateChannel,
|
||||||
|
IdleStateChannel
|
||||||
|
} from '../types/channel.type';
|
||||||
|
|
||||||
export class ElectronNativeService implements NativeService {
|
export class ElectronNativeService implements NativeService {
|
||||||
private windowStateChangedSubject: Subject<WindowState> | null = null;
|
private windowStateChangedSubject: Subject<WindowState> | null = null;
|
||||||
|
@ -18,22 +24,18 @@ export class ElectronNativeService implements NativeService {
|
||||||
private idleStateChangedSubject: Subject<WindowIdle> | null = null;
|
private idleStateChangedSubject: Subject<WindowIdle> | null = null;
|
||||||
private idleStateChanged$: Observable<WindowIdle> | null = null;
|
private idleStateChanged$: Observable<WindowIdle> | null = null;
|
||||||
|
|
||||||
showNotify(noti: NotiRequest): void {
|
notify(noti: NotificationRequest): void {
|
||||||
ipcRenderer.send(
|
ipcRenderer.send(NotificationChannel.Notify, noti);
|
||||||
Channel.showNotify,
|
}
|
||||||
noti.roomSeq,
|
|
||||||
noti.title,
|
closeAllNotify(): void {
|
||||||
noti.contents,
|
ipcRenderer.send(NotificationChannel.CloseAllNotify);
|
||||||
noti.image,
|
|
||||||
noti.useSound,
|
|
||||||
noti.interval
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
checkForUpdates(): Observable<boolean> {
|
checkForUpdates(): Observable<boolean> {
|
||||||
return new Observable<boolean>(subscriber => {
|
return new Observable<boolean>(subscriber => {
|
||||||
try {
|
try {
|
||||||
subscriber.next(ipcRenderer.sendSync(Channel.checkForUpdates));
|
subscriber.next(ipcRenderer.sendSync(UpdaterChannel.Check));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
subscriber.error(error);
|
subscriber.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -43,13 +45,13 @@ export class ElectronNativeService implements NativeService {
|
||||||
}
|
}
|
||||||
|
|
||||||
showImageViewer(): void {
|
showImageViewer(): void {
|
||||||
ipcRenderer.send(Channel.showImageViewer);
|
ipcRenderer.send(FileChannel.ShowImageViewer);
|
||||||
}
|
}
|
||||||
|
|
||||||
readFile(path: string): Observable<Buffer> {
|
readFile(path: string): Observable<Buffer> {
|
||||||
return new Observable<Buffer>(subscriber => {
|
return new Observable<Buffer>(subscriber => {
|
||||||
try {
|
try {
|
||||||
subscriber.next(ipcRenderer.sendSync(Channel.readFile, path));
|
subscriber.next(ipcRenderer.sendSync(FileChannel.ReadFile, path));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
subscriber.error(error);
|
subscriber.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -66,7 +68,7 @@ export class ElectronNativeService implements NativeService {
|
||||||
return new Observable<string>(subscriber => {
|
return new Observable<string>(subscriber => {
|
||||||
try {
|
try {
|
||||||
subscriber.next(
|
subscriber.next(
|
||||||
ipcRenderer.sendSync(Channel.saveFile, buffer, fileName, path)
|
ipcRenderer.sendSync(FileChannel.SaveFile, buffer, fileName, path)
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
subscriber.error(error);
|
subscriber.error(error);
|
||||||
|
@ -85,9 +87,8 @@ export class ElectronNativeService implements NativeService {
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcRenderer.on(
|
ipcRenderer.on(
|
||||||
Channel.windowStateChanged,
|
WindowStateChannel.Changed,
|
||||||
(event: IpcRendererEvent, windowState: WindowState) => {
|
(event: IpcRendererEvent, windowState: WindowState) => {
|
||||||
console.log('windowStateChanged', windowState);
|
|
||||||
this.windowStateChangedSubject.next(windowState);
|
this.windowStateChangedSubject.next(windowState);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -133,10 +134,10 @@ export class ElectronNativeService implements NativeService {
|
||||||
.pipe(share());
|
.pipe(share());
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcRenderer.send(Channel.idleStateStart, 'start');
|
ipcRenderer.send(IdleStateChannel.StartCheck);
|
||||||
|
|
||||||
ipcRenderer.on(
|
ipcRenderer.on(
|
||||||
Channel.idleStateChanged,
|
IdleStateChannel.Changed,
|
||||||
(event: IpcRendererEvent, idleState: WindowIdle) => {
|
(event: IpcRendererEvent, idleState: WindowIdle) => {
|
||||||
this.idleStateChangedSubject.next(idleState);
|
this.idleStateChangedSubject.next(idleState);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,23 @@
|
||||||
export enum Channel {
|
export enum NotificationChannel {
|
||||||
windowStateChanged = 'window-state-changed',
|
Notify = 'UCAP::notification::notify',
|
||||||
idleStateChanged = 'window-idle-state-changed',
|
CloseAllNotify = 'UCAP::notification::closeAllNotify'
|
||||||
idleStateStart = 'window-idle-state-check-start',
|
}
|
||||||
|
|
||||||
showNotify = 'UCAP::showNotify',
|
export enum UpdaterChannel {
|
||||||
checkForUpdates = 'UCAP::checkForUpdates',
|
Check = 'UCAP::updater::check'
|
||||||
showImageViewer = 'UCAP::showImageViewer',
|
}
|
||||||
saveFile = 'UCAP::saveFile',
|
|
||||||
readFile = 'UCAP::readFile'
|
export enum FileChannel {
|
||||||
|
ShowImageViewer = 'UCAP::file::showImageViewer',
|
||||||
|
SaveFile = 'UCAP::file::saveFile',
|
||||||
|
ReadFile = 'UCAP::file::readFile'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum WindowStateChannel {
|
||||||
|
Changed = 'UCAP::windowState::windowStateChanged'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum IdleStateChannel {
|
||||||
|
Changed = 'UCAP::idleState::changed',
|
||||||
|
StartCheck = 'UCAP::idleState::startCheck'
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
export interface NotificationRequest {
|
||||||
|
roomSeq: string;
|
||||||
|
title: string;
|
||||||
|
contents: string;
|
||||||
|
image: string;
|
||||||
|
useSound: boolean;
|
||||||
|
interval?: number;
|
||||||
|
}
|
|
@ -2,9 +2,11 @@ import { Observable } from 'rxjs';
|
||||||
|
|
||||||
import { WindowState } from '../types/window-state.type';
|
import { WindowState } from '../types/window-state.type';
|
||||||
import { WindowIdle } from '../types/window-idle.type';
|
import { WindowIdle } from '../types/window-idle.type';
|
||||||
|
import { NotificationRequest } from '../models/notification';
|
||||||
|
|
||||||
export interface NativeService {
|
export interface NativeService {
|
||||||
showNotify(noti: NotiRequest): void;
|
notify(noti: NotificationRequest): void;
|
||||||
|
closeAllNotify(): void;
|
||||||
|
|
||||||
checkForUpdates(): Observable<boolean>;
|
checkForUpdates(): Observable<boolean>;
|
||||||
|
|
||||||
|
@ -20,12 +22,3 @@ export interface NativeService {
|
||||||
|
|
||||||
idleStateChanged(): Observable<WindowIdle>;
|
idleStateChanged(): Observable<WindowIdle>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NotiRequest {
|
|
||||||
roomSeq: string;
|
|
||||||
title: string;
|
|
||||||
contents: string;
|
|
||||||
image: string;
|
|
||||||
useSound: boolean;
|
|
||||||
interval?: number;
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
* Public API Surface of ucap-webmessenger-native
|
* Public API Surface of ucap-webmessenger-native
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export * from './lib/models/notification';
|
||||||
|
|
||||||
export * from './lib/services/native.service';
|
export * from './lib/services/native.service';
|
||||||
|
|
||||||
export * from './lib/types/token';
|
export * from './lib/types/token';
|
||||||
|
|
|
@ -1,3 +1,11 @@
|
||||||
|
import {
|
||||||
|
EventType,
|
||||||
|
EventJson,
|
||||||
|
FileEventJson,
|
||||||
|
MassTextEventJson
|
||||||
|
} from '@ucap-webmessenger/protocol-event';
|
||||||
|
import { FileType } from '@ucap-webmessenger/protocol-file';
|
||||||
|
|
||||||
export class StringUtil {
|
export class StringUtil {
|
||||||
/**
|
/**
|
||||||
* linefeed > <br>
|
* linefeed > <br>
|
||||||
|
@ -134,4 +142,58 @@ export class StringUtil {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static convertFinalEventMessage(
|
||||||
|
eventType: EventType,
|
||||||
|
finalEventMessage: EventJson
|
||||||
|
): string | null {
|
||||||
|
let eventMessage: string = null;
|
||||||
|
|
||||||
|
switch (eventType) {
|
||||||
|
case EventType.Join:
|
||||||
|
case EventType.Exit:
|
||||||
|
case EventType.RenameRoom:
|
||||||
|
case EventType.NotificationForTimerRoom:
|
||||||
|
case EventType.GuideForRoomTimerChanged:
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 해당 타입은 메시지를 갱신하지 않는다.
|
||||||
|
* @description Edit with ui-chat > messages.component.ts
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EventType.Sticker:
|
||||||
|
eventMessage = '스티커';
|
||||||
|
break;
|
||||||
|
case EventType.File:
|
||||||
|
{
|
||||||
|
const m = finalEventMessage as FileEventJson;
|
||||||
|
|
||||||
|
if (FileType.Image === m.fileType) {
|
||||||
|
eventMessage = '이미지';
|
||||||
|
} else {
|
||||||
|
eventMessage = '첨부파일';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EventType.VideoConference:
|
||||||
|
eventMessage = '화상회의';
|
||||||
|
break;
|
||||||
|
case EventType.MassText:
|
||||||
|
{
|
||||||
|
const m = finalEventMessage as MassTextEventJson;
|
||||||
|
eventMessage = m.content;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
const m = finalEventMessage as string;
|
||||||
|
eventMessage = m;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return eventMessage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,6 +125,12 @@
|
||||||
],
|
],
|
||||||
"@ucap-webmessenger/util": [
|
"@ucap-webmessenger/util": [
|
||||||
"projects/ucap-webmessenger-util/src/public-api"
|
"projects/ucap-webmessenger-util/src/public-api"
|
||||||
|
],
|
||||||
|
"@ucap-webmessenger/electron-core": [
|
||||||
|
"projects/ucap-webmessenger-electron-core/src/public-api"
|
||||||
|
],
|
||||||
|
"@ucap-webmessenger/electron-notification": [
|
||||||
|
"projects/ucap-webmessenger-electron-notification/src/public-api"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|