This commit is contained in:
Park Byung Eun 2020-08-10 14:05:14 +09:00
parent 1f24033b98
commit 5ccfc1260b
67 changed files with 2143 additions and 10034 deletions

2
.gitignore vendored
View File

@ -4,6 +4,8 @@
/dist
/tmp
/out-tsc
/dist-resources
/package
# Only exists if Bazel was run
/bazel-out

View File

@ -0,0 +1,2 @@
APPLEID=erggie@lfcorp.com
APPLEIDPASS=Ahqkdlf1!

View 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}`);
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View 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>

Binary file not shown.

View File

@ -0,0 +1,7 @@
!macro customInit
!macroend
!macro customInstall
!macroend

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View 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

View File

@ -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
View 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
View 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

File diff suppressed because it is too large Load Diff

View File

@ -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",

View File

@ -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() {}
}

View File

@ -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
View 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() {}
}

View 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')
})
);
}
}

View 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;
}
}

View 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);
}
});
}
}

View 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
);
}
}
}
}

View 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);
}
});
}
}

View 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);
}
});
}
}

View 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
};
}
}

View 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);
}
});
}
}

View 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
View 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;

View 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;
}
}
}

View 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
View File

@ -0,0 +1,3 @@
import { AppWindow } from './app.window';
export const WINDOWS = [AppWindow];

View File

@ -0,0 +1,6 @@
{
"logout": "Logout",
"settings": "Settings",
"version": "Version",
"exit": "Exit"
}

View File

@ -0,0 +1,6 @@
{
"logout": "로그아웃",
"settings": "설정",
"version": "버전",
"exit": "종료"
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 842 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View 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;

Binary file not shown.

View 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;
}

View 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>

View File

@ -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);
});
}
}
}
}

View File

@ -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 });
}

View File

@ -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);
}

View File

@ -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
}
}
});
};
}

View File

@ -1,12 +0,0 @@
import { AppChannel } from '@ucap/electron-core';
export interface AppEventHandlerMetadata {
channel: AppChannel;
methodClassName: string;
}
export interface AppProviderMetadata {
handlers: {
[propertyKey: string]: AppEventHandlerMetadata;
};
}

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

@ -1 +0,0 @@
export const ELECTRON_APP = Symbol.for('ELECTRON_APP');

View File

@ -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;
}
});

View File

@ -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;
}
});
};
}

View File

@ -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
}
}
});
};
}

View File

@ -1,12 +0,0 @@
import { BrowserWindowChannel } from '@ucap/electron-core';
export interface BrowserWindowEventHandlerMetadata {
channel: BrowserWindowChannel;
methodClassName: string;
}
export interface BrowserWindowProviderMetadata {
handlers: {
[propertyKey: string]: BrowserWindowEventHandlerMetadata;
};
}

View File

@ -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
);

View File

@ -1 +0,0 @@
export const ELECTRON_BROWSER_WINDOW = Symbol.for('ELECTRON_BROWSER_WINDOW');

View File

@ -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);
}

View File

@ -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,

View File

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