sync
2
.gitignore
vendored
|
@ -4,6 +4,8 @@
|
|||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
/dist-resources
|
||||
/package
|
||||
# Only exists if Bazel was run
|
||||
/bazel-out
|
||||
|
||||
|
|
2
config/build/mac/hooks/.env
Normal file
|
@ -0,0 +1,2 @@
|
|||
APPLEID=erggie@lfcorp.com
|
||||
APPLEIDPASS=Ahqkdlf1!
|
41
config/build/mac/hooks/notarize.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
var electron_notarize = require('electron-notarize');
|
||||
|
||||
require('dotenv').config({
|
||||
path: path.join(__dirname, '.env')
|
||||
});
|
||||
|
||||
module.exports = async function(params) {
|
||||
// Only notarize the app on Mac OS only.
|
||||
if (process.platform !== 'darwin') {
|
||||
return;
|
||||
}
|
||||
console.log('afterSign hook triggered', params);
|
||||
|
||||
// Same appId in electron-builder.
|
||||
let appId = 'com.lfcorp.miaps.maclftalk';
|
||||
|
||||
let appPath = path.join(
|
||||
params.appOutDir,
|
||||
`${params.packager.appInfo.productFilename}.app`
|
||||
);
|
||||
if (!fs.existsSync(appPath)) {
|
||||
throw new Error(`Cannot find application at: ${appPath}`);
|
||||
}
|
||||
|
||||
console.log(`Notarizing ${appId} found at ${appPath}`);
|
||||
|
||||
try {
|
||||
await electron_notarize.notarize({
|
||||
appBundleId: appId,
|
||||
appPath: appPath,
|
||||
appleId: process.env.APPLEID,
|
||||
appleIdPassword: process.env.APPLEIDPASS
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
console.log(`Done notarizing ${appId}`);
|
||||
};
|
BIN
config/build/mac/images/background_dmg.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
config/build/mac/images/background_dmg@2x.png
Normal file
After Width: | Height: | Size: 34 KiB |
12
config/build/mac/plist/entitlements.plist
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
BIN
config/build/mac/sign/com.lfcorp.miaps.maclftalk.p12
Normal file
7
config/build/win/nsis/installer.nsh
Normal file
|
@ -0,0 +1,7 @@
|
|||
!macro customInit
|
||||
|
||||
!macroend
|
||||
|
||||
!macro customInstall
|
||||
|
||||
!macroend
|
BIN
config/build/win/sign/lgcns.pvk
Normal file
BIN
config/build/win/sign/lgcns.spc
Normal file
BIN
config/build/win/sign/www.lgcns.com.pfx
Normal file
BIN
config/build/win/sign/인증서 서명방법.doc
Normal file
13
config/build/win/sign/인증서 패스워드.txt
Normal file
|
@ -0,0 +1,13 @@
|
|||
개인키 암호 :
|
||||
it15itsm
|
||||
|
||||
LG UCAP
|
||||
http://www.lgucap.com/
|
||||
|
||||
|
||||
http://timestamp.versign.com/scripts/timstamp.dll
|
||||
위에꺼 안되서 아래꺼 사용
|
||||
http://timestamp.globalsign.com/scripts/timstamp.dll
|
||||
|
||||
|
||||
|
|
@ -2,22 +2,58 @@ const path = require('path');
|
|||
const webpack = require('webpack');
|
||||
const webpackNodeExternals = require('webpack-node-externals');
|
||||
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
|
||||
const getEnviroment = require('./enviroment');
|
||||
|
||||
const rootPath = path.join(__dirname, '..');
|
||||
const enviroment = getEnviroment();
|
||||
|
||||
const IS_PRODUCTION =
|
||||
!!process.env.NODE_ENV && 'production' === process.env.NODE_ENV;
|
||||
|
||||
let optimization = undefined;
|
||||
let copyPluginOptions = [
|
||||
{
|
||||
from: '**/*',
|
||||
to: path.resolve(__dirname, '..', 'dist/assets'),
|
||||
context: 'src/assets'
|
||||
}
|
||||
];
|
||||
|
||||
if (IS_PRODUCTION) {
|
||||
optimization = {
|
||||
minimize: true,
|
||||
minimizer: [new TerserPlugin()]
|
||||
};
|
||||
|
||||
copyPluginOptions.push({
|
||||
from: '**/*',
|
||||
to: path.resolve(__dirname, '..', 'dist/'),
|
||||
context: 'dist-resources/ucap-lg-renderer'
|
||||
});
|
||||
}
|
||||
|
||||
const plugins = [
|
||||
new CleanWebpackPlugin({ verbose: false }),
|
||||
new CopyWebpackPlugin(copyPluginOptions),
|
||||
new webpack.DefinePlugin(Object.assign({}, enviroment, {}))
|
||||
];
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
mode: process.env.ENV || 'development',
|
||||
mode: IS_PRODUCTION ? 'production' : 'development',
|
||||
entry: path.join(rootPath, 'src', 'main'),
|
||||
node: {
|
||||
__dirname: false
|
||||
},
|
||||
output: {
|
||||
path: path.join(rootPath, 'dist'),
|
||||
filename: 'electron-main.js'
|
||||
},
|
||||
target: 'electron-main',
|
||||
devtool: 'source-map',
|
||||
devtool: IS_PRODUCTION ? undefined : 'source-map',
|
||||
externals: [webpackNodeExternals()],
|
||||
resolve: {
|
||||
extensions: ['.ts', '.js']
|
||||
|
@ -38,9 +74,7 @@ module.exports = [
|
|||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new CleanWebpackPlugin({ verbose: false }),
|
||||
new webpack.DefinePlugin(Object.assign({}, enviroment, {}))
|
||||
]
|
||||
optimization,
|
||||
plugins
|
||||
}
|
||||
];
|
||||
|
|
2
electron-builder.env
Normal file
|
@ -0,0 +1,2 @@
|
|||
CSC_LINK=config/build/mac/sign/com.lfcorp.miaps.maclftalk.p12
|
||||
CSC_KEY_PASSWORD=uc#uc@pLG
|
80
electron-builder.json
Normal file
|
@ -0,0 +1,80 @@
|
|||
{
|
||||
"appId": "com.lgucap.mmessenger",
|
||||
"productName": "M Messenger",
|
||||
"asar": true,
|
||||
"extraMetadata": {
|
||||
"name": "M Messenger"
|
||||
},
|
||||
"directories": {
|
||||
"buildResources": "./config/build/",
|
||||
"output": "./package/"
|
||||
},
|
||||
"files": [
|
||||
"**/*",
|
||||
"!**/*.ts",
|
||||
"!**/*.scss",
|
||||
"!LICENSE.md",
|
||||
"!package.json",
|
||||
"!package-lock.json",
|
||||
"!config/",
|
||||
"!dist-resources/",
|
||||
"!src/",
|
||||
"!.prettierrc",
|
||||
"!**/.vscode",
|
||||
"!tsconfig.app.json",
|
||||
"!tsconfig.json",
|
||||
"!tsconfig.spec.json",
|
||||
"!tslint.json"
|
||||
],
|
||||
"protocols": {
|
||||
"name": "M Messenger",
|
||||
"schemes": ["M Messenger"]
|
||||
},
|
||||
"publish": {
|
||||
"provider": "generic",
|
||||
"url": "http://mmessenger.lfcorp.com/update/desktop"
|
||||
},
|
||||
"mac": {
|
||||
"target": ["dmg", "zip"],
|
||||
"category": "public.app-category.business",
|
||||
"icon": "./src/assets/app/images/icon/icon.icns",
|
||||
"hardenedRuntime": true,
|
||||
"gatekeeperAssess": false,
|
||||
"extendInfo": {
|
||||
"NSAppTransportSecurity": {
|
||||
"NSAllowsArbitraryLoads": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"dmg": {
|
||||
"title": "M Messenger",
|
||||
"background": "./config/build/mac/images/background_dmg.png"
|
||||
},
|
||||
"win": {
|
||||
"target": ["nsis-web"],
|
||||
"icon": "./src/assets/app/images/icon/icon.ico",
|
||||
"legalTrademarks": "(c) 2015 lgucap.com",
|
||||
"publisherName": "LG CNS Co.,Ltd",
|
||||
"signingHashAlgorithms": ["sha1"],
|
||||
"certificateFile": "./config/build/win/sign/www.lgcns.com.pfx",
|
||||
"certificatePassword": "it15itsm"
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": true,
|
||||
"allowToChangeInstallationDirectory": false,
|
||||
"perMachine": false,
|
||||
"differentialPackage": true,
|
||||
"include": "./config/build/win/nsis/installer.nsh"
|
||||
},
|
||||
"nsisWeb": {
|
||||
"oneClick": true,
|
||||
"allowToChangeInstallationDirectory": false,
|
||||
"perMachine": false,
|
||||
"differentialPackage": true,
|
||||
"include": "./config/build/win/nsis/installer.nsh"
|
||||
},
|
||||
"linux": {
|
||||
"target": ["AppImage", "deb", "rpm", "zip", "tar.gz"],
|
||||
"icon": "./src/assets/app/images/icon/"
|
||||
}
|
||||
}
|
9509
package-lock.json
generated
51
package.json
|
@ -1,39 +1,59 @@
|
|||
{
|
||||
"name": "ucap-lg-desktop",
|
||||
"version": "0.0.0",
|
||||
"author": {
|
||||
"name": "LG CNS",
|
||||
"url": "https://"
|
||||
},
|
||||
"description": "M Messenger for PC",
|
||||
"scripts": {
|
||||
"start": "npm run build && electron --nolazy --inspect-brk=9229 ./dist/electron-main.js",
|
||||
"build": "webpack --config ./config/webpack.config.js"
|
||||
"start": "npm run build:development && cross-env NODE_ENV=development electron --nolazy --inspect-brk=9229 ./dist/electron-main.js",
|
||||
"build": "cross-env NODE_ENV=production webpack --config ./config/webpack.config.js",
|
||||
"build:development": "cross-env NODE_ENV=development webpack --config ./config/webpack.config.js",
|
||||
"pack:windows:ia32": "electron-builder build --windows --ia32",
|
||||
"pack:windows:x64": "electron-builder build --windows --x64",
|
||||
"pack:mac": "electron-builder build --mac"
|
||||
},
|
||||
"private": true,
|
||||
"main": "./dist/electron-main.js",
|
||||
"dependencies": {
|
||||
"@tsed/core": "^5.44.11",
|
||||
"@tsed/di": "^5.44.11",
|
||||
"@ucap/electron-core": "~0.0.1",
|
||||
"@ucap/electron-core": "~0.0.16",
|
||||
"@ucap/electron-common": "~0.0.32",
|
||||
"@ucap/electron-native": "~0.0.18",
|
||||
"@ucap/electron-logger": "~0.0.1",
|
||||
"@ucap/electron-notify-window": "~0.0.13",
|
||||
"@ucap/core": "~0.0.14",
|
||||
"@ucap/logger": "~0.0.14",
|
||||
"@ucap/i18n": "~0.0.2",
|
||||
"@ucap/native": "~0.0.26",
|
||||
"auto-launch": "^5.0.5",
|
||||
"electron-log": "^4.1.0",
|
||||
"electron-store": "^5.1.1",
|
||||
"electron-updater": "^4.2.5",
|
||||
"electron-log": "^4.2.1",
|
||||
"electron-updater": "^4.3.1",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"file-type": "^14.1.4",
|
||||
"fs-extra": "^9.0.0",
|
||||
"i18next": "^19.3.2",
|
||||
"i18next-node-fs-backend": "^2.1.3",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rxjs": "^6.5.4",
|
||||
"semver": "^7.1.3",
|
||||
"semver": "^7.3.2",
|
||||
"tmp": "^0.1.0",
|
||||
"tslib": "^1.10.0",
|
||||
"v8-compile-cache": "^2.1.0"
|
||||
"v8-compile-cache": "^2.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/auto-launch": "^5.0.1",
|
||||
"@types/electron-devtools-installer": "^2.2.0",
|
||||
"@types/node": "^12.11.1",
|
||||
"@types/jasmine": "~3.5.0",
|
||||
"@types/jasminewd2": "~2.0.3",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"copy-webpack-plugin": "^5.1.1",
|
||||
"cross-env": "^7.0.2",
|
||||
"devtron": "^1.4.0",
|
||||
"electron": "^8.1.1",
|
||||
"electron-builder": "^22.4.1",
|
||||
"electron-debug": "^3.0.1",
|
||||
"electron-devtools-installer": "^2.2.4",
|
||||
"electron": "^9.1.0",
|
||||
"electron-builder": "^22.7.0",
|
||||
"electron-debug": "^3.1.0",
|
||||
"electron-devtools-installer": "^3.0.0",
|
||||
"electron-reload": "^1.5.0",
|
||||
"i18next": "^19.3.3",
|
||||
"jasmine-core": "~3.5.0",
|
||||
|
@ -45,6 +65,7 @@
|
|||
"karma-jasmine-html-reporter": "^1.4.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"protractor": "~5.4.3",
|
||||
"terser-webpack-plugin": "^3.0.2",
|
||||
"ts-loader": "^6.2.1",
|
||||
"ts-node": "~8.3.0",
|
||||
"tslint": "~5.18.0",
|
||||
|
|
301
src/app/app.ts
|
@ -1,51 +1,294 @@
|
|||
import * as path from 'path';
|
||||
import * as url from 'url';
|
||||
|
||||
import * as Electron from 'electron';
|
||||
|
||||
import log from 'electron-log';
|
||||
|
||||
import { AppChannel } from '@ucap/electron-core';
|
||||
import { AppLoader } from '../common/app/app-loader';
|
||||
import { AppSettings } from '../common/app/decorators/app-settings';
|
||||
import { On } from '../common/app/decorators/on';
|
||||
import fs_backend from 'i18next-node-fs-backend';
|
||||
|
||||
import { AppWindow } from './app.window';
|
||||
import { BrowserWindowRegistry } from '../common/browser-window/registries/browser-window.registry';
|
||||
import {
|
||||
AppChannel,
|
||||
WebContentsChannel,
|
||||
PlatformUtil,
|
||||
BrowserWindowChannel
|
||||
} from '@ucap/electron-core';
|
||||
|
||||
@AppSettings({
|
||||
bootstrap: AppWindow
|
||||
import { ElectronLogAppender } from '@ucap/electron-logger';
|
||||
|
||||
import { AppApi, PlatformApi } from '@ucap/electron-common';
|
||||
|
||||
import { AppWindow } from './windows/app.window';
|
||||
|
||||
import { AppChatService } from './services/app-chat.service';
|
||||
import { AppFileService } from './services/app-file.service';
|
||||
import { AppIdleService } from './services/app-idle.service';
|
||||
import { AppMessageService } from './services/app-message.service';
|
||||
import { AppPlatformService } from './services/app-platform.service';
|
||||
import { AppService } from './services/app.service';
|
||||
import { AppLoggerService } from './services/app-logger.service';
|
||||
import { AppI18nService } from './services/app-i18n.service';
|
||||
|
||||
Electron.app.disableHardwareAcceleration();
|
||||
Electron.app.commandLine.appendSwitch('disable-software-rasterizer');
|
||||
|
||||
const rootPath = PlatformUtil.rootPath(__dirname).split(path.sep).join('/');
|
||||
const distPath = path.join(rootPath, 'dist').split(path.sep).join('/');
|
||||
|
||||
const appIconPath = path
|
||||
.join(
|
||||
distPath,
|
||||
'assets/app/images/icon',
|
||||
__WIN32__ ? 'icon.ico' : __DARWIN__ ? 'icon.png' : 'icon.png'
|
||||
)
|
||||
.split(path.sep)
|
||||
.join('/');
|
||||
|
||||
export const SERVICES = [
|
||||
AppChatService,
|
||||
AppFileService,
|
||||
AppIdleService,
|
||||
AppMessageService,
|
||||
AppPlatformService,
|
||||
AppLoggerService,
|
||||
AppI18nService,
|
||||
AppService
|
||||
];
|
||||
|
||||
@AppApi.AppSettings({
|
||||
bootstrap: AppWindow,
|
||||
services: [...SERVICES],
|
||||
|
||||
assets: {
|
||||
appIcon: appIconPath
|
||||
},
|
||||
|
||||
logger: {
|
||||
moduleConfig: {
|
||||
appenders: [new ElectronLogAppender()]
|
||||
}
|
||||
},
|
||||
|
||||
i18n: {
|
||||
moduleConfig: {},
|
||||
i18next: {
|
||||
options: {
|
||||
whitelist: ['ko', 'en'],
|
||||
fallbackLng: 'en',
|
||||
debug: true,
|
||||
saveMissing: true,
|
||||
returnEmptyString: false,
|
||||
ns: ['electron'],
|
||||
backend: {
|
||||
loadPath: `${distPath}/assets/app/i18n/{{lng}}/{{ns}}.json`,
|
||||
addPath: `${distPath}/assets/app/i18n/{{lng}}/{{ns}}.missing.json`
|
||||
}
|
||||
},
|
||||
useBackends: [fs_backend]
|
||||
}
|
||||
},
|
||||
|
||||
notification: {
|
||||
option: {
|
||||
width: 340,
|
||||
height: 100,
|
||||
padding: 0,
|
||||
borderRadius: 0,
|
||||
browserWindowPool: {
|
||||
min: 0,
|
||||
max: 7
|
||||
},
|
||||
displayTime: 5000,
|
||||
defaultStyleContainer: {},
|
||||
defaultStyleAppIcon: { display: 'none' },
|
||||
defaultStyleImage: {},
|
||||
defaultStyleClose: {},
|
||||
defaultStyleText: {}
|
||||
},
|
||||
preloadPath: path.join(distPath, 'assets/notification/preload.js'),
|
||||
templatePath: path.join(distPath, 'assets/notification/template.html'),
|
||||
defaultImagePath: path.join(
|
||||
distPath,
|
||||
'assets/notification/images/nophoto_50.png'
|
||||
),
|
||||
defaultSoundPath: path.join(
|
||||
distPath,
|
||||
'assets/notification/sounds/message-alarm.mp3'
|
||||
)
|
||||
}
|
||||
})
|
||||
export class App extends AppLoader {
|
||||
constructor() {
|
||||
super();
|
||||
export class App implements PlatformApi.AfterInit, PlatformApi.BeforeDestroy {
|
||||
static readonly ucapClassName: string = 'app';
|
||||
|
||||
appLoggerService: AppLoggerService;
|
||||
appI18nService: AppI18nService;
|
||||
|
||||
appChatService: AppChatService;
|
||||
appFileService: AppFileService;
|
||||
appIdleService: AppIdleService;
|
||||
appMessageService: AppMessageService;
|
||||
appPlatformService: AppPlatformService;
|
||||
appService: AppService;
|
||||
|
||||
appWindow: AppWindow;
|
||||
|
||||
constructor(private appConfiguration: AppApi.AppConfiguration) {}
|
||||
|
||||
ucapAfterInit(): void | Promise<void> {
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
try {
|
||||
const backends = this.appConfiguration.i18n.i18next.useBackends;
|
||||
if (!!backends && 0 < backends.length) {
|
||||
for (const backend of backends) {
|
||||
this.appI18nService.use(backend);
|
||||
}
|
||||
}
|
||||
|
||||
await this.appI18nService.init(
|
||||
this.appConfiguration.i18n.i18next.options
|
||||
);
|
||||
await this.appI18nService.changeLanguage('ko');
|
||||
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@On(AppChannel.SecondInstance)
|
||||
onSecondInstance(event: Event, argv: string[], workingDirectory: string) {
|
||||
ucapBeforeDestroy(): void | Promise<void> {
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
try {
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@AppApi.On(AppChannel.secondInstance)
|
||||
onSecondInstance() {
|
||||
log.info('AppChannel.SecondInstance');
|
||||
if (!!this.appWindow && !!this.appWindow.native) {
|
||||
if (this.appWindow.native.isMinimized()) {
|
||||
this.appWindow.native.restore();
|
||||
}
|
||||
|
||||
if (!this.appWindow.native.isVisible()) {
|
||||
this.appWindow.native.show();
|
||||
}
|
||||
|
||||
this.appWindow.native.focus();
|
||||
}
|
||||
}
|
||||
|
||||
@On(AppChannel.Ready)
|
||||
onReady(launchInfo: any) {
|
||||
@AppApi.On(AppChannel.ready)
|
||||
onReady() {
|
||||
log.info('AppChannel.Ready');
|
||||
|
||||
this.createMainWindow();
|
||||
this.appWindow = PlatformApi.Platform.buildBrowserWindow(AppWindow);
|
||||
|
||||
if (__DEV__) {
|
||||
import('electron-debug').then((m) => {
|
||||
m.default({ showDevTools: true });
|
||||
});
|
||||
|
||||
import('electron-devtools-installer').then((edi) => {
|
||||
const extensions = [edi.REDUX_DEVTOOLS];
|
||||
|
||||
for (const extension of extensions) {
|
||||
try {
|
||||
edi.default(extension);
|
||||
} catch (e) {
|
||||
log.error(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// log.debug('services', this.configuration.services);
|
||||
const nameOfAssets = '/assets/';
|
||||
|
||||
Electron.protocol.interceptFileProtocol(
|
||||
'file',
|
||||
(request, callback) => {
|
||||
let requestUrl = decodeURIComponent(
|
||||
request.url.replace(/file:[/\\]*/, '')
|
||||
);
|
||||
|
||||
const li = requestUrl.lastIndexOf('?');
|
||||
if (-1 !== li) {
|
||||
requestUrl = requestUrl.slice(0, li);
|
||||
}
|
||||
|
||||
if (-1 !== requestUrl.indexOf(nameOfAssets)) {
|
||||
if (!requestUrl.startsWith(distPath)) {
|
||||
// log.info('requestUrl', requestUrl);
|
||||
const parts = requestUrl.split(nameOfAssets);
|
||||
if (2 !== parts.length) {
|
||||
log.error('file:// error', parts, requestUrl);
|
||||
} else {
|
||||
requestUrl = `${distPath}${nameOfAssets}${parts[1]}`;
|
||||
}
|
||||
// log.info('fixed', requestUrl);
|
||||
}
|
||||
}
|
||||
|
||||
callback(requestUrl);
|
||||
},
|
||||
(error) => {
|
||||
log.error(error);
|
||||
}
|
||||
);
|
||||
|
||||
this._loadURL();
|
||||
|
||||
this.appWindow.native.on(BrowserWindowChannel.closed, () => {
|
||||
this.appWindow = null;
|
||||
});
|
||||
|
||||
this.appWindow.native.webContents.on(
|
||||
WebContentsChannel.didFailLoad,
|
||||
(
|
||||
event: Event,
|
||||
errorCode: number,
|
||||
errorDescription: string,
|
||||
validatedURL: string,
|
||||
isMainFrame: boolean
|
||||
) => {
|
||||
if ('ERR_FILE_NOT_FOUND' === errorDescription) {
|
||||
// log.warn('validatedURL', validatedURL);
|
||||
const parts = validatedURL.split('/#');
|
||||
if (2 === parts.length) {
|
||||
this.appService.setInitInfo({ initUrl: parts[1] });
|
||||
}
|
||||
|
||||
this._loadURL();
|
||||
return;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@On(AppChannel.Activate)
|
||||
onActivate(event: Event, hasVisibleWindows: boolean) {
|
||||
log.info('AppChannel.Activate');
|
||||
}
|
||||
@AppApi.On(AppChannel.activate)
|
||||
onActivate() {}
|
||||
|
||||
@On(AppChannel.WindowAllClosed)
|
||||
@AppApi.On(AppChannel.windowAllClosed)
|
||||
onWindowAllClosed() {
|
||||
log.info('AppChannel.WindowAllClosed');
|
||||
// if ('darwin' !== process.platform) {
|
||||
// Electron.app.quit();
|
||||
// }
|
||||
}
|
||||
|
||||
private createMainWindow() {
|
||||
const bootstrap = this.settings.get('bootstrap');
|
||||
|
||||
const provider = BrowserWindowRegistry.get(bootstrap);
|
||||
const i = provider.useFactory(this.electronApp);
|
||||
private _loadURL() {
|
||||
this.appWindow.native.loadURL(
|
||||
__DEV__
|
||||
? 'http://localhost:4200'
|
||||
: url.format({
|
||||
protocol: 'file:',
|
||||
slashes: true,
|
||||
pathname: path.resolve(distPath, 'index.html')
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private createTrayIcon() {}
|
||||
}
|
||||
|
|
|
@ -1,106 +0,0 @@
|
|||
import * as Electron from 'electron';
|
||||
import log from 'electron-log';
|
||||
import * as windowStateKeeper from 'electron-window-state';
|
||||
|
||||
import { BrowserWindow } from '../common/browser-window/decorators/browser-window';
|
||||
import { On } from '../common/browser-window/decorators/on';
|
||||
import { BrowserWindowChannel, AppChannel } from '@ucap/electron-core';
|
||||
|
||||
const MIN_WIDTH = 700;
|
||||
const MIN_HEIGHT = 600;
|
||||
const DEFAULT_WIDTH = 1160;
|
||||
const DEFAULT_HEIGHT = 800;
|
||||
|
||||
let savedWindowState: windowStateKeeper.State;
|
||||
|
||||
@BrowserWindow({
|
||||
constructorOptions: {
|
||||
minWidth: MIN_WIDTH,
|
||||
minHeight: MIN_HEIGHT,
|
||||
center: true,
|
||||
backgroundColor: '#fff',
|
||||
webPreferences: {
|
||||
// Disable auxclick event
|
||||
// See https://developers.google.com/web/updates/2016/10/auxclick
|
||||
disableBlinkFeatures: 'Auxclick',
|
||||
// Enable, among other things, the ResizeObserver
|
||||
experimentalFeatures: true,
|
||||
nodeIntegration: true
|
||||
},
|
||||
acceptFirstMouse: true
|
||||
},
|
||||
beforeConstructor: constructorOptions => {
|
||||
savedWindowState = windowStateKeeper({
|
||||
defaultWidth: DEFAULT_WIDTH,
|
||||
defaultHeight: DEFAULT_HEIGHT
|
||||
});
|
||||
|
||||
return {
|
||||
...constructorOptions,
|
||||
x: savedWindowState.x,
|
||||
y: savedWindowState.y,
|
||||
width: savedWindowState.width,
|
||||
height: savedWindowState.height
|
||||
};
|
||||
}
|
||||
})
|
||||
export class AppWindow {
|
||||
constructor(
|
||||
private app: Electron.App,
|
||||
private window: Electron.BrowserWindow
|
||||
) {
|
||||
savedWindowState.manage(this.window);
|
||||
|
||||
this.attachHandlers();
|
||||
}
|
||||
|
||||
@On(BrowserWindowChannel.ReadyToShow)
|
||||
onReadyToShow() {
|
||||
log.info('BrowserWindowChannel.ReadyToShow');
|
||||
}
|
||||
|
||||
@On(BrowserWindowChannel.Close)
|
||||
onClose(event: Event) {
|
||||
log.info('BrowserWindowChannel.Close');
|
||||
}
|
||||
|
||||
@On(BrowserWindowChannel.Focus)
|
||||
onFocus() {
|
||||
log.info('BrowserWindowChannel.Focus');
|
||||
}
|
||||
|
||||
@On(BrowserWindowChannel.Blur)
|
||||
onBlur() {
|
||||
log.info('BrowserWindowChannel.Blur');
|
||||
}
|
||||
|
||||
@On(BrowserWindowChannel.Minimize)
|
||||
onMinimize() {
|
||||
log.info('BrowserWindowChannel.Minimize');
|
||||
}
|
||||
|
||||
@On(BrowserWindowChannel.Maximize)
|
||||
onMaximize() {
|
||||
log.info('BrowserWindowChannel.Maximize');
|
||||
}
|
||||
|
||||
@On(BrowserWindowChannel.Unmaximize)
|
||||
onUnmaximize() {
|
||||
log.info('BrowserWindowChannel.Unmaximize');
|
||||
}
|
||||
|
||||
@On(BrowserWindowChannel.Closed)
|
||||
onClosed() {
|
||||
log.info('BrowserWindowChannel.Closed');
|
||||
}
|
||||
|
||||
private attachHandlers() {
|
||||
this.attachAppHandlers();
|
||||
}
|
||||
|
||||
private attachAppHandlers() {
|
||||
this.app.on(AppChannel.BeforeQuit, (event: Electron.Event) => {
|
||||
log.info('AppChannel.BeforeQuit');
|
||||
});
|
||||
}
|
||||
}
|
73
src/app/auto-updater.ts
Normal file
|
@ -0,0 +1,73 @@
|
|||
import * as Electron from 'electron';
|
||||
|
||||
import log from 'electron-log';
|
||||
|
||||
import {
|
||||
autoUpdater,
|
||||
CancellationToken,
|
||||
UpdateCheckResult
|
||||
} from 'electron-updater';
|
||||
|
||||
import { AutoUpdaterApi } from '@ucap/electron-common';
|
||||
import {
|
||||
AutoUpdaterChannel,
|
||||
BrowserWindowUtil,
|
||||
BrowserWindowChannel
|
||||
} from '@ucap/electron-core';
|
||||
import { AppChannel } from '@ucap/electron-core';
|
||||
|
||||
@AutoUpdaterApi.AutoUpdaterSettings({})
|
||||
export class AutoUpdater extends AutoUpdaterApi.ElectronAutoUpdater {
|
||||
@AutoUpdaterApi.On(AutoUpdaterChannel.checkingForUpdate)
|
||||
onCheckingForUpdate() {
|
||||
log.info('Checking for update...');
|
||||
}
|
||||
|
||||
@AutoUpdaterApi.On(AutoUpdaterChannel.updateAvailable)
|
||||
onUpdateAvailable(info: any) {
|
||||
log.info('Update available.', info);
|
||||
}
|
||||
|
||||
@AutoUpdaterApi.On(AutoUpdaterChannel.updateNotAvailable)
|
||||
onUpdateNotAvailable() {
|
||||
log.info('Update not available.');
|
||||
}
|
||||
|
||||
@AutoUpdaterApi.On(AutoUpdaterChannel.error)
|
||||
onError(err) {
|
||||
log.info('Error in autoUpdater. ' + err);
|
||||
}
|
||||
|
||||
@AutoUpdaterApi.On(AutoUpdaterChannel.downloadProgress)
|
||||
onDownloadProgress(progress: any) {
|
||||
let logMessage = 'Download speed: ' + progress.bytesPerSecond;
|
||||
logMessage = logMessage + ' - Downloaded ' + progress.percent + '%';
|
||||
logMessage =
|
||||
logMessage + ' (' + progress.transferred + '/' + progress.total + ')';
|
||||
log.info(logMessage);
|
||||
}
|
||||
|
||||
@AutoUpdaterApi.On(AutoUpdaterChannel.updateDownloaded)
|
||||
onUpdateDownloaded() {
|
||||
log.info('Update downloaded');
|
||||
|
||||
Electron.app.removeAllListeners(AppChannel.windowAllClosed);
|
||||
const browserWindows = BrowserWindowUtil.all();
|
||||
|
||||
// https://github.com/electron-userland/electron-builder/issues/1604#issuecomment-372091881
|
||||
browserWindows.forEach((browserWindow) => {
|
||||
browserWindow.removeAllListeners(BrowserWindowChannel.close);
|
||||
browserWindow.removeAllListeners(BrowserWindowChannel.closed);
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
autoUpdater.quitAndInstall(true, true);
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
@AutoUpdaterApi.On(AutoUpdaterChannel.updateCancelled)
|
||||
onUpdateCancelled() {}
|
||||
|
||||
@AutoUpdaterApi.On(AutoUpdaterChannel.beforeQuitForUpdate)
|
||||
onBeforeQuitForUpdate() {}
|
||||
}
|
63
src/app/services/app-chat.service.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
import * as path from 'path';
|
||||
import * as url from 'url';
|
||||
|
||||
import { PlatformUtil } from '@ucap/electron-core';
|
||||
import { ChatChannel } from '@ucap/electron-native';
|
||||
import {
|
||||
PlatformApi,
|
||||
IpcMainApi,
|
||||
BrowserWindowApi,
|
||||
AppApi
|
||||
} from '@ucap/electron-common';
|
||||
import { ChatRoomWindow } from '../windows/chat-room.window';
|
||||
|
||||
const rootPath = PlatformUtil.rootPath(__dirname).split(path.sep).join('/');
|
||||
const distPath = path.join(rootPath, 'dist').split(path.sep).join('/');
|
||||
|
||||
export class AppChatService
|
||||
implements PlatformApi.AfterInit, PlatformApi.BeforeDestroy {
|
||||
static readonly ucapClassName: string = 'appChatService';
|
||||
|
||||
constructor(private configuration: AppApi.AppConfiguration) {}
|
||||
|
||||
ucapAfterInit(): void | Promise<void> {
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
try {
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ucapBeforeDestroy(): void | Promise<void> {
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
try {
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@IpcMainApi.Handle(ChatChannel.openRoom)
|
||||
async handleOpenRoom(
|
||||
event: Electron.IpcMainEvent,
|
||||
openUrl: string
|
||||
): Promise<void> {
|
||||
const win = BrowserWindowApi.Builder.build(
|
||||
this.configuration,
|
||||
ChatRoomWindow
|
||||
);
|
||||
|
||||
win.native.loadURL(
|
||||
__DEV__
|
||||
? 'http://localhost:4200'
|
||||
: url.format({
|
||||
protocol: 'file:',
|
||||
slashes: true,
|
||||
pathname: path.resolve(distPath, 'index.html')
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
189
src/app/services/app-file.service.ts
Normal file
|
@ -0,0 +1,189 @@
|
|||
import * as path from 'path';
|
||||
import fse from 'fs-extra';
|
||||
|
||||
import * as Electron from 'electron';
|
||||
|
||||
import log from 'electron-log';
|
||||
|
||||
import { IpcMainApi, PlatformApi } from '@ucap/electron-common';
|
||||
|
||||
import { FileChannel } from '@ucap/electron-native';
|
||||
import { FileUtil, BrowserWindowUtil } from '@ucap/electron-core';
|
||||
|
||||
export class AppFileService
|
||||
implements PlatformApi.AfterInit, PlatformApi.BeforeDestroy {
|
||||
static readonly ucapClassName: string = 'appFileService';
|
||||
|
||||
ucapAfterInit(): void | Promise<void> {
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
try {
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ucapBeforeDestroy(): void | Promise<void> {
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
try {
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@IpcMainApi.Handle(FileChannel.save)
|
||||
async handleSave(
|
||||
event: Electron.IpcMainEvent,
|
||||
buffer: Buffer,
|
||||
fileName: string,
|
||||
mimeType: string,
|
||||
savePath?: string
|
||||
): Promise<string> {
|
||||
log.debug('handleSave');
|
||||
|
||||
let savedDir: string;
|
||||
let savedFileName: string;
|
||||
let savedPath: string;
|
||||
|
||||
if (!!savePath) {
|
||||
fse.ensureDirSync(savePath);
|
||||
savedDir = savePath;
|
||||
} else {
|
||||
savedDir = Electron.app.getPath('downloads');
|
||||
}
|
||||
|
||||
savedFileName = await FileUtil.uniqueFileName(
|
||||
path.join(savedDir, fileName)
|
||||
);
|
||||
|
||||
savedPath = path.join(savedDir, savedFileName);
|
||||
|
||||
fse.writeFileSync(savedPath, buffer);
|
||||
|
||||
return savedPath;
|
||||
}
|
||||
|
||||
@IpcMainApi.Handle(FileChannel.read)
|
||||
async handleRead(
|
||||
event: Electron.IpcMainEvent,
|
||||
path: string
|
||||
): Promise<Buffer> {
|
||||
return fse.readFileSync(path);
|
||||
}
|
||||
|
||||
@IpcMainApi.Handle(FileChannel.openFolder)
|
||||
async handleOpenFolder(
|
||||
event: Electron.IpcMainEvent,
|
||||
folderPath?: string,
|
||||
make?: boolean
|
||||
): Promise<boolean> {
|
||||
log.debug('handleOpenFolder');
|
||||
let openDir: string;
|
||||
|
||||
if (!!folderPath) {
|
||||
if (!fse.existsSync(folderPath)) {
|
||||
if (!make) {
|
||||
return false;
|
||||
}
|
||||
fse.ensureDirSync(folderPath);
|
||||
}
|
||||
openDir = folderPath;
|
||||
} else {
|
||||
openDir = Electron.app.getPath('downloads');
|
||||
}
|
||||
|
||||
Electron.shell.openPath(openDir);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@IpcMainApi.Handle(FileChannel.openItem)
|
||||
async handleOpenItem(
|
||||
event: Electron.IpcMainEvent,
|
||||
filePath: string
|
||||
): Promise<boolean> {
|
||||
log.debug('handleOpenItem');
|
||||
|
||||
if (!fse.existsSync(filePath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Electron.shell.openPath(filePath);
|
||||
}
|
||||
|
||||
@IpcMainApi.Handle(FileChannel.path)
|
||||
async handlePath(
|
||||
event: Electron.IpcMainEvent,
|
||||
name: 'home' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos',
|
||||
appendPaths: string[]
|
||||
): Promise<string> {
|
||||
log.debug('handlePath');
|
||||
|
||||
let appPath = Electron.app.getPath(name);
|
||||
if (!!appendPaths && 0 < appendPaths.length) {
|
||||
appPath = path.join(appPath, ...appendPaths);
|
||||
}
|
||||
return appPath;
|
||||
}
|
||||
|
||||
@IpcMainApi.Handle(FileChannel.selectForOpen)
|
||||
async handleSelectForOpen(
|
||||
event: Electron.IpcMainEvent,
|
||||
option: {
|
||||
title?: string;
|
||||
defaultPath?: string;
|
||||
filters?: { extensions: string[]; name: string }[];
|
||||
properties?: Array<
|
||||
| 'openFile'
|
||||
| 'openDirectory'
|
||||
| 'multiSelections'
|
||||
| 'showHiddenFiles'
|
||||
| 'createDirectory'
|
||||
| 'promptToCreate'
|
||||
| 'noResolveAliases'
|
||||
| 'treatPackageAsDirectory'
|
||||
>;
|
||||
message?: string;
|
||||
}
|
||||
): Promise<string> {
|
||||
log.debug('handleSelectForOpen');
|
||||
|
||||
const result = await Electron.dialog.showOpenDialog(
|
||||
BrowserWindowUtil.main(),
|
||||
option
|
||||
);
|
||||
|
||||
if (result.canceled || !result.filePaths || 0 === result.filePaths.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return result.filePaths[0];
|
||||
}
|
||||
|
||||
@IpcMainApi.Handle(FileChannel.selectForSave)
|
||||
async handleSelectForSave(
|
||||
event: Electron.IpcMainEvent,
|
||||
option: {
|
||||
title?: string;
|
||||
defaultPath?: string;
|
||||
filters?: { extensions: string[]; name: string }[];
|
||||
message?: string;
|
||||
}
|
||||
): Promise<string> {
|
||||
log.debug('handleSelectForSave');
|
||||
|
||||
const result = await Electron.dialog.showSaveDialog(
|
||||
BrowserWindowUtil.main(),
|
||||
option
|
||||
);
|
||||
|
||||
if (result.canceled || !result.filePath) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return result.filePath;
|
||||
}
|
||||
}
|
31
src/app/services/app-i18n.service.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { I18nService } from '@ucap/i18n';
|
||||
import { AppApi, PlatformApi } from '@ucap/electron-common';
|
||||
|
||||
export class AppI18nService extends I18nService
|
||||
implements PlatformApi.AfterInit, PlatformApi.BeforeDestroy {
|
||||
static readonly ucapClassName: string = 'appI18nService';
|
||||
|
||||
constructor(private configuration: AppApi.AppConfiguration) {
|
||||
super(configuration.i18n.moduleConfig);
|
||||
}
|
||||
|
||||
ucapAfterInit(): void | Promise<void> {
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
try {
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ucapBeforeDestroy(): void | Promise<void> {
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
try {
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
103
src/app/services/app-idle.service.ts
Normal file
|
@ -0,0 +1,103 @@
|
|||
import * as Electron from 'electron';
|
||||
import log from 'electron-log';
|
||||
|
||||
import { WindowIdle } from '@ucap/native';
|
||||
|
||||
import { BrowserWindowUtil } from '@ucap/electron-core';
|
||||
import { IpcMainApi, PlatformApi } from '@ucap/electron-common';
|
||||
import { IdleChannel } from '@ucap/electron-native';
|
||||
|
||||
export class AppIdleService
|
||||
implements PlatformApi.AfterInit, PlatformApi.BeforeDestroy {
|
||||
static readonly ucapClassName: string = 'appIdleService';
|
||||
|
||||
private limitTimeInSecond: number;
|
||||
private idle: WindowIdle;
|
||||
private handle: any;
|
||||
|
||||
ucapAfterInit(): void | Promise<void> {
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
try {
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ucapBeforeDestroy(): void | Promise<void> {
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
try {
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@IpcMainApi.Handle(IdleChannel.started)
|
||||
async handleStarted(event: Electron.IpcMainEvent): Promise<void> {
|
||||
log.debug('handleStarted');
|
||||
}
|
||||
|
||||
@IpcMainApi.Handle(IdleChannel.startCheck)
|
||||
async handleStartCheck(
|
||||
event: Electron.IpcMainEvent,
|
||||
limitTime: number
|
||||
): Promise<void> {
|
||||
log.debug('handleStartCheck');
|
||||
const __this = this;
|
||||
|
||||
if (!!this.handle) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.limitTimeInSecond = limitTime;
|
||||
this.handle = setInterval(() => {
|
||||
__this.idleCheck();
|
||||
}, 1000 * 10);
|
||||
}
|
||||
|
||||
@IpcMainApi.Handle(IdleChannel.stopCheck)
|
||||
async handleStopCheck(event: Electron.IpcMainEvent): Promise<void> {
|
||||
log.debug('handleStopCheck');
|
||||
|
||||
if (!this.handle) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearInterval(this.handle);
|
||||
this.handle = undefined;
|
||||
}
|
||||
|
||||
@IpcMainApi.Handle(IdleChannel.changeLimitTime)
|
||||
async handleChangeLimitTime(
|
||||
event: Electron.IpcMainEvent,
|
||||
limitTime: number
|
||||
): Promise<void> {
|
||||
log.debug('handleChangeLimitTime');
|
||||
|
||||
this.limitTimeInSecond = limitTime;
|
||||
}
|
||||
|
||||
private idleCheck() {
|
||||
const idle: number = Electron.powerMonitor.getSystemIdleTime();
|
||||
if (idle > this.limitTimeInSecond) {
|
||||
if (WindowIdle.Active === this.idle) {
|
||||
this.idle = WindowIdle.Idle;
|
||||
BrowserWindowUtil.main().webContents.send(
|
||||
IdleChannel.onState$,
|
||||
this.idle
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (WindowIdle.Idle === this.idle) {
|
||||
this.idle = WindowIdle.Active;
|
||||
BrowserWindowUtil.main().webContents.send(
|
||||
IdleChannel.onState$,
|
||||
this.idle
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
32
src/app/services/app-logger.service.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { LogService } from '@ucap/logger';
|
||||
|
||||
import { AppApi, PlatformApi } from '@ucap/electron-common';
|
||||
|
||||
export class AppLoggerService extends LogService
|
||||
implements PlatformApi.AfterInit, PlatformApi.BeforeDestroy {
|
||||
static readonly ucapClassName: string = 'appLoggerService';
|
||||
|
||||
constructor(private configuration: AppApi.AppConfiguration) {
|
||||
super(configuration.logger.moduleConfig);
|
||||
}
|
||||
|
||||
ucapAfterInit(): void | Promise<void> {
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
try {
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ucapBeforeDestroy(): void | Promise<void> {
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
try {
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
28
src/app/services/app-message.service.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { IpcMainApi, PlatformApi } from '@ucap/electron-common';
|
||||
|
||||
import { MessageChannel } from '@ucap/electron-native';
|
||||
|
||||
export class AppMessageService
|
||||
implements PlatformApi.AfterInit, PlatformApi.BeforeDestroy {
|
||||
static readonly ucapClassName: string = 'appMessageService';
|
||||
|
||||
ucapAfterInit(): void | Promise<void> {
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
try {
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ucapBeforeDestroy(): void | Promise<void> {
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
try {
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
109
src/app/services/app-platform.service.ts
Normal file
|
@ -0,0 +1,109 @@
|
|||
import * as os from 'os';
|
||||
import child_process from 'child_process';
|
||||
|
||||
import log from 'electron-log';
|
||||
|
||||
import { IpcMainApi, PlatformApi } from '@ucap/electron-common';
|
||||
|
||||
import { PlatformChannel } from '@ucap/electron-native';
|
||||
|
||||
export class AppPlatformService
|
||||
implements PlatformApi.AfterInit, PlatformApi.BeforeDestroy {
|
||||
static readonly ucapClassName: string = 'appPlatformService';
|
||||
|
||||
ucapAfterInit(): void | Promise<void> {
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
try {
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ucapBeforeDestroy(): void | Promise<void> {
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
try {
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@IpcMainApi.Handle(PlatformChannel.networkInfo)
|
||||
async handleNetworkInfo(event: Electron.IpcMainEvent): Promise<any> {
|
||||
log.debug('handleNetworkInfo');
|
||||
|
||||
const interfaces = os.networkInterfaces();
|
||||
const addresses: { ip: string; mac: string }[] = [];
|
||||
|
||||
for (const k in interfaces) {
|
||||
for (const k2 in interfaces[k]) {
|
||||
const address = interfaces[k][k2];
|
||||
if (address.family === 'IPv4' && !address.internal) {
|
||||
addresses.push({ ip: address.address, mac: address.mac });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return addresses;
|
||||
}
|
||||
|
||||
@IpcMainApi.Handle(PlatformChannel.execute)
|
||||
async handleExecute(
|
||||
event: Electron.IpcMainEvent,
|
||||
executableName: string
|
||||
): Promise<number> {
|
||||
log.debug('handleExecute');
|
||||
|
||||
const executablePath = __WIN32__ ? `${executableName}.exe` : executableName;
|
||||
|
||||
const p = child_process.spawn(executablePath, [], {
|
||||
stdio: ['ignore', 'ignore', 'ignore'],
|
||||
detached: true
|
||||
});
|
||||
|
||||
return p.pid;
|
||||
}
|
||||
|
||||
@IpcMainApi.Handle(PlatformChannel.openDefaultBrowser)
|
||||
async handleOpenDefaultBrowser(
|
||||
event: Electron.IpcMainEvent,
|
||||
url: string,
|
||||
options?: {
|
||||
name?: string;
|
||||
features?: string;
|
||||
replace?: boolean;
|
||||
}
|
||||
): Promise<void> {
|
||||
const shell = (window as any).require('electron').shell;
|
||||
shell.openExternal(url);
|
||||
}
|
||||
|
||||
@IpcMainApi.Handle(PlatformChannel.readFromClipboard)
|
||||
async handleReadFromClipboard(
|
||||
event: Electron.IpcMainEvent
|
||||
): Promise<{
|
||||
text?: string;
|
||||
rtf?: string;
|
||||
html?: string;
|
||||
image?: Buffer;
|
||||
imageDataUrl?: string;
|
||||
}> {
|
||||
log.debug('handleReadFromClipboard');
|
||||
|
||||
const text = Electron.clipboard.readText('clipboard');
|
||||
const rtf = Electron.clipboard.readRTF('clipboard');
|
||||
const html = Electron.clipboard.readHTML('clipboard');
|
||||
const image = Electron.clipboard.readImage('clipboard');
|
||||
|
||||
return {
|
||||
text,
|
||||
rtf,
|
||||
html,
|
||||
image: !image.isEmpty() ? image.toBitmap() : undefined,
|
||||
imageDataUrl: !image.isEmpty() ? image.toDataURL() : undefined
|
||||
};
|
||||
}
|
||||
}
|
35
src/app/services/app-window.service.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { AppApi, PlatformApi, IpcMainApi } from '@ucap/electron-common';
|
||||
import { WindowChannel } from '@ucap/electron-native';
|
||||
import { AppI18nService } from './app-i18n.service';
|
||||
import { AppLoggerService } from './app-logger.service';
|
||||
import { WindowState } from '@ucap/native';
|
||||
|
||||
export class AppWindowService
|
||||
implements PlatformApi.AfterInit, PlatformApi.BeforeDestroy {
|
||||
static readonly ucapClassName: string = 'appWindowService';
|
||||
|
||||
appI18nService: AppI18nService;
|
||||
appLoggerService: AppLoggerService;
|
||||
|
||||
constructor(private configuration: AppApi.AppConfiguration) {}
|
||||
|
||||
ucapAfterInit(): void | Promise<void> {
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
try {
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ucapBeforeDestroy(): void | Promise<void> {
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
try {
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
284
src/app/services/app.service.ts
Normal file
|
@ -0,0 +1,284 @@
|
|||
import * as Electron from 'electron';
|
||||
|
||||
import AutoLaunch from 'auto-launch';
|
||||
|
||||
import {
|
||||
NotificationRequest,
|
||||
NotificationType,
|
||||
AppInitInfo
|
||||
} from '@ucap/native';
|
||||
|
||||
import { AppChannel as ElectronAppChannel } from '@ucap/electron-core';
|
||||
import { IpcMainApi, AppApi, PlatformApi } from '@ucap/electron-common';
|
||||
import { AppChannel, ChatChannel, MessageChannel } from '@ucap/electron-native';
|
||||
import { NotifyWindowService } from '@ucap/electron-notify-window';
|
||||
import { BrowserWindowUtil } from '@ucap/electron-core';
|
||||
import { ObjectUtil } from '@ucap/core';
|
||||
import { AppI18nService } from './app-i18n.service';
|
||||
import { AppLoggerService } from './app-logger.service';
|
||||
|
||||
export class AppService
|
||||
implements PlatformApi.AfterInit, PlatformApi.BeforeDestroy {
|
||||
static readonly ucapClassName: string = 'appService';
|
||||
|
||||
appI18nService: AppI18nService;
|
||||
appLoggerService: AppLoggerService;
|
||||
|
||||
private autoLaunch: AutoLaunch;
|
||||
private notifyWindowService: NotifyWindowService;
|
||||
private appInitInfo: AppInitInfo;
|
||||
private logined: boolean;
|
||||
private appTray: Electron.Tray;
|
||||
|
||||
constructor(private configuration: AppApi.AppConfiguration) {
|
||||
this.autoLaunch = new AutoLaunch({ name: '' });
|
||||
}
|
||||
|
||||
ucapAfterInit(): void | Promise<void> {
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
try {
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ucapBeforeDestroy(): void | Promise<void> {
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
try {
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@AppApi.On(ElectronAppChannel.ready)
|
||||
onAppReady() {
|
||||
this.notifyWindowService = new NotifyWindowService(
|
||||
this.configuration?.notification?.option
|
||||
);
|
||||
|
||||
this.notifyWindowService.options.defaultWindow.webPreferences.preload = this.configuration?.notification?.preloadPath;
|
||||
this.notifyWindowService.templatePath = this.configuration?.notification?.templatePath;
|
||||
|
||||
this._setTray();
|
||||
}
|
||||
|
||||
@IpcMainApi.Handle(AppChannel.version)
|
||||
async handleVersion(): Promise<string> {
|
||||
this.appLoggerService.debug('handleVersion');
|
||||
return Electron.app.getVersion();
|
||||
}
|
||||
|
||||
@IpcMainApi.Handle(AppChannel.postInit)
|
||||
async handlePostInit(): Promise<AppInitInfo> {
|
||||
this.appLoggerService.debug('handlePostInit');
|
||||
const appInitInfo = ObjectUtil.deepClone(this.appInitInfo);
|
||||
this.appInitInfo = undefined;
|
||||
return appInitInfo;
|
||||
}
|
||||
|
||||
@IpcMainApi.Handle(AppChannel.postLogin)
|
||||
async handlePostLogin(): Promise<void> {
|
||||
this.appLoggerService.debug('handlePostLogin');
|
||||
this.logined = true;
|
||||
this._setTray();
|
||||
}
|
||||
|
||||
@IpcMainApi.Handle(AppChannel.postLogout)
|
||||
async handlePostLogout(): Promise<void> {
|
||||
this.appLoggerService.debug('handlePostLogout');
|
||||
this.logined = false;
|
||||
this._setTray();
|
||||
}
|
||||
|
||||
@IpcMainApi.Handle(AppChannel.postDestroy)
|
||||
async handlePostDestroy(): Promise<void> {
|
||||
this.appLoggerService.debug('handlePostDestroy');
|
||||
}
|
||||
|
||||
@IpcMainApi.Handle(AppChannel.changeAutoLaunch)
|
||||
async handleChangeAutoLaunch(
|
||||
event: Electron.IpcMainEvent,
|
||||
isAutoLaunch: boolean
|
||||
): Promise<boolean> {
|
||||
this.appLoggerService.debug('handleChangeAutoLaunch');
|
||||
try {
|
||||
if (isAutoLaunch) {
|
||||
await this.autoLaunch.enable();
|
||||
} else {
|
||||
await this.autoLaunch.disable();
|
||||
}
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@IpcMainApi.Handle(AppChannel.showNotify)
|
||||
async handleShowNotify(
|
||||
event: Electron.IpcMainEvent,
|
||||
req: NotificationRequest
|
||||
): Promise<void> {
|
||||
this.appLoggerService.debug('handleShowNotify');
|
||||
const mainWindow = BrowserWindowUtil.main();
|
||||
|
||||
this.notifyWindowService.notify({
|
||||
title: req.title,
|
||||
text: req.contents,
|
||||
image: req.image || this.configuration.notification.defaultImagePath,
|
||||
sound: req.useSound
|
||||
? this.configuration.notification.defaultSoundPath
|
||||
: undefined,
|
||||
displayTime: req.displayTime,
|
||||
onClick: (event) => {
|
||||
if (mainWindow) {
|
||||
mainWindow.flashFrame(false);
|
||||
|
||||
switch (req.type) {
|
||||
case NotificationType.Event:
|
||||
{
|
||||
mainWindow.webContents.send(ChatChannel.onOpen$, req.seq);
|
||||
}
|
||||
break;
|
||||
case NotificationType.Message:
|
||||
{
|
||||
mainWindow.webContents.send(MessageChannel.onOpen$, req.seq);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
mainWindow.show();
|
||||
event.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!mainWindow.isVisible()) {
|
||||
mainWindow.minimize();
|
||||
}
|
||||
|
||||
mainWindow.flashFrame(true);
|
||||
}
|
||||
|
||||
@IpcMainApi.Handle(AppChannel.closeAllNotify)
|
||||
async handleCloseAllNotify(): Promise<void> {
|
||||
this.appLoggerService.debug('handleCloseAllNotify');
|
||||
|
||||
const mainWindow = BrowserWindowUtil.main();
|
||||
|
||||
if (!!mainWindow) {
|
||||
mainWindow.flashFrame(false);
|
||||
}
|
||||
this.notifyWindowService.closeAll();
|
||||
}
|
||||
|
||||
@IpcMainApi.Handle(AppChannel.checkForUpdates)
|
||||
async handleCheckForUpdates(): Promise<void> {
|
||||
this.appLoggerService.debug('handleCheckForUpdates');
|
||||
}
|
||||
|
||||
@IpcMainApi.Handle(AppChannel.applyInstantUpdates)
|
||||
async handleApplyInstantUpdates(): Promise<void> {
|
||||
this.appLoggerService.debug('handleApplyInstantUpdates');
|
||||
}
|
||||
|
||||
@IpcMainApi.Handle(AppChannel.startedCheckForUpdate)
|
||||
async handleStartedCheckForUpdate(): Promise<void> {
|
||||
this.appLoggerService.debug('handleStartedCheckForUpdate');
|
||||
}
|
||||
|
||||
@IpcMainApi.Handle(AppChannel.startCheckForUpdate)
|
||||
async handleStartCheckForUpdate(): Promise<void> {
|
||||
this.appLoggerService.debug('handleStartCheckForUpdate');
|
||||
}
|
||||
|
||||
@IpcMainApi.Handle(AppChannel.stopCheckForUpdate)
|
||||
async handleStopCheckForUpdate(): Promise<void> {
|
||||
this.appLoggerService.debug('handleStopCheckForUpdate');
|
||||
}
|
||||
|
||||
@IpcMainApi.Handle(AppChannel.exit)
|
||||
async handleExit(): Promise<void> {
|
||||
this.appLoggerService.debug('handleExit');
|
||||
|
||||
Electron.app.exit();
|
||||
}
|
||||
|
||||
setInitInfo(appInitInfo: AppInitInfo) {
|
||||
if (!this.appInitInfo) {
|
||||
this.appInitInfo = {};
|
||||
}
|
||||
|
||||
this.appInitInfo = {
|
||||
...this.appInitInfo,
|
||||
...appInitInfo
|
||||
};
|
||||
}
|
||||
|
||||
private _setTray() {
|
||||
const trayMenu: Electron.MenuItemConstructorOptions[] = [];
|
||||
|
||||
if (this.logined) {
|
||||
trayMenu.push(
|
||||
{
|
||||
label: this.appI18nService.t('electron:logout'),
|
||||
click: () => {
|
||||
const mainWindow = BrowserWindowUtil.main();
|
||||
if (!!mainWindow) {
|
||||
mainWindow.show();
|
||||
mainWindow.webContents.send(AppChannel.onLogout$);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: this.appI18nService.t('electron:settings'),
|
||||
click: () => {
|
||||
const mainWindow = BrowserWindowUtil.main();
|
||||
if (!!mainWindow) {
|
||||
mainWindow.show();
|
||||
mainWindow.webContents.send(AppChannel.onShowSetting$);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
trayMenu.push(
|
||||
{
|
||||
label: this.appI18nService.t('electron:version'),
|
||||
submenu: [{ label: Electron.app.getVersion() }]
|
||||
},
|
||||
{
|
||||
label: this.appI18nService.t('electron:exit'),
|
||||
click: () => {
|
||||
const mainWindow = BrowserWindowUtil.main();
|
||||
if (!!mainWindow) {
|
||||
mainWindow.show();
|
||||
mainWindow.webContents.send(AppChannel.onExit$);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const menu = Electron.Menu.buildFromTemplate(trayMenu);
|
||||
|
||||
if (__DARWIN__) {
|
||||
Electron.app.dock.setMenu(menu);
|
||||
} else {
|
||||
if (!this.appTray) {
|
||||
const appIcon = this.configuration['assets']['appIcon'];
|
||||
this.appTray = new Electron.Tray(appIcon);
|
||||
this.appTray.setToolTip('M Messenger');
|
||||
this.appTray.on('click', () => {
|
||||
const mainWindow = BrowserWindowUtil.main();
|
||||
mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show();
|
||||
});
|
||||
}
|
||||
|
||||
this.appTray.setContextMenu(menu);
|
||||
}
|
||||
}
|
||||
}
|
15
src/app/services/index.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { AppService } from './app.service';
|
||||
import { AppPlatformService } from './app-platform.service';
|
||||
import { AppFileService } from './app-file.service';
|
||||
import { AppIdleService } from './app-idle.service';
|
||||
import { AppChatService } from './app-chat.service';
|
||||
import { AppMessageService } from './app-message.service';
|
||||
import { AppI18nService } from './app-i18n.service';
|
||||
|
||||
export let appService: AppService;
|
||||
export let appPlatformService: AppPlatformService;
|
||||
export let appFileService: AppFileService;
|
||||
export let appIdleService: AppIdleService;
|
||||
export let appChatService: AppChatService;
|
||||
export let appMessageService: AppMessageService;
|
||||
export let appI18nService: AppI18nService;
|
169
src/app/windows/app.window.ts
Normal file
|
@ -0,0 +1,169 @@
|
|||
import * as Electron from 'electron';
|
||||
import log from 'electron-log';
|
||||
import * as windowStateKeeper from 'electron-window-state';
|
||||
|
||||
import { BrowserWindowChannel, AppChannel } from '@ucap/electron-core';
|
||||
import { AppApi, BrowserWindowApi, IpcMainApi } from '@ucap/electron-common';
|
||||
import { WindowState } from '@ucap/native';
|
||||
import { WindowChannel } from '@ucap/electron-native';
|
||||
import { AppLoggerService } from '../services/app-logger.service';
|
||||
|
||||
const MIN_WIDTH = 420;
|
||||
const MIN_HEIGHT = 640;
|
||||
const DEFAULT_WIDTH = 820;
|
||||
const DEFAULT_HEIGHT = 650;
|
||||
|
||||
let savedWindowState: windowStateKeeper.State;
|
||||
|
||||
@BrowserWindowApi.BrowserWindowSettings({
|
||||
constructorOptions: (appConfiguration: AppApi.AppConfiguration) => {
|
||||
savedWindowState = windowStateKeeper({
|
||||
defaultWidth: DEFAULT_WIDTH,
|
||||
defaultHeight: DEFAULT_HEIGHT
|
||||
});
|
||||
|
||||
return {
|
||||
x: savedWindowState.x,
|
||||
y: savedWindowState.y,
|
||||
width: savedWindowState.width,
|
||||
height: savedWindowState.height,
|
||||
minWidth: MIN_WIDTH,
|
||||
minHeight: MIN_HEIGHT,
|
||||
center: true,
|
||||
backgroundColor: '#fff',
|
||||
webPreferences: {
|
||||
// Disable auxclick event
|
||||
// See https://developers.google.com/web/updates/2016/10/auxclick
|
||||
disableBlinkFeatures: 'Auxclick',
|
||||
// Enable, among other things, the ResizeObserver
|
||||
experimentalFeatures: true,
|
||||
nodeIntegration: true
|
||||
},
|
||||
acceptFirstMouse: true,
|
||||
icon: appConfiguration.assets.appIcon,
|
||||
titleBarStyle: __DARWIN__ ? 'hidden' : 'default',
|
||||
frame: __WIN32__ || __LINUX__ ? false : true
|
||||
};
|
||||
}
|
||||
})
|
||||
export class AppWindow implements BrowserWindowApi.ElectronBrowserWindow {
|
||||
static readonly ucapClassName: string = 'appWindow';
|
||||
|
||||
appLoggerService: AppLoggerService;
|
||||
|
||||
constructor(
|
||||
private configuration: BrowserWindowApi.BrowserWindowConfiguration,
|
||||
public native: Electron.BrowserWindow
|
||||
) {
|
||||
savedWindowState.manage(native);
|
||||
}
|
||||
|
||||
@BrowserWindowApi.On(BrowserWindowChannel.readyToShow)
|
||||
onReadyToShow() {
|
||||
log.info('BrowserWindowChannel.ReadyToShow');
|
||||
}
|
||||
|
||||
@BrowserWindowApi.On(BrowserWindowChannel.close)
|
||||
onClose() {
|
||||
log.info('BrowserWindowChannel.Close');
|
||||
}
|
||||
|
||||
@BrowserWindowApi.On(BrowserWindowChannel.closed)
|
||||
onClosed() {
|
||||
log.info('BrowserWindowChannel.Closed');
|
||||
}
|
||||
|
||||
@BrowserWindowApi.On(BrowserWindowChannel.focus)
|
||||
onFocus() {
|
||||
this.sendWindowFocus(true);
|
||||
}
|
||||
|
||||
@BrowserWindowApi.On(BrowserWindowChannel.blur)
|
||||
onBlur() {
|
||||
this.sendWindowFocus(false);
|
||||
}
|
||||
|
||||
@BrowserWindowApi.On(BrowserWindowChannel.enterFullScreen)
|
||||
onEnterFullScreen() {
|
||||
this.sendWindowState(WindowState.FullScreen);
|
||||
}
|
||||
|
||||
@BrowserWindowApi.On(BrowserWindowChannel.leaveFullScreen)
|
||||
onLeaveFullScreen() {
|
||||
this.sendWindowState(this.windowState());
|
||||
}
|
||||
|
||||
@BrowserWindowApi.On(BrowserWindowChannel.minimize)
|
||||
onMinimize() {
|
||||
this.sendWindowState(WindowState.Minimized);
|
||||
}
|
||||
|
||||
@BrowserWindowApi.On(BrowserWindowChannel.maximize)
|
||||
onMaximize() {
|
||||
this.sendWindowState(WindowState.Maximized);
|
||||
}
|
||||
|
||||
@BrowserWindowApi.On(BrowserWindowChannel.unmaximize)
|
||||
onUnmaximize() {
|
||||
this.sendWindowState(WindowState.Normal);
|
||||
}
|
||||
|
||||
@BrowserWindowApi.On(BrowserWindowChannel.restore)
|
||||
onRestore() {
|
||||
this.sendWindowState(WindowState.Normal);
|
||||
}
|
||||
|
||||
@BrowserWindowApi.On(BrowserWindowChannel.hide)
|
||||
onHide() {
|
||||
this.sendWindowState(WindowState.Hidden);
|
||||
}
|
||||
|
||||
@BrowserWindowApi.On(BrowserWindowChannel.show)
|
||||
onShow() {
|
||||
this.sendWindowState(this.windowState());
|
||||
}
|
||||
|
||||
@IpcMainApi.Handle(WindowChannel.state)
|
||||
async handleState(event: Electron.IpcMainEvent): Promise<WindowState> {
|
||||
this.appLoggerService.debug('handleState');
|
||||
return this.windowState();
|
||||
}
|
||||
|
||||
@IpcMainApi.Handle(WindowChannel.focused)
|
||||
async handleFocused(event: Electron.IpcMainEvent): Promise<boolean> {
|
||||
this.appLoggerService.debug('handleFocused');
|
||||
return this.native.isFocused();
|
||||
}
|
||||
|
||||
private attachHandlers() {
|
||||
this.attachAppHandlers();
|
||||
}
|
||||
|
||||
private attachAppHandlers() {
|
||||
Electron.app.on(AppChannel.beforeQuit, (event: Electron.Event) => {
|
||||
log.info('AppChannel.BeforeQuit');
|
||||
});
|
||||
}
|
||||
|
||||
private sendWindowFocus(focus: boolean) {
|
||||
this.native.webContents.send(WindowChannel.onFocus$, focus);
|
||||
}
|
||||
|
||||
private sendWindowState(windowState: WindowState) {
|
||||
this.native.webContents.send(WindowChannel.onState$, windowState);
|
||||
}
|
||||
|
||||
private windowState(): WindowState {
|
||||
if (this.native.isFullScreen()) {
|
||||
return WindowState.FullScreen;
|
||||
} else if (this.native.isMaximized()) {
|
||||
return WindowState.Maximized;
|
||||
} else if (this.native.isMinimized()) {
|
||||
return WindowState.Minimized;
|
||||
} else if (!this.native.isVisible()) {
|
||||
return WindowState.Hidden;
|
||||
} else {
|
||||
return WindowState.Normal;
|
||||
}
|
||||
}
|
||||
}
|
140
src/app/windows/chat-room.window.ts
Normal file
|
@ -0,0 +1,140 @@
|
|||
import * as Electron from 'electron';
|
||||
import log from 'electron-log';
|
||||
|
||||
import { BrowserWindowChannel, AppChannel } from '@ucap/electron-core';
|
||||
import { AppApi, BrowserWindowApi } from '@ucap/electron-common';
|
||||
import { WindowState } from '@ucap/native';
|
||||
import { WindowChannel } from '@ucap/electron-native';
|
||||
|
||||
const MIN_WIDTH = 420;
|
||||
const MIN_HEIGHT = 640;
|
||||
const DEFAULT_WIDTH = 820;
|
||||
const DEFAULT_HEIGHT = 650;
|
||||
|
||||
@BrowserWindowApi.BrowserWindowSettings({
|
||||
constructorOptions: (appConfiguration: AppApi.AppConfiguration) => {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: DEFAULT_WIDTH,
|
||||
height: DEFAULT_HEIGHT,
|
||||
minWidth: MIN_WIDTH,
|
||||
minHeight: MIN_HEIGHT,
|
||||
center: true,
|
||||
backgroundColor: '#fff',
|
||||
webPreferences: {
|
||||
// Disable auxclick event
|
||||
// See https://developers.google.com/web/updates/2016/10/auxclick
|
||||
disableBlinkFeatures: 'Auxclick',
|
||||
// Enable, among other things, the ResizeObserver
|
||||
experimentalFeatures: true,
|
||||
nodeIntegration: true
|
||||
},
|
||||
acceptFirstMouse: true,
|
||||
icon: appConfiguration.assets.appIcon,
|
||||
titleBarStyle: __DARWIN__ ? 'hidden' : 'default',
|
||||
frame: __WIN32__ || __LINUX__ ? false : true
|
||||
};
|
||||
}
|
||||
})
|
||||
export class ChatRoomWindow implements BrowserWindowApi.ElectronBrowserWindow {
|
||||
static readonly ucapClassName: string = 'chatRoomWindow';
|
||||
|
||||
constructor(
|
||||
private configuration: BrowserWindowApi.BrowserWindowConfiguration,
|
||||
public native: Electron.BrowserWindow
|
||||
) {}
|
||||
|
||||
@BrowserWindowApi.On(BrowserWindowChannel.readyToShow)
|
||||
onReadyToShow() {
|
||||
log.info('BrowserWindowChannel.ReadyToShow');
|
||||
}
|
||||
|
||||
@BrowserWindowApi.On(BrowserWindowChannel.close)
|
||||
onClose() {
|
||||
log.info('BrowserWindowChannel.Close');
|
||||
}
|
||||
|
||||
@BrowserWindowApi.On(BrowserWindowChannel.closed)
|
||||
onClosed() {
|
||||
log.info('BrowserWindowChannel.Closed');
|
||||
}
|
||||
|
||||
@BrowserWindowApi.On(BrowserWindowChannel.focus)
|
||||
onFocus() {
|
||||
log.info('BrowserWindowChannel.Focus');
|
||||
}
|
||||
|
||||
@BrowserWindowApi.On(BrowserWindowChannel.blur)
|
||||
onBlur() {
|
||||
log.info('BrowserWindowChannel.Blur');
|
||||
}
|
||||
|
||||
@BrowserWindowApi.On(BrowserWindowChannel.enterFullScreen)
|
||||
onEnterFullScreen() {
|
||||
this.sendWindowState(WindowState.FullScreen);
|
||||
}
|
||||
|
||||
@BrowserWindowApi.On(BrowserWindowChannel.leaveFullScreen)
|
||||
onLeaveFullScreen() {
|
||||
this.sendWindowState(this.windowState());
|
||||
}
|
||||
|
||||
@BrowserWindowApi.On(BrowserWindowChannel.minimize)
|
||||
onMinimize() {
|
||||
this.sendWindowState(WindowState.Minimized);
|
||||
}
|
||||
|
||||
@BrowserWindowApi.On(BrowserWindowChannel.maximize)
|
||||
onMaximize() {
|
||||
this.sendWindowState(WindowState.Maximized);
|
||||
}
|
||||
|
||||
@BrowserWindowApi.On(BrowserWindowChannel.unmaximize)
|
||||
onUnmaximize() {
|
||||
this.sendWindowState(WindowState.Normal);
|
||||
}
|
||||
|
||||
@BrowserWindowApi.On(BrowserWindowChannel.restore)
|
||||
onRestore() {
|
||||
this.sendWindowState(WindowState.Normal);
|
||||
}
|
||||
|
||||
@BrowserWindowApi.On(BrowserWindowChannel.hide)
|
||||
onHide() {
|
||||
this.sendWindowState(WindowState.Hidden);
|
||||
}
|
||||
|
||||
@BrowserWindowApi.On(BrowserWindowChannel.show)
|
||||
onShow() {
|
||||
this.sendWindowState(this.windowState());
|
||||
}
|
||||
|
||||
private attachHandlers() {
|
||||
this.attachAppHandlers();
|
||||
}
|
||||
|
||||
private attachAppHandlers() {
|
||||
Electron.app.on(AppChannel.beforeQuit, (event: Electron.Event) => {
|
||||
log.info('AppChannel.BeforeQuit');
|
||||
});
|
||||
}
|
||||
|
||||
private sendWindowState(windowState: WindowState) {
|
||||
this.native.webContents.send(WindowChannel.onState$, windowState);
|
||||
}
|
||||
|
||||
private windowState(): WindowState {
|
||||
if (this.native.isFullScreen()) {
|
||||
return WindowState.FullScreen;
|
||||
} else if (this.native.isMaximized()) {
|
||||
return WindowState.Maximized;
|
||||
} else if (this.native.isMinimized()) {
|
||||
return WindowState.Minimized;
|
||||
} else if (!this.native.isVisible()) {
|
||||
return WindowState.Hidden;
|
||||
} else {
|
||||
return WindowState.Normal;
|
||||
}
|
||||
}
|
||||
}
|
3
src/app/windows/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
import { AppWindow } from './app.window';
|
||||
|
||||
export const WINDOWS = [AppWindow];
|
6
src/assets/app/i18n/en/electron.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"logout": "Logout",
|
||||
"settings": "Settings",
|
||||
"version": "Version",
|
||||
"exit": "Exit"
|
||||
}
|
6
src/assets/app/i18n/ko/electron.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"logout": "로그아웃",
|
||||
"settings": "설정",
|
||||
"version": "버전",
|
||||
"exit": "종료"
|
||||
}
|
BIN
src/assets/app/images/icon/icon.icns
Normal file
BIN
src/assets/app/images/icon/icon.ico
Normal file
After Width: | Height: | Size: 107 KiB |
BIN
src/assets/app/images/icon/icon.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
src/assets/notification/images/btn_call_message.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
src/assets/notification/images/btn_call_receive.png
Normal file
After Width: | Height: | Size: 842 B |
BIN
src/assets/notification/images/btn_call_refuse.png
Normal file
After Width: | Height: | Size: 643 B |
BIN
src/assets/notification/images/btn_call_transfer.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
src/assets/notification/images/btn_close.png
Normal file
After Width: | Height: | Size: 226 B |
BIN
src/assets/notification/images/btn_close_gray.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
src/assets/notification/images/btn_noti_call.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
src/assets/notification/images/nophoto_50.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
155
src/assets/notification/preload.js
Normal file
|
@ -0,0 +1,155 @@
|
|||
'use strict';
|
||||
|
||||
const electron = require('electron');
|
||||
const ipcRenderer = electron.ipcRenderer;
|
||||
const winId = electron.remote.getCurrentWindow().id;
|
||||
|
||||
function setStyle(config) {
|
||||
// Style it
|
||||
let notiDoc = global.window.document;
|
||||
let container = notiDoc.getElementById('container');
|
||||
let appIcon = notiDoc.getElementById('appIcon');
|
||||
let image = notiDoc.getElementById('image');
|
||||
let close = notiDoc.getElementById('close');
|
||||
let message = notiDoc.getElementById('message');
|
||||
// Default style
|
||||
setStyleOnDomElement(config.defaultStyleContainer, container);
|
||||
// Size and radius
|
||||
let style = {
|
||||
height:
|
||||
config.height -
|
||||
2 * config.borderRadius -
|
||||
2 * config.defaultStyleContainer.padding,
|
||||
width:
|
||||
config.width -
|
||||
2 * config.borderRadius -
|
||||
2 * config.defaultStyleContainer.padding,
|
||||
borderRadius: config.borderRadius + 'px'
|
||||
};
|
||||
setStyleOnDomElement(style, container);
|
||||
// Style appIcon or hide
|
||||
if (config.appIcon) {
|
||||
setStyleOnDomElement(config.defaultStyleAppIcon, appIcon);
|
||||
appIcon.src = config.appIcon;
|
||||
} else {
|
||||
setStyleOnDomElement(
|
||||
{
|
||||
display: 'none'
|
||||
},
|
||||
appIcon
|
||||
);
|
||||
}
|
||||
// Style image
|
||||
setStyleOnDomElement(config.defaultStyleImage, image);
|
||||
// Style close button
|
||||
setStyleOnDomElement(config.defaultStyleClose, close);
|
||||
// Remove margin from text p
|
||||
setStyleOnDomElement(config.defaultStyleText, message);
|
||||
}
|
||||
|
||||
function setContents(event, _notificationObj) {
|
||||
const notificationObj = JSON.parse(_notificationObj);
|
||||
// sound
|
||||
if (notificationObj.sound) {
|
||||
// Check if file is accessible
|
||||
try {
|
||||
// If it's a local file, check it's existence
|
||||
// Won't check remote files e.g. http://
|
||||
if (
|
||||
notificationObj.sound.match(/^file\:/) !== null ||
|
||||
notificationObj.sound.match(/^\//) !== null
|
||||
) {
|
||||
let audio = new global.window.Audio(notificationObj.sound);
|
||||
audio.play();
|
||||
}
|
||||
} catch (e) {
|
||||
log(
|
||||
'electron-notify: ERROR could not find sound file: ' +
|
||||
notificationObj.sound.replace('file://', ''),
|
||||
e,
|
||||
e.stack
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let notiDoc = global.window.document;
|
||||
// Title
|
||||
let titleDoc = notiDoc.getElementById('title');
|
||||
titleDoc.innerHTML = notificationObj.title || '';
|
||||
// message
|
||||
let messageDoc = notiDoc.getElementById('message');
|
||||
messageDoc.innerHTML = notificationObj.text || '';
|
||||
// Image
|
||||
let imageDoc = notiDoc.getElementById('image');
|
||||
if (notificationObj.image) {
|
||||
imageDoc.src = notificationObj.image;
|
||||
} else {
|
||||
setStyleOnDomElement({ display: 'none' }, imageDoc);
|
||||
}
|
||||
|
||||
// Close button
|
||||
let closeButton = notiDoc.getElementById('close');
|
||||
closeButton.addEventListener('click', function (event) {
|
||||
event.stopPropagation();
|
||||
ipcRenderer.send(
|
||||
'ucap::electron::notify-window::close',
|
||||
winId,
|
||||
JSON.stringify(notificationObj)
|
||||
);
|
||||
});
|
||||
|
||||
// URL
|
||||
let container = notiDoc.getElementById('container');
|
||||
container.addEventListener('click', function () {
|
||||
ipcRenderer.send(
|
||||
'ucap::electron::notify-window::click',
|
||||
winId,
|
||||
JSON.stringify(notificationObj)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function setStyleOnDomElement(styleObj, domElement) {
|
||||
try {
|
||||
for (let styleAttr in styleObj) {
|
||||
domElement.style[styleAttr] = styleObj[styleAttr];
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
'electron-notify: Could not set style on domElement',
|
||||
styleObj,
|
||||
domElement
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function loadConfig(event, conf) {
|
||||
setStyle(conf || {});
|
||||
}
|
||||
|
||||
function reset() {
|
||||
let notiDoc = global.window.document;
|
||||
let container = notiDoc.getElementById('container');
|
||||
let closeButton = notiDoc.getElementById('close');
|
||||
|
||||
// Remove event listener
|
||||
let newContainer = container.cloneNode(true);
|
||||
container.parentNode.replaceChild(newContainer, container);
|
||||
let newCloseButton = closeButton.cloneNode(true);
|
||||
closeButton.parentNode.replaceChild(newCloseButton, closeButton);
|
||||
}
|
||||
|
||||
ipcRenderer.on(
|
||||
'ucap::electron::notify-window::browserWindowSetContents',
|
||||
setContents
|
||||
);
|
||||
ipcRenderer.on('ucap::electron::notify-window::loadConfig', loadConfig);
|
||||
ipcRenderer.on('ucap::electron::notify-window::reset', reset);
|
||||
|
||||
function log() {
|
||||
console.log.apply(console, arguments);
|
||||
}
|
||||
|
||||
delete global.require;
|
||||
delete global.exports;
|
||||
delete global.module;
|
BIN
src/assets/notification/sounds/message-alarm.mp3
Normal file
130
src/assets/notification/styles/noti_messege.css
Normal file
|
@ -0,0 +1,130 @@
|
|||
html {
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
body {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
color: #333;
|
||||
font-family: '나눔고딕', Malgun Gothic, '맑은고딕', Arial, Dotum, '돋움',
|
||||
Gulim, '굴림';
|
||||
font-size: 12px;
|
||||
line-height: 18px !important;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body * {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
ul,
|
||||
ol {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
img {
|
||||
border: none;
|
||||
}
|
||||
a:link,
|
||||
a:visited,
|
||||
a:hover,
|
||||
a:active {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.noti_messege {
|
||||
width: 340px;
|
||||
height: 100px;
|
||||
border: 1px solid #666;
|
||||
background-color: #fff;
|
||||
box-shadow: 0px 0px 3px 0px #e7e7e7;
|
||||
}
|
||||
.info {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 16px 14px;
|
||||
color: #fff;
|
||||
}
|
||||
.btn_close {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
right: 6px;
|
||||
top: 6px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: url(../images/btn_close_gray.png) no-repeat 50% 50%;
|
||||
}
|
||||
.btn_close:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
.photo {
|
||||
position: relative;
|
||||
top: 0px;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
margin: 4px 0;
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
border-radius: 50%;
|
||||
background: #5bc1ff url(../images/nophoto_50.png) no-repeat 50% 50%;
|
||||
border: 2px solid #ddd;
|
||||
}
|
||||
.info .profile {
|
||||
position: absolute;
|
||||
width: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
.photo img {
|
||||
overflow: hidden;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50px;
|
||||
}
|
||||
.noti_messege .info .profile + div {
|
||||
padding-left: 70px;
|
||||
position: relative;
|
||||
line-height: 180%;
|
||||
height: 100%;
|
||||
}
|
||||
.sender {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
color: #333;
|
||||
width: 94%;
|
||||
}
|
||||
.sender .name {
|
||||
color: #2e7fb5;
|
||||
}
|
||||
.message {
|
||||
color: #666;
|
||||
}
|
||||
.ellipsis {
|
||||
display: block;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
word-wrap: normal;
|
||||
overflow: hidden;
|
||||
}
|
||||
.ellipsis_row2 {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
word-wrap: break-word;
|
||||
line-height: 1.6em;
|
||||
height: 3.2em;
|
||||
}
|
46
src/assets/notification/template.html
Normal file
|
@ -0,0 +1,46 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>[개발]M Messenger - 메시지 알림</title>
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta charset="UTF-8" />
|
||||
<link type="text/css" rel="stylesheet" href="styles/noti_messege.css" />
|
||||
</head>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
overflow-y: hidden;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<div class="noti_messege" id="container">
|
||||
<div class="info">
|
||||
<a class="btn_close" id="close"></a>
|
||||
<div class="profile">
|
||||
<div class="photo">
|
||||
<img src="" id="appIcon" />
|
||||
<img
|
||||
src=""
|
||||
id="image"
|
||||
onerror="this.src='images/nophoto_50.png';"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<ul id="text">
|
||||
<li class="sender ellipsis" id="title">
|
||||
<span class="name">김 수안무 거북이와 두루미</span>님이 <br />
|
||||
메시지를보냈습니다.
|
||||
</li>
|
||||
<li class="message ellipsis_row2" id="message">
|
||||
홍길동 대리(솔루션사업팀)홍길동 대리(솔루션사업팀)홍길동
|
||||
대리(솔루션사업팀)홍길동 대리(솔루션사업팀)
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,114 +0,0 @@
|
|||
import { Type, constructorOf, Store } from '@tsed/core';
|
||||
import { GlobalProviders, InjectorService, registerProvider } from '@tsed/di';
|
||||
|
||||
import * as Electron from 'electron';
|
||||
|
||||
import { AppOptions } from './decorators/app-settings';
|
||||
import { AppSettingsService } from './services/app-settings.service';
|
||||
import { ELECTRON_APP } from './types/electron-app';
|
||||
import {
|
||||
AppProviderMetadata,
|
||||
AppEventHandlerMetadata
|
||||
} from './models/app-provider.metadata';
|
||||
import { createElectronApp } from './utils/electron';
|
||||
import { ElectronApp } from './decorators/electron-app';
|
||||
|
||||
export abstract class AppLoader {
|
||||
readonly injector: InjectorService;
|
||||
private startedAt = new Date();
|
||||
|
||||
constructor(settings: Partial<AppOptions> = {}) {
|
||||
// create injector with initial configuration
|
||||
this.injector = this.createInjector(this.getConfiguration(this, settings));
|
||||
|
||||
createElectronApp(this.injector);
|
||||
this.attachEventHandler();
|
||||
}
|
||||
|
||||
get settings(): AppSettingsService {
|
||||
return this.injector.settings as AppSettingsService;
|
||||
}
|
||||
|
||||
get electronApp(): Electron.App {
|
||||
return this.injector.get<ElectronApp>(ElectronApp);
|
||||
}
|
||||
|
||||
static async bootstrap<App extends AppLoader>(
|
||||
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 async invoke(
|
||||
instance: any,
|
||||
handlerMetadata: AppEventHandlerMetadata,
|
||||
args: any[]
|
||||
): Promise<any> {
|
||||
const { methodClassName } = handlerMetadata;
|
||||
|
||||
return await instance[methodClassName](...args);
|
||||
}
|
||||
|
||||
private attachEventHandler() {
|
||||
const handlerMetadata: AppProviderMetadata = Store.from(this).get(
|
||||
ELECTRON_APP
|
||||
);
|
||||
|
||||
if (!handlerMetadata || !handlerMetadata.handlers) {
|
||||
return;
|
||||
}
|
||||
|
||||
const __this = this;
|
||||
|
||||
for (const handler in handlerMetadata.handlers) {
|
||||
if (handlerMetadata.handlers.hasOwnProperty(handler)) {
|
||||
const metadata = handlerMetadata.handlers[handler];
|
||||
|
||||
Electron.app.on(metadata.channel as any, (...args: any[]) => {
|
||||
__this.invoke(__this, metadata, args);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
import { Type, Store } from '@tsed/core';
|
||||
import { IModuleOptions, registerProvider, Module } from '@tsed/di';
|
||||
|
||||
export const APP_OPTIONS = Symbol.for('APP_OPTIONS');
|
||||
|
||||
export interface AppOptions extends IModuleOptions {
|
||||
bootstrap?: Type<any>;
|
||||
}
|
||||
|
||||
export function AppSettings(options?: AppOptions): Function {
|
||||
return Module({ ...options, root: true });
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
import * as Electron from 'electron';
|
||||
import { Type } from '@tsed/core';
|
||||
import { Inject } from '@tsed/di';
|
||||
|
||||
declare global {
|
||||
namespace Electron {
|
||||
interface App {}
|
||||
}
|
||||
}
|
||||
|
||||
export type ElectronApp = Electron.App;
|
||||
|
||||
export function ElectronApp(
|
||||
target: Type<any>,
|
||||
targetKey: string,
|
||||
descriptor: TypedPropertyDescriptor<Function> | number
|
||||
) {
|
||||
return Inject(ElectronApp)(target, targetKey, descriptor);
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
import { Store } from '@tsed/core';
|
||||
|
||||
import { AppChannel } from '@ucap/electron-core';
|
||||
|
||||
import { ELECTRON_APP } from '../types/electron-app';
|
||||
|
||||
export function On(channel: AppChannel) {
|
||||
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
|
||||
Store.from(target).merge(ELECTRON_APP, {
|
||||
handlers: {
|
||||
[propertyKey]: {
|
||||
channel,
|
||||
methodClassName: propertyKey
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
import { AppChannel } from '@ucap/electron-core';
|
||||
|
||||
export interface AppEventHandlerMetadata {
|
||||
channel: AppChannel;
|
||||
methodClassName: string;
|
||||
}
|
||||
|
||||
export interface AppProviderMetadata {
|
||||
handlers: {
|
||||
[propertyKey: string]: AppEventHandlerMetadata;
|
||||
};
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
export const ELECTRON_APP = Symbol.for('ELECTRON_APP');
|
|
@ -1,20 +0,0 @@
|
|||
import { InjectorService, ProviderScope, registerProvider } from '@tsed/di';
|
||||
|
||||
import * as Electron from 'electron';
|
||||
|
||||
import { ElectronApp } from '../decorators/electron-app';
|
||||
|
||||
export function createElectronApp(injector: InjectorService): void {
|
||||
injector.forkProvider(ElectronApp);
|
||||
}
|
||||
|
||||
registerProvider({
|
||||
provide: ElectronApp,
|
||||
scope: ProviderScope.SINGLETON,
|
||||
global: true,
|
||||
useFactory() {
|
||||
const app = Electron.app;
|
||||
app.allowRendererProcessReuse = true;
|
||||
return app;
|
||||
}
|
||||
});
|
|
@ -1,64 +0,0 @@
|
|||
import * as Electron from 'electron';
|
||||
import { registerBrowserWindow } from '../registries/browser-window.registry';
|
||||
import { Store } from '@tsed/core';
|
||||
import { ELECTRON_BROWSER_WINDOW } from '../types/electron-browser-window';
|
||||
import {
|
||||
BrowserWindowEventHandlerMetadata,
|
||||
BrowserWindowProviderMetadata
|
||||
} from '../models/browser-window-provider.metadata';
|
||||
|
||||
export interface BrowserWindowOptions {
|
||||
constructorOptions?: Electron.BrowserWindowConstructorOptions;
|
||||
beforeConstructor?: (
|
||||
constructorOptions: Electron.BrowserWindowConstructorOptions
|
||||
) => Electron.BrowserWindowConstructorOptions;
|
||||
}
|
||||
|
||||
export function BrowserWindow(options?: BrowserWindowOptions): Function {
|
||||
return (target: any): void => {
|
||||
registerBrowserWindow({
|
||||
provide: target,
|
||||
useFactory(app: Electron.App) {
|
||||
options = options || {};
|
||||
let constructorOptions = options.constructorOptions || {};
|
||||
|
||||
if (!!options.beforeConstructor) {
|
||||
constructorOptions = options.beforeConstructor(constructorOptions);
|
||||
}
|
||||
|
||||
const window = new Electron.BrowserWindow(constructorOptions);
|
||||
|
||||
const w = new target(app, window);
|
||||
|
||||
const providerMetadata: BrowserWindowProviderMetadata = Store.from(
|
||||
w
|
||||
).get(ELECTRON_BROWSER_WINDOW);
|
||||
|
||||
if (!!providerMetadata && !!providerMetadata.handlers) {
|
||||
const __this = w;
|
||||
const invoke = async (
|
||||
instance: any,
|
||||
handlerMetadata: BrowserWindowEventHandlerMetadata,
|
||||
args: any[]
|
||||
) => {
|
||||
const { methodClassName } = handlerMetadata;
|
||||
|
||||
return await instance[methodClassName](...args);
|
||||
};
|
||||
|
||||
for (const handler in providerMetadata.handlers) {
|
||||
if (providerMetadata.handlers.hasOwnProperty(handler)) {
|
||||
const metadata = providerMetadata.handlers[handler];
|
||||
|
||||
window.on(metadata.channel as any, (...args: any[]) => {
|
||||
invoke(__this, metadata, args);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
import { Store } from '@tsed/core';
|
||||
|
||||
import { BrowserWindowChannel } from '@ucap/electron-core';
|
||||
|
||||
import { ELECTRON_BROWSER_WINDOW } from '../types/electron-browser-window';
|
||||
|
||||
export function On(channel: BrowserWindowChannel) {
|
||||
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
|
||||
Store.from(target).merge(ELECTRON_BROWSER_WINDOW, {
|
||||
handlers: {
|
||||
[propertyKey]: {
|
||||
channel,
|
||||
methodClassName: propertyKey
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
import { BrowserWindowChannel } from '@ucap/electron-core';
|
||||
|
||||
export interface BrowserWindowEventHandlerMetadata {
|
||||
channel: BrowserWindowChannel;
|
||||
methodClassName: string;
|
||||
}
|
||||
|
||||
export interface BrowserWindowProviderMetadata {
|
||||
handlers: {
|
||||
[propertyKey: string]: BrowserWindowEventHandlerMetadata;
|
||||
};
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
import { Provider, GlobalProviders, TypedProvidersRegistry } from '@tsed/di';
|
||||
|
||||
export const PROVIDER_TYPE_BROWSER_WINDOW = 'BrowserWindow';
|
||||
|
||||
export const BrowserWindowRegistry: TypedProvidersRegistry = GlobalProviders.createRegistry(
|
||||
PROVIDER_TYPE_BROWSER_WINDOW,
|
||||
Provider
|
||||
);
|
||||
|
||||
export const registerBrowserWindow = GlobalProviders.createRegisterFn(
|
||||
PROVIDER_TYPE_BROWSER_WINDOW
|
||||
);
|
|
@ -1 +0,0 @@
|
|||
export const ELECTRON_BROWSER_WINDOW = Symbol.for('ELECTRON_BROWSER_WINDOW');
|
|
@ -1,14 +1,17 @@
|
|||
import * as Electron from 'electron';
|
||||
import log from 'electron-log';
|
||||
import { AppLoader } from './common/app/app-loader';
|
||||
|
||||
import { PlatformApi } from '@ucap/electron-common';
|
||||
|
||||
import { App } from './app/app';
|
||||
|
||||
async function bootstrap() {
|
||||
try {
|
||||
log.debug('Start app...');
|
||||
const app = await AppLoader.bootstrap(App);
|
||||
const app = await PlatformApi.Platform.bootstrap(App, {});
|
||||
|
||||
await app.start();
|
||||
log.debug('App initialized');
|
||||
log.debug('App started');
|
||||
} catch (er) {
|
||||
log.error(er);
|
||||
}
|
||||
|
|
|
@ -7,15 +7,15 @@
|
|||
"declaration": false,
|
||||
"downlevelIteration": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"target": "es2015",
|
||||
"lib": ["es2018", "dom"],
|
||||
"types": ["node"],
|
||||
"paths": {
|
||||
"@app/*": ["src/app/*"]
|
||||
}
|
||||
"paths": {}
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"fullTemplateTypeCheck": true,
|
||||
|
|
52
tslint.json
|
@ -9,28 +9,12 @@
|
|||
"component-class-suffix": true,
|
||||
"contextual-lifecycle": true,
|
||||
"directive-class-suffix": true,
|
||||
"directive-selector": [
|
||||
true,
|
||||
"attribute",
|
||||
"app",
|
||||
"camelCase"
|
||||
],
|
||||
"component-selector": [
|
||||
true,
|
||||
"element",
|
||||
"app",
|
||||
"kebab-case"
|
||||
],
|
||||
"import-blacklist": [
|
||||
true,
|
||||
"rxjs/Rx"
|
||||
],
|
||||
"directive-selector": [true, "attribute", "app", "camelCase"],
|
||||
"component-selector": [true, "element", "app", "kebab-case"],
|
||||
"import-blacklist": [true, "rxjs/Rx"],
|
||||
"interface-name": false,
|
||||
"max-classes-per-file": false,
|
||||
"max-line-length": [
|
||||
true,
|
||||
140
|
||||
],
|
||||
"max-line-length": [true, 140],
|
||||
"member-access": false,
|
||||
"member-ordering": [
|
||||
true,
|
||||
|
@ -44,33 +28,17 @@
|
|||
}
|
||||
],
|
||||
"no-consecutive-blank-lines": false,
|
||||
"no-console": [
|
||||
true,
|
||||
"debug",
|
||||
"info",
|
||||
"time",
|
||||
"timeEnd",
|
||||
"trace"
|
||||
],
|
||||
"no-console": [true, "debug", "info", "time", "timeEnd", "trace"],
|
||||
"no-empty": false,
|
||||
"no-inferrable-types": [
|
||||
true,
|
||||
"ignore-params"
|
||||
],
|
||||
"no-inferrable-types": [true, "ignore-params"],
|
||||
"no-non-null-assertion": true,
|
||||
"no-redundant-jsdoc": true,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-var-requires": false,
|
||||
"object-literal-key-quotes": [
|
||||
true,
|
||||
"as-needed"
|
||||
],
|
||||
"object-literal-key-quotes": [true, "as-needed"],
|
||||
"object-literal-sort-keys": false,
|
||||
"ordered-imports": false,
|
||||
"quotemark": [
|
||||
true,
|
||||
"single"
|
||||
],
|
||||
"quotemark": [true, "single"],
|
||||
"trailing-comma": false,
|
||||
"no-conflicting-lifecycle": true,
|
||||
"no-host-metadata-property": true,
|
||||
|
@ -85,7 +53,5 @@
|
|||
"use-lifecycle-interface": true,
|
||||
"use-pipe-transform-interface": true
|
||||
},
|
||||
"rulesDirectory": [
|
||||
"codelyzer"
|
||||
]
|
||||
"rulesDirectory": ["codelyzer"]
|
||||
}
|