This commit is contained in:
Park Byung Eun 2020-08-10 14:04:56 +09:00
parent e1d820434e
commit 5fade48f5e
92 changed files with 3325 additions and 6274 deletions

1
.gitignore vendored
View File

@ -2,6 +2,7 @@
# compiled output
/dist
/pack
/docs
/tmp
/out-tsc

6003
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,29 +4,44 @@
"private": true,
"scripts": {
"clean:all": "rimraf dist/*",
"build:all": "npm-run-all -s build:core build:common build:notify-window build:updater-window",
"build:all": "npm-run-all -s build:logger build:core build:common build:notify-window build:updater-window build:native",
"build:core": "node ./scripts/build.js core",
"build:common": "node ./scripts/build.js common",
"build:logger": "node ./scripts/build.js logger",
"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",
"build:native": "node ./scripts/build.js native",
"publish:all": "npm-run-all -s publish:logger publish:core publish:common 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"
"publish:logger": "cd ./dist/logger && npm publish",
"publish:notify-window": "cd ./dist/notify-window && npm publish",
"publish:updater-window": "cd ./dist/updater-window && npm publish",
"publish:native": "cd ./dist/native && npm publish"
},
"dependencies": {},
"devDependencies": {
"@tsed/core": "^5.44.11",
"@tsed/di": "^5.44.11",
"@types/fs-extra": "^8.1.0",
"@types/fs-extra": "^9.0.1",
"@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",
"@ucap/core": "~0.0.15",
"@ucap/domain-common": "~0.0.1",
"@ucap/domain-organization": "~0.0.1",
"@ucap/domain-status": "~0.0.1",
"@ucap/electron-common": "file:pack/ucap-electron-common-0.0.32.tgz",
"@ucap/electron-core": "file:pack/ucap-electron-core-0.0.16.tgz",
"@ucap/electron-logger": "file:pack/ucap-electron-logger-0.0.1.tgz",
"@ucap/electron-native": "file:pack/ucap-electron-native-0.0.19.tgz",
"@ucap/electron-notify-window": "file:pack/ucap-electron-notify-window-0.0.13.tgz",
"@ucap/logger": "~0.0.14",
"@ucap/native": "~0.0.27",
"axios": "^0.19.2",
"concurrently": "^5.2.0",
"electron": "^9.0.3",
"electron-log": "^4.2.1",
"file-type": "^14.1.4",
"fs-extra": "^9.0.1",
"npm-run-all": "^4.1.5",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^6.5.4",
"terser-webpack-plugin": "^2.3.5",

View File

@ -1,15 +1,12 @@
{
"name": "@ucap/electron-common",
"version": "0.0.1",
"version": "0.0.32",
"publishConfig": {
"registry": "http://10.81.13.221:8081/nexus/repository/npm-ucap/"
"registry": "https://nexus.loafle.net/repository/npm-ucap/"
},
"scripts": {},
"dependencies": {
"@tsed/core": "^5.44.11",
"@tsed/di": "^5.44.11",
"electron": "^8.0.0",
"peerDependencies": {
"electron": "^9.0.3",
"rxjs": "^6.5.4"
},
"devDependencies": {}
}
}

View File

@ -0,0 +1,86 @@
import * as Electron from 'electron';
import { ObjectUtil } from '@ucap/core';
import { EventHandlerRegistry } from './registries/event-handler.registry';
import { EventAttachType } from './types/event-attach.type';
import { EventHandlerMetadata } from './models/event-handler.metadata';
export abstract class Attacher {
private static readonly eventHandlerMap: Map<
any,
{ channel: string; listener: (...args: any[]) => void }[]
> = new Map();
static attach(instance: any) {
const metadatas = EventHandlerRegistry.getEventHandlers(
ObjectUtil.classOf(instance)
);
if (!!metadatas) {
metadatas.reverse().forEach((metadata) => {
metadata.eventHandlers.forEach((channels, attachType) => {
switch (attachType) {
case EventAttachType.on:
{
channels.forEach((channel) => {
const listener = (...args: any[]) => {
Attacher.invoke(metadata, instance, args);
};
Electron.app.on(channel as any, listener);
Attacher.addEventHandler(instance, channel, listener);
});
}
break;
}
});
});
}
}
static dettach(instance: any) {
Attacher.removeEventHandler(instance);
}
static dettachAll() {
Electron.app.removeAllListeners();
}
static invoke(
metadata: EventHandlerMetadata,
instance: any,
args: any[]
): any {
if (!!metadata.parent) {
return Attacher.invoke(metadata.parent, instance, args);
}
// tslint:disable-next-line: ban-types
return (metadata.target.prototype[metadata.propertyKey] as Function).call(
instance,
...args
);
}
private static addEventHandler(instance: any, channel: any, listener: any) {
let listeners = Attacher.eventHandlerMap.get(instance);
if (!listeners) {
listeners = [];
Attacher.eventHandlerMap.set(instance, listeners);
}
listeners.push({ channel, listener });
}
private static removeEventHandler(instance: any) {
const listeners = Attacher.eventHandlerMap.get(instance);
if (!listeners) {
return;
}
for (const listener of listeners) {
Electron.app.removeListener(listener.channel as any, listener.listener);
}
}
}

View File

@ -0,0 +1,15 @@
import * as Electron from 'electron';
import { Type } from '@ucap/core';
import { AppConfiguration } from './models/app-configuration';
export abstract class Builder {
static build<T>(
appType: Type<T>,
configuration: Partial<AppConfiguration>
): T {
const app = new appType(configuration);
return app;
}
}

View File

@ -0,0 +1,30 @@
import { Type, DecoratorUtil, Store } from '@ucap/core';
import { AppConfiguration } from '../models/app-configuration';
export const STORE_KEY_APP_SETTINGS = '@ucap::electron::app::app-settings';
export function AppSettings(
configuration: Partial<AppConfiguration> = {}
// tslint:disable-next-line: ban-types
): Function {
return <T>(target: Type<any>): TypedPropertyDescriptor<T> | void => {
switch (DecoratorUtil.getDecoratorType([target])) {
case 'class':
Store.setBy(STORE_KEY_APP_SETTINGS, configuration)(target);
break;
}
};
}
export function getAppSettings(
target: Type<any>,
configuration: Partial<AppConfiguration> = {}
): AppConfiguration {
const store = Store.from(target).get(STORE_KEY_APP_SETTINGS) || {};
return {
...store,
...configuration
};
}

View File

@ -0,0 +1,26 @@
import { DecoratorUtil } from '@ucap/core';
import { AppChannel } from '@ucap/electron-core';
import { EventHandlerRegistry } from '../registries/event-handler.registry';
import { EventAttachType } from '../types/event-attach.type';
export function On(channel: AppChannel) {
return <T>(
target: any,
targetKey?: string,
descriptor?: TypedPropertyDescriptor<T>
): TypedPropertyDescriptor<T> | void => {
switch (DecoratorUtil.getDecoratorType([target, targetKey, descriptor])) {
case 'method':
EventHandlerRegistry.use(
target,
targetKey!,
EventAttachType.on,
channel
);
return descriptor;
}
};
}

View File

@ -0,0 +1,28 @@
import { AppSettings, getAppSettings } from './decorators/app-settings';
import { On } from './decorators/on';
import { AppConfiguration } from './models/app-configuration';
import {
EventHandlerConstructorOptions,
EventHandlerMetadata
} from './models/event-handler.metadata';
import { EventHandlerRegistry } from './registries/event-handler.registry';
import { EventAttachType } from './types/event-attach.type';
import { Attacher } from './attacher';
import { Builder } from './builder';
export {
AppSettings,
getAppSettings,
On,
AppConfiguration,
EventHandlerConstructorOptions,
EventHandlerMetadata,
EventHandlerRegistry,
EventAttachType,
Attacher,
Builder
};

View File

@ -0,0 +1,9 @@
import { Type } from '@ucap/core';
export interface AppConfiguration {
rootDir?: string;
bootstrap?: Type<any>;
services?: Type<any>[];
[key: string]: any;
}

View File

@ -0,0 +1,73 @@
import { Type, Metadata, ReflectUtil, ObjectUtil, Store } from '@ucap/core';
import { AppChannel } from '@ucap/electron-core';
import { EventAttachType } from '../types/event-attach.type';
export interface EventHandlerConstructorOptions {
target: Type<any>;
propertyKey: string | symbol;
eventHandlers?: Map<EventAttachType, AppChannel[]>;
type?: any;
parent?: EventHandlerMetadata;
}
export class EventHandlerMetadata extends Metadata
implements EventHandlerConstructorOptions {
eventHandlers: Map<EventAttachType, AppChannel[]> = new Map();
readonly parent: EventHandlerMetadata | undefined;
constructor(options: EventHandlerConstructorOptions) {
super(
options.target,
options.propertyKey,
Object.getOwnPropertyDescriptor(options.target, options.propertyKey)
);
const {
target,
parent,
propertyKey,
eventHandlers = new Map(),
type
} = options;
this._type = ReflectUtil.getReturnType(target, propertyKey);
this.eventHandlers = eventHandlers;
this.type = type;
this.parent = parent;
}
get type(): Type<any> {
return ObjectUtil.isPromise(this._type) ||
ObjectUtil.isArrayOrArrayClass(this._type) ||
this._type === Object
? undefined!
: this._type;
}
set type(type: Type<any>) {
this._type = type;
}
get(key: any) {
const ctrlValue = Store.from(this.target).get(key);
let meta = ObjectUtil.deepExtends(undefined, ctrlValue);
const endpointValue = this.store.get(key);
if (endpointValue !== undefined) {
meta = ObjectUtil.deepExtends(meta, endpointValue);
}
return meta;
}
merge(attachType: EventAttachType, channel: AppChannel): this {
if (!this.eventHandlers.has(attachType)) {
this.eventHandlers.set(attachType, []);
}
this.eventHandlers.get(attachType).push(channel);
return this;
}
}

View File

@ -0,0 +1,94 @@
import { Type, ReflectUtil, ObjectUtil } from '@ucap/core';
import { AppChannel } from '@ucap/electron-core';
import { EventHandlerMetadata } from '../models/event-handler.metadata';
import { EventAttachType } from '../types/event-attach.type';
export const REFLECT_KEY_EVENTHANDLERS = 'ucap_electron_app_event_handlers';
export class EventHandlerRegistry {
static getOwnEventHandlers(target: Type<any>): EventHandlerMetadata[] {
if (!this.hasEventHandlers(target)) {
ReflectUtil.set(REFLECT_KEY_EVENTHANDLERS, [], target);
}
return ReflectUtil.getOwn(REFLECT_KEY_EVENTHANDLERS, target);
}
static getEventHandlers(target: Type<any>): EventHandlerMetadata[] {
return this.getOwnEventHandlers(target).concat(this.inherit(target));
}
static hasEventHandlers(target: Type<any>): boolean {
return ReflectUtil.hasOwn(REFLECT_KEY_EVENTHANDLERS, target);
}
static get(
target: Type<any>,
propertyKey: string | symbol
): EventHandlerMetadata {
if (!this.has(target, propertyKey)) {
const evnetHandler = new EventHandlerMetadata({
target,
propertyKey
});
this.getOwnEventHandlers(target).push(evnetHandler);
ReflectUtil.set(
REFLECT_KEY_EVENTHANDLERS,
evnetHandler,
target,
propertyKey
);
}
return ReflectUtil.getOwn(REFLECT_KEY_EVENTHANDLERS, target, propertyKey);
}
static has(target: Type<any>, method: string | symbol): boolean {
return ReflectUtil.hasOwn(REFLECT_KEY_EVENTHANDLERS, target, method);
}
static use(
target: Type<any>,
targetKey: string | symbol,
attachType: EventAttachType,
channel: AppChannel
) {
this.get(target, targetKey).merge(attachType, channel);
return this;
}
private static inherit(clazz: Type<any>) {
const eventHandlers: EventHandlerMetadata[] = [];
let inheritedClass = ObjectUtil.getInheritedClass(clazz);
while (
inheritedClass &&
EventHandlerRegistry.hasEventHandlers(inheritedClass)
) {
this.getOwnEventHandlers(inheritedClass).forEach(
(eventHandler: EventHandlerMetadata) => {
eventHandlers.push(inheritEndpoint(clazz, eventHandler));
}
);
inheritedClass = ObjectUtil.getInheritedClass(inheritedClass);
}
return eventHandlers;
}
}
function inheritEndpoint(
target: Type<any>,
eventHandler: EventHandlerMetadata
): EventHandlerMetadata {
return new EventHandlerMetadata({
...eventHandler,
target,
type: eventHandler.type,
parent: eventHandler
});
}

View File

@ -0,0 +1,3 @@
export enum EventAttachType {
on = 'on'
}

View File

@ -0,0 +1,89 @@
import * as Electron from 'electron';
import { ObjectUtil } from '@ucap/core';
import { EventHandlerRegistry } from './registries/event-handler.registry';
import { EventAttachType } from './types/event-attach.type';
import { EventHandlerMetadata } from './models/event-handler.metadata';
export abstract class Attacher {
private static readonly eventHandlerMap: Map<
any,
{ channel: string; listener: (...args: any[]) => void }[]
> = new Map();
static attach(instance: any) {
const metadatas = EventHandlerRegistry.getEventHandlers(
ObjectUtil.classOf(instance)
);
if (!!metadatas) {
metadatas.reverse().forEach((metadata) => {
metadata.eventHandlers.forEach((channels, attachType) => {
switch (attachType) {
case EventAttachType.on:
{
channels.forEach((channel) => {
const listener = (...args: any[]) => {
Attacher.invoke(metadata, instance, args);
};
Electron.autoUpdater.on(channel as any, listener);
Attacher.addEventHandler(instance, channel, listener);
});
}
break;
}
});
});
}
}
static dettach(instance: any) {
Attacher.removeEventHandler(instance);
}
static dettachAll() {
Electron.autoUpdater.removeAllListeners();
}
static invoke(
metadata: EventHandlerMetadata,
instance: any,
args: any[]
): any {
if (!!metadata.parent) {
return Attacher.invoke(metadata.parent, instance, args);
}
// tslint:disable-next-line: ban-types
return (metadata.target.prototype[metadata.propertyKey] as Function).call(
instance,
...args
);
}
private static addEventHandler(instance: any, channel: any, listener: any) {
let listeners = Attacher.eventHandlerMap.get(instance);
if (!listeners) {
listeners = [];
Attacher.eventHandlerMap.set(instance, listeners);
}
listeners.push({ channel, listener });
}
private static removeEventHandler(instance: any) {
const listeners = Attacher.eventHandlerMap.get(instance);
if (!listeners) {
return;
}
for (const listener of listeners) {
Electron.autoUpdater.removeListener(
listener.channel as any,
listener.listener
);
}
}
}

View File

@ -0,0 +1,16 @@
import * as Electron from 'electron';
import { Type } from '@ucap/core';
import { AutoUpdaterConfiguration } from './models/auto-updater-configuration';
export abstract class Builder {
static build<T>(
autoUpdaterType: Type<T>,
configuration: Partial<AutoUpdaterConfiguration>
): T {
const autoUpdater = new autoUpdaterType(configuration);
return autoUpdater;
}
}

View File

@ -0,0 +1,31 @@
import { Type, DecoratorUtil, Store } from '@ucap/core';
import { AutoUpdaterConfiguration } from '../models/auto-updater-configuration';
export const STORE_KEY_AUTO_UPDATER_SETTINGS =
'@ucap::electron::auto-updater::auto-updater-settings';
export function AutoUpdaterSettings(
configuration: Partial<AutoUpdaterConfiguration> = {}
// tslint:disable-next-line: ban-types
): Function {
return <T>(target: Type<any>): TypedPropertyDescriptor<T> | void => {
switch (DecoratorUtil.getDecoratorType([target])) {
case 'class':
Store.setBy(STORE_KEY_AUTO_UPDATER_SETTINGS, configuration)(target);
break;
}
};
}
export function getAppSettings(
target: Type<any>,
configuration: Partial<AutoUpdaterConfiguration> = {}
): AutoUpdaterConfiguration {
const store = Store.from(target).get(STORE_KEY_AUTO_UPDATER_SETTINGS) || {};
return {
...store,
...configuration
};
}

View File

@ -0,0 +1,26 @@
import { DecoratorUtil } from '@ucap/core';
import { AutoUpdaterChannel } from '@ucap/electron-core';
import { EventHandlerRegistry } from '../registries/event-handler.registry';
import { EventAttachType } from '../types/event-attach.type';
export function On(channel: AutoUpdaterChannel) {
return <T>(
target: any,
targetKey?: string,
descriptor?: TypedPropertyDescriptor<T>
): TypedPropertyDescriptor<T> | void => {
switch (DecoratorUtil.getDecoratorType([target, targetKey, descriptor])) {
case 'method':
EventHandlerRegistry.use(
target,
targetKey!,
EventAttachType.on,
channel
);
return descriptor;
}
};
}

View File

@ -0,0 +1,31 @@
import {
AutoUpdaterSettings,
getAppSettings
} from './decorators/auto-updater-settings';
import { On } from './decorators/on';
import { AutoUpdaterConfiguration } from './models/auto-updater-configuration';
import {
EventHandlerConstructorOptions,
EventHandlerMetadata
} from './models/event-handler.metadata';
import { EventHandlerRegistry } from './registries/event-handler.registry';
import { EventAttachType } from './types/event-attach.type';
import { Attacher } from './attacher';
import { Builder } from './builder';
export {
AutoUpdaterSettings,
getAppSettings,
On,
AutoUpdaterConfiguration,
EventHandlerConstructorOptions,
EventHandlerMetadata,
EventHandlerRegistry,
EventAttachType,
Attacher,
Builder
};

View File

@ -0,0 +1,3 @@
export interface AutoUpdaterConfiguration {
rootDir?: string;
}

View File

@ -0,0 +1,74 @@
import { Type, Metadata, ReflectUtil, ObjectUtil, Store } from '@ucap/core';
import { AutoUpdaterChannel } from '@ucap/electron-core';
import { EventAttachType } from '../types/event-attach.type';
export interface EventHandlerConstructorOptions {
target: Type<any>;
propertyKey: string | symbol;
eventHandlers?: Map<EventAttachType, AutoUpdaterChannel[]>;
type?: any;
parent?: EventHandlerMetadata;
}
export class EventHandlerMetadata extends Metadata
implements EventHandlerConstructorOptions {
eventHandlers: Map<EventAttachType, AutoUpdaterChannel[]> = new Map();
readonly parent: EventHandlerMetadata | undefined;
constructor(options: EventHandlerConstructorOptions) {
super(
options.target,
options.propertyKey,
Object.getOwnPropertyDescriptor(options.target, options.propertyKey)
);
const {
target,
parent,
propertyKey,
eventHandlers = new Map(),
type
} = options;
this._type = ReflectUtil.getReturnType(target, propertyKey);
this.eventHandlers = eventHandlers;
this.type = type;
this.parent = parent;
}
get type(): Type<any> {
return ObjectUtil.isPromise(this._type) ||
ObjectUtil.isArrayOrArrayClass(this._type) ||
this._type === Object
? undefined!
: this._type;
}
set type(type: Type<any>) {
this._type = type;
}
get(key: any) {
const ctrlValue = Store.from(this.target).get(key);
let meta = ObjectUtil.deepExtends(undefined, ctrlValue);
const endpointValue = this.store.get(key);
if (endpointValue !== undefined) {
meta = ObjectUtil.deepExtends(meta, endpointValue);
}
return meta;
}
merge(attachType: EventAttachType, channel: AutoUpdaterChannel): this {
if (!this.eventHandlers.has(attachType)) {
this.eventHandlers.set(attachType, []);
}
this.eventHandlers.get(attachType).push(channel);
return this;
}
}

View File

@ -0,0 +1,95 @@
import { Type, ReflectUtil, ObjectUtil } from '@ucap/core';
import { AutoUpdaterChannel } from '@ucap/electron-core';
import { EventHandlerMetadata } from '../models/event-handler.metadata';
import { EventAttachType } from '../types/event-attach.type';
export const REFLECT_KEY_EVENTHANDLERS =
'ucap_electron_auto_updater_event_handlers';
export class EventHandlerRegistry {
static getOwnEventHandlers(target: Type<any>) {
if (!this.hasEventHandlers(target)) {
ReflectUtil.set(REFLECT_KEY_EVENTHANDLERS, [], target);
}
return ReflectUtil.getOwn(REFLECT_KEY_EVENTHANDLERS, target);
}
static getEventHandlers(target: Type<any>): EventHandlerMetadata[] {
return this.getOwnEventHandlers(target).concat(this.inherit(target));
}
static hasEventHandlers(target: Type<any>): boolean {
return ReflectUtil.hasOwn(REFLECT_KEY_EVENTHANDLERS, target);
}
static get(
target: Type<any>,
propertyKey: string | symbol
): EventHandlerMetadata {
if (!this.has(target, propertyKey)) {
const evnetHandler = new EventHandlerMetadata({
target,
propertyKey
});
this.getOwnEventHandlers(target).push(evnetHandler);
ReflectUtil.set(
REFLECT_KEY_EVENTHANDLERS,
evnetHandler,
target,
propertyKey
);
}
return ReflectUtil.getOwn(REFLECT_KEY_EVENTHANDLERS, target, propertyKey);
}
static has(target: Type<any>, method: string | symbol): boolean {
return ReflectUtil.hasOwn(REFLECT_KEY_EVENTHANDLERS, target, method);
}
static use(
target: Type<any>,
targetKey: string | symbol,
attachType: EventAttachType,
channel: AutoUpdaterChannel
) {
this.get(target, targetKey).merge(attachType, channel);
return this;
}
private static inherit(clazz: Type<any>) {
const eventHandlers: EventHandlerMetadata[] = [];
let inheritedClass = ObjectUtil.getInheritedClass(clazz);
while (
inheritedClass &&
EventHandlerRegistry.hasEventHandlers(inheritedClass)
) {
this.getOwnEventHandlers(inheritedClass).forEach(
(eventHandler: EventHandlerMetadata) => {
eventHandlers.push(inheritEndpoint(clazz, eventHandler));
}
);
inheritedClass = ObjectUtil.getInheritedClass(inheritedClass);
}
return eventHandlers;
}
}
function inheritEndpoint(
target: Type<any>,
eventHandler: EventHandlerMetadata
): EventHandlerMetadata {
return new EventHandlerMetadata({
...eventHandler,
target,
type: eventHandler.type,
parent: eventHandler
});
}

View File

@ -0,0 +1,3 @@
export enum EventAttachType {
on = 'on'
}

View File

@ -0,0 +1,55 @@
import * as Electron from 'electron';
import { ObjectUtil } from '@ucap/core';
import { EventHandlerRegistry } from './registries/event-handler.registry';
import { EventAttachType } from './types/event-attach.type';
import { EventHandlerMetadata } from './models/event-handler.metadata';
import { ElectronBrowserWindow } from './electron-browser-window';
export abstract class Attacher {
static attach<T extends ElectronBrowserWindow>(instance: T) {
const metadatas = EventHandlerRegistry.getEventHandlers(
ObjectUtil.classOf(instance)
);
if (!!metadatas) {
metadatas.reverse().forEach((metadata) => {
metadata.eventHandlers.forEach((channels, attachType) => {
switch (attachType) {
case EventAttachType.on:
{
channels.forEach((channel) => {
instance.native.on(channel as any, (...args: any[]) => {
Attacher.invoke(metadata, instance, args);
});
});
}
break;
}
});
});
}
}
static dettach<T extends ElectronBrowserWindow>(instance: T) {
instance.native.removeAllListeners();
}
static invoke(
metadata: EventHandlerMetadata,
instance: any,
args: any[]
): any {
if (!!metadata.parent) {
return Attacher.invoke(metadata.parent, instance, args);
}
// tslint:disable-next-line: ban-types
return (metadata.target.prototype[metadata.propertyKey] as Function).call(
instance,
...args
);
}
}

View File

@ -0,0 +1,36 @@
import * as Electron from 'electron';
import { Type, ObjectUtil } from '@ucap/core';
import { getBrowserWindowSettings } from './decorators/browser-window-configuration';
import { AppConfiguration } from '../app';
import { Attacher } from './attacher';
import { ElectronBrowserWindow } from './electron-browser-window';
export abstract class Builder {
static build<T extends ElectronBrowserWindow>(
configuration: Partial<AppConfiguration>,
browserWindowType: Type<T>
): T {
const _configuration = getBrowserWindowSettings(
ObjectUtil.classOf(browserWindowType)
);
const constructorOptions = ObjectUtil.isFunction(
_configuration.constructorOptions
)
? _configuration.constructorOptions(configuration)
: _configuration.constructorOptions;
const _browserWindow = new Electron.BrowserWindow(constructorOptions);
const browserWindow = new browserWindowType(_configuration, _browserWindow);
Attacher.attach(browserWindow);
return browserWindow;
}
static destroy<T extends ElectronBrowserWindow>(instance: T) {
Attacher.dettach(instance);
}
}

View File

@ -0,0 +1,29 @@
import * as Elctron from 'electron';
import { Type, DecoratorUtil, Store } from '@ucap/core';
import { BrowserWindowConfiguration } from '../models/browser-window-configuration';
export const STORE_KEY_BROWSER_WINDOW_SETTINGS =
'@ucap::electron::browser-window::browser-window-settings';
export function BrowserWindowSettings(
configuration: Partial<BrowserWindowConfiguration> = {}
// tslint:disable-next-line: ban-types
): Function {
return <T extends Elctron.BrowserWindow>(
target: Type<T>
): TypedPropertyDescriptor<T> | void => {
switch (DecoratorUtil.getDecoratorType([target])) {
case 'class':
Store.setBy(STORE_KEY_BROWSER_WINDOW_SETTINGS, configuration)(target);
break;
}
};
}
export function getBrowserWindowSettings<T extends Elctron.BrowserWindow>(
target: Type<T>
): BrowserWindowConfiguration {
return Store.from(target).get(STORE_KEY_BROWSER_WINDOW_SETTINGS) || {};
}

View File

@ -0,0 +1,26 @@
import { DecoratorUtil } from '@ucap/core';
import { BrowserWindowChannel } from '@ucap/electron-core';
import { EventHandlerRegistry } from '../registries/event-handler.registry';
import { EventAttachType } from '../types/event-attach.type';
export function On(channel: BrowserWindowChannel) {
return <T>(
target: any,
targetKey?: string,
descriptor?: TypedPropertyDescriptor<T>
): TypedPropertyDescriptor<T> | void => {
switch (DecoratorUtil.getDecoratorType([target, targetKey, descriptor])) {
case 'method':
EventHandlerRegistry.use(
target,
targetKey!,
EventAttachType.on,
channel
);
return descriptor;
}
};
}

View File

@ -0,0 +1,5 @@
import * as Electron from 'electron';
export interface ElectronBrowserWindow {
native: Electron.BrowserWindow;
}

View File

@ -0,0 +1,34 @@
import {
BrowserWindowSettings,
getBrowserWindowSettings
} from './decorators/browser-window-configuration';
import { On } from './decorators/on';
import { BrowserWindowConfiguration } from './models/browser-window-configuration';
import {
EventHandlerConstructorOptions,
EventHandlerMetadata
} from './models/event-handler.metadata';
import { EventHandlerRegistry } from './registries/event-handler.registry';
import { EventAttachType } from './types/event-attach.type';
import { Attacher } from './attacher';
import { Builder } from './builder';
import { ElectronBrowserWindow } from './electron-browser-window';
export {
BrowserWindowSettings,
getBrowserWindowSettings,
On,
BrowserWindowConfiguration,
EventHandlerConstructorOptions,
EventHandlerMetadata,
EventHandlerRegistry,
EventAttachType,
Attacher,
Builder,
ElectronBrowserWindow
};

View File

@ -0,0 +1,12 @@
import * as Electron from 'electron';
import { AppConfiguration } from '../../app';
export type BrowserWindowConstructorOptionsFunc = (
appConfiguration: AppConfiguration
) => Electron.BrowserWindowConstructorOptions;
export interface BrowserWindowConfiguration {
constructorOptions?:
| Electron.BrowserWindowConstructorOptions
| BrowserWindowConstructorOptionsFunc;
}

View File

@ -0,0 +1,74 @@
import { Type, Metadata, ReflectUtil, ObjectUtil, Store } from '@ucap/core';
import { BrowserWindowChannel } from '@ucap/electron-core';
import { EventAttachType } from '../types/event-attach.type';
export interface EventHandlerConstructorOptions {
target: Type<any>;
propertyKey: string | symbol;
eventHandlers?: Map<EventAttachType, BrowserWindowChannel[]>;
type?: any;
parent?: EventHandlerMetadata;
}
export class EventHandlerMetadata extends Metadata
implements EventHandlerConstructorOptions {
eventHandlers: Map<EventAttachType, BrowserWindowChannel[]> = new Map();
readonly parent: EventHandlerMetadata | undefined;
constructor(options: EventHandlerConstructorOptions) {
super(
options.target,
options.propertyKey,
Object.getOwnPropertyDescriptor(options.target, options.propertyKey)
);
const {
target,
parent,
propertyKey,
eventHandlers = new Map(),
type
} = options;
this._type = ReflectUtil.getReturnType(target, propertyKey);
this.eventHandlers = eventHandlers;
this.type = type;
this.parent = parent;
}
get type(): Type<any> {
return ObjectUtil.isPromise(this._type) ||
ObjectUtil.isArrayOrArrayClass(this._type) ||
this._type === Object
? undefined!
: this._type;
}
set type(type: Type<any>) {
this._type = type;
}
get(key: any) {
const ctrlValue = Store.from(this.target).get(key);
let meta = ObjectUtil.deepExtends(undefined, ctrlValue);
const endpointValue = this.store.get(key);
if (endpointValue !== undefined) {
meta = ObjectUtil.deepExtends(meta, endpointValue);
}
return meta;
}
merge(attachType: EventAttachType, channel: BrowserWindowChannel): this {
if (!this.eventHandlers.has(attachType)) {
this.eventHandlers.set(attachType, []);
}
this.eventHandlers.get(attachType).push(channel);
return this;
}
}

View File

@ -0,0 +1,97 @@
import { Type, ReflectUtil, ObjectUtil } from '@ucap/core';
import { BrowserWindowChannel } from '@ucap/electron-core';
import { EventHandlerMetadata } from '../models/event-handler.metadata';
import { EventAttachType } from '../types/event-attach.type';
export const REFLECT_KEY_EVENTHANDLERS =
'ucap_electron_browser_window_event_handlers';
export class EventHandlerRegistry {
static getOwnEventHandlers(target: Type<any>) {
if (!EventHandlerRegistry.hasEventHandlers(target)) {
ReflectUtil.set(REFLECT_KEY_EVENTHANDLERS, [], target);
}
return ReflectUtil.getOwn(REFLECT_KEY_EVENTHANDLERS, target);
}
static getEventHandlers(target: Type<any>): EventHandlerMetadata[] {
return EventHandlerRegistry.getOwnEventHandlers(target).concat(
EventHandlerRegistry.inherit(target)
);
}
static hasEventHandlers(target: Type<any>): boolean {
return ReflectUtil.hasOwn(REFLECT_KEY_EVENTHANDLERS, target);
}
static get(
target: Type<any>,
propertyKey: string | symbol
): EventHandlerMetadata {
if (!EventHandlerRegistry.has(target, propertyKey)) {
const evnetHandler = new EventHandlerMetadata({
target,
propertyKey
});
EventHandlerRegistry.getOwnEventHandlers(target).push(evnetHandler);
ReflectUtil.set(
REFLECT_KEY_EVENTHANDLERS,
evnetHandler,
target,
propertyKey
);
}
return ReflectUtil.getOwn(REFLECT_KEY_EVENTHANDLERS, target, propertyKey);
}
static has(target: Type<any>, method: string | symbol): boolean {
return ReflectUtil.hasOwn(REFLECT_KEY_EVENTHANDLERS, target, method);
}
static use(
target: Type<any>,
targetKey: string | symbol,
attachType: EventAttachType,
channel: BrowserWindowChannel
) {
EventHandlerRegistry.get(target, targetKey).merge(attachType, channel);
return EventHandlerRegistry;
}
private static inherit(clazz: Type<any>) {
const eventHandlers: EventHandlerMetadata[] = [];
let inheritedClass = ObjectUtil.getInheritedClass(clazz);
while (
inheritedClass &&
EventHandlerRegistry.hasEventHandlers(inheritedClass)
) {
EventHandlerRegistry.getOwnEventHandlers(inheritedClass).forEach(
(eventHandler: EventHandlerMetadata) => {
eventHandlers.push(inheritEndpoint(clazz, eventHandler));
}
);
inheritedClass = ObjectUtil.getInheritedClass(inheritedClass);
}
return eventHandlers;
}
}
function inheritEndpoint(
target: Type<any>,
eventHandler: EventHandlerMetadata
): EventHandlerMetadata {
return new EventHandlerMetadata({
...eventHandler,
target,
type: eventHandler.type,
parent: eventHandler
});
}

View File

@ -0,0 +1,3 @@
export enum EventAttachType {
on = 'on'
}

View File

@ -0,0 +1,118 @@
import * as Electron from 'electron';
import { ObjectUtil } from '@ucap/core';
import { EventHandlerRegistry } from './registries/event-handler.registry';
import { EventAttachType } from './types/event-attach.type';
import { EventHandlerMetadata } from './models/event-handler.metadata';
export abstract class Attacher {
private static readonly eventHandlerMap: Map<
any,
{ channel: string; listener: (...args: any[]) => void }[]
> = new Map();
static attach(instance: any) {
const metadatas = EventHandlerRegistry.getEventHandlers(
ObjectUtil.classOf(instance)
);
if (!!metadatas) {
metadatas.reverse().forEach((metadata) => {
metadata.eventHandlers.forEach((channels, attachType) => {
switch (attachType) {
case EventAttachType.on:
{
channels.forEach((channel) => {
const listener = (...args: any[]) => {
Attacher.invoke(metadata, instance, args);
};
Electron.ipcMain.on(channel as any, listener);
Attacher.addEventHandler(instance, channel, listener);
});
}
break;
case EventAttachType.once:
{
channels.forEach((channel) => {
const listener = (...args: any[]) => {
Attacher.invoke(metadata, instance, args);
};
Electron.ipcMain.once(channel as any, listener);
Attacher.addEventHandler(instance, channel, listener);
});
}
break;
case EventAttachType.handle:
{
channels.forEach((channel) => {
const listener = (...args: any[]) => {
return Attacher.invoke(metadata, instance, args);
};
Electron.ipcMain.handle(channel as any, listener);
Attacher.addEventHandler(instance, channel, listener);
});
}
break;
case EventAttachType.handleOnce:
{
channels.forEach((channel) => {
const listener = (...args: any[]) => {
return Attacher.invoke(metadata, instance, args);
};
Electron.ipcMain.handleOnce(channel as any, listener);
Attacher.addEventHandler(instance, channel, listener);
});
}
break;
}
});
});
}
}
static dettach(instance: any) {
Attacher.removeEventHandler(instance);
}
static dettachAll() {
Electron.ipcMain.removeAllListeners();
}
static invoke(
metadata: EventHandlerMetadata,
instance: any,
args: any[]
): any {
if (!!metadata.parent) {
return Attacher.invoke(metadata.parent, instance, args);
}
// tslint:disable-next-line: ban-types
return (metadata.target.prototype[metadata.propertyKey] as Function).call(
instance,
...args
);
}
private static addEventHandler(instance: any, channel: any, listener: any) {
let listeners = Attacher.eventHandlerMap.get(instance);
if (!listeners) {
listeners = [];
Attacher.eventHandlerMap.set(instance, listeners);
}
listeners.push({ channel, listener });
}
private static removeEventHandler(instance: any) {
const listeners = Attacher.eventHandlerMap.get(instance);
if (!listeners) {
return;
}
for (const listener of listeners) {
Electron.ipcMain.removeListener(listener.channel, listener.listener);
}
}
}

View File

@ -0,0 +1,24 @@
import { DecoratorUtil } from '@ucap/core';
import { EventHandlerRegistry } from '../registries/event-handler.registry';
import { EventAttachType } from '../types/event-attach.type';
export function HandleOnce(channel: string) {
return <T>(
target: any,
targetKey?: string,
descriptor?: TypedPropertyDescriptor<T>
): TypedPropertyDescriptor<T> | void => {
switch (DecoratorUtil.getDecoratorType([target, targetKey, descriptor])) {
case 'method':
EventHandlerRegistry.use(
target,
targetKey!,
EventAttachType.handleOnce,
channel
);
return descriptor;
}
};
}

View File

@ -0,0 +1,24 @@
import { DecoratorUtil } from '@ucap/core';
import { EventHandlerRegistry } from '../registries/event-handler.registry';
import { EventAttachType } from '../types/event-attach.type';
export function Handle(channel: string) {
return <T>(
target: any,
targetKey?: string,
descriptor?: TypedPropertyDescriptor<T>
): TypedPropertyDescriptor<T> | void => {
switch (DecoratorUtil.getDecoratorType([target, targetKey, descriptor])) {
case 'method':
EventHandlerRegistry.use(
target,
targetKey!,
EventAttachType.handle,
channel
);
return descriptor;
}
};
}

View File

@ -0,0 +1,24 @@
import { DecoratorUtil } from '@ucap/core';
import { EventHandlerRegistry } from '../registries/event-handler.registry';
import { EventAttachType } from '../types/event-attach.type';
export function On(channel: string) {
return <T>(
target: any,
targetKey?: string,
descriptor?: TypedPropertyDescriptor<T>
): TypedPropertyDescriptor<T> | void => {
switch (DecoratorUtil.getDecoratorType([target, targetKey, descriptor])) {
case 'method':
EventHandlerRegistry.use(
target,
targetKey!,
EventAttachType.on,
channel
);
return descriptor;
}
};
}

View File

@ -0,0 +1,24 @@
import { DecoratorUtil } from '@ucap/core';
import { EventHandlerRegistry } from '../registries/event-handler.registry';
import { EventAttachType } from '../types/event-attach.type';
export function Once(channel: string) {
return <T>(
target: any,
targetKey?: string,
descriptor?: TypedPropertyDescriptor<T>
): TypedPropertyDescriptor<T> | void => {
switch (DecoratorUtil.getDecoratorType([target, targetKey, descriptor])) {
case 'method':
EventHandlerRegistry.use(
target,
targetKey!,
EventAttachType.once,
channel
);
return descriptor;
}
};
}

View File

@ -0,0 +1,27 @@
import { Handle } from './decorators/handle';
import { HandleOnce } from './decorators/handle-once';
import { On } from './decorators/on';
import { Once } from './decorators/once';
import {
EventHandlerConstructorOptions,
EventHandlerMetadata
} from './models/event-handler.metadata';
import { EventHandlerRegistry } from './registries/event-handler.registry';
import { EventAttachType } from './types/event-attach.type';
import { Attacher } from './attacher';
export {
Handle,
HandleOnce,
On,
Once,
EventHandlerConstructorOptions,
EventHandlerMetadata,
EventHandlerRegistry,
EventAttachType,
Attacher
};

View File

@ -0,0 +1,72 @@
import { Type, Metadata, ReflectUtil, ObjectUtil, Store } from '@ucap/core';
import { EventAttachType } from '../types/event-attach.type';
export interface EventHandlerConstructorOptions {
target: Type<any>;
propertyKey: string | symbol;
eventHandlers?: Map<EventAttachType, string[]>;
type?: any;
parent?: EventHandlerMetadata;
}
export class EventHandlerMetadata extends Metadata
implements EventHandlerConstructorOptions {
eventHandlers: Map<EventAttachType, string[]> = new Map();
readonly parent: EventHandlerMetadata | undefined;
constructor(options: EventHandlerConstructorOptions) {
super(
options.target,
options.propertyKey,
Object.getOwnPropertyDescriptor(options.target, options.propertyKey)
);
const {
target,
parent,
propertyKey,
eventHandlers = new Map(),
type
} = options;
this._type = ReflectUtil.getReturnType(target, propertyKey);
this.eventHandlers = eventHandlers;
this.type = type;
this.parent = parent;
}
get type(): Type<any> {
return ObjectUtil.isPromise(this._type) ||
ObjectUtil.isArrayOrArrayClass(this._type) ||
this._type === Object
? undefined!
: this._type;
}
set type(type: Type<any>) {
this._type = type;
}
get(key: any) {
const ctrlValue = Store.from(this.target).get(key);
let meta = ObjectUtil.deepExtends(undefined, ctrlValue);
const endpointValue = this.store.get(key);
if (endpointValue !== undefined) {
meta = ObjectUtil.deepExtends(meta, endpointValue);
}
return meta;
}
merge(attachType: EventAttachType, channel: string): this {
if (!this.eventHandlers.has(attachType)) {
this.eventHandlers.set(attachType, []);
}
this.eventHandlers.get(attachType).push(channel);
return this;
}
}

View File

@ -0,0 +1,92 @@
import { Type, ReflectUtil, ObjectUtil } from '@ucap/core';
import { EventHandlerMetadata } from '../models/event-handler.metadata';
import { EventAttachType } from '../types/event-attach.type';
export const REFLECT_KEY_EVENTHANDLERS = 'ucap_electron_app_event_handlers';
export class EventHandlerRegistry {
static getOwnEventHandlers(target: Type<any>) {
if (!this.hasEventHandlers(target)) {
ReflectUtil.set(REFLECT_KEY_EVENTHANDLERS, [], target);
}
return ReflectUtil.getOwn(REFLECT_KEY_EVENTHANDLERS, target);
}
static getEventHandlers(target: Type<any>): EventHandlerMetadata[] {
return this.getOwnEventHandlers(target).concat(this.inherit(target));
}
static hasEventHandlers(target: Type<any>): boolean {
return ReflectUtil.hasOwn(REFLECT_KEY_EVENTHANDLERS, target);
}
static get(
target: Type<any>,
propertyKey: string | symbol
): EventHandlerMetadata {
if (!this.has(target, propertyKey)) {
const evnetHandler = new EventHandlerMetadata({
target,
propertyKey
});
this.getOwnEventHandlers(target).push(evnetHandler);
ReflectUtil.set(
REFLECT_KEY_EVENTHANDLERS,
evnetHandler,
target,
propertyKey
);
}
return ReflectUtil.getOwn(REFLECT_KEY_EVENTHANDLERS, target, propertyKey);
}
static has(target: Type<any>, method: string | symbol): boolean {
return ReflectUtil.hasOwn(REFLECT_KEY_EVENTHANDLERS, target, method);
}
static use(
target: Type<any>,
targetKey: string | symbol,
attachType: EventAttachType,
channel: string
) {
this.get(target, targetKey).merge(attachType, channel);
return this;
}
private static inherit(clazz: Type<any>) {
const eventHandlers: EventHandlerMetadata[] = [];
let inheritedClass = ObjectUtil.getInheritedClass(clazz);
while (
inheritedClass &&
EventHandlerRegistry.hasEventHandlers(inheritedClass)
) {
this.getOwnEventHandlers(inheritedClass).forEach(
(eventHandler: EventHandlerMetadata) => {
eventHandlers.push(inheritEndpoint(clazz, eventHandler));
}
);
inheritedClass = ObjectUtil.getInheritedClass(inheritedClass);
}
return eventHandlers;
}
}
function inheritEndpoint(
target: Type<any>,
eventHandler: EventHandlerMetadata
): EventHandlerMetadata {
return new EventHandlerMetadata({
...eventHandler,
target,
type: eventHandler.type,
parent: eventHandler
});
}

View File

@ -0,0 +1,6 @@
export enum EventAttachType {
on = 'on',
once = 'once',
handle = 'handle',
handleOnce = 'handleOnce'
}

View File

@ -1,91 +0,0 @@
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
});

View File

@ -1,10 +0,0 @@
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 });
}

View File

@ -1,14 +0,0 @@
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);
}

View File

@ -1,16 +0,0 @@
import {
DIConfiguration,
Injectable,
ProviderScope,
ProviderType
} from '@tsed/di';
@Injectable({
scope: ProviderScope.SINGLETON,
global: true
})
export class AppSettingsService extends DIConfiguration {
constructor() {
super();
}
}

View File

@ -0,0 +1,25 @@
import * as Electron from 'electron';
import { Attacher as AppAttacher } from '../api/app/attacher';
import { Attacher as AutoUpdaterAttacher } from '../api/auto-updater/attacher';
import { Attacher as IpcMainAttacher } from '../api/ipc-main/attacher';
export abstract class Attacher {
static attach(instance: any) {
AppAttacher.attach(instance);
AutoUpdaterAttacher.attach(instance);
IpcMainAttacher.attach(instance);
}
static dettach(instance: any) {
AppAttacher.dettach(instance);
AutoUpdaterAttacher.dettach(instance);
IpcMainAttacher.dettach(instance);
}
static dettachAll() {
AppAttacher.dettachAll();
AutoUpdaterAttacher.dettachAll();
IpcMainAttacher.dettachAll();
}
}

View File

@ -0,0 +1,39 @@
import { ObjectUtil } from '@ucap/core';
export enum LifecycleHooks {
AfterInit,
BeforeDestroy
}
export interface AfterInit {
ucapAfterInit(): void | Promise<void>;
}
export interface BeforeDestroy {
ucapBeforeDestroy(): void | Promise<void>;
}
export const hookNameOf = (lifecycleHook: LifecycleHooks) => {
switch (lifecycleHook) {
case LifecycleHooks.AfterInit:
return 'ucapAfterInit';
case LifecycleHooks.BeforeDestroy:
return 'ucapBeforeDestroy';
}
return undefined;
};
export const getHook = (instance: any, lifecycleHook: LifecycleHooks) => {
const n = hookNameOf(lifecycleHook);
if (!n) {
return undefined;
}
const h = instance[n];
if (!ObjectUtil.isFunction(h)) {
return undefined;
}
return h;
};

View File

@ -0,0 +1,21 @@
import { Attacher } from './attacher';
import {
AfterInit,
BeforeDestroy,
LifecycleHooks,
hookNameOf,
getHook
} from './hooks';
import { Platform } from './platform';
import { ServiceBuilder } from './service-builder';
export {
Attacher,
Platform,
ServiceBuilder,
AfterInit,
BeforeDestroy,
LifecycleHooks,
getHook,
hookNameOf
};

View File

@ -0,0 +1,157 @@
import * as Electron from 'electron';
import { Type, ObjectUtil } from '@ucap/core';
import { AppChannel } from '@ucap/electron-core';
import { Builder as AppBuilder } from '../api/app/builder';
import { AppConfiguration } from '../api/app/models/app-configuration';
import { getAppSettings } from '../api/app/decorators/app-settings';
import { Builder as BrowserWindowBuilder } from '../api/browser-window/builder';
import { ElectronBrowserWindow } from '../api/browser-window/electron-browser-window';
import { ServiceBuilder } from './service-builder';
import { LifecycleHooks, getHook } from './hooks';
import { Attacher } from './attacher';
export const CLASS_NAME = 'ucapClassName';
export class Platform {
protected static instanceMap: Map<string, any> = new Map();
protected static instance: Platform;
protected startedAt = new Date();
protected appConfig: AppConfiguration;
protected _app: any;
static getInstance<T>(typeOfInstance: Type<any>): T {
return Platform.instanceMap.get(Platform.getClassName(typeOfInstance)) as T;
}
static getAppConfig(): AppConfiguration {
return Platform.instance.appConfig;
}
static buildBrowserWindow<T extends ElectronBrowserWindow>(
browserWindowType: Type<T>
): T {
const win = BrowserWindowBuilder.build(
Platform.instance.appConfig,
browserWindowType
);
Platform.setInstance(browserWindowType, win);
Platform.inject(win);
Attacher.attach(win);
return win;
}
static destroyBrowserWindow<T extends ElectronBrowserWindow>(
browserWindow: T
) {
BrowserWindowBuilder.destroy(browserWindow);
Platform.removeInstance(browserWindow);
Attacher.dettach(browserWindow);
}
static async bootstrap(
app: Type<any>,
configuration: Partial<AppConfiguration> = {}
): Promise<Platform> {
const _configuration = getAppSettings(app, configuration);
Platform.instance = new Platform();
Platform.instance.appConfig = _configuration;
Platform.instance._app = AppBuilder.build(app, _configuration);
Platform.setInstance(app, Platform.instance._app);
if (!!_configuration.services && 0 < _configuration.services.length) {
_configuration.services.forEach((serviceType) => {
const i = ServiceBuilder.build(serviceType, _configuration);
Platform.setInstance(serviceType, i);
});
}
Platform.instanceMap.forEach((i, n) => {
Platform.inject(i);
});
let afterInitHook: (...args: any[]) => any;
let afterInitHookReturn: Promise<any> | undefined;
Platform.instanceMap.forEach(async (i, n) => {
afterInitHook = getHook(i, LifecycleHooks.AfterInit);
if (!!afterInitHook) {
afterInitHookReturn = afterInitHook.call(i);
if (afterInitHookReturn instanceof Promise) {
await Promise.resolve(afterInitHookReturn);
}
}
});
Platform.instanceMap.forEach((i, n) => {
Attacher.attach(i);
});
await Platform.instance.runLifecycle();
return Platform.instance;
}
private static getClassName(typeOfInstance: Type<any>): string {
const name = typeOfInstance[CLASS_NAME];
if (!name) {
throw Error(
`name of class is not defined. use static property ${CLASS_NAME}`
);
}
return name;
}
private static setInstance(typeOfInstance: Type<any>, instance: any) {
const name = typeOfInstance[CLASS_NAME];
if (Platform.instanceMap.has(name)) {
throw Error(`duplicated name of class[${name}]`);
}
Platform.instanceMap.set(name, instance);
}
private static removeInstance(instance: any) {
const name = ObjectUtil.getConstructor(instance)[CLASS_NAME];
if (!Platform.instanceMap.has(name)) {
return;
}
Platform.instanceMap.delete(name);
}
private static inject(instance: any) {
Platform.instanceMap.forEach((i, n) => {
instance[n] = i;
});
}
async start(): Promise<void> {}
private async runLifecycle(): Promise<void> {
// tslint:disable-next-line: variable-name
const __this = this;
Electron.app.once(AppChannel.beforeQuit, async () => {
let beforeDestroyHook: (...args: any[]) => any;
let beforeDestroyHookReturn: Promise<any> | undefined;
Platform.instanceMap.forEach(async (i, n) => {
beforeDestroyHook = getHook(i, LifecycleHooks.AfterInit);
if (!!beforeDestroyHook) {
beforeDestroyHookReturn = beforeDestroyHook.call(i);
if (beforeDestroyHookReturn instanceof Promise) {
await Promise.resolve(beforeDestroyHookReturn);
}
}
});
});
}
}

View File

@ -0,0 +1,13 @@
import { Type } from '@ucap/core';
import { AppConfiguration } from '../api/app/models/app-configuration';
export abstract class ServiceBuilder {
static build<T>(
serviceType: Type<T>,
configuration: Partial<AppConfiguration>
): T {
const service = new serviceType(configuration);
return service;
}
}

View File

@ -2,9 +2,10 @@
* Public API Surface of common
*/
export * from './lib/app/decorators/app-settings';
export * from './lib/app/decorators/electron-app';
import * as AppApi from './lib/api/app';
import * as AutoUpdaterApi from './lib/api/auto-updater';
import * as BrowserWindowApi from './lib/api/browser-window';
import * as IpcMainApi from './lib/api/ipc-main';
import * as PlatformApi from './lib/platform';
export * from './lib/app/services/app-settings.service';
export * from './lib/app/app';
export { AppApi, AutoUpdaterApi, BrowserWindowApi, IpcMainApi, PlatformApi };

View File

@ -1,10 +1,9 @@
{
"name": "@ucap/electron-core",
"version": "0.0.1",
"version": "0.0.16",
"publishConfig": {
"registry": "http://10.81.13.221:8081/nexus/repository/npm-ucap/"
"registry": "https://nexus.loafle.net/repository/npm-ucap/"
},
"scripts": {},
"dependencies": {},
"devDependencies": {}
"peerDependencies": {}
}

View File

@ -0,0 +1,36 @@
export enum AppChannel {
accessibilitySupportChanged = 'accessibility-support-changed',
activate = 'activate',
activityWasContinued = 'activity-was-continued',
beforeQuit = 'before-quit',
browserWindowBlur = 'browser-window-blur',
browserWindowCreated = 'browser-window-created',
browserWindowFocus = 'browser-window-focus',
certificateError = 'certificate-error',
continueActivity = 'continue-activity',
continueActivityError = 'continue-activity-error',
desktopCapturerGetSources = 'desktop-capturer-get-sources',
gpuInfoUpdate = 'gpu-info-update',
gpuProcessCrashed = 'gpu-process-crashed',
login = 'login',
newWindowForTab = 'new-window-for-tab',
openFile = 'open-file',
openUrl = 'open-url',
quit = 'quit',
ready = 'ready',
remoteGetBuiltin = 'remote-get-builtin',
remoteGetCurrentWebContents = 'remote-get-current-web-contents',
remoteGetCurrentWindow = 'remote-get-current-window',
remoteGetGlobal = 'remote-get-global',
remoteRequire = 'remote-require',
rendererProcessCrashed = 'renderer-process-crashed',
secondInstance = 'second-instance',
selectClientCertificate = 'select-client-certificate',
sessionCreated = 'session-created',
updateActivityState = 'update-activity-state',
webContentsCreated = 'web-contents-created',
willContinueActivity = 'will-continue-activity',
willFinishLaunching = 'will-finish-launching',
willQuit = 'will-quit',
windowAllClosed = 'window-all-closed'
}

View File

@ -0,0 +1,11 @@
export enum AutoUpdaterChannel {
beforeQuitForUpdate = 'before-quit-for-update',
checkingForUpdate = 'checking-for-update',
downloadProgress = 'download-progress',
error = 'error',
login = 'login',
updateAvailable = 'update-available',
updateCancelled = 'update-cancelled',
updateDownloaded = 'update-downloaded',
updateNotAvailable = 'update-not-available'
}

View File

@ -0,0 +1,36 @@
export enum BrowserWindowChannel {
alwaysOnTopChanged = 'always-on-top-changed',
appCommand = 'app-command',
blur = 'blur',
close = 'close',
closed = 'closed',
enterFullScreen = 'enter-full-screen',
enterHtmlFullScreen = 'enter-html-full-screen',
focus = 'focus',
hide = 'hide',
leaveFullScreen = 'leave-full-screen',
leaveHtmlFullScreen = 'leave-html-full-screen',
maximize = 'maximize',
minimize = 'minimize',
move = 'move',
moved = 'moved',
newWindowForTab = 'new-window-for-tab',
pageTitleUpdated = 'page-title-updated',
readyToShow = 'ready-to-show',
resize = 'resize',
responsive = 'responsive',
restore = 'restore',
rotateGesture = 'rotate-gesture',
scrollTouchBegin = 'scroll-touch-begin',
scrollTouchEdge = 'scroll-touch-edge',
scrollTouchEnd = 'scroll-touch-end',
sessionEnd = 'session-end',
sheetBegin = 'sheet-begin',
sheetEnd = 'sheet-end',
show = 'show',
swipe = 'swipe',
unmaximize = 'unmaximize',
unresponsive = 'unresponsive',
willMove = 'will-move',
willResize = 'will-resize'
}

View File

@ -1,39 +0,0 @@
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'
}

View File

@ -0,0 +1,57 @@
export enum WebContentsChannel {
beforeInputEvent = 'before-input-event',
certificateError = 'certificate-error',
consoleMessage = 'console-message',
contextMenu = 'context-menu',
crashed = 'crashed',
cursorChanged = 'cursor-changed',
desktopCapturerGetSources = 'desktop-capturer-get-sources',
destroyed = 'destroyed',
devtoolsClosed = 'devtools-closed',
devtoolsFocused = 'devtools-focused',
devtoolsOpened = 'devtools-opened',
devtoolsReloadPage = 'devtools-reload-page',
didAttachWebview = 'did-attach-webview',
didChangeThemeColor = 'did-change-theme-color',
didFailLoad = 'did-fail-load',
didFailProvisionalLoad = 'did-fail-provisional-load',
didFinishLoad = 'did-finish-load',
didFrameFinishLoad = 'did-frame-finish-load',
didFrameNavigate = 'did-frame-navigate',
didNavigate = 'did-navigate',
didNavigateInPage = 'did-navigate-in-page',
didRedirectNavigation = 'did-redirect-navigation',
didStartLoading = 'did-start-loading',
didStartNavigation = 'did-start-navigation',
didStopLoading = 'did-stop-loading',
domReady = 'dom-ready',
enterHtmlFullScreen = 'enter-html-full-screen',
foundInPage = 'found-in-page',
ipcMessage = 'ipc-message',
ipcMessageSync = 'ipc-message-sync',
leaveHtmlFullScreen = 'leave-html-full-screen',
login = 'login',
mediaPaused = 'media-paused',
mediaStartedPlaying = 'media-started-playing',
newWindow = 'new-window',
pageFaviconUpdated = 'page-favicon-updated',
pageTitleUpdated = 'page-title-updated',
paint = 'paint',
pluginCrashed = 'plugin-crashed',
preloadError = 'preload-error',
remoteGetBuiltin = 'remote-get-builtin',
remoteGetCurrentWebContents = 'remote-get-current-web-contents',
remoteGetCurrentWindow = 'remote-get-current-window',
remoteGetGlobal = 'remote-get-global',
remoteRequire = 'remote-require',
responsive = 'responsive',
selectBluetoothDevice = 'select-bluetooth-device',
selectClientCertificate = 'select-client-certificate',
unresponsive = 'unresponsive',
updateTargetUrl = 'update-target-url',
willAttachWebview = 'will-attach-webview',
willNavigate = 'will-navigate',
willPreventUnload = 'will-prevent-unload',
willRedirect = 'will-redirect',
zoomChanged = 'zoom-changed'
}

View File

@ -0,0 +1,22 @@
import * as Electron from 'electron';
import { PlatformUtil } from './platform.util';
export class BrowserWindowUtil {
static all(): Electron.BrowserWindow[] | undefined {
if (PlatformUtil.isRenderer()) {
return Electron.remote.BrowserWindow.getAllWindows();
} else {
return Electron.BrowserWindow.getAllWindows();
}
}
static main(): Electron.BrowserWindow | undefined {
const browserWindows: Electron.BrowserWindow[] = BrowserWindowUtil.all();
if (!browserWindows || 0 === browserWindows.length) {
return undefined;
}
return browserWindows[browserWindows.length - 1];
}
}

View File

@ -0,0 +1,192 @@
import * as path from 'path';
import * as fse from 'fs-extra';
export interface UniqueFileNameOption {
separator?: string;
mode?:
| 'numeric'
| 'alpha'
| 'ALPHA'
| 'alphanumeric'
| 'ALPHANUMERIC'
| 'charset';
paddingCharacter?: string;
paddingSize?: number;
alwaysAppend?: boolean;
charset?: string;
}
const charsets = {
alpha: 'abcdefghijklmnopqrstuvwxyz',
alphanumeric: '0123456789abcdefghijklmnopqrstuvwxyz',
ALPHA: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
ALPHANUMERIC: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
};
interface UniqueFile {
dir?: string;
ext?: string;
base?: string;
increment?: number;
}
export class FileUtil {
static blobToBuffer(blob: Blob): Promise<Buffer> {
if (typeof Blob === 'undefined' || !(blob instanceof Blob)) {
throw new Error('first argument must be a Blob');
}
return new Promise<Buffer>((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => {
resolve(Buffer.from(reader.result as ArrayBuffer));
};
reader.onerror = () => {
reader.abort();
reject(reader.error);
};
reader.readAsArrayBuffer(blob);
});
}
static uniqueFileName(
filePath: string,
options?: UniqueFileNameOption
): Promise<string> {
return new Promise<string>((resolve, reject) => {
const dir = path.dirname(filePath);
const ext = path.extname(filePath);
const base = path.basename(filePath, ext);
const uniqueFile: UniqueFile = {
dir,
ext,
base
};
options = options || {};
options.separator = options.separator || '-';
options.mode = options.mode || 'numeric';
if ('numeric' !== options.mode) {
if (charsets[options.mode]) {
options.charset = charsets[options.mode];
options.mode = 'charset';
} else if (
'charset' !== options.mode ||
('charset' === options.mode && !options.charset)
) {
options.mode = 'numeric';
}
}
if (options.paddingSize && !options.paddingCharacter) {
options.paddingCharacter = '0';
}
FileUtil.uniqueFileNameProcess(
uniqueFile,
options,
(fileName: string) => {
resolve(fileName);
}
);
});
}
private static uniqueFileNameProcess(
uniqueFile: UniqueFile,
options: UniqueFileNameOption,
callback: (fileName: string) => void
) {
let fileName: string;
let append = '';
if (options.alwaysAppend && !uniqueFile.increment) {
uniqueFile.increment = 1;
}
if (uniqueFile.increment) {
if ('numeric' === options.mode) {
append = '' + uniqueFile.increment;
} else {
append = FileUtil.numberToString(uniqueFile.increment, options.charset);
}
if (options.paddingSize) {
while (append.length < options.paddingSize) {
append = options.paddingCharacter + append;
}
}
append = options.separator + append;
}
fileName = path.join(
uniqueFile.dir,
uniqueFile.base + append + uniqueFile.ext
);
if (fse.existsSync(fileName)) {
if (uniqueFile.increment) {
uniqueFile.increment += 1;
} else {
uniqueFile.increment = 'numeric' === options.mode ? 2 : 1;
}
return FileUtil.uniqueFileNameProcess(uniqueFile, options, callback);
} else {
return callback(fileName);
}
}
private static numberToString(nbr: number, charset: string) {
const charsetLen = charset.length;
let strLen = 0;
let strThisLen = 0;
let tmp: number;
for (let maxpower = 20; maxpower >= 0; maxpower--) {
const maxvalue = FileUtil.sumOfPowerFromOne(charsetLen, maxpower);
if (maxvalue < nbr) {
strLen = maxpower + 1;
strThisLen = maxvalue + Math.pow(charsetLen, maxpower + 1) - maxvalue;
break;
}
}
if (0 === strLen) {
return null;
}
let str = '';
while (--strLen >= 0) {
if (strLen === 0) {
str += charset.charAt(nbr - 1);
break;
}
strThisLen = Math.pow(charsetLen, strLen);
const initial = FileUtil.sumOfPowerFromOne(charsetLen, strLen - 1);
for (tmp = charsetLen; tmp >= 1; tmp--) {
if (initial + tmp * strThisLen < nbr) {
break;
}
}
nbr -= tmp * strThisLen;
str += charset.charAt(tmp - 1);
}
return str;
}
private static sumOfPowerFromOne(base: number, maxpower: number) {
let value = 0;
for (let tmp = maxpower; tmp >= 1; tmp--) {
value += Math.pow(base, tmp);
}
return value;
}
}

View File

@ -0,0 +1,65 @@
import * as path from 'path';
export const NODE_ENV = 'NODE_ENV';
export class PlatformUtil {
static isRenderer() {
return !!process && !!process.type && 'renderer' === process.type;
}
static isProduction() {
return (
!!process &&
!!process.env &&
!!process.env[NODE_ENV] &&
'production' === process.env[NODE_ENV]
);
}
static isPackaged() {
if (
!!process.mainModule &&
-1 !== process.mainModule.filename.indexOf('app.asar')
) {
return true;
} else if (
0 < process.argv.filter((a) => -1 !== a.indexOf('app.asar')).length
) {
return true;
}
return false;
}
static isWindows() {
return !!process && !!process.platform && 'win32' === process.platform;
}
static isDarwin() {
return !!process && !!process.platform && 'darwin' === process.platform;
}
static isLinux() {
return !!process && !!process.platform && 'linux' === process.platform;
}
static rootPath(basePath: string = './') {
if (PlatformUtil.isPackaged()) {
if (PlatformUtil.isWindows()) {
return path.join(basePath, '..');
} else {
return path.join(basePath, '..');
}
} else if (PlatformUtil.isProduction()) {
if (PlatformUtil.isRenderer()) {
return path.join(basePath, '..');
} else if (!module.parent) {
return path.join(basePath, '..');
} else {
return path.join(basePath, '..');
}
} else if (PlatformUtil.isRenderer()) {
return path.join(basePath, '..');
} else {
return path.join(basePath, '..');
}
}
}

View File

@ -2,4 +2,11 @@
* Public API Surface of core
*/
export * from './lib/types/channel.type';
export * from './lib/types/app-channel.type';
export * from './lib/types/auto-updater-channel.type';
export * from './lib/types/browser-window.type';
export * from './lib/types/web-contents-channel.type';
export * from './lib/utils/browser-window.util';
export * from './lib/utils/file.util';
export * from './lib/utils/platform.util';

View File

@ -0,0 +1,14 @@
{
"name": "@ucap/electron-i18n",
"version": "0.0.1",
"publishConfig": {
"registry": "https://nexus.loafle.net/repository/npm-ucap/"
},
"scripts": {},
"peerDependencies": {
"electron": "^9.0.3",
"electron-log": "^4.2.1",
"i18next": "^19.3.2",
"tslib": "^1.10.0"
}
}

View File

@ -0,0 +1 @@
export class I18nService {}

View File

@ -0,0 +1,7 @@
/*
* Public API Surface of electron-native
*/
export * from './lib/types/channel.type';
export * from './lib/services/electron-native.service';

View 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"]
}

View File

@ -0,0 +1,3 @@
{
"extends": "./tsconfig.lib.json"
}

View 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"]
}

View File

@ -0,0 +1,3 @@
{
"extends": "../../tslint.json"
}

View File

@ -0,0 +1,7 @@
{
"dest": "../../dist/native",
"docDest": "../../docs/native",
"lib": {
"entryFile": "src/public-api.ts"
}
}

View File

@ -0,0 +1,13 @@
{
"name": "@ucap/electron-logger",
"version": "0.0.1",
"publishConfig": {
"registry": "https://nexus.loafle.net/repository/npm-ucap/"
},
"scripts": {},
"peerDependencies": {
"electron": "^9.0.3",
"electron-log": "^4.2.1",
"tslib": "^1.10.0"
}
}

View File

@ -0,0 +1,49 @@
import { LogAppender, LogEntry, LogLevel } from '@ucap/logger';
import electron_log from 'electron-log';
export class ElectronLogAppender extends LogAppender {
constructor() {
super();
}
log(entry: LogEntry): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => {
try {
switch (entry.level) {
case LogLevel.Trace:
electron_log.silly(...entry.args);
break;
case LogLevel.Debug:
electron_log.debug(...entry.args);
break;
case LogLevel.Info:
electron_log.verbose(...entry.args);
break;
case LogLevel.Warn:
electron_log.info(...entry.args);
break;
case LogLevel.Error:
electron_log.warn(...entry.args);
break;
case LogLevel.Fatal:
electron_log.error(...entry.args);
break;
}
resolve();
} catch (error) {
reject(error);
}
});
}
clear(): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => {
try {
resolve();
} catch (error) {
reject(error);
}
});
}
}

View File

@ -0,0 +1,5 @@
/*
* Public API Surface of electron-logger
*/
export * from './lib/appenders/electron-log.appender';

View File

@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../out-tsc/lib",
"target": "es2015",
"declaration": true,
"inlineSources": true,
"types": ["node"],
"lib": ["dom", "es2018"]
},
"exclude": ["src/test.ts", "**/*.spec.ts"]
}

View File

@ -0,0 +1,3 @@
{
"extends": "./tsconfig.lib.json"
}

View 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"]
}

View File

@ -0,0 +1,3 @@
{
"extends": "../../tslint.json"
}

View File

@ -0,0 +1,7 @@
{
"dest": "../../dist/logger",
"docDest": "../../docs/logger",
"lib": {
"entryFile": "src/public-api.ts"
}
}

View File

@ -0,0 +1,20 @@
{
"name": "@ucap/electron-native",
"version": "0.0.19",
"publishConfig": {
"registry": "https://nexus.loafle.net/repository/npm-ucap/"
},
"scripts": {},
"peerDependencies": {
"electron": "^9.0.3",
"electron-log": "^4.2.1",
"@ucap/core": "~0.0.1",
"@ucap/domain-common": "~0.0.1",
"@ucap/domain-organization": "~0.0.1",
"@ucap/domain-status": "~0.0.1",
"@ucap/native": "~0.0.1",
"axios": "^0.19.2",
"rxjs": "~6.5.4",
"tslib": "^1.10.0"
}
}

View File

@ -0,0 +1,717 @@
import { Subject, BehaviorSubject } from 'rxjs';
import { AxiosInstance } from 'axios';
import * as Electron from 'electron';
import { StatusCode } from '@ucap/domain-status';
import {
NativeService,
UpdateCheckConfig,
UpdateInfo,
NativeType,
WindowState,
WindowIdle,
NotificationRequest,
AppInitInfo
} from '@ucap/native';
import {
PlatformChannel,
FileChannel,
WindowChannel,
IdleChannel,
AppChannel,
ChatChannel,
MessageChannel
} from '../types/channel.type';
const listenersOfChannel = [
WindowChannel.onState$,
WindowChannel.onFocus$,
IdleChannel.onState$,
AppChannel.onUpdate$,
AppChannel.onLogout$,
AppChannel.onStatus$,
AppChannel.onShowSetting$,
AppChannel.onExit$,
ChatChannel.onOpen$,
MessageChannel.onOpen$
];
export class ElectronNativeService extends NativeService {
private ipcRenderer: typeof Electron.ipcRenderer;
private webFrame: typeof Electron.webFrame;
private remote: typeof Electron.remote;
private shell: typeof Electron.shell;
private platform: string;
constructor(private axios: AxiosInstance) {
super();
this.ipcRenderer = (window as any).require('electron').ipcRenderer;
this.remote = (window as any).require('electron').remote;
this.shell = (window as any).require('electron').shell;
this.webFrame = (window as any).require('electron').webFrame;
this.platform = (window as any).require('os').platform();
}
platform_nativeType(): Promise<NativeType> {
return new Promise<any>(async (resolve, reject) => {
try {
resolve(NativeType.Electron);
} catch (error) {
reject(error);
}
});
}
platform_networkInfo(): Promise<any> {
return new Promise<any>(async (resolve, reject) => {
try {
const networkInfo = await this.ipcRenderer.invoke(
PlatformChannel.networkInfo
);
resolve(networkInfo);
} catch (error) {
reject(error);
}
});
}
platform_execute(executableName: string): Promise<number> {
return new Promise<number>(async (resolve, reject) => {
try {
const id = await this.ipcRenderer.invoke(
PlatformChannel.execute,
executableName
);
resolve(id);
} catch (error) {
reject(error);
}
});
}
platform_openDefaultBrowser(
url: string,
options?: {
name?: string;
features?: string;
replace?: boolean;
}
): Promise<void> {
return new Promise<void>(async (resolve, reject) => {
try {
await this.ipcRenderer.invoke(
PlatformChannel.openDefaultBrowser,
url,
options
);
resolve();
} catch (error) {
reject(error);
}
});
}
platform_readFromClipboard(): Promise<{
text?: string;
rtf?: string;
html?: string;
image?: Buffer;
imageDataUrl?: string;
}> {
return new Promise<{
text?: string;
rtf?: string;
html?: string;
image?: Buffer;
imageDataUrl?: string;
}>(async (resolve, reject) => {
try {
const data = await this.ipcRenderer.invoke(
PlatformChannel.readFromClipboard
);
resolve(data);
} catch (error) {
reject(error);
}
});
}
file_save(
buffer: Buffer,
fileName: string,
mimeType: string,
path?: string
): Promise<string> {
return new Promise<string>(async (resolve, reject) => {
try {
const data = await this.ipcRenderer.invoke(
FileChannel.save,
buffer,
fileName,
mimeType,
path
);
resolve(data);
} catch (error) {
reject(error);
}
});
}
file_read(path: string): Promise<Buffer> {
return new Promise<Buffer>(async (resolve, reject) => {
try {
const data = await this.ipcRenderer.invoke(FileChannel.read, path);
resolve(data);
} catch (error) {
reject(error);
}
});
}
file_openFolder(folderPath?: string, make?: boolean): Promise<boolean> {
return new Promise<boolean>(async (resolve, reject) => {
try {
const data = await this.ipcRenderer.invoke(
FileChannel.openFolder,
folderPath,
make
);
resolve(data);
} catch (error) {
reject(error);
}
});
}
file_openItem(filePath?: string): Promise<boolean> {
return new Promise<boolean>(async (resolve, reject) => {
try {
const data = await this.ipcRenderer.invoke(
FileChannel.openItem,
filePath
);
resolve(data);
} catch (error) {
reject(error);
}
});
}
file_path(
name: 'home' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos',
...appendPaths: string[]
): Promise<string> {
return new Promise<string>(async (resolve, reject) => {
try {
const data = await this.ipcRenderer.invoke(
FileChannel.path,
name,
appendPaths
);
resolve(data);
} catch (error) {
reject(error);
}
});
}
file_selectForOpen(option: {
title?: string;
defaultPath?: string;
filters?: {
extensions: string[];
name: string;
}[];
properties?: Array<
| 'openFile'
| 'openDirectory'
| 'multiSelections'
| 'showHiddenFiles'
| 'createDirectory'
| 'promptToCreate'
| 'noResolveAliases'
| 'treatPackageAsDirectory'
>;
message?: string;
}): Promise<string> {
return new Promise<string>(async (resolve, reject) => {
try {
const data = await this.ipcRenderer.invoke(
FileChannel.selectForOpen,
option
);
resolve(data);
} catch (error) {
reject(error);
}
});
}
file_selectForSave(option: {
title?: string;
defaultPath?: string;
filters?: {
extensions: string[];
name: string;
}[];
message?: string;
}): Promise<{
canceled: boolean;
filePath: string;
}> {
return new Promise<{
canceled: boolean;
filePath: string;
}>(async (resolve, reject) => {
try {
const data = await this.ipcRenderer.invoke(
FileChannel.selectForSave,
option
);
resolve(data);
} catch (error) {
reject(error);
}
});
}
window_state(): Promise<WindowState> {
return new Promise<WindowState>(async (resolve, reject) => {
try {
const data = await this.ipcRenderer.invoke(WindowChannel.state);
resolve(data);
} catch (error) {
reject(error);
}
});
}
window_onState$(): BehaviorSubject<WindowState> {
if (!this._window_onStateSubject) {
this.ipcRenderer.on(
WindowChannel.onState$,
(event: any, state: WindowState) => {
if (!!this._window_onStateSubject) {
this._window_onStateSubject.next(state);
}
}
);
this.window_state().then((state) => {
this._window_onStateSubject.next(state);
});
}
return super.window_onState$();
}
window_focused(): Promise<boolean> {
return new Promise<boolean>(async (resolve, reject) => {
try {
const data = await this.ipcRenderer.invoke(WindowChannel.focused);
resolve(data);
} catch (error) {
reject(error);
}
});
}
window_onFocus$(): BehaviorSubject<boolean> {
if (!this._window_onFocusSubject) {
this.ipcRenderer.on(
WindowChannel.onFocus$,
(event: any, focused: boolean) => {
if (!!this._window_onFocusSubject) {
this._window_onFocusSubject.next(focused);
}
}
);
this.window_focused().then((focused) => {
this._window_onFocusSubject.next(focused);
});
}
return super.window_onFocus$();
}
window_close(): Promise<void> {
return new Promise<void>(async (resolve, reject) => {
try {
const currentWindow = this.remote.getCurrentWindow();
if (!!currentWindow) {
currentWindow.hide();
}
resolve();
} catch (error) {
reject(error);
}
});
}
window_minimize(): Promise<void> {
return new Promise<void>(async (resolve, reject) => {
try {
const currentWindow = this.remote.getCurrentWindow();
if (!!currentWindow) {
currentWindow.minimize();
}
resolve();
} catch (error) {
reject(error);
}
});
}
window_maximize(altKey: boolean = false): Promise<void> {
return new Promise<void>(async (resolve, reject) => {
try {
const currentWindow = this.remote.getCurrentWindow();
if (!!currentWindow) {
if ('darwin' === this.platform) {
if (altKey) {
if (currentWindow.isMaximizable()) {
if (currentWindow.isMaximized()) {
currentWindow.unmaximize();
} else {
currentWindow.maximize();
}
}
} else {
currentWindow.setFullScreen(!currentWindow.isFullScreen());
}
} else {
if (currentWindow.isMaximized()) {
currentWindow.unmaximize();
} else {
currentWindow.maximize();
}
}
}
resolve();
} catch (error) {
reject(error);
}
});
}
idle_startCheck(limitTime: number): Promise<void> {
return new Promise<void>(async (resolve, reject) => {
try {
const started = await this.ipcRenderer.invoke(IdleChannel.started);
if (started) {
resolve();
return;
}
await this.ipcRenderer.invoke(IdleChannel.startCheck, limitTime);
this.ipcRenderer.on(
IdleChannel.onState$,
this._idle_onState$Callback.bind(this)
);
resolve();
} catch (error) {
reject(error);
}
});
}
_idle_onState$Callback(event: any, idleState: WindowIdle) {
if (!!this._idle_onStateSubject) {
this._idle_onStateSubject.next(idleState);
}
}
idle_onState$(): BehaviorSubject<WindowIdle> {
return super.idle_onState$();
}
idle_stopCheck(): Promise<void> {
return new Promise<void>(async (resolve, reject) => {
try {
const started = await this.ipcRenderer.invoke(IdleChannel.started);
if (!started) {
resolve();
return;
}
await this.ipcRenderer.invoke(IdleChannel.stopCheck);
this.ipcRenderer.off(
IdleChannel.onState$,
this._idle_onState$Callback.bind(this)
);
resolve();
} catch (error) {
reject(error);
}
});
}
idle_changeLimitTime(limitTime: number): Promise<void> {
return new Promise<void>(async (resolve, reject) => {
try {
await this.ipcRenderer.invoke(IdleChannel.changeLimitTime, limitTime);
resolve();
} catch (error) {
reject(error);
}
});
}
app_version(): Promise<string> {
return new Promise<any>(async (resolve, reject) => {
try {
const data = await this.ipcRenderer.invoke(AppChannel.version);
resolve(data);
} catch (error) {
reject(error);
}
});
}
app_postInit(): Promise<AppInitInfo> {
return new Promise<AppInitInfo>(async (resolve, reject) => {
try {
const data = await this.ipcRenderer.invoke(AppChannel.postInit);
resolve(data);
} catch (error) {
reject(error);
}
});
}
app_postLogin(): Promise<void> {
return new Promise<void>(async (resolve, reject) => {
try {
await this.ipcRenderer.invoke(AppChannel.postLogin);
resolve();
} catch (error) {
reject(error);
}
});
}
app_postLogout(): Promise<void> {
return new Promise<void>(async (resolve, reject) => {
try {
await this.ipcRenderer.invoke(AppChannel.postLogout);
resolve();
} catch (error) {
reject(error);
}
});
}
app_postDestroy(): Promise<void> {
return new Promise<void>(async (resolve, reject) => {
try {
await this.ipcRenderer.invoke(AppChannel.postDestroy);
for (const channel of listenersOfChannel) {
this.ipcRenderer.removeAllListeners(channel);
}
resolve();
} catch (error) {
reject(error);
}
});
}
app_changeAutoLaunch(autoLaunch: boolean): Promise<boolean> {
return new Promise<boolean>(async (resolve, reject) => {
try {
const data = await this.ipcRenderer.invoke(
AppChannel.changeAutoLaunch,
autoLaunch
);
resolve(data);
} catch (error) {
reject(error);
}
});
}
app_showNotify(req: NotificationRequest): Promise<void> {
return new Promise<void>(async (resolve, reject) => {
try {
await this.ipcRenderer.invoke(AppChannel.showNotify, req);
resolve();
} catch (error) {
reject(error);
}
});
}
app_closeAllNotify(): Promise<void> {
return new Promise<void>(async (resolve, reject) => {
try {
await this.ipcRenderer.invoke(AppChannel.closeAllNotify);
resolve();
} catch (error) {
reject(error);
}
});
}
app_checkForUpdates(currentVersion: string): Promise<void> {
return new Promise<void>(async (resolve, reject) => {
try {
await this.ipcRenderer.invoke(
AppChannel.checkForUpdates,
currentVersion
);
resolve();
} catch (error) {
reject(error);
}
});
}
app_applyInstantUpdates(): Promise<void> {
return new Promise<void>(async (resolve, reject) => {
try {
await this.ipcRenderer.invoke(AppChannel.applyInstantUpdates);
resolve();
} catch (error) {
reject(error);
}
});
}
app_startCheckForUpdate(config: UpdateCheckConfig): Promise<void> {
return new Promise<void>(async (resolve, reject) => {
try {
const started = await this.ipcRenderer.invoke(
AppChannel.startedCheckForUpdate
);
if (started) {
resolve();
return;
}
await this.ipcRenderer.invoke(AppChannel.startCheckForUpdate, config);
this.ipcRenderer.on(
AppChannel.onUpdate$,
this._app_onUpdate$Callback.bind(this)
);
resolve();
} catch (error) {
reject(error);
}
});
}
_app_onUpdate$Callback(event: any, info: UpdateInfo) {
if (!!this._app_onUpdateSubject) {
this._app_onUpdateSubject.next(info);
}
}
app_onUpdate$(): BehaviorSubject<UpdateInfo> {
return super.app_onUpdate$();
}
app_stopCheckForUpdate(): Promise<void> {
return new Promise<void>(async (resolve, reject) => {
try {
const started = await this.ipcRenderer.invoke(
AppChannel.startedCheckForUpdate
);
if (!started) {
resolve();
return;
}
await this.ipcRenderer.invoke(AppChannel.stopCheckForUpdate);
this.ipcRenderer.off(
AppChannel.onUpdate$,
this._app_onUpdate$Callback.bind(this)
);
resolve();
} catch (error) {
reject(error);
}
});
}
app_exit(): Promise<void> {
return new Promise<void>(async (resolve, reject) => {
try {
await this.ipcRenderer.invoke(AppChannel.exit);
resolve();
} catch (error) {
reject(error);
}
});
}
app_onLogout$(): Subject<void> {
if (!this._app_onLogoutSubject) {
this.ipcRenderer.on(AppChannel.onLogout$, (event: any) => {
if (!!this._app_onLogoutSubject) {
this._app_onLogoutSubject.next();
}
});
}
return super.app_onLogout$();
}
app_onStatus$(): BehaviorSubject<StatusCode> {
if (!this._app_onStatusSubject) {
this.ipcRenderer.on(
AppChannel.onStatus$,
(event: any, code: StatusCode) => {
if (!!this._app_onStatusSubject) {
this._app_onStatusSubject.next(code);
}
}
);
}
return super.app_onStatus$();
}
app_onShowSetting$(): Subject<void> {
if (!this._app_onShowSettingSubject) {
this.ipcRenderer.on(AppChannel.onShowSetting$, (event: any) => {
if (!!this._app_onShowSettingSubject) {
this._app_onShowSettingSubject.next();
}
});
}
return super.app_onShowSetting$();
}
app_onExit$(): Subject<void> {
if (!this._app_onExitSubject) {
this.ipcRenderer.on(AppChannel.onExit$, (event: any) => {
if (!!this._app_onExitSubject) {
this._app_onExitSubject.next();
}
});
}
return super.app_onExit$();
}
chat_openRoom(openUrl: string): Promise<void> {
return new Promise<void>(async (resolve, reject) => {
try {
await this.ipcRenderer.invoke(ChatChannel.openRoom, openUrl);
resolve();
} catch (error) {
reject(error);
}
});
}
chat_onOpen$(): Subject<string> {
if (!this._chat_onOpenSubject) {
this.ipcRenderer.on(ChatChannel.onOpen$, (event: any, seq: string) => {
if (!!this._chat_onOpenSubject) {
this._chat_onOpenSubject.next(seq);
}
});
}
return super.chat_onOpen$();
}
message_onOpen$(): Subject<string> {
if (!this._message_onOpenSubject) {
this.ipcRenderer.on(MessageChannel.onOpen$, (event: any, seq: string) => {
if (!!this._message_onOpenSubject) {
this._message_onOpenSubject.next(seq);
}
});
}
return super.message_onOpen$();
}
}

View File

@ -0,0 +1,65 @@
export enum PlatformChannel {
networkInfo = 'ucap::native::platform::networkInfo',
execute = 'ucap::native::platform::execute',
openDefaultBrowser = 'ucap::native::platform::openDefaultBrowser',
readFromClipboard = 'ucap::native::platform::readFromClipboard'
}
export enum FileChannel {
save = 'ucap::native::file::save',
read = 'ucap::native::file::read',
openFolder = 'ucap::native::file::openFolder',
openItem = 'ucap::native::file::openItem',
path = 'ucap::native::file::path',
selectForOpen = 'ucap::native::file::selectForOpen',
selectForSave = 'ucap::native::file::selectForSave'
}
export enum WindowChannel {
close = 'ucap::native::window::close',
minimize = 'ucap::native::window::minimize',
maximize = 'ucap::native::window::maximize',
state = 'ucap::native::window::state',
onState$ = 'ucap::native::window::onState$',
focused = 'ucap::native::window::focused',
onFocus$ = 'ucap::native::window::onFocus$'
}
export enum IdleChannel {
started = 'ucap::native::idle::started',
startCheck = 'ucap::native::idle::startCheck',
onState$ = 'ucap::native::idle::onState$',
stopCheck = 'ucap::native::idle::stopCheck',
changeLimitTime = 'ucap::native::idle::changeLimitTime'
}
export enum AppChannel {
version = 'ucap::native::app::version',
postInit = 'ucap::native::app::postInit',
postLogin = 'ucap::native::app::postLogin',
postLogout = 'ucap::native::app::postLogout',
postDestroy = 'ucap::native::app::postDestroy',
changeAutoLaunch = 'ucap::native::app::changeAutoLaunch',
showNotify = 'ucap::native::app::showNotify',
closeAllNotify = 'ucap::native::app::closeAllNotify',
checkForUpdates = 'ucap::native::app::checkForUpdates',
applyInstantUpdates = 'ucap::native::app::applyInstantUpdates',
startedCheckForUpdate = 'ucap::native::app::startedCheckForUpdate',
startCheckForUpdate = 'ucap::native::app::startCheckForUpdate',
stopCheckForUpdate = 'ucap::native::app::stopCheckForUpdate',
onUpdate$ = 'ucap::native::app::onUpdate$',
exit = 'ucap::native::app::exit',
onLogout$ = 'ucap::native::app::onLogout$',
onStatus$ = 'ucap::native::app::onStatus$',
onShowSetting$ = 'ucap::native::app::onShowSetting$',
onExit$ = 'ucap::native::app::onExit$'
}
export enum ChatChannel {
openRoom = 'ucap::native::chat::openRoom',
onOpen$ = 'ucap::native::chat::onOpen$'
}
export enum MessageChannel {
onOpen$ = 'ucap::native::message::onOpen$'
}

View File

@ -0,0 +1,7 @@
/*
* Public API Surface of electron-native
*/
export * from './lib/types/channel.type';
export * from './lib/services/electron-native.service';

View 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"]
}

View File

@ -0,0 +1,3 @@
{
"extends": "./tsconfig.lib.json"
}

View 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"]
}

View File

@ -0,0 +1,3 @@
{
"extends": "../../tslint.json"
}

View File

@ -0,0 +1,7 @@
{
"dest": "../../dist/native",
"docDest": "../../docs/native",
"lib": {
"entryFile": "src/public-api.ts"
}
}

View File

@ -1,15 +1,15 @@
{
"name": "@ucap/electron-notify-window",
"version": "0.0.1",
"version": "0.0.13",
"publishConfig": {
"registry": "http://10.81.13.221:8081/nexus/repository/npm-ucap/"
"registry": "https://nexus.loafle.net/repository/npm-ucap/"
},
"scripts": {},
"dependencies": {
"peerDependencies": {
"@ucap/electron-core": "~0.0.1",
"electron": "^8.0.0",
"electron-log": "^4.1.0",
"fs-extra": "^8.1.0",
"electron": "^9.0.3",
"electron-log": "^4.2.1",
"fs-extra": "^9.0.1",
"rxjs": "^6.5.4"
},
"devDependencies": {}

View File

@ -1,5 +1,5 @@
import * as path from 'path';
import { BrowserWindowConstructorOptions } from 'electron';
import * as Electron from 'electron';
export interface NotifyWindowOptions {
width?: number;
@ -32,7 +32,7 @@ export interface NotifyWindowOptions {
defaultStyleText?: {
[attribute: string]: any;
};
defaultWindow?: BrowserWindowConstructorOptions;
defaultWindow?: Electron.BrowserWindowConstructorOptions;
templatePath?: string;
htmlTemplate?: string;
}

View File

@ -1,5 +1,6 @@
import url from 'url';
import fse from 'fs-extra';
import * as Electron from 'electron';
import log from 'electron-log';
import { WebContentsChannel } from '@ucap/electron-core';
@ -9,7 +10,6 @@ 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';
@ -28,18 +28,18 @@ interface ENDimension {
}
class BrowserWindowPooler {
private readonly inactiveWindows: BrowserWindow[];
private readonly inactiveWindows: Electron.BrowserWindow[];
constructor(private readonly minSize: number) {
this.minSize = 0 > this.minSize ? 0 : this.minSize;
this.inactiveWindows = [];
}
getLength() {
get length() {
return this.inactiveWindows.length;
}
push(...items: BrowserWindow[]): number {
push(...items: Electron.BrowserWindow[]): number {
const length = this.inactiveWindows.push(...items);
if (this.minSize < length) {
@ -48,7 +48,7 @@ class BrowserWindowPooler {
return length;
}
pop(): BrowserWindow {
pop(): Electron.BrowserWindow {
if (!this.inactiveWindows || 0 === this.inactiveWindows.length) {
return undefined;
}
@ -80,7 +80,7 @@ export class NotifyWindowService {
private firstPosition: ENPoint;
private lowerRightCornerPosition: ENPoint;
private maxVisibleNotifications: number;
private activeNotifications: BrowserWindow[];
private activeNotifications: Electron.BrowserWindow[];
private browserWindowPooler: BrowserWindowPooler;
private notificationQueue: NotifyWindow[];
@ -103,7 +103,7 @@ export class NotifyWindowService {
this.setupEvents();
}
setOptions(options: NotifyWindowOptions) {
set options(options: NotifyWindowOptions) {
if (!!options) {
this.customOptions = {
...this.customOptions,
@ -113,18 +113,18 @@ export class NotifyWindowService {
this.calcDimensions();
}
getOptions(): NotifyWindowOptions {
get options(): NotifyWindowOptions {
return this.customOptions;
}
setTemplatePath(templatePath: string) {
set templatePath(templatePath: string) {
if (!!templatePath) {
this.customOptions.templatePath = templatePath;
this.updateTemplatePath();
}
}
getTemplatePath(): string {
get templatePath(): string {
if (!this.templateUrl) {
this.updateTemplatePath();
}
@ -143,13 +143,13 @@ export class NotifyWindowService {
dispose(): void {
this.animationQueue.clear();
this.activeNotifications.forEach(window => window.close());
this.activeNotifications.forEach((window) => window.close());
this.browserWindowPooler.closeAll();
}
closeAll(): void {
this.animationQueue.clear();
this.activeNotifications.forEach(window => window.close());
this.activeNotifications.forEach((window) => window.close());
this.browserWindowPooler.closeAll();
this.setup();
@ -161,7 +161,7 @@ export class NotifyWindowService {
this.firstPosition = { x: 0, y: 0 };
this.activeNotifications = [];
this.browserWindowPooler = new BrowserWindowPooler(
this.getOptions().browserWindowPool.min
this.options.browserWindowPool.min
);
this.notificationQueue = [];
this.closedNotifications = new Map();
@ -169,7 +169,7 @@ export class NotifyWindowService {
this.animationQueue = new AnimationQueue();
const display = screen.getPrimaryDisplay();
const display = Electron.screen.getPrimaryDisplay();
this.lowerRightCornerPosition = {
x: display.bounds.x + display.workArea.x + display.workAreaSize.width,
@ -183,35 +183,45 @@ export class NotifyWindowService {
);
this.maxVisibleNotifications =
this.getOptions().browserWindowPool.max < this.maxVisibleNotifications
? this.getOptions().browserWindowPool.max
this.options.browserWindowPool.max < this.maxVisibleNotifications
? this.options.browserWindowPool.max
: this.maxVisibleNotifications;
}
private setupEvents(): void {
const self = this;
ipcMain.on(
Electron.ipcMain.on(
Channel.close,
(event: IpcMainEvent, windowId: number, notification: NotifyWindow) => {
(
event: Electron.IpcMainEvent,
windowId: number,
sNotification: string
) => {
const notification: NotifyWindow = JSON.parse(sNotification);
const onClose = self.buildCloseNotification(
BrowserWindow.fromId(windowId),
Electron.BrowserWindow.fromId(windowId),
notification
);
self.buildCloseNotificationSafely(onClose)('close');
}
);
ipcMain.on(
Electron.ipcMain.on(
Channel.click,
(event: IpcMainEvent, windowId: number, notification: NotifyWindow) => {
(
event: Electron.IpcMainEvent,
windowId: number,
sNotification: string
) => {
const notification: NotifyWindow = JSON.parse(sNotification);
if (!!notification.url) {
shell.openExternal(notification.url);
Electron.shell.openExternal(notification.url);
}
const notificationWindow = BrowserWindow.fromId(windowId);
const notificationWindow = Electron.BrowserWindow.fromId(windowId);
if (notificationWindow && notificationWindow[onClickNotifyWindow]) {
const onClose = self.buildCloseNotification(
BrowserWindow.fromId(windowId),
Electron.BrowserWindow.fromId(windowId),
notification
);
notificationWindow[onClickNotifyWindow]({
@ -275,7 +285,7 @@ export class NotifyWindowService {
const self = this;
return new Promise<any>((resolve, reject) => {
if (this.activeNotifications.length < this.maxVisibleNotifications) {
self.getWindow().then(notificationWindow => {
self.getWindow().then((notificationWindow) => {
self.calcInsertPosition();
notificationWindow.setPosition(
self.nextInsertPosition.x,
@ -324,7 +334,7 @@ export class NotifyWindowService {
notificationWindow.webContents.send(
Channel.browserWindowSetContents,
notification
JSON.stringify(notification)
);
notificationWindow.showInactive();
resolve(notificationWindow);
@ -337,7 +347,7 @@ export class NotifyWindowService {
}
private buildCloseNotification(
notificationWindow: BrowserWindow,
notificationWindow: Electron.BrowserWindow,
notification: NotifyWindow,
timeoutIdFunc?: () => number
) {
@ -349,7 +359,7 @@ export class NotifyWindowService {
if (self.closedNotifications.has(notification.id)) {
self.closedNotifications.delete(notification.id);
return new Promise<void>(resolve => {
return new Promise<void>((resolve) => {
resolve();
});
} else {
@ -409,24 +419,24 @@ export class NotifyWindowService {
}
}
private getWindow(): Promise<BrowserWindow> {
private getWindow(): Promise<Electron.BrowserWindow> {
const slef = this;
return new Promise<BrowserWindow>((resolve, reject) => {
if (0 < slef.browserWindowPooler.getLength()) {
return new Promise<Electron.BrowserWindow>((resolve, reject) => {
if (0 < slef.browserWindowPooler.length) {
resolve(slef.browserWindowPooler.pop());
} else {
const windowProperties = slef.customOptions.defaultWindow;
windowProperties.width = slef.customOptions.width;
windowProperties.height = slef.customOptions.height;
const notificationWindow = new BrowserWindow({
const notificationWindow = new Electron.BrowserWindow({
...windowProperties,
title: 'Notification'
});
notificationWindow.setVisibleOnAllWorkspaces(true);
notificationWindow.loadURL(slef.getTemplatePath());
notificationWindow.loadURL(slef.templatePath);
notificationWindow.webContents.on(
WebContentsChannel.DidFinishLoad,
WebContentsChannel.didFinishLoad,
() => {
// Done
notificationWindow.webContents.send(
@ -437,7 +447,7 @@ export class NotifyWindowService {
}
);
notificationWindow.webContents.on(
WebContentsChannel.DevtoolsOpened,
WebContentsChannel.devtoolsOpened,
() => {
notificationWindow.webContents.closeDevTools();
}
@ -460,7 +470,7 @@ export class NotifyWindowService {
}
await Promise.all(
aryNotificationPos.map(async index => {
aryNotificationPos.map(async (index) => {
await self.moveNotificationAnimation(index);
})
);

View File

@ -2,4 +2,12 @@
* Public API Surface of notification
*/
export * from './lib/models/notify-window';
export * from './lib/models/notify-window-options';
export * from './lib/services/notify-window.service';
export * from './lib/types/channel.type';
export * from './lib/types/event.type';
export * from './lib/utils/animation-queue';

View File

@ -2,14 +2,14 @@
"name": "@ucap/electron-updater-window",
"version": "0.0.1",
"publishConfig": {
"registry": "http://10.81.13.221:8081/nexus/repository/npm-ucap/"
"registry": "https://nexus.loafle.net/repository/npm-ucap/"
},
"scripts": {},
"dependencies": {
"peerDependencies": {
"@ucap/electron-core": "~0.0.1",
"electron": "^8.0.0",
"electron-log": "^4.1.0",
"fs-extra": "^8.1.0",
"electron": "^9.0.3",
"electron-log": "^4.2.1",
"fs-extra": "^9.0.1",
"rxjs": "^6.5.4"
},
"devDependencies": {}

View File

@ -19,6 +19,7 @@ async function buildForProduction(args) {
const distPath = path.join(projectPath, ucapPackageJson.dest);
const docPath = path.join(projectPath, ucapPackageJson.docDest);
const packPath = path.join(rootPath, 'pack');
const webpackConfig = (overrideConfig, compilerOptions) => {
const commonConfig = {
@ -150,7 +151,7 @@ async function buildForProduction(args) {
resolve();
}
);
}).catch(reason => {
}).catch((reason) => {
reject(reason);
});
@ -172,7 +173,7 @@ async function buildForProduction(args) {
resolve();
}
);
}).catch(reason => {
}).catch((reason) => {
reject(reason);
});
@ -211,7 +212,7 @@ async function buildForProduction(args) {
resolve();
}
);
}).catch(reason => {
}).catch((reason) => {
reject(reason);
});
@ -250,7 +251,7 @@ async function buildForProduction(args) {
resolve();
}
);
}).catch(reason => {
}).catch((reason) => {
reject(reason);
});
@ -288,31 +289,28 @@ async function buildForProduction(args) {
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`
)}`,
const packFileName = `ucap-electron-${projectName}-${projectVersion}.tgz`;
process.chdir(path.join(distPath));
execSync(`npm pack`, { stdio: 'inherit' });
fse.moveSync(
path.join(distPath, packFileName),
path.join(packPath, packFileName),
{
stdio: 'inherit'
overwrite: true
}
);
execSync(
`rimraf ${path.join(
distPath,
`ucap-electron-${projectName}-${projectVersion}.tgz`
)}`,
{
stdio: 'inherit'
}
);
process.chdir(path.join(rootPath));
execSync(`npm install -D ${path.join(packPath, packFileName)}`, {
stdio: 'inherit'
});
console.log(`${projectName}: installation for local complete`);
resolve();