project initialized
This commit is contained in:
commit
79b0bd78d5
2
.eslintignore
Normal file
2
.eslintignore
Normal file
|
@ -0,0 +1,2 @@
|
|||
build/
|
||||
dist/
|
3
.eslintrc.json
Normal file
3
.eslintrc.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "./node_modules/gts/"
|
||||
}
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
node_modules
|
||||
build/
|
||||
dist/
|
||||
pack/
|
4
.npmignore
Normal file
4
.npmignore
Normal file
|
@ -0,0 +1,4 @@
|
|||
node_modules
|
||||
build/
|
||||
dist/
|
||||
pack/
|
3
.prettierrc.js
Normal file
3
.prettierrc.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
...require('gts/.prettierrc.json'),
|
||||
};
|
14
.vscode/settings.json
vendored
Normal file
14
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"editor.tabSize": 2,
|
||||
"editor.insertSpaces": true,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnPaste": true,
|
||||
"editor.autoClosingBrackets": "languageDefined",
|
||||
"editor.trimAutoWhitespace": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"files.trimFinalNewlines": true,
|
||||
"files.watcherExclude": {
|
||||
"**/dist": true
|
||||
}
|
||||
}
|
33
package.json
Normal file
33
package.json
Normal file
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"name": "notification",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"description": "",
|
||||
"license": "Apache-2.0",
|
||||
"keywords": [],
|
||||
"scripts": {
|
||||
"lint": "gts lint",
|
||||
"clean": "gts clean",
|
||||
"fix": "gts fix",
|
||||
"build:notification-electron": "ts-node ./scripts/build.ts notification-electron"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"lodash": "^4.17.20",
|
||||
"rxjs": "^6.6.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "^9.0.4",
|
||||
"@types/lodash": "^4.14.165",
|
||||
"@types/node": "^14.11.2",
|
||||
"cross-env": "^7.0.2",
|
||||
"electron": "^11.0.3",
|
||||
"fs-extra": "^9.0.1",
|
||||
"gts": "^3.0.2",
|
||||
"husky": "^4.3.0",
|
||||
"notification-electron": "file:pack/notification-electron-0.0.1.tgz",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-node": "^9.0.0",
|
||||
"typescript": "^4.0.3"
|
||||
}
|
||||
}
|
7
projects/notification-electron/package.json
Normal file
7
projects/notification-electron/package.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"name": "notification-electron",
|
||||
"version": "0.0.1",
|
||||
"peerDependencies": {
|
||||
"electron": "^7.0.0"
|
||||
}
|
||||
}
|
6
projects/notification-electron/project.json
Normal file
6
projects/notification-electron/project.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"dest": "../../dist/notification-electron",
|
||||
"lib": {
|
||||
"entryFile": "src/public-api.ts"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,651 @@
|
|||
import * as url from 'url';
|
||||
import * as _ from 'lodash';
|
||||
import * as Electron from 'electron';
|
||||
|
||||
import {AnimationQueue} from '../utils/animation.queue';
|
||||
import {
|
||||
NotificationChannel,
|
||||
NotificationWindowChannel,
|
||||
} from '../types/channel.type';
|
||||
|
||||
export interface Template {
|
||||
filePath?: string;
|
||||
html?: string;
|
||||
preload?: string;
|
||||
}
|
||||
|
||||
export interface DefaultOptions {
|
||||
appIcon?: string;
|
||||
optionsForBrowserWindow?: Electron.BrowserWindowConstructorOptions;
|
||||
displayTime?: number;
|
||||
template: Template;
|
||||
padding?: {
|
||||
item?: number;
|
||||
bottom?: number;
|
||||
};
|
||||
animation?: {
|
||||
step: number;
|
||||
timeForStep: number;
|
||||
};
|
||||
windowPool?: {
|
||||
min?: number;
|
||||
max?: number;
|
||||
};
|
||||
}
|
||||
|
||||
export const defaultOptions: DefaultOptions = {
|
||||
optionsForBrowserWindow: {
|
||||
alwaysOnTop: true,
|
||||
skipTaskbar: true,
|
||||
resizable: false,
|
||||
show: false,
|
||||
frame: false,
|
||||
transparent: true,
|
||||
acceptFirstMouse: true,
|
||||
},
|
||||
displayTime: 5000,
|
||||
padding: {
|
||||
item: 10,
|
||||
bottom: 10,
|
||||
},
|
||||
animation: {
|
||||
step: 5,
|
||||
timeForStep: 20,
|
||||
},
|
||||
windowPool: {
|
||||
min: 0,
|
||||
max: 7,
|
||||
},
|
||||
template: {
|
||||
html: `
|
||||
<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>
|
||||
`,
|
||||
},
|
||||
};
|
||||
|
||||
export type OnShow4NotificationWindow = () => Promise<void>;
|
||||
export type OnClose4NotificationWindow = () => Promise<void>;
|
||||
export type OnClick4NotificationWindow = () => Promise<void>;
|
||||
export type OnUser4NotificationWindow = (
|
||||
channel: string,
|
||||
...args: any[]
|
||||
) => Promise<void>;
|
||||
|
||||
export interface Notification {
|
||||
id?: number;
|
||||
optionsForBrowserWindow?: Omit<
|
||||
Electron.BrowserWindowConstructorOptions,
|
||||
'width'
|
||||
>;
|
||||
displayTime?: number;
|
||||
template: Template;
|
||||
data?: any;
|
||||
onShow?: OnShow4NotificationWindow;
|
||||
onClose?: OnClose4NotificationWindow;
|
||||
onClick?: OnClick4NotificationWindow;
|
||||
onUser?: OnUser4NotificationWindow;
|
||||
}
|
||||
|
||||
class WindowPooler {
|
||||
private readonly __windows: Electron.BrowserWindow[];
|
||||
private handle: any;
|
||||
|
||||
constructor(private readonly __minSize: number) {
|
||||
this.__minSize = 0 > this.__minSize ? 0 : this.__minSize;
|
||||
this.__windows = [];
|
||||
}
|
||||
|
||||
get length() {
|
||||
return this.__windows.length;
|
||||
}
|
||||
|
||||
push(...items: Electron.BrowserWindow[]): number {
|
||||
const length = this.__windows.push(...items);
|
||||
|
||||
if (this.__minSize < length) {
|
||||
this.__start();
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
pop(): Electron.BrowserWindow | undefined {
|
||||
if (!this.__windows || 0 === this.__windows.length) {
|
||||
return undefined;
|
||||
}
|
||||
return this.__windows.pop();
|
||||
}
|
||||
|
||||
closeAll() {
|
||||
while (this.__windows.length > 0) {
|
||||
const w = this.__windows.pop();
|
||||
if (!!w) {
|
||||
w.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private __start() {
|
||||
setTimeout(() => {
|
||||
while (this.__minSize < this.__windows.length) {
|
||||
const w = this.__windows.pop();
|
||||
if (!!w) {
|
||||
w.close();
|
||||
}
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
type Activated = {
|
||||
notification: Notification;
|
||||
browserWindow: Electron.BrowserWindow;
|
||||
};
|
||||
|
||||
export class NotificationService {
|
||||
private readonly __defaultOptions: DefaultOptions;
|
||||
private __animationQueue = new AnimationQueue();
|
||||
private __windowPooler: WindowPooler;
|
||||
private __notificationId = 0;
|
||||
private __width = 0;
|
||||
private __availableHeight = 0;
|
||||
private __paddingBottom: number;
|
||||
private __paddingItem: number;
|
||||
private __basePosition: Electron.Point;
|
||||
private __activated: Activated[] = [];
|
||||
private __delayQueue: Notification[] = [];
|
||||
private __closed = new Map<number, boolean>();
|
||||
|
||||
constructor(options?: DefaultOptions) {
|
||||
this.__defaultOptions = _.assignIn(defaultOptions, options);
|
||||
this.__windowPooler = new WindowPooler(
|
||||
!!this.__defaultOptions.windowPool!.min
|
||||
? this.__defaultOptions.windowPool!.min
|
||||
: 0
|
||||
);
|
||||
this.__basePosition = {x: 0, y: 0};
|
||||
this.__paddingBottom = !!this.__defaultOptions.padding?.bottom
|
||||
? this.__defaultOptions.padding?.bottom
|
||||
: 0;
|
||||
this.__paddingItem = !!this.__defaultOptions.padding?.item
|
||||
? this.__defaultOptions.padding?.item
|
||||
: 0;
|
||||
}
|
||||
|
||||
init(): void {
|
||||
this.__initDimension();
|
||||
this.__initListeners();
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.__destroyListeners();
|
||||
}
|
||||
|
||||
push(notification: Notification): void {
|
||||
notification.id = this.__nextNotificationId();
|
||||
this.__animationQueue.push({
|
||||
animate: this.__show,
|
||||
context: this,
|
||||
args: [notification],
|
||||
});
|
||||
}
|
||||
|
||||
private __show(
|
||||
notification: Notification
|
||||
): Promise<Electron.BrowserWindow | undefined> {
|
||||
const __this = this;
|
||||
return new Promise<Electron.BrowserWindow | undefined>(
|
||||
(resolve, reject) => {
|
||||
const nextPosition = __this.__nextPosition(notification);
|
||||
if (!!nextPosition) {
|
||||
__this
|
||||
.__getWindow(notification)
|
||||
.then(browserWindow => {
|
||||
browserWindow.setPosition(nextPosition.x, nextPosition.y);
|
||||
__this.__activated.push({
|
||||
notification,
|
||||
browserWindow,
|
||||
});
|
||||
|
||||
const displayTime = !!notification.displayTime
|
||||
? notification.displayTime
|
||||
: !!__this.__defaultOptions.displayTime
|
||||
? __this.__defaultOptions.displayTime
|
||||
: 5000;
|
||||
let timeoutId: any;
|
||||
const onCloseFnc = __this.__onCloseFnc(
|
||||
browserWindow,
|
||||
notification,
|
||||
() => timeoutId
|
||||
);
|
||||
const onCloseFncGraceful = __this.__onCloseFnc4Graceful(
|
||||
onCloseFnc
|
||||
);
|
||||
timeoutId = setTimeout(() => {
|
||||
if (browserWindow.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
onCloseFncGraceful('timeout');
|
||||
}, displayTime);
|
||||
|
||||
if (!!notification.onShow) {
|
||||
notification.onShow();
|
||||
}
|
||||
|
||||
browserWindow.webContents.send(
|
||||
NotificationChannel.ready4NotificationWindow
|
||||
);
|
||||
|
||||
browserWindow.showInactive();
|
||||
return resolve(browserWindow);
|
||||
})
|
||||
.catch(reason => {});
|
||||
} else {
|
||||
__this.__delayQueue.push(notification);
|
||||
return resolve(undefined);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private __getWindow(
|
||||
notification: Notification
|
||||
): Promise<Electron.BrowserWindow> {
|
||||
const __this = this;
|
||||
return new Promise<Electron.BrowserWindow>((resolve, reject) => {
|
||||
if (0 < __this.__windowPooler.length) {
|
||||
const browserWindow = __this.__windowPooler.pop();
|
||||
if (!!browserWindow) {
|
||||
resolve(browserWindow);
|
||||
}
|
||||
} else {
|
||||
const constructorOptions = _.assignIn(
|
||||
this.__defaultOptions.optionsForBrowserWindow,
|
||||
notification.optionsForBrowserWindow,
|
||||
{
|
||||
width: this.__width,
|
||||
title: 'Notification',
|
||||
}
|
||||
);
|
||||
|
||||
const browserWindow = new Electron.BrowserWindow(constructorOptions);
|
||||
browserWindow.setVisibleOnAllWorkspaces(true);
|
||||
browserWindow.loadURL(__this.__templateUrl(notification));
|
||||
browserWindow.webContents.once('did-finish-load', () => {
|
||||
browserWindow.webContents.send(
|
||||
NotificationChannel.init4NotificationWindow,
|
||||
notification.data
|
||||
);
|
||||
resolve(browserWindow);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private __nextNotificationId(): number {
|
||||
this.__notificationId++;
|
||||
|
||||
if (Number.MAX_SAFE_INTEGER < this.__notificationId) {
|
||||
this.__notificationId = 0;
|
||||
}
|
||||
|
||||
return this.__notificationId;
|
||||
}
|
||||
|
||||
private __nextPosition(
|
||||
notification: Notification
|
||||
): Electron.Point | undefined {
|
||||
const itemTotalHeight = this.__activated.reduce((a, b) => {
|
||||
return a + b.browserWindow.getBounds().height;
|
||||
}, 0);
|
||||
|
||||
const itemWidth = this.__width;
|
||||
const itemHeight = !!notification.optionsForBrowserWindow?.height
|
||||
? notification.optionsForBrowserWindow?.height
|
||||
: !!this.__defaultOptions.optionsForBrowserWindow?.height
|
||||
? this.__defaultOptions.optionsForBrowserWindow?.height
|
||||
: 10;
|
||||
|
||||
const totalHeight =
|
||||
itemTotalHeight +
|
||||
this.__paddingBottom +
|
||||
this.__paddingItem *
|
||||
(1 < this.__activated.length ? this.__activated.length - 1 : 0);
|
||||
const nextTotalHeight = totalHeight + itemHeight + this.__paddingItem;
|
||||
|
||||
if (this.__availableHeight < nextTotalHeight) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!!notification.optionsForBrowserWindow) {
|
||||
notification.optionsForBrowserWindow.height = itemHeight;
|
||||
}
|
||||
|
||||
return {
|
||||
x: this.__basePosition.x - this.__width,
|
||||
y: this.__basePosition.y - nextTotalHeight,
|
||||
};
|
||||
}
|
||||
|
||||
private __onCloseFnc4Graceful(onClose: OnClose4NotificationWindow) {
|
||||
const __this = this;
|
||||
return (reason: any): void => {
|
||||
if (!reason) {
|
||||
reason = 'closedGracefully';
|
||||
}
|
||||
|
||||
__this.__animationQueue.push({
|
||||
animate: onClose,
|
||||
context: __this,
|
||||
args: [reason],
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
private __onCloseFnc(
|
||||
browserWindow: Electron.BrowserWindow,
|
||||
notification: Notification,
|
||||
timeoutIdFnc?: () => any
|
||||
) {
|
||||
const __this = this;
|
||||
return (): Promise<void> => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (browserWindow.isDestroyed()) {
|
||||
return resolve();
|
||||
}
|
||||
const notificationId = !!notification.id ? notification.id : 0;
|
||||
if (__this.__closed.has(notificationId)) {
|
||||
__this.__closed.delete(notificationId);
|
||||
return resolve();
|
||||
} else {
|
||||
__this.__closed.set(notificationId, true);
|
||||
}
|
||||
|
||||
browserWindow.webContents.send(
|
||||
NotificationChannel.reset4NotificationWindow
|
||||
);
|
||||
|
||||
if (!!timeoutIdFnc) {
|
||||
clearTimeout(timeoutIdFnc());
|
||||
}
|
||||
const i = __this.__activated.findIndex(
|
||||
w => w.browserWindow.id === browserWindow.id
|
||||
);
|
||||
__this.__activated.splice(i, 1);
|
||||
__this.__windowPooler.push(browserWindow);
|
||||
|
||||
browserWindow.hide();
|
||||
__this.__check4Queued();
|
||||
|
||||
__this.__animate4Move(i);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
private __findActivatedByNotificationId(id: number): Activated | undefined {
|
||||
const i = this.__activated.findIndex(a => a.notification.id === id);
|
||||
if (-1 === i) {
|
||||
return undefined;
|
||||
}
|
||||
return this.__activated[i];
|
||||
}
|
||||
|
||||
private __findActivatedByBrowserWindowId(id: number): Activated | undefined {
|
||||
const i = this.__activated.findIndex(a => a.browserWindow.id === id);
|
||||
if (-1 === i) {
|
||||
return undefined;
|
||||
}
|
||||
return this.__activated[i];
|
||||
}
|
||||
|
||||
private __check4Queued(): void {
|
||||
if (
|
||||
0 < this.__delayQueue.length &&
|
||||
!!this.__nextPosition(this.__delayQueue[0])
|
||||
) {
|
||||
this.__animationQueue.push({
|
||||
animate: this.__show,
|
||||
context: this,
|
||||
args: [this.__delayQueue.shift()],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private __animate4Move(startIndex: number): Promise<void> {
|
||||
const __this = this;
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
if (startIndex >= __this.__activated.length || -1 === startIndex) {
|
||||
return resolve();
|
||||
}
|
||||
|
||||
const indexes: number[] = [];
|
||||
for (let i = startIndex; i < __this.__activated.length; i++) {
|
||||
indexes.push(i);
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
indexes.map(async index => {
|
||||
await __this.__animateFnc4Move(index);
|
||||
})
|
||||
);
|
||||
return resolve();
|
||||
});
|
||||
}
|
||||
|
||||
private __animateFnc4Move(index: number): Promise<void> {
|
||||
const __this = this;
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const activated = __this.__activated[index];
|
||||
const startY = activated.browserWindow.getPosition()[1];
|
||||
const endY =
|
||||
startY +
|
||||
(!!activated.notification.optionsForBrowserWindow?.height
|
||||
? activated.notification.optionsForBrowserWindow?.height
|
||||
: 10);
|
||||
const animationStep = !!__this.__defaultOptions.animation?.step
|
||||
? __this.__defaultOptions.animation?.step
|
||||
: 5;
|
||||
const animationTimeForStep = !!this.__defaultOptions.animation
|
||||
?.timeForStep
|
||||
? this.__defaultOptions.animation?.timeForStep
|
||||
: 20;
|
||||
const step = (endY - startY) / animationStep;
|
||||
|
||||
let currentStep = 1;
|
||||
const intervalId = setInterval(() => {
|
||||
if (currentStep === animationStep) {
|
||||
activated.browserWindow.setPosition(__this.__basePosition.x, endY);
|
||||
clearInterval(intervalId);
|
||||
return resolve();
|
||||
}
|
||||
|
||||
activated.browserWindow.setPosition(
|
||||
__this.__basePosition.x,
|
||||
Math.trunc(startY + currentStep * step)
|
||||
);
|
||||
currentStep++;
|
||||
}, animationTimeForStep);
|
||||
});
|
||||
}
|
||||
|
||||
private __templateUrl(notification: Notification): string {
|
||||
let u: string = '';
|
||||
|
||||
const filePath2Url = (filePath: string) => {
|
||||
return url.format({
|
||||
protocol: 'file:',
|
||||
pathname: filePath,
|
||||
slashes: true,
|
||||
});
|
||||
};
|
||||
|
||||
const html2Url = (html: string) => {
|
||||
return 'data:text/html,' + encodeURIComponent(html);
|
||||
};
|
||||
|
||||
if (!!notification.template.filePath) {
|
||||
return filePath2Url(notification.template.filePath);
|
||||
}
|
||||
|
||||
if (!!notification.template.html) {
|
||||
return html2Url(notification.template.html);
|
||||
}
|
||||
|
||||
if (!!this.__defaultOptions.template.filePath) {
|
||||
return filePath2Url(this.__defaultOptions.template.filePath);
|
||||
}
|
||||
|
||||
if (!!this.__defaultOptions.template.html) {
|
||||
return html2Url(this.__defaultOptions.template.html);
|
||||
}
|
||||
|
||||
return !!defaultOptions.template.html ? defaultOptions.template.html : '';
|
||||
}
|
||||
|
||||
private __initDimension(): void {
|
||||
const display = Electron.screen.getPrimaryDisplay();
|
||||
|
||||
this.__basePosition = {
|
||||
x: display.bounds.x + display.workArea.x + display.workAreaSize.width,
|
||||
y: display.bounds.y + display.workArea.y + display.workAreaSize.height,
|
||||
};
|
||||
|
||||
this.__width =
|
||||
!!this.__defaultOptions.optionsForBrowserWindow?.width &&
|
||||
this.__defaultOptions.optionsForBrowserWindow?.width <
|
||||
display.workAreaSize.width
|
||||
? this.__defaultOptions.optionsForBrowserWindow?.width
|
||||
: display.workAreaSize.width;
|
||||
this.__availableHeight = display.workAreaSize.height;
|
||||
}
|
||||
|
||||
private __initListeners(): void {
|
||||
Electron.ipcMain.on(
|
||||
NotificationWindowChannel.onClose,
|
||||
this.__onClose4NotificationWindow.bind(this)
|
||||
);
|
||||
Electron.ipcMain.on(
|
||||
NotificationWindowChannel.onClick,
|
||||
this.__onClick4NotificationWindow.bind(this)
|
||||
);
|
||||
Electron.ipcMain.on(
|
||||
NotificationWindowChannel.onUser,
|
||||
this.__onUser4NotificationWindow.bind(this)
|
||||
);
|
||||
|
||||
Electron.screen.on(
|
||||
'display-added',
|
||||
this.__onDisplayAdded4ElectronScreen.bind(this)
|
||||
);
|
||||
Electron.screen.on(
|
||||
'display-removed',
|
||||
this.__onDisplayRemoved4ElectronScreen.bind(this)
|
||||
);
|
||||
Electron.screen.on(
|
||||
'display-metrics-changed',
|
||||
this.__onDisplayMetricsChanged4ElectronScreen.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
private __destroyListeners(): void {
|
||||
Electron.ipcMain.off(
|
||||
NotificationWindowChannel.onClose,
|
||||
this.__onClose4NotificationWindow.bind(this)
|
||||
);
|
||||
Electron.ipcMain.off(
|
||||
NotificationWindowChannel.onClick,
|
||||
this.__onClick4NotificationWindow.bind(this)
|
||||
);
|
||||
Electron.ipcMain.off(
|
||||
NotificationWindowChannel.onUser,
|
||||
this.__onUser4NotificationWindow.bind(this)
|
||||
);
|
||||
|
||||
Electron.screen.off(
|
||||
'display-added',
|
||||
this.__onDisplayAdded4ElectronScreen.bind(this)
|
||||
);
|
||||
Electron.screen.off(
|
||||
'display-removed',
|
||||
this.__onDisplayRemoved4ElectronScreen.bind(this)
|
||||
);
|
||||
Electron.screen.off(
|
||||
'display-metrics-changed',
|
||||
this.__onDisplayMetricsChanged4ElectronScreen.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
private __onClose4NotificationWindow(
|
||||
event: Electron.IpcMainEvent,
|
||||
notificationId: number
|
||||
): void {
|
||||
const activated = this.__findActivatedByNotificationId(notificationId);
|
||||
if (!activated || !activated.notification.onClose) {
|
||||
return;
|
||||
}
|
||||
|
||||
activated.notification.onClose();
|
||||
}
|
||||
|
||||
private __onClick4NotificationWindow(
|
||||
event: Electron.IpcMainEvent,
|
||||
notificationId: number
|
||||
): void {
|
||||
const activated = this.__findActivatedByNotificationId(notificationId);
|
||||
if (!activated || !activated.notification.onClick) {
|
||||
return;
|
||||
}
|
||||
|
||||
activated.notification.onClick();
|
||||
}
|
||||
|
||||
private __onUser4NotificationWindow(
|
||||
event: Electron.IpcMainEvent,
|
||||
notificationId: number,
|
||||
channel: string,
|
||||
...args: any[]
|
||||
): void {
|
||||
const activated = this.__findActivatedByNotificationId(notificationId);
|
||||
if (!activated || !activated.notification.onUser) {
|
||||
return;
|
||||
}
|
||||
|
||||
activated.notification.onUser(channel, ...args);
|
||||
}
|
||||
|
||||
private __onDisplayAdded4ElectronScreen(
|
||||
event: Electron.Event,
|
||||
newDisplay: Electron.Display
|
||||
): void {
|
||||
this.__initDimension();
|
||||
}
|
||||
|
||||
private __onDisplayRemoved4ElectronScreen(
|
||||
event: Electron.Event,
|
||||
oldDisplay: Electron.Display
|
||||
): void {
|
||||
this.__initDimension();
|
||||
}
|
||||
|
||||
private __onDisplayMetricsChanged4ElectronScreen(
|
||||
event: Electron.Event,
|
||||
display: Electron.Display,
|
||||
changedMetrics: string[] // bounds, workArea, scaleFactor, rotation
|
||||
): void {
|
||||
this.__initDimension();
|
||||
}
|
||||
}
|
11
projects/notification-electron/src/lib/types/channel.type.ts
Normal file
11
projects/notification-electron/src/lib/types/channel.type.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
export enum NotificationChannel {
|
||||
init4NotificationWindow = 'notification-electron::NotificationChannel::init4NotificationWindow',
|
||||
ready4NotificationWindow = 'notification-electron::NotificationChannel::ready4NotificationWindow',
|
||||
reset4NotificationWindow = 'notification-electron::NotificationChannel::reset4NotificationWindow',
|
||||
}
|
||||
|
||||
export enum NotificationWindowChannel {
|
||||
onClose = 'notification-electron::NotificationWindowChannel::onClose',
|
||||
onClick = 'notification-electron::NotificationWindowChannel::onClick',
|
||||
onUser = 'notification-electron::NotificationWindowChannel::onUser',
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
export interface QueueObject {
|
||||
animate: (...args: any[]) => Promise<any>;
|
||||
context: any;
|
||||
args?: any[];
|
||||
onError?: (error: any) => void;
|
||||
}
|
||||
|
||||
export class AnimationQueue {
|
||||
private __running = false;
|
||||
private __queueObjects: QueueObject[] = [];
|
||||
|
||||
push(o: QueueObject): void {
|
||||
if (this.__running) {
|
||||
this.__queueObjects.push(o);
|
||||
} else {
|
||||
this.__running = true;
|
||||
this.__animate(o);
|
||||
}
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.__queueObjects = [];
|
||||
}
|
||||
|
||||
private __animate(o: QueueObject): void {
|
||||
const __this = this;
|
||||
try {
|
||||
if (!o.animate) {
|
||||
return;
|
||||
}
|
||||
|
||||
o.animate
|
||||
.apply(o.context, !!o.args ? o.args : [])
|
||||
.then(() => {
|
||||
if (0 < __this.__queueObjects.length) {
|
||||
const __o = __this.__queueObjects.shift();
|
||||
if (!!__o) {
|
||||
__this.__animate.call(__this, __o);
|
||||
} else {
|
||||
__this.__running = false;
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(reason => {
|
||||
if (!!o.onError) {
|
||||
o.onError.apply(o.context, [reason]);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
if (!!o.onError) {
|
||||
o.onError.apply(o.context, [e]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
5
projects/notification-electron/src/public-api.ts
Normal file
5
projects/notification-electron/src/public-api.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export * from './lib/services/notification.service';
|
||||
|
||||
export * from './lib/types/channel.type';
|
||||
|
||||
export * from './lib/utils/animation.queue';
|
9
projects/notification-electron/tsconfig.lib.json
Normal file
9
projects/notification-electron/tsconfig.lib.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../out-tsc/lib",
|
||||
"target": "es2015",
|
||||
"lib": ["dom"]
|
||||
},
|
||||
"exclude": ["src/test.ts", "**/*.spec.ts"]
|
||||
}
|
3
projects/notification-electron/tsconfig.lib.prod.json
Normal file
3
projects/notification-electron/tsconfig.lib.prod.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "./tsconfig.lib.json"
|
||||
}
|
9
projects/notification-electron/tsconfig.spec.json
Normal file
9
projects/notification-electron/tsconfig.spec.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../out-tsc/spec",
|
||||
"types": ["jasmine", "node"]
|
||||
},
|
||||
"files": ["src/test.ts"],
|
||||
"include": ["**/*.spec.ts", "**/*.d.ts"]
|
||||
}
|
148
scripts/build.ts
Normal file
148
scripts/build.ts
Normal file
|
@ -0,0 +1,148 @@
|
|||
import * as path from 'path';
|
||||
import {execSync} from 'child_process';
|
||||
import * as fse from 'fs-extra';
|
||||
|
||||
async function build(args: string[]): Promise<any[]> {
|
||||
const rootPath = path.join(__dirname, '..');
|
||||
const packPath = path.join(rootPath, 'pack');
|
||||
const projectName = args[0];
|
||||
|
||||
const projectPath = path.join(rootPath, 'projects', projectName);
|
||||
const packageJson = require(path.join(projectPath, 'package.json'));
|
||||
const projectJson = require(path.join(projectPath, 'project.json'));
|
||||
|
||||
const tsconfigJsonPath = path.join(projectPath, 'tsconfig.lib.prod.json');
|
||||
const distPath = path.join(projectPath, projectJson.dest);
|
||||
|
||||
const clean = () =>
|
||||
new Promise<void>((resolve, reject) => {
|
||||
try {
|
||||
console.log(`Cleaning is started.`);
|
||||
execSync(`rimraf ${distPath}`, {stdio: 'inherit'});
|
||||
console.log(`Cleaning is completed.`);
|
||||
resolve();
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
const declaration = () =>
|
||||
new Promise<void>((resolve, reject) => {
|
||||
try {
|
||||
const outDir = path.join(distPath, 'types');
|
||||
console.log(`Generating typescript declaration files is started.`);
|
||||
execSync(
|
||||
`tsc --project ${tsconfigJsonPath} --module commonjs --target es5 --declaration true --emitDeclarationOnly --outDir ${outDir}`,
|
||||
{stdio: 'inherit'}
|
||||
);
|
||||
console.log(`Generating typescript declaration files is completed.`);
|
||||
resolve();
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
|
||||
const transpile = (
|
||||
name: string,
|
||||
dirName: string,
|
||||
moduleName: string,
|
||||
targetName: string
|
||||
) =>
|
||||
new Promise<void>((resolve, reject) => {
|
||||
try {
|
||||
let out = '';
|
||||
switch (moduleName.toLowerCase()) {
|
||||
case 'amd':
|
||||
case 'system':
|
||||
out = `--outFile ${path.join(distPath, dirName, 'public-api.js')}`;
|
||||
break;
|
||||
default:
|
||||
out = `--outDir ${path.join(distPath, dirName)}`;
|
||||
break;
|
||||
}
|
||||
|
||||
const outDir = path.join(distPath, dirName);
|
||||
console.log(`Generating typescript ${name} module is started.`);
|
||||
execSync(
|
||||
`tsc --project ${tsconfigJsonPath} --module ${moduleName} --target ${targetName} ${out}`,
|
||||
{stdio: 'inherit'}
|
||||
);
|
||||
console.log(`Generating typescript ${name} module is completed.`);
|
||||
resolve();
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
|
||||
const packagejson = () =>
|
||||
new Promise<void>((resolve, reject) => {
|
||||
try {
|
||||
console.log(`Generating project package.json is started.`);
|
||||
const projectPackageJson = {
|
||||
...packageJson,
|
||||
main: `cjs/public-api.js`,
|
||||
module: `esm5/public-api.js`,
|
||||
es2015: `es2015/public-api.js`,
|
||||
types: `types/public-api.d.ts`,
|
||||
sideEffects: false,
|
||||
};
|
||||
|
||||
fse.writeFileSync(
|
||||
path.join(distPath, 'package.json'),
|
||||
JSON.stringify(projectPackageJson, null, 2)
|
||||
);
|
||||
|
||||
console.log(`Generating project package.json is completed.`);
|
||||
resolve();
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
|
||||
const install2Local = () =>
|
||||
new Promise<void>((resolve, reject) => {
|
||||
try {
|
||||
console.log(`Installing to local is started.`);
|
||||
|
||||
const distPackageJson = require(path.join(distPath, 'package.json'));
|
||||
const packFileName = `${projectName}-${distPackageJson.version}.tgz`;
|
||||
const distFilePath = path.join(distPath, packFileName);
|
||||
const packFilePath = path.join(packPath, packFileName);
|
||||
|
||||
process.chdir(path.join(distPath));
|
||||
execSync(`npm pack`, {stdio: 'inherit'});
|
||||
|
||||
fse.moveSync(distFilePath, packFilePath, {
|
||||
overwrite: true,
|
||||
});
|
||||
|
||||
process.chdir(path.join(rootPath));
|
||||
|
||||
execSync(`npm install -D ${packFilePath}`, {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
console.log(`Installing to local is completed.`);
|
||||
resolve();
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
|
||||
return Promise.all([
|
||||
clean(),
|
||||
declaration(),
|
||||
transpile('commonjs', 'cjs', 'commonjs', 'es5'),
|
||||
transpile('amd', 'amd', 'amd', 'es5'),
|
||||
transpile('umd', 'umd', 'umd', 'es5'),
|
||||
transpile('ESM5', 'esm5', 'es2015', 'es5'),
|
||||
transpile('ES2015', 'es2015', 'es2015', 'es2015'),
|
||||
packagejson(),
|
||||
install2Local(),
|
||||
]);
|
||||
}
|
||||
|
||||
build(process.argv.slice(2))
|
||||
.then()
|
||||
.catch(reason => {
|
||||
console.error(reason);
|
||||
});
|
8
tsconfig.json
Normal file
8
tsconfig.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"extends": "./node_modules/gts/tsconfig-google.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"declaration": false,
|
||||
"moduleResolution": "node"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user