project initialized
This commit is contained in:
commit
e1d820434e
13
.editorconfig
Normal file
13
.editorconfig
Normal file
|
@ -0,0 +1,13 @@
|
|||
# Editor configuration, see https://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
47
.gitignore
vendored
Normal file
47
.gitignore
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
/docs
|
||||
/tmp
|
||||
/out-tsc
|
||||
# Only exists if Bazel was run
|
||||
/bazel-out
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# profiling files
|
||||
chrome-profiler-events*.json
|
||||
speed-measure-plugin*.json
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history/*
|
||||
|
||||
# misc
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
5
.prettierrc
Normal file
5
.prettierrc
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"trailingComma": "none",
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true
|
||||
}
|
9
.vscode/extensions.json
vendored
Normal file
9
.vscode/extensions.json
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"msjsdiag.debugger-for-chrome",
|
||||
"eamodio.gitlens",
|
||||
"esbenp.prettier-vscode",
|
||||
"ms-vscode.vscode-typescript-tslint-plugin",
|
||||
"VisualStudioExptTeam.vscodeintellicode"
|
||||
]
|
||||
}
|
7
.vscode/launch.json
vendored
Normal file
7
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": []
|
||||
}
|
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,
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"files.trimFinalNewlines": true,
|
||||
"files.watcherExclude": {
|
||||
"**/dist": true
|
||||
},
|
||||
"debug.node.autoAttach": "on"
|
||||
}
|
15
.vscode/tasks.json
vendored
Normal file
15
.vscode/tasks.json
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "build:main:dev",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
6003
package-lock.json
generated
Normal file
6003
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
43
package.json
Normal file
43
package.json
Normal file
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"name": "ucap-electron",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"clean:all": "rimraf dist/*",
|
||||
"build:all": "npm-run-all -s build:core build:common build:notify-window build:updater-window",
|
||||
"build:core": "node ./scripts/build.js core",
|
||||
"build:common": "node ./scripts/build.js common",
|
||||
"build:notify-window": "node ./scripts/build.js notify-window",
|
||||
"build:updater-window": "node ./scripts/build.js updater-window",
|
||||
"publish:all": "npm-run-all -s publish:core publish:notify-window publish:updater-window",
|
||||
"publish:core": "cd ./dist/core && npm publish",
|
||||
"publish:common": "cd ./dist/common && npm publish",
|
||||
"publish:notify-window": "cd ./dist/core && npm publish",
|
||||
"publish:updater-window": "cd ./dist/core && npm publish"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@tsed/core": "^5.44.11",
|
||||
"@tsed/di": "^5.44.11",
|
||||
"@types/fs-extra": "^8.1.0",
|
||||
"@types/node": "^12.11.1",
|
||||
"@ucap/electron-core": "file:dist/core/ucap-electron-core-0.0.1.tgz",
|
||||
"concurrently": "^5.1.0",
|
||||
"electron": "^8.1.1",
|
||||
"electron-log": "^4.1.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"rimraf": "^3.0.2",
|
||||
"rxjs": "^6.5.4",
|
||||
"terser-webpack-plugin": "^2.3.5",
|
||||
"ts-loader": "^6.2.1",
|
||||
"ts-node": "^8.6.2",
|
||||
"tslib": "^1.11.1",
|
||||
"tslint": "^6.1.0",
|
||||
"typedoc": "^0.16.11",
|
||||
"typescript": "^3.8.3",
|
||||
"webpack": "^4.42.0",
|
||||
"webpack-merge": "^4.2.2",
|
||||
"webpack-node-externals": "^1.7.2"
|
||||
}
|
||||
}
|
15
projects/common/package.json
Normal file
15
projects/common/package.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"name": "@ucap/electron-common",
|
||||
"version": "0.0.1",
|
||||
"publishConfig": {
|
||||
"registry": "http://10.81.13.221:8081/nexus/repository/npm-ucap/"
|
||||
},
|
||||
"scripts": {},
|
||||
"dependencies": {
|
||||
"@tsed/core": "^5.44.11",
|
||||
"@tsed/di": "^5.44.11",
|
||||
"electron": "^8.0.0",
|
||||
"rxjs": "^6.5.4"
|
||||
},
|
||||
"devDependencies": {}
|
||||
}
|
91
projects/common/src/lib/app/app.ts
Normal file
91
projects/common/src/lib/app/app.ts
Normal file
|
@ -0,0 +1,91 @@
|
|||
import { Type, constructorOf } from '@tsed/core';
|
||||
import {
|
||||
GlobalProviders,
|
||||
InjectorService,
|
||||
ProviderScope,
|
||||
registerProvider
|
||||
} from '@tsed/di';
|
||||
|
||||
import * as Electron from 'electron';
|
||||
|
||||
import { AppOptions } from './decorators/app-settings';
|
||||
import { AppSettingsService } from './services/app-settings.service';
|
||||
import { ElectronApp } from './decorators/electron-app';
|
||||
|
||||
export abstract class App {
|
||||
readonly injector: InjectorService;
|
||||
private startedAt = new Date();
|
||||
|
||||
constructor(settings: Partial<AppOptions> = {}) {
|
||||
// create injector with initial configuration
|
||||
this.injector = this.createInjector(this.getConfiguration(this, settings));
|
||||
|
||||
this.createElectronApp(this.injector);
|
||||
}
|
||||
|
||||
get settings(): AppSettingsService {
|
||||
return this.injector.settings as AppSettingsService;
|
||||
}
|
||||
|
||||
get electronApp(): ElectronApp {
|
||||
return this.injector.get<ElectronApp>(ElectronApp)!;
|
||||
}
|
||||
|
||||
static async bootstrap(
|
||||
module: Type<App>,
|
||||
settings: Partial<AppOptions> = {}
|
||||
): Promise<App> {
|
||||
const app = new module(settings);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
async start(): Promise<any> {
|
||||
try {
|
||||
} catch (err) {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
private createInjector(settings: Partial<AppOptions> = {}) {
|
||||
const injector = new InjectorService();
|
||||
injector.settings = this.createSettingsService(injector);
|
||||
// injector.logger = $log;
|
||||
|
||||
// @ts-ignore
|
||||
injector.settings.set(settings);
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (injector.settings.env === 'test') {
|
||||
injector.logger.stop();
|
||||
}
|
||||
|
||||
return injector;
|
||||
}
|
||||
|
||||
private createSettingsService(injector: InjectorService): AppSettingsService {
|
||||
const provider = GlobalProviders.get(AppSettingsService)!.clone();
|
||||
|
||||
provider.instance = injector.invoke<AppSettingsService>(provider.useClass);
|
||||
injector.addProvider(AppSettingsService, provider);
|
||||
|
||||
return provider.instance as any;
|
||||
}
|
||||
|
||||
private getConfiguration(module: any, configuration: any = {}) {
|
||||
const provider = GlobalProviders.get(constructorOf(module))!;
|
||||
|
||||
return { ...provider.configuration, ...configuration };
|
||||
}
|
||||
|
||||
private createElectronApp(injector: InjectorService): void {
|
||||
injector.forkProvider(ElectronApp);
|
||||
}
|
||||
}
|
||||
|
||||
registerProvider({
|
||||
provide: ElectronApp,
|
||||
scope: ProviderScope.SINGLETON,
|
||||
global: true,
|
||||
useValue: Electron.app
|
||||
});
|
10
projects/common/src/lib/app/decorators/app-settings.ts
Normal file
10
projects/common/src/lib/app/decorators/app-settings.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { Type } from '@tsed/core';
|
||||
import { IModuleOptions, Module } from '@tsed/di';
|
||||
|
||||
export interface AppOptions extends IModuleOptions {
|
||||
bootstrap: Type<any>;
|
||||
}
|
||||
|
||||
export function AppSettings(settings: Partial<AppOptions> = {}): any {
|
||||
return Module({ ...settings, root: true });
|
||||
}
|
14
projects/common/src/lib/app/decorators/electron-app.ts
Normal file
14
projects/common/src/lib/app/decorators/electron-app.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { Type } from '@tsed/core';
|
||||
import { Inject } from '@tsed/di';
|
||||
import * as Electron from 'electron';
|
||||
|
||||
export type ElectronApp = Electron.App;
|
||||
|
||||
export function ElectronApp(
|
||||
target: Type<any>,
|
||||
targetKey: string,
|
||||
// tslint:disable-next-line: ban-types
|
||||
descriptor: TypedPropertyDescriptor<Function> | number
|
||||
) {
|
||||
return Inject(ElectronApp)(target, targetKey, descriptor);
|
||||
}
|
16
projects/common/src/lib/app/services/app-settings.service.ts
Normal file
16
projects/common/src/lib/app/services/app-settings.service.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import {
|
||||
DIConfiguration,
|
||||
Injectable,
|
||||
ProviderScope,
|
||||
ProviderType
|
||||
} from '@tsed/di';
|
||||
|
||||
@Injectable({
|
||||
scope: ProviderScope.SINGLETON,
|
||||
global: true
|
||||
})
|
||||
export class AppSettingsService extends DIConfiguration {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
}
|
10
projects/common/src/public-api.ts
Normal file
10
projects/common/src/public-api.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* Public API Surface of common
|
||||
*/
|
||||
|
||||
export * from './lib/app/decorators/app-settings';
|
||||
export * from './lib/app/decorators/electron-app';
|
||||
|
||||
export * from './lib/app/services/app-settings.service';
|
||||
|
||||
export * from './lib/app/app';
|
12
projects/common/tsconfig.lib.json
Normal file
12
projects/common/tsconfig.lib.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../out-tsc/lib",
|
||||
"target": "es2015",
|
||||
"declaration": true,
|
||||
"inlineSources": true,
|
||||
"types": [],
|
||||
"lib": ["dom", "es2018"]
|
||||
},
|
||||
"exclude": ["src/test.ts", "**/*.spec.ts"]
|
||||
}
|
3
projects/common/tsconfig.lib.prod.json
Normal file
3
projects/common/tsconfig.lib.prod.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "./tsconfig.lib.json"
|
||||
}
|
9
projects/common/tsconfig.spec.json
Normal file
9
projects/common/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"]
|
||||
}
|
3
projects/common/tslint.json
Normal file
3
projects/common/tslint.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "../../tslint.json"
|
||||
}
|
7
projects/common/ucap-package.json
Normal file
7
projects/common/ucap-package.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"dest": "../../dist/common",
|
||||
"docDest": "../../docs/common",
|
||||
"lib": {
|
||||
"entryFile": "src/public-api.ts"
|
||||
}
|
||||
}
|
10
projects/core/package.json
Normal file
10
projects/core/package.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name": "@ucap/electron-core",
|
||||
"version": "0.0.1",
|
||||
"publishConfig": {
|
||||
"registry": "http://10.81.13.221:8081/nexus/repository/npm-ucap/"
|
||||
},
|
||||
"scripts": {},
|
||||
"dependencies": {},
|
||||
"devDependencies": {}
|
||||
}
|
39
projects/core/src/lib/types/channel.type.ts
Normal file
39
projects/core/src/lib/types/channel.type.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
export enum AppChannel {
|
||||
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 BrowserWindowChannel {
|
||||
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',
|
||||
Focus = 'focus',
|
||||
Blur = 'blur'
|
||||
}
|
||||
|
||||
export enum WebContentsChannel {
|
||||
DevtoolsOpened = 'devtools-opened',
|
||||
DidStartLoading = 'did-start-loading',
|
||||
DidFinishLoad = 'did-finish-load',
|
||||
DidFailLoad = 'did-fail-load'
|
||||
}
|
5
projects/core/src/public-api.ts
Normal file
5
projects/core/src/public-api.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
/*
|
||||
* Public API Surface of core
|
||||
*/
|
||||
|
||||
export * from './lib/types/channel.type';
|
12
projects/core/tsconfig.lib.json
Normal file
12
projects/core/tsconfig.lib.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../out-tsc/lib",
|
||||
"target": "es2015",
|
||||
"declaration": true,
|
||||
"inlineSources": true,
|
||||
"types": [],
|
||||
"lib": ["dom", "es2018"]
|
||||
},
|
||||
"exclude": ["src/test.ts", "**/*.spec.ts"]
|
||||
}
|
3
projects/core/tsconfig.lib.prod.json
Normal file
3
projects/core/tsconfig.lib.prod.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "./tsconfig.lib.json"
|
||||
}
|
9
projects/core/tsconfig.spec.json
Normal file
9
projects/core/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"]
|
||||
}
|
3
projects/core/tslint.json
Normal file
3
projects/core/tslint.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "../../tslint.json"
|
||||
}
|
7
projects/core/ucap-package.json
Normal file
7
projects/core/ucap-package.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"dest": "../../dist/core",
|
||||
"docDest": "../../docs/core",
|
||||
"lib": {
|
||||
"entryFile": "src/public-api.ts"
|
||||
}
|
||||
}
|
16
projects/notify-window/package.json
Normal file
16
projects/notify-window/package.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "@ucap/electron-notify-window",
|
||||
"version": "0.0.1",
|
||||
"publishConfig": {
|
||||
"registry": "http://10.81.13.221:8081/nexus/repository/npm-ucap/"
|
||||
},
|
||||
"scripts": {},
|
||||
"dependencies": {
|
||||
"@ucap/electron-core": "~0.0.1",
|
||||
"electron": "^8.0.0",
|
||||
"electron-log": "^4.1.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"rxjs": "^6.5.4"
|
||||
},
|
||||
"devDependencies": {}
|
||||
}
|
120
projects/notify-window/src/lib/models/notify-window-options.ts
Normal file
120
projects/notify-window/src/lib/models/notify-window-options.ts
Normal file
|
@ -0,0 +1,120 @@
|
|||
import * as path from 'path';
|
||||
import { BrowserWindowConstructorOptions } from 'electron';
|
||||
|
||||
export interface NotifyWindowOptions {
|
||||
width?: number;
|
||||
height?: number;
|
||||
padding?: number;
|
||||
borderRadius?: number;
|
||||
displayTime?: number;
|
||||
animationSteps?: number;
|
||||
animationStepMs?: number;
|
||||
animateInParallel?: boolean;
|
||||
appIcon?: string;
|
||||
pathToModule?: string;
|
||||
logging?: boolean;
|
||||
browserWindowPool?: {
|
||||
min?: number;
|
||||
max?: number;
|
||||
};
|
||||
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 DefaultNotifyWindowOptions: NotifyWindowOptions = {
|
||||
width: 300,
|
||||
height: 65,
|
||||
padding: 10,
|
||||
borderRadius: 5,
|
||||
displayTime: 5000,
|
||||
animationSteps: 5,
|
||||
animationStepMs: 20,
|
||||
appIcon: null,
|
||||
pathToModule: '',
|
||||
logging: true,
|
||||
browserWindowPool: {
|
||||
min: 0,
|
||||
max: 7
|
||||
},
|
||||
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>'
|
||||
};
|
20
projects/notify-window/src/lib/models/notify-window.ts
Normal file
20
projects/notify-window/src/lib/models/notify-window.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { NotifyWindowEventType } from '../types/event.type';
|
||||
|
||||
export interface NotifyWindowEvent {
|
||||
type: NotifyWindowEventType;
|
||||
id: number;
|
||||
close?: (reason?: any) => void;
|
||||
}
|
||||
|
||||
export interface NotifyWindow {
|
||||
id?: number;
|
||||
displayTime?: number;
|
||||
title?: string;
|
||||
text?: string;
|
||||
image?: string;
|
||||
url?: string;
|
||||
sound?: string;
|
||||
onClick?: (e: NotifyWindowEvent) => void;
|
||||
onShow?: (e: NotifyWindowEvent) => void;
|
||||
onClose?: (e: NotifyWindowEvent) => void;
|
||||
}
|
497
projects/notify-window/src/lib/services/notify-window.service.ts
Normal file
497
projects/notify-window/src/lib/services/notify-window.service.ts
Normal file
|
@ -0,0 +1,497 @@
|
|||
import url from 'url';
|
||||
import fse from 'fs-extra';
|
||||
import log from 'electron-log';
|
||||
|
||||
import { WebContentsChannel } from '@ucap/electron-core';
|
||||
|
||||
import { AnimationQueue } from '../utils/animation-queue';
|
||||
import {
|
||||
NotifyWindowOptions,
|
||||
DefaultNotifyWindowOptions
|
||||
} from '../models/notify-window-options';
|
||||
import { screen, BrowserWindow, ipcMain, IpcMainEvent, shell } from 'electron';
|
||||
import { NotifyWindow } from '../models/notify-window';
|
||||
import { NotifyWindowEventType } from '../types/event.type';
|
||||
import { Channel } from '../types/channel.type';
|
||||
|
||||
const onClickNotifyWindow = 'onClickNotifyWindow';
|
||||
const onCloseNotifyWindow = 'onCloseNotifyWindow';
|
||||
|
||||
interface ENPoint {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
interface ENDimension {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
class BrowserWindowPooler {
|
||||
private readonly inactiveWindows: BrowserWindow[];
|
||||
|
||||
constructor(private readonly minSize: number) {
|
||||
this.minSize = 0 > this.minSize ? 0 : this.minSize;
|
||||
this.inactiveWindows = [];
|
||||
}
|
||||
|
||||
getLength() {
|
||||
return this.inactiveWindows.length;
|
||||
}
|
||||
|
||||
push(...items: BrowserWindow[]): number {
|
||||
const length = this.inactiveWindows.push(...items);
|
||||
|
||||
if (this.minSize < length) {
|
||||
this.start();
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
pop(): BrowserWindow {
|
||||
if (!this.inactiveWindows || 0 === this.inactiveWindows.length) {
|
||||
return undefined;
|
||||
}
|
||||
return this.inactiveWindows.pop();
|
||||
}
|
||||
|
||||
closeAll() {
|
||||
while (this.inactiveWindows.length > 0) {
|
||||
const w = this.inactiveWindows.pop();
|
||||
w.close();
|
||||
}
|
||||
}
|
||||
|
||||
private start() {
|
||||
setTimeout(() => {
|
||||
while (this.minSize < this.inactiveWindows.length) {
|
||||
const w = this.inactiveWindows.pop();
|
||||
w.close();
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
export class NotifyWindowService {
|
||||
private animationQueue: AnimationQueue;
|
||||
private customOptions: NotifyWindowOptions;
|
||||
private nextInsertPosition: ENPoint;
|
||||
private totalDimension: ENDimension;
|
||||
private firstPosition: ENPoint;
|
||||
private lowerRightCornerPosition: ENPoint;
|
||||
private maxVisibleNotifications: number;
|
||||
private activeNotifications: BrowserWindow[];
|
||||
private browserWindowPooler: BrowserWindowPooler;
|
||||
|
||||
private notificationQueue: NotifyWindow[];
|
||||
private closedNotifications: Map<number, boolean>;
|
||||
private latestId: number;
|
||||
private templateUrl: string;
|
||||
|
||||
constructor(options?: NotifyWindowOptions) {
|
||||
this.customOptions = {
|
||||
...DefaultNotifyWindowOptions
|
||||
};
|
||||
if (!!options) {
|
||||
this.customOptions = {
|
||||
...this.customOptions,
|
||||
...options
|
||||
};
|
||||
}
|
||||
|
||||
this.setup();
|
||||
this.setupEvents();
|
||||
}
|
||||
|
||||
setOptions(options: NotifyWindowOptions) {
|
||||
if (!!options) {
|
||||
this.customOptions = {
|
||||
...this.customOptions,
|
||||
...options
|
||||
};
|
||||
}
|
||||
this.calcDimensions();
|
||||
}
|
||||
|
||||
getOptions(): NotifyWindowOptions {
|
||||
return this.customOptions;
|
||||
}
|
||||
|
||||
setTemplatePath(templatePath: string) {
|
||||
if (!!templatePath) {
|
||||
this.customOptions.templatePath = templatePath;
|
||||
this.updateTemplatePath();
|
||||
}
|
||||
}
|
||||
|
||||
getTemplatePath(): string {
|
||||
if (!this.templateUrl) {
|
||||
this.updateTemplatePath();
|
||||
}
|
||||
return this.templateUrl;
|
||||
}
|
||||
|
||||
notify(notification: NotifyWindow): 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.browserWindowPooler.closeAll();
|
||||
}
|
||||
|
||||
closeAll(): void {
|
||||
this.animationQueue.clear();
|
||||
this.activeNotifications.forEach(window => window.close());
|
||||
this.browserWindowPooler.closeAll();
|
||||
|
||||
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.browserWindowPooler = new BrowserWindowPooler(
|
||||
this.getOptions().browserWindowPool.min
|
||||
);
|
||||
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 =
|
||||
this.getOptions().browserWindowPool.max < this.maxVisibleNotifications
|
||||
? this.getOptions().browserWindowPool.max
|
||||
: this.maxVisibleNotifications;
|
||||
}
|
||||
|
||||
private setupEvents(): void {
|
||||
const self = this;
|
||||
ipcMain.on(
|
||||
Channel.close,
|
||||
(event: IpcMainEvent, windowId: number, notification: NotifyWindow) => {
|
||||
const onClose = self.buildCloseNotification(
|
||||
BrowserWindow.fromId(windowId),
|
||||
notification
|
||||
);
|
||||
self.buildCloseNotificationSafely(onClose)('close');
|
||||
}
|
||||
);
|
||||
|
||||
ipcMain.on(
|
||||
Channel.click,
|
||||
(event: IpcMainEvent, windowId: number, notification: NotifyWindow) => {
|
||||
if (!!notification.url) {
|
||||
shell.openExternal(notification.url);
|
||||
}
|
||||
const notificationWindow = BrowserWindow.fromId(windowId);
|
||||
|
||||
if (notificationWindow && notificationWindow[onClickNotifyWindow]) {
|
||||
const onClose = self.buildCloseNotification(
|
||||
BrowserWindow.fromId(windowId),
|
||||
notification
|
||||
);
|
||||
notificationWindow[onClickNotifyWindow]({
|
||||
type: NotifyWindowEventType.Click,
|
||||
id: notification.id,
|
||||
close: self.buildCloseNotificationSafely(onClose)
|
||||
});
|
||||
delete notificationWindow[onClickNotifyWindow];
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
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 {
|
||||
fse.statSync(this.customOptions.templatePath).isFile();
|
||||
|
||||
this.templateUrl = url.format({
|
||||
pathname: this.customOptions.templatePath,
|
||||
protocol: 'file:',
|
||||
slashes: true
|
||||
});
|
||||
} catch (e) {
|
||||
log.error(
|
||||
'electron-notify: Could not find template ("' +
|
||||
this.customOptions.templatePath +
|
||||
'").'
|
||||
);
|
||||
log.error(
|
||||
'electron-notify: To use a different template you need to correct the config.templatePath or simply adapt config.htmlTemplate'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private showNotification(notification: NotifyWindow): 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: NotifyWindowEventType.Show,
|
||||
id: notification.id,
|
||||
close: onCloseNotificationSafely
|
||||
});
|
||||
}
|
||||
|
||||
if (!!notification.onClick) {
|
||||
notificationWindow[onClickNotifyWindow] = notification.onClick;
|
||||
} else {
|
||||
delete notificationWindow[onClickNotifyWindow];
|
||||
}
|
||||
|
||||
if (!!notification.onClose) {
|
||||
notificationWindow[onCloseNotifyWindow] = notification.onClose;
|
||||
} else {
|
||||
delete notificationWindow[onCloseNotifyWindow];
|
||||
}
|
||||
|
||||
notificationWindow.webContents.send(
|
||||
Channel.browserWindowSetContents,
|
||||
notification
|
||||
);
|
||||
notificationWindow.showInactive();
|
||||
resolve(notificationWindow);
|
||||
});
|
||||
} else {
|
||||
self.notificationQueue.push(notification);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private buildCloseNotification(
|
||||
notificationWindow: BrowserWindow,
|
||||
notification: NotifyWindow,
|
||||
timeoutIdFunc?: () => number
|
||||
) {
|
||||
const self = this;
|
||||
return (e: NotifyWindowEventType): 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[onCloseNotifyWindow]) {
|
||||
notificationWindow[onCloseNotifyWindow]({
|
||||
type: e,
|
||||
id: notification.id
|
||||
});
|
||||
delete notificationWindow[onCloseNotifyWindow];
|
||||
}
|
||||
|
||||
notificationWindow.webContents.send(Channel.reset);
|
||||
|
||||
if (!!timeoutIdFunc) {
|
||||
clearTimeout(timeoutIdFunc());
|
||||
}
|
||||
const i = self.activeNotifications.indexOf(notificationWindow);
|
||||
self.activeNotifications.splice(i, 1);
|
||||
self.browserWindowPooler.push(notificationWindow);
|
||||
|
||||
notificationWindow.hide();
|
||||
self.checkForQueuedNotifications();
|
||||
|
||||
return self.moveOneDown(i);
|
||||
};
|
||||
}
|
||||
|
||||
private buildCloseNotificationSafely(
|
||||
onClose: (e: NotifyWindowEventType) => 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.browserWindowPooler.getLength()) {
|
||||
resolve(slef.browserWindowPooler.pop());
|
||||
} else {
|
||||
const windowProperties = slef.customOptions.defaultWindow;
|
||||
windowProperties.width = slef.customOptions.width;
|
||||
windowProperties.height = slef.customOptions.height;
|
||||
|
||||
const notificationWindow = new BrowserWindow({
|
||||
...windowProperties,
|
||||
title: 'Notification'
|
||||
});
|
||||
notificationWindow.setVisibleOnAllWorkspaces(true);
|
||||
notificationWindow.loadURL(slef.getTemplatePath());
|
||||
notificationWindow.webContents.on(
|
||||
WebContentsChannel.DidFinishLoad,
|
||||
() => {
|
||||
// Done
|
||||
notificationWindow.webContents.send(
|
||||
Channel.loadConfig,
|
||||
slef.customOptions
|
||||
);
|
||||
resolve(notificationWindow);
|
||||
}
|
||||
);
|
||||
notificationWindow.webContents.on(
|
||||
WebContentsChannel.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);
|
||||
});
|
||||
}
|
||||
}
|
7
projects/notify-window/src/lib/types/channel.type.ts
Normal file
7
projects/notify-window/src/lib/types/channel.type.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
export enum Channel {
|
||||
close = 'ucap::electron::notify-window::close',
|
||||
click = 'ucap::electron::notify-window::click',
|
||||
loadConfig = 'ucap::electron::notify-window::loadConfig',
|
||||
reset = 'ucap::electron::notify-window::reset',
|
||||
browserWindowSetContents = 'ucap::electron::notify-window::browserWindowSetContents'
|
||||
}
|
5
projects/notify-window/src/lib/types/event.type.ts
Normal file
5
projects/notify-window/src/lib/types/event.type.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export enum NotifyWindowEventType {
|
||||
Show = 'Show',
|
||||
Click = 'Click',
|
||||
Close = 'Close'
|
||||
}
|
44
projects/notify-window/src/lib/utils/animation-queue.ts
Normal file
44
projects/notify-window/src/lib/utils/animation-queue.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
import log from 'electron-log';
|
||||
|
||||
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 => {
|
||||
log.error(reason);
|
||||
});
|
||||
} catch (e) {
|
||||
log.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.queue = [];
|
||||
}
|
||||
}
|
5
projects/notify-window/src/public-api.ts
Normal file
5
projects/notify-window/src/public-api.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
/*
|
||||
* Public API Surface of notification
|
||||
*/
|
||||
|
||||
export * from './lib/types/channel.type';
|
12
projects/notify-window/tsconfig.lib.json
Normal file
12
projects/notify-window/tsconfig.lib.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../out-tsc/lib",
|
||||
"target": "es2015",
|
||||
"declaration": true,
|
||||
"inlineSources": true,
|
||||
"types": [],
|
||||
"lib": ["dom", "es2018"]
|
||||
},
|
||||
"exclude": ["src/test.ts", "**/*.spec.ts"]
|
||||
}
|
3
projects/notify-window/tsconfig.lib.prod.json
Normal file
3
projects/notify-window/tsconfig.lib.prod.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "./tsconfig.lib.json"
|
||||
}
|
9
projects/notify-window/tsconfig.spec.json
Normal file
9
projects/notify-window/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"]
|
||||
}
|
3
projects/notify-window/tslint.json
Normal file
3
projects/notify-window/tslint.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "../../tslint.json"
|
||||
}
|
7
projects/notify-window/ucap-package.json
Normal file
7
projects/notify-window/ucap-package.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"dest": "../../dist/notify-window",
|
||||
"docDest": "../../docs/notify-window",
|
||||
"lib": {
|
||||
"entryFile": "src/public-api.ts"
|
||||
}
|
||||
}
|
16
projects/updater-window/package.json
Normal file
16
projects/updater-window/package.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "@ucap/electron-updater-window",
|
||||
"version": "0.0.1",
|
||||
"publishConfig": {
|
||||
"registry": "http://10.81.13.221:8081/nexus/repository/npm-ucap/"
|
||||
},
|
||||
"scripts": {},
|
||||
"dependencies": {
|
||||
"@ucap/electron-core": "~0.0.1",
|
||||
"electron": "^8.0.0",
|
||||
"electron-log": "^4.1.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"rxjs": "^6.5.4"
|
||||
},
|
||||
"devDependencies": {}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import { BrowserWindowConstructorOptions } from 'electron';
|
||||
|
||||
export interface UpdaterWindowOptions extends BrowserWindowConstructorOptions {
|
||||
templatePath?: string;
|
||||
onReady?: () => void;
|
||||
onAcceptUpdate?: () => void;
|
||||
onDenyUpdate?: () => void;
|
||||
onCancelDownload?: () => void;
|
||||
}
|
||||
|
||||
export const DefaultUpdaterWindowOptions: UpdaterWindowOptions = {
|
||||
width: 500,
|
||||
height: 160,
|
||||
frame: false,
|
||||
skipTaskbar: true,
|
||||
alwaysOnTop: true,
|
||||
maximizable: false,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
};
|
|
@ -0,0 +1,129 @@
|
|||
import { BrowserWindow, ipcMain } from 'electron';
|
||||
import url from 'url';
|
||||
import fse from 'fs-extra';
|
||||
import log from 'electron-log';
|
||||
import {
|
||||
UpdaterWindowOptions,
|
||||
DefaultUpdaterWindowOptions
|
||||
} from '../models/updater-window-options';
|
||||
|
||||
import { Channel } from '../types/channel.type';
|
||||
import { WebContentsChannel } from '@ucap/electron-core';
|
||||
|
||||
export class UpdaterWindowService {
|
||||
private customOptions: UpdaterWindowOptions;
|
||||
private browserWindow: BrowserWindow;
|
||||
private templateUrl: string;
|
||||
|
||||
constructor(options: UpdaterWindowOptions) {
|
||||
this.customOptions = {
|
||||
...DefaultUpdaterWindowOptions
|
||||
};
|
||||
if (!!options) {
|
||||
this.customOptions = {
|
||||
...this.customOptions,
|
||||
...options
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
setOptions(options: UpdaterWindowOptions) {
|
||||
if (!!options) {
|
||||
this.customOptions = {
|
||||
...this.customOptions,
|
||||
...options
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
getOptions(): UpdaterWindowOptions {
|
||||
return this.customOptions;
|
||||
}
|
||||
|
||||
setTemplatePath(templatePath: string) {
|
||||
if (!!templatePath) {
|
||||
this.customOptions.templatePath = templatePath;
|
||||
this.updateTemplatePath();
|
||||
}
|
||||
}
|
||||
|
||||
getTemplatePath(): string {
|
||||
if (!this.templateUrl) {
|
||||
this.updateTemplatePath();
|
||||
}
|
||||
return this.templateUrl;
|
||||
}
|
||||
|
||||
show(versionInfo: { installed: string; latest: string }) {
|
||||
this.browserWindow = new BrowserWindow(this.customOptions);
|
||||
this.browserWindow.loadURL(this.getTemplatePath());
|
||||
|
||||
this.browserWindow.on('closed', () => {
|
||||
this.browserWindow = null;
|
||||
});
|
||||
this.browserWindow.webContents.on(WebContentsChannel.DidFinishLoad, () => {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
this.browserWindow.webContents.openDevTools();
|
||||
}
|
||||
if (!!this.customOptions.onReady) {
|
||||
this.customOptions.onReady();
|
||||
}
|
||||
|
||||
this.browserWindow.webContents.send(
|
||||
Channel.browserWindowSetContents,
|
||||
versionInfo
|
||||
);
|
||||
});
|
||||
|
||||
ipcMain.on(Channel.acceptUpdate, this._acceptUpdateHandler.bind(this));
|
||||
ipcMain.on(Channel.denyUpdate, this._denyUpdateHandler.bind(this));
|
||||
ipcMain.on(Channel.cancelDownload, this._cancelDownloadHandler.bind(this));
|
||||
}
|
||||
|
||||
setDownloadValue(value: number, total: number) {
|
||||
this.browserWindow.webContents.send(Channel.downloadProcess, value, total);
|
||||
}
|
||||
setDownloadComplete() {
|
||||
this.browserWindow.webContents.send(Channel.downloadComplete);
|
||||
}
|
||||
close() {
|
||||
if (!this.browserWindow || this.browserWindow.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
this.browserWindow.destroy();
|
||||
}
|
||||
|
||||
_acceptUpdateHandler() {
|
||||
if (!!this.customOptions.onAcceptUpdate) {
|
||||
this.customOptions.onAcceptUpdate();
|
||||
}
|
||||
}
|
||||
_denyUpdateHandler() {
|
||||
if (!!this.customOptions.onDenyUpdate) {
|
||||
this.customOptions.onDenyUpdate();
|
||||
}
|
||||
}
|
||||
_cancelDownloadHandler() {
|
||||
if (!!this.customOptions.onCancelDownload) {
|
||||
this.customOptions.onCancelDownload();
|
||||
}
|
||||
}
|
||||
|
||||
private updateTemplatePath() {
|
||||
try {
|
||||
fse.statSync(this.customOptions.templatePath).isFile();
|
||||
|
||||
this.templateUrl = url.format({
|
||||
pathname: this.customOptions.templatePath,
|
||||
protocol: 'file:',
|
||||
slashes: true
|
||||
});
|
||||
} catch (e) {
|
||||
log.error(
|
||||
'electron-update-window: Could not find template ("' +
|
||||
this.customOptions.templatePath +
|
||||
'").'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
10
projects/updater-window/src/lib/types/channel.type.ts
Normal file
10
projects/updater-window/src/lib/types/channel.type.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
export enum Channel {
|
||||
acceptUpdate = 'ucap::electron::updater-window::acceptUpdate',
|
||||
denyUpdate = 'ucap::electron::updater-window::denyUpdate',
|
||||
cancelDownload = 'ucap::electron::updater-window::cancelDownload',
|
||||
|
||||
downloadProcess = 'ucap::electron::updater-window::downloadProcess',
|
||||
downloadComplete = 'ucap::electron::updater-window::downloadComplete',
|
||||
|
||||
browserWindowSetContents = 'ucap::electron::updater-window::browserWindowSetContents'
|
||||
}
|
5
projects/updater-window/src/public-api.ts
Normal file
5
projects/updater-window/src/public-api.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
/*
|
||||
* Public API Surface of core
|
||||
*/
|
||||
|
||||
export * from './lib/types/channel.type';
|
12
projects/updater-window/tsconfig.lib.json
Normal file
12
projects/updater-window/tsconfig.lib.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../out-tsc/lib",
|
||||
"target": "es2015",
|
||||
"declaration": true,
|
||||
"inlineSources": true,
|
||||
"types": [],
|
||||
"lib": ["dom", "es2018"]
|
||||
},
|
||||
"exclude": ["src/test.ts", "**/*.spec.ts"]
|
||||
}
|
3
projects/updater-window/tsconfig.lib.prod.json
Normal file
3
projects/updater-window/tsconfig.lib.prod.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "./tsconfig.lib.json"
|
||||
}
|
9
projects/updater-window/tsconfig.spec.json
Normal file
9
projects/updater-window/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"]
|
||||
}
|
3
projects/updater-window/tslint.json
Normal file
3
projects/updater-window/tslint.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "../../tslint.json"
|
||||
}
|
7
projects/updater-window/ucap-package.json
Normal file
7
projects/updater-window/ucap-package.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"dest": "../../dist/updater-window",
|
||||
"docDest": "../../docs/updater-window",
|
||||
"lib": {
|
||||
"entryFile": "src/public-api.ts"
|
||||
}
|
||||
}
|
351
scripts/build.js
Normal file
351
scripts/build.js
Normal file
|
@ -0,0 +1,351 @@
|
|||
const { execSync } = require('child_process');
|
||||
const path = require('path');
|
||||
const fse = require('fs-extra');
|
||||
|
||||
const webpack = require('webpack');
|
||||
const webpackMerge = require('webpack-merge');
|
||||
const webpackNodeExternals = require('webpack-node-externals');
|
||||
const webpackNodeTerser = require('terser-webpack-plugin');
|
||||
|
||||
async function buildForProduction(args) {
|
||||
const rootPath = path.join(__dirname, '..');
|
||||
|
||||
const projectName = args[0];
|
||||
|
||||
const projectPath = path.join(rootPath, 'projects', projectName);
|
||||
|
||||
const packageJson = require(path.join(projectPath, 'package.json'));
|
||||
const ucapPackageJson = require(path.join(projectPath, 'ucap-package.json'));
|
||||
|
||||
const distPath = path.join(projectPath, ucapPackageJson.dest);
|
||||
const docPath = path.join(projectPath, ucapPackageJson.docDest);
|
||||
|
||||
const webpackConfig = (overrideConfig, compilerOptions) => {
|
||||
const commonConfig = {
|
||||
name: projectName,
|
||||
target: 'node',
|
||||
mode: 'production',
|
||||
context: path.join(projectPath, 'src'),
|
||||
entry: path.join(projectPath, ucapPackageJson.lib.entryFile),
|
||||
output: {
|
||||
path: path.join(distPath, 'bundles'),
|
||||
filename: `${projectName}.umd.js`,
|
||||
libraryTarget: 'umd',
|
||||
library: projectName,
|
||||
umdNamedDefine: true
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.ts', '.jsx', '.tsx', '.json'],
|
||||
modules: ['node_modules', 'src']
|
||||
},
|
||||
externals: [webpackNodeExternals()],
|
||||
devtool: 'source-map',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
configFile: path.join(projectPath, 'tsconfig.lib.prod.json'),
|
||||
compilerOptions: !!compilerOptions
|
||||
? compilerOptions
|
||||
: {
|
||||
declaration: false,
|
||||
module: 'ES2015',
|
||||
target: 'es5'
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
exclude: /node_modules/
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
return webpackMerge(commonConfig, overrideConfig);
|
||||
};
|
||||
|
||||
const clean = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const _distPath = path.join(distPath);
|
||||
console.log(`${projectName}: cleaning started [${_distPath}]`);
|
||||
execSync(`rimraf ${_distPath}`, {
|
||||
stdio: 'inherit'
|
||||
});
|
||||
console.log(`${projectName}: cleaning complete`);
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
const genDeclaration = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log(`${projectName}: generating of declaration started`);
|
||||
execSync(
|
||||
`tsc --project ${path.join(
|
||||
projectPath,
|
||||
'tsconfig.lib.prod.json'
|
||||
)} --module commonjs --target es5 --outDir ${path.join(
|
||||
distPath,
|
||||
'lib'
|
||||
)} --emitDeclarationOnly true --declarationDir ${path.join(distPath)}`,
|
||||
{ stdio: 'inherit' }
|
||||
);
|
||||
console.log(`${projectName}: generating of declaration complete`);
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
const genEs5 = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log(`${projectName}: generating of es5 started`);
|
||||
execSync(
|
||||
`tsc --project ${path.join(
|
||||
projectPath,
|
||||
'tsconfig.lib.prod.json'
|
||||
)} --module es2015 --target es5 --outDir ${path.join(
|
||||
distPath,
|
||||
'es5'
|
||||
)} --declaration false --sourceMap false --inlineSourceMap true`,
|
||||
{ stdio: 'inherit' }
|
||||
);
|
||||
console.log(`${projectName}: generating of es5 complete`);
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
const genEs2015 = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log(`${projectName}: generating of es2015 started`);
|
||||
execSync(
|
||||
`tsc --project ${path.join(
|
||||
projectPath,
|
||||
'tsconfig.lib.prod.json'
|
||||
)} --module es2015 --target es2015 --outDir ${path.join(
|
||||
distPath,
|
||||
'es2015'
|
||||
)} --declaration false --sourceMap false --inlineSourceMap true`,
|
||||
{ stdio: 'inherit' }
|
||||
);
|
||||
console.log(`${projectName}: generating of es2015 complete`);
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
const genBundles = () => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
console.log(`${projectName}: generating of bundle[umd] started`);
|
||||
await new Promise((resolve, reject) => {
|
||||
webpack(
|
||||
webpackConfig({ optimization: { minimize: false } }),
|
||||
(err, status) => {
|
||||
if (!!err) {
|
||||
console.err(err);
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
}).catch(reason => {
|
||||
reject(reason);
|
||||
});
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
webpack(
|
||||
webpackConfig({
|
||||
output: {
|
||||
filename: `${projectName}.umd.min.js`
|
||||
},
|
||||
optimization: { minimizer: [new webpackNodeTerser()] }
|
||||
}),
|
||||
(err, status) => {
|
||||
if (!!err) {
|
||||
console.err(err);
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
}).catch(reason => {
|
||||
reject(reason);
|
||||
});
|
||||
|
||||
console.log(`${projectName}: generating of bundle[umd] complete`);
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
const genFes5 = () => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
console.log(`${projectName}: generating of fes5 started`);
|
||||
await new Promise((resolve, reject) => {
|
||||
webpack(
|
||||
webpackConfig(
|
||||
{
|
||||
output: {
|
||||
path: path.join(distPath, 'fes5'),
|
||||
filename: `${projectName}.js`,
|
||||
library: projectName
|
||||
},
|
||||
optimization: { minimize: false }
|
||||
},
|
||||
{
|
||||
declaration: false,
|
||||
module: 'es2015',
|
||||
target: 'es5'
|
||||
}
|
||||
),
|
||||
(err, status) => {
|
||||
if (!!err) {
|
||||
console.err(err);
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
}).catch(reason => {
|
||||
reject(reason);
|
||||
});
|
||||
|
||||
console.log(`${projectName}: generating of fes5 complete`);
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
const genFes2015 = () => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
console.log(`${projectName}: generating of fes2015 started`);
|
||||
await new Promise((resolve, reject) => {
|
||||
webpack(
|
||||
webpackConfig(
|
||||
{
|
||||
output: {
|
||||
path: path.join(distPath, 'fes2015'),
|
||||
filename: `${projectName}.js`,
|
||||
library: projectName
|
||||
},
|
||||
optimization: { minimize: false }
|
||||
},
|
||||
{
|
||||
declaration: false,
|
||||
module: 'es2015',
|
||||
target: 'es2015'
|
||||
}
|
||||
),
|
||||
(err, status) => {
|
||||
if (!!err) {
|
||||
console.err(err);
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
}).catch(reason => {
|
||||
reject(reason);
|
||||
});
|
||||
|
||||
console.log(`${projectName}: generating of fes2015 complete`);
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
const genPackageJson = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log(`${projectName}: generating of package.json started`);
|
||||
const projectpackageJson = {
|
||||
...packageJson,
|
||||
main: `bundles/${projectName}.umd.js`,
|
||||
module: `fes5/${projectName}.js`,
|
||||
es2015: `fes2015/${projectName}.js`,
|
||||
esm5: `es5/public-api.js`,
|
||||
esm2015: `es2016/public-api.js`,
|
||||
fesm5: `fes5/${projectName}.js`,
|
||||
fesm2015: `fes2015/${projectName}.js`,
|
||||
typings: `public-api.d.ts`,
|
||||
sideEffects: false
|
||||
};
|
||||
|
||||
fse.writeFileSync(
|
||||
path.join(distPath, 'package.json'),
|
||||
JSON.stringify(projectpackageJson)
|
||||
);
|
||||
|
||||
console.log(`${projectName}: generating of package.json complete`);
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
const installPackage = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log(`${projectName}: installation for local started`);
|
||||
process.chdir(path.join(distPath));
|
||||
execSync(`npm pack`, { stdio: 'inherit' });
|
||||
process.chdir(path.join(rootPath));
|
||||
const projectVersion = require(path.join(distPath, 'package.json'))
|
||||
.version;
|
||||
|
||||
execSync(
|
||||
`npm install -D ${path.join(
|
||||
distPath,
|
||||
`ucap-electron-${projectName}-${projectVersion}.tgz`
|
||||
)}`,
|
||||
{
|
||||
stdio: 'inherit'
|
||||
}
|
||||
);
|
||||
|
||||
execSync(
|
||||
`rimraf ${path.join(
|
||||
distPath,
|
||||
`ucap-electron-${projectName}-${projectVersion}.tgz`
|
||||
)}`,
|
||||
{
|
||||
stdio: 'inherit'
|
||||
}
|
||||
);
|
||||
|
||||
console.log(`${projectName}: installation for local complete`);
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
const genDoc = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log(`${projectName}: generating of doc started`);
|
||||
execSync(`rimraf ${path.join(docPath)}`, {
|
||||
stdio: 'inherit'
|
||||
});
|
||||
|
||||
execSync(
|
||||
`typedoc --out ${path.join(docPath)} ${path.join(projectPath, 'src')}`,
|
||||
{ stdio: 'inherit' }
|
||||
);
|
||||
|
||||
console.log(`${projectName}: generating of doc complete`);
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
await clean();
|
||||
await genDeclaration();
|
||||
await genEs5();
|
||||
await genEs2015();
|
||||
await genBundles();
|
||||
await genFes5();
|
||||
await genFes2015();
|
||||
await genPackageJson();
|
||||
await installPackage();
|
||||
await genDoc();
|
||||
}
|
||||
|
||||
buildForProduction(process.argv.slice(2));
|
20
tsconfig.json
Normal file
20
tsconfig.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"outDir": "./dist/out-tsc",
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"downlevelIteration": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"esModuleInterop": true,
|
||||
"target": "es5",
|
||||
"typeRoots": ["node_modules/@types"],
|
||||
"lib": ["es2018", "dom"],
|
||||
"paths": {}
|
||||
}
|
||||
}
|
54
tslint.json
Normal file
54
tslint.json
Normal file
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
"extends": "tslint:recommended",
|
||||
"rules": {
|
||||
"array-type": false,
|
||||
"arrow-parens": false,
|
||||
"deprecation": {
|
||||
"severity": "warning"
|
||||
},
|
||||
"import-blacklist": [true, "rxjs/Rx"],
|
||||
"interface-name": false,
|
||||
"max-classes-per-file": false,
|
||||
"max-line-length": [true, 140],
|
||||
"member-access": false,
|
||||
"member-ordering": [
|
||||
true,
|
||||
{
|
||||
"order": [
|
||||
"static-field",
|
||||
"instance-field",
|
||||
"static-method",
|
||||
"instance-method"
|
||||
]
|
||||
}
|
||||
],
|
||||
"no-consecutive-blank-lines": false,
|
||||
"no-console": [true, "debug", "info", "time", "timeEnd", "trace"],
|
||||
"no-empty": false,
|
||||
"no-inferrable-types": [true, "ignore-params"],
|
||||
"no-non-null-assertion": false,
|
||||
"no-redundant-jsdoc": true,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-var-requires": false,
|
||||
"object-literal-key-quotes": [true, "as-needed"],
|
||||
"object-literal-sort-keys": false,
|
||||
"ordered-imports": false,
|
||||
"quotemark": [true, "single"],
|
||||
"trailing-comma": false,
|
||||
"component-class-suffix": true,
|
||||
"contextual-lifecycle": true,
|
||||
"directive-class-suffix": true,
|
||||
"no-conflicting-lifecycle": true,
|
||||
"no-host-metadata-property": true,
|
||||
"no-input-rename": true,
|
||||
"no-inputs-metadata-property": true,
|
||||
"no-output-native": true,
|
||||
"no-output-on-prefix": true,
|
||||
"no-output-rename": true,
|
||||
"no-outputs-metadata-property": true,
|
||||
"template-banana-in-box": true,
|
||||
"template-no-negated-async": true,
|
||||
"use-lifecycle-interface": true,
|
||||
"use-pipe-transform-interface": true
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user