This commit is contained in:
cyber-dream 2022-08-23 22:09:39 +03:00
parent 06e70b2c4c
commit 768bbcdd70
73 changed files with 38567 additions and 0 deletions

39
HOW_TO.md Normal file
View File

@ -0,0 +1,39 @@
###*First check it exists a version of your library compatible with the version of Angular defined in package.json.*
### How to install ng-bootstrap
``` bash
ng add @ng-bootstrap/ng-bootstrap
```
### How to install ngx-bootstrap
``` bash
ng add ngx-bootstrap
```
### How to install Angular Material
Add Angular Material using `ng add` command:
``` bash
ng add @angular/material
```
You will get the following questions:
``` bash
? Choose a prebuilt theme name, or "custom" for a custom theme: *Choose any theme you like here*
? Set up global Angular Material typography styles? *Yes*
? Set up browser animations for Angular Material? *Yes*
```
Angular Material will start installing, but you will get the following error after installation:
``` bash
Your project is not using the default builders for "build". The Angular Material schematics cannot add a theme to the workspace configuration if the builder has been changed.
```
*No need to Panic!* Just add your desired theme in style.scss:
``` bash
@import '@angular/material/prebuilt-themes/indigo-pink.css'
```
Angular Material Library is now installed in your project.

1
_config.yml Normal file
View File

@ -0,0 +1 @@
theme: jekyll-theme-architect

188
angular.json Normal file
View File

@ -0,0 +1,188 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"cli": {
"defaultCollection": "@angular-eslint/schematics",
"analytics": false
},
"version": 1,
"newProjectRoot": "projects",
"projects": {
"angular-electron": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"schematics": {
"@schematics/angular:application": {
"strict": true
},
"@schematics/angular:component": {
"style": "scss"
}
},
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-builders/custom-webpack:browser",
"options": {
"outputPath": "dist",
"index": "src/index.html",
"main": "src/main.ts",
"tsConfig": "src/tsconfig.app.json",
"polyfills": "src/polyfills.ts",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": [],
"customWebpackConfig": {
"path": "./angular.webpack.js",
"replaceDuplicatePlugins": true
}
},
"configurations": {
"dev": {
"optimization": false,
"outputHashing": "none",
"sourceMap": true,
"namedChunks": false,
"aot": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": false,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.dev.ts"
}
]
},
"production": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
]
},
"web": {
"optimization": false,
"outputHashing": "none",
"sourceMap": true,
"namedChunks": false,
"aot": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": false,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.web.ts"
}
]
},
"web-production": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.web.prod.ts"
}
]
}
}
},
"serve": {
"builder": "@angular-builders/custom-webpack:dev-server",
"options": {
"browserTarget": "angular-electron:build"
},
"configurations": {
"dev": {
"browserTarget": "angular-electron:build:dev"
},
"production": {
"browserTarget": "angular-electron:build:production"
},
"web": {
"browserTarget": "angular-electron:build:web"
},
"web-production": {
"browserTarget": "angular-electron:build:web-production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "angular-electron:build"
}
},
"test": {
"builder": "@angular-builders/custom-webpack:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills-test.ts",
"tsConfig": "src/tsconfig.spec.json",
"karmaConfig": "src/karma.conf.js",
"inlineStyleLanguage": "scss",
"scripts": [],
"styles": [
"src/styles.scss"
],
"assets": [
"src/favicon.ico",
"src/assets"
],
"customWebpackConfig": {
"path": "./angular.webpack.js",
"replaceDuplicatePlugins": true
}
}
},
"lint": {
"builder": "@angular-eslint/builder:lint",
"options": {
"lintFilePatterns": [
"src/**/*.ts",
"src/**/*.html"
]
}
}
}
},
"angular-electron-e2e": {
"root": "e2e",
"projectType": "application",
"architect": {
"lint": {
"builder": "@angular-eslint/builder:lint",
"options": {
"lintFilePatterns": [
"e2e/**/*.ts"
]
}
}
}
}
},
"defaultProject": "angular-electron"
}

32
angular.webpack.js Normal file
View File

@ -0,0 +1,32 @@
//Polyfill Node.js core modules in Webpack. This module is only needed for webpack 5+.
const NodePolyfillPlugin = require("node-polyfill-webpack-plugin");
/**
* Custom angular webpack configuration
*/
module.exports = (config, options) => {
config.target = 'electron-renderer';
if (options.fileReplacements) {
for(let fileReplacement of options.fileReplacements) {
if (fileReplacement.replace !== 'src/environments/environment.ts') {
continue;
}
let fileReplacementParts = fileReplacement['with'].split('.');
if (fileReplacementParts.length > 1 && ['web'].indexOf(fileReplacementParts[1]) >= 0) {
config.target = 'web';
}
break;
}
}
config.plugins = [
...config.plugins,
new NodePolyfillPlugin({
excludeAliases: ["console"]
})
];
return config;
}

73
app/main.js Normal file
View File

@ -0,0 +1,73 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const electron_1 = require("electron");
const path = require("path");
const fs = require("fs");
let win = null;
const args = process.argv.slice(1), serve = args.some(val => val === '--serve');
function createWindow() {
const size = electron_1.screen.getPrimaryDisplay().workAreaSize;
// Create the browser window.
win = new electron_1.BrowserWindow({
x: 0,
y: 0,
width: size.width,
height: size.height,
webPreferences: {
nodeIntegration: true,
allowRunningInsecureContent: (serve),
contextIsolation: false, // false if you want to run e2e test with Spectron
},
});
if (serve) {
const debug = require('electron-debug');
debug();
require('electron-reloader')(module);
win.loadURL('http://localhost:4200');
}
else {
// Path when running electron executable
let pathIndex = './index.html';
if (fs.existsSync(path.join(__dirname, '../dist/index.html'))) {
// Path when running electron in local folder
pathIndex = '../dist/index.html';
}
const url = new URL(path.join('file:', __dirname, pathIndex));
win.loadURL(url.href);
}
// Emitted when the window is closed.
win.on('closed', () => {
// Dereference the window object, usually you would store window
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
win = null;
});
return win;
}
try {
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
// Added 400 ms to fix the black background issue while using transparent window. More detais at https://github.com/electron/electron/issues/15947
electron_1.app.on('ready', () => setTimeout(createWindow, 400));
// Quit when all windows are closed.
electron_1.app.on('window-all-closed', () => {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
electron_1.app.quit();
}
});
electron_1.app.on('activate', () => {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (win === null) {
createWindow();
}
});
}
catch (e) {
// Catch Error
// throw e;
}
//# sourceMappingURL=main.js.map

1
app/main.js.map Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"main.js","sourceRoot":"","sources":["main.ts"],"names":[],"mappings":";;AAAA,uCAAoD;AACpD,6BAA6B;AAC7B,yBAAyB;AAEzB,IAAI,GAAG,GAAkB,IAAI,CAAC;AAC9B,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAChC,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC;AAE9C,SAAS,YAAY;IAEnB,MAAM,IAAI,GAAG,iBAAM,CAAC,iBAAiB,EAAE,CAAC,YAAY,CAAC;IAErD,6BAA6B;IAC7B,GAAG,GAAG,IAAI,wBAAa,CAAC;QACtB,CAAC,EAAE,CAAC;QACJ,CAAC,EAAE,CAAC;QACJ,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,cAAc,EAAE;YACd,eAAe,EAAE,IAAI;YACrB,2BAA2B,EAAE,CAAC,KAAK,CAAC;YACpC,gBAAgB,EAAE,KAAK,EAAG,kDAAkD;SAC7E;KACF,CAAC,CAAC;IAEH,IAAI,KAAK,EAAE;QACT,MAAM,KAAK,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;QACxC,KAAK,EAAE,CAAC;QAER,OAAO,CAAC,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;QACrC,GAAG,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC;KACtC;SAAM;QACL,wCAAwC;QACxC,IAAI,SAAS,GAAG,cAAc,CAAC;QAE/B,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,oBAAoB,CAAC,CAAC,EAAE;YAC5D,6CAA6C;YAC9C,SAAS,GAAG,oBAAoB,CAAC;SAClC;QAED,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC;QAC9D,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;KACvB;IAED,qCAAqC;IACrC,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;QACpB,gEAAgE;QAChE,mEAAmE;QACnE,oDAAoD;QACpD,GAAG,GAAG,IAAI,CAAC;IACb,CAAC,CAAC,CAAC;IAEH,OAAO,GAAG,CAAC;AACb,CAAC;AAED,IAAI;IACF,wDAAwD;IACxD,yDAAyD;IACzD,sDAAsD;IACtD,kJAAkJ;IAClJ,cAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC,CAAC;IAErD,oCAAoC;IACpC,cAAG,CAAC,EAAE,CAAC,mBAAmB,EAAE,GAAG,EAAE;QAC/B,2DAA2D;QAC3D,8DAA8D;QAC9D,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE;YACjC,cAAG,CAAC,IAAI,EAAE,CAAC;SACZ;IACH,CAAC,CAAC,CAAC;IAEH,cAAG,CAAC,EAAE,CAAC,UAAU,EAAE,GAAG,EAAE;QACtB,gEAAgE;QAChE,4DAA4D;QAC5D,IAAI,GAAG,KAAK,IAAI,EAAE;YAChB,YAAY,EAAE,CAAC;SAChB;IACH,CAAC,CAAC,CAAC;CAEJ;AAAC,OAAO,CAAC,EAAE;IACV,cAAc;IACd,WAAW;CACZ"}

83
app/main.ts Normal file
View File

@ -0,0 +1,83 @@
import {app, BrowserWindow, screen} from 'electron';
import * as path from 'path';
import * as fs from 'fs';
let win: BrowserWindow = null;
const args = process.argv.slice(1),
serve = args.some(val => val === '--serve');
function createWindow(): BrowserWindow {
const size = screen.getPrimaryDisplay().workAreaSize;
// Create the browser window.
win = new BrowserWindow({
x: 0,
y: 0,
width: size.width,
height: size.height,
webPreferences: {
nodeIntegration: true,
allowRunningInsecureContent: (serve),
contextIsolation: false, // false if you want to run e2e test with Spectron
},
});
if (serve) {
const debug = require('electron-debug');
debug();
require('electron-reloader')(module);
win.loadURL('http://localhost:4200');
} else {
// Path when running electron executable
let pathIndex = './index.html';
if (fs.existsSync(path.join(__dirname, '../dist/index.html'))) {
// Path when running electron in local folder
pathIndex = '../dist/index.html';
}
const url = new URL(path.join('file:', __dirname, pathIndex));
win.loadURL(url.href);
}
// Emitted when the window is closed.
win.on('closed', () => {
// Dereference the window object, usually you would store window
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
win = null;
});
return win;
}
try {
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
// Added 400 ms to fix the black background issue while using transparent window. More detais at https://github.com/electron/electron/issues/15947
app.on('ready', () => setTimeout(createWindow, 400));
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (win === null) {
createWindow();
}
});
} catch (e) {
// Catch Error
// throw e;
}

12
app/package-lock.json generated Normal file
View File

@ -0,0 +1,12 @@
{
"name": "angular-electron",
"version": "11.1.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "angular-electron",
"version": "11.1.0"
}
}
}

13
app/package.json Normal file
View File

@ -0,0 +1,13 @@
{
"name": "angular-electron",
"description": "Angular Electron sample",
"author": {
"name": "Maxime GRIS",
"email": "maxime.gris@gmail.com"
},
"version": "11.1.0",
"main": "main.js",
"private": true,
"dependencies": {
}
}

59
e2e/main.spec.ts Normal file
View File

@ -0,0 +1,59 @@
import { BrowserContext, ElectronApplication, Page, _electron as electron } from 'playwright';
import { test, expect } from '@playwright/test';
const PATH = require('path');
test.describe('Check Home Page', async () => {
let app: ElectronApplication;
let firstWindow: Page;
let context: BrowserContext;
test.beforeAll( async () => {
app = await electron.launch({ args: [PATH.join(__dirname, '../app/main.js'), PATH.join(__dirname, '../app/package.json')] });
context = app.context();
await context.tracing.start({ screenshots: true, snapshots: true });
firstWindow = await app.firstWindow();
await firstWindow.waitForLoadState('domcontentloaded');
});
test('Launch electron app', async () => {
const windowState: { isVisible: boolean; isDevToolsOpened: boolean; isCrashed: boolean } = await app.evaluate(async (process) => {
const mainWindow = process.BrowserWindow.getAllWindows()[0];
const getState = () => ({
isVisible: mainWindow.isVisible(),
isDevToolsOpened: mainWindow.webContents.isDevToolsOpened(),
isCrashed: mainWindow.webContents.isCrashed(),
});
return new Promise((resolve) => {
if (mainWindow.isVisible()) {
resolve(getState());
} else {
mainWindow.once('ready-to-show', () => setTimeout(() => resolve(getState()), 0));
}
});
});
expect(windowState.isVisible).toBeTruthy();
expect(windowState.isDevToolsOpened).toBeFalsy();
expect(windowState.isCrashed).toBeFalsy();
});
// test('Check Home Page design', async ({ browserName}) => {
// // Uncomment if you change the design of Home Page in order to create a new screenshot
// const screenshot = await firstWindow.screenshot({ path: '/tmp/home.png' });
// expect(screenshot).toMatchSnapshot(`home-${browserName}.png`);
// });
test('Check title', async () => {
const elem = await firstWindow.$('app-home h1');
const text = await elem.innerText();
expect(text).toBe('App works !');
});
test.afterAll( async () => {
await context.tracing.stop({ path: 'e2e/tracing/trace.zip' });
await app.close();
});
});

19
e2e/playwright.config.ts Normal file
View File

@ -0,0 +1,19 @@
/** @type {import('@playwright/test').PlaywrightTestConfig} */
const config = {
testDir: '.',
timeout: 45000,
outputDir: './screenshots',
use: {
headless: false,
viewport: { width: 1280, height: 720 },
launchOptions: {
slowMo: 1000,
},
trace: 'on',
},
expect: {
toMatchSnapshot: { threshold: 0.2 },
},
};
module.exports = config;

13
e2e/tsconfig.e2e.json Normal file
View File

@ -0,0 +1,13 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"types": [
"node"
]
},
"include": [
"**.spec.ts"
],
}

38
electron-builder.json Normal file
View File

@ -0,0 +1,38 @@
{
"asar": true,
"directories": {
"output": "release/"
},
"files": [
"**/*",
"!**/*.ts",
"!*.map",
"!package.json",
"!package-lock.json",
{
"from": "../dist",
"filter": ["**/*"]
}
],
"win": {
"icon": "dist/assets/icons",
"target": [
"portable"
]
},
"portable": {
"splashImage": "dist/assets/icons/electron.bmp"
},
"mac": {
"icon": "dist/assets/icons",
"target": [
"dmg"
]
},
"linux": {
"icon": "dist/assets/icons",
"target": [
"AppImage"
]
}
}

37082
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

108
package.json Normal file
View File

@ -0,0 +1,108 @@
{
"name": "skirda-launcher",
"version": "0.0.0",
"description": "no text",
"homepage": "https://github.com/maximegris/angular-electron",
"author": {
"name": "Greg Brzezinski",
"email": "gregory_brzezinski@protonmail.com"
},
"keywords": [
"angular",
"angular 14",
"electron",
"electron 19",
"nodejs",
"typescript",
"playwright",
"eslint",
"sass",
"windows",
"mac",
"linux"
],
"main": "app/main.js",
"private": true,
"scripts": {
"postinstall": "electron-builder install-app-deps",
"ng": "ng",
"start": "npm-run-all -p electron:serve ng:serve",
"ng:serve": "ng serve -c web -o",
"build": "npm run electron:serve-tsc && ng build --base-href ./",
"build:dev": "npm run build -- -c dev",
"build:prod": "npm run build -- -c production",
"web:build": "npm run build -- -c web-production",
"electron": "electron",
"electron:serve-tsc": "tsc -p tsconfig.serve.json",
"electron:serve": "wait-on tcp:4200 && npm run electron:serve-tsc && electron . --serve",
"electron:local": "npm run build:prod && electron .",
"electron:build": "npm run build:prod && electron-builder build --publish=never",
"test": "ng test --watch=false",
"test:watch": "ng test",
"e2e": "npm run build:prod && playwright test -c e2e/playwright.config.ts e2e/",
"e2e:show-trace": "playwright show-trace e2e/tracing/trace.zip",
"version": "conventional-changelog -i CHANGELOG.md -s -r 0 && git add CHANGELOG.md",
"lint": "ng lint"
},
"dependencies": {
"@angular/common": "14.0.6",
"@angular/compiler": "14.0.6",
"@angular/core": "14.0.6",
"@angular/forms": "14.0.6",
"@angular/language-service": "14.0.6",
"@angular/platform-browser": "14.0.6",
"@angular/platform-browser-dynamic": "14.0.6",
"@angular/router": "14.0.6",
"rxjs": "7.5.6",
"tslib": "^2.4.0",
"zone.js": "~0.11.6"
},
"devDependencies": {
"@angular-builders/custom-webpack": "14.0.0",
"@angular-devkit/build-angular": "14.0.6",
"@angular-eslint/builder": "14.0.2",
"@angular-eslint/eslint-plugin": "14.0.2",
"@angular-eslint/eslint-plugin-template": "14.0.2",
"@angular-eslint/schematics": "14.0.2",
"@angular-eslint/template-parser": "14.0.2",
"@angular/cli": "14.0.6",
"@angular/compiler-cli": "14.0.6",
"@ngx-translate/core": "14.0.0",
"@ngx-translate/http-loader": "7.0.0",
"@playwright/test": "1.23.4",
"@types/jasmine": "4.0.3",
"@types/jasminewd2": "2.0.10",
"@types/node": "18.0.6",
"@typescript-eslint/eslint-plugin": "5.30.7",
"@typescript-eslint/parser": "5.30.7",
"conventional-changelog-cli": "2.2.2",
"electron": "19.0.8",
"electron-builder": "23.1.0",
"electron-debug": "3.2.0",
"electron-reloader": "1.2.3",
"eslint": "8.20.0",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-jsdoc": "39.3.3",
"eslint-plugin-prefer-arrow": "1.2.3",
"jasmine-core": "4.2.0",
"jasmine-spec-reporter": "7.0.0",
"karma": "6.4.0",
"karma-coverage-istanbul-reporter": "3.0.3",
"karma-electron": "7.2.0",
"karma-jasmine": "5.1.0",
"karma-jasmine-html-reporter": "2.0.0",
"node-polyfill-webpack-plugin": "2.0.0",
"npm-run-all": "4.1.5",
"playwright": "1.23.4",
"ts-node": "10.9.1",
"typescript": "~4.7.4",
"wait-on": "6.0.1",
"webdriver-manager": "12.1.8"
},
"engines": {
"node": ">= 14.15.0"
},
"browserslist": [
"chrome 100"
]
}

View File

@ -0,0 +1,28 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { PageNotFoundComponent } from './shared/components';
import { HomeRoutingModule } from './home/home-routing.module';
import { DetailRoutingModule } from './detail/detail-routing.module';
const routes: Routes = [
{
path: '',
redirectTo: 'home',
pathMatch: 'full'
},
{
path: '**',
component: PageNotFoundComponent
}
];
@NgModule({
imports: [
RouterModule.forRoot(routes, { relativeLinkResolution: 'legacy' }),
HomeRoutingModule,
DetailRoutingModule
],
exports: [RouterModule]
})
export class AppRoutingModule { }

View File

@ -0,0 +1 @@
<router-outlet></router-outlet>

View File

@ -0,0 +1,3 @@
:host {
}

View File

@ -0,0 +1,21 @@
import { TestBed, waitForAsync } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
import { TranslateModule } from '@ngx-translate/core';
import { ElectronService } from './core/services';
describe('AppComponent', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [AppComponent],
providers: [ElectronService],
imports: [RouterTestingModule, TranslateModule.forRoot()]
}).compileComponents();
}));
it('should create the app', waitForAsync(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
});

28
src/app/app.component.ts Normal file
View File

@ -0,0 +1,28 @@
import { Component } from '@angular/core';
import { ElectronService } from './core/services';
import { TranslateService } from '@ngx-translate/core';
import { APP_CONFIG } from '../environments/environment';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
constructor(
private electronService: ElectronService,
private translate: TranslateService
) {
this.translate.setDefaultLang('en');
console.log('APP_CONFIG', APP_CONFIG);
if (electronService.isElectron) {
console.log(process.env);
console.log('Run in electron');
console.log('Electron ipcRenderer', this.electronService.ipcRenderer);
console.log('NodeJS childProcess', this.electronService.childProcess);
} else {
console.log('Run in browser');
}
}
}

44
src/app/app.module.ts Normal file
View File

@ -0,0 +1,44 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpClientModule, HttpClient } from '@angular/common/http';
import { CoreModule } from './core/core.module';
import { SharedModule } from './shared/shared.module';
import { AppRoutingModule } from './app-routing.module';
// NG Translate
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { HomeModule } from './home/home.module';
import { DetailModule } from './detail/detail.module';
import { AppComponent } from './app.component';
// AoT requires an exported function for factories
const httpLoaderFactory = (http: HttpClient): TranslateHttpLoader => new TranslateHttpLoader(http, './assets/i18n/', '.json');
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
FormsModule,
HttpClientModule,
CoreModule,
SharedModule,
HomeModule,
DetailModule,
AppRoutingModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: httpLoaderFactory,
deps: [HttpClient]
}
})
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {}

View File

@ -0,0 +1,10 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
@NgModule({
declarations: [],
imports: [
CommonModule
]
})
export class CoreModule { }

View File

@ -0,0 +1,12 @@
import { TestBed } from '@angular/core/testing';
import { ElectronService } from './electron.service';
describe('ElectronService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: ElectronService = TestBed.get(ElectronService);
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,56 @@
import { Injectable } from '@angular/core';
// If you import a module but never use any of the imported values other than as TypeScript types,
// the resulting javascript file will look as if you never imported the module at all.
import { ipcRenderer, webFrame } from 'electron';
import * as childProcess from 'child_process';
import * as fs from 'fs';
@Injectable({
providedIn: 'root'
})
export class ElectronService {
ipcRenderer: typeof ipcRenderer;
webFrame: typeof webFrame;
childProcess: typeof childProcess;
fs: typeof fs;
constructor() {
// Conditional imports
if (this.isElectron) {
this.ipcRenderer = window.require('electron').ipcRenderer;
this.webFrame = window.require('electron').webFrame;
this.fs = window.require('fs');
this.childProcess = window.require('child_process');
this.childProcess.exec('node -v', (error, stdout, stderr) => {
if (error) {
console.error(`error: ${error.message}`);
return;
}
if (stderr) {
console.error(`stderr: ${stderr}`);
return;
}
console.log(`stdout:\n${stdout}`);
});
// Notes :
// * A NodeJS's dependency imported with 'window.require' MUST BE present in `dependencies` of both `app/package.json`
// and `package.json (root folder)` in order to make it work here in Electron's Renderer process (src folder)
// because it will loaded at runtime by Electron.
// * A NodeJS's dependency imported with TS module import (ex: import { Dropbox } from 'dropbox') CAN only be present
// in `dependencies` of `package.json (root folder)` because it is loaded during build phase and does not need to be
// in the final bundle. Reminder : only if not used in Electron's Main process (app folder)
// If you want to use a NodeJS 3rd party deps in Renderer process,
// ipcRenderer.invoke can serve many common use cases.
// https://www.electronjs.org/docs/latest/api/ipc-renderer#ipcrendererinvokechannel-args
}
}
get isElectron(): boolean {
return !!(window && window.process && window.process.type);
}
}

View File

@ -0,0 +1 @@
export * from './electron/electron.service';

View File

@ -0,0 +1,18 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Routes, RouterModule } from '@angular/router';
import { DetailComponent } from './detail.component';
const routes: Routes = [
{
path: 'detail',
component: DetailComponent
}
];
@NgModule({
declarations: [],
imports: [CommonModule, RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class DetailRoutingModule {}

View File

@ -0,0 +1,7 @@
<div class="container">
<h1 class="title">
{{ 'PAGES.DETAIL.TITLE' | translate }}
</h1>
<a routerLink="/">{{ 'PAGES.DETAIL.BACK_TO_HOME' | translate }}</a>
</div>

View File

@ -0,0 +1,3 @@
:host {
}

View File

@ -0,0 +1,33 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { DetailComponent } from './detail.component';
import { TranslateModule } from '@ngx-translate/core';
import { RouterTestingModule } from '@angular/router/testing';
describe('DetailComponent', () => {
let component: DetailComponent;
let fixture: ComponentFixture<DetailComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [DetailComponent],
imports: [TranslateModule.forRoot(), RouterTestingModule]
}).compileComponents();
fixture = TestBed.createComponent(DetailComponent);
component = fixture.componentInstance;
fixture.detectChanges();
}));
it('should create', () => {
expect(component).toBeTruthy();
});
it('should render title in a h1 tag', waitForAsync(() => {
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain(
'PAGES.DETAIL.TITLE'
);
}));
});

View File

@ -0,0 +1,16 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-detail',
templateUrl: './detail.component.html',
styleUrls: ['./detail.component.scss']
})
export class DetailComponent implements OnInit {
constructor() { }
ngOnInit(): void {
console.log('DetailComponent INIT');
}
}

View File

@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DetailRoutingModule } from './detail-routing.module';
import { DetailComponent } from './detail.component';
import { SharedModule } from '../shared/shared.module';
@NgModule({
declarations: [DetailComponent],
imports: [CommonModule, SharedModule, DetailRoutingModule]
})
export class DetailModule {}

View File

@ -0,0 +1,18 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './home.component';
const routes: Routes = [
{
path: 'home',
component: HomeComponent
}
];
@NgModule({
declarations: [],
imports: [CommonModule, RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class HomeRoutingModule {}

View File

@ -0,0 +1,7 @@
<div class="container">
<h1 class="title">
{{ 'PAGES.HOME.TITLE' | translate }}
</h1>
<a routerLink="/detail">{{ 'PAGES.HOME.GO_TO_DETAIL' | translate }}</a>
</div>

View File

@ -0,0 +1,3 @@
:host {
}

View File

@ -0,0 +1,32 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { HomeComponent } from './home.component';
import { TranslateModule } from '@ngx-translate/core';
import { RouterTestingModule } from '@angular/router/testing';
describe('HomeComponent', () => {
let component: HomeComponent;
let fixture: ComponentFixture<HomeComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [HomeComponent],
imports: [TranslateModule.forRoot(), RouterTestingModule]
}).compileComponents();
fixture = TestBed.createComponent(HomeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
}));
it('should create', () => {
expect(component).toBeTruthy();
});
it('should render title in a h1 tag', waitForAsync(() => {
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain(
'PAGES.HOME.TITLE'
);
}));
});

View File

@ -0,0 +1,17 @@
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
constructor(private router: Router) { }
ngOnInit(): void {
console.log('HomeComponent INIT');
}
}

View File

@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HomeRoutingModule } from './home-routing.module';
import { HomeComponent } from './home.component';
import { SharedModule } from '../shared/shared.module';
@NgModule({
declarations: [HomeComponent],
imports: [CommonModule, SharedModule, HomeRoutingModule]
})
export class HomeModule {}

View File

@ -0,0 +1 @@
export * from './page-not-found/page-not-found.component';

View File

@ -0,0 +1,3 @@
<p>
page-not-found works!
</p>

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { PageNotFoundComponent } from './page-not-found.component';
describe('PageNotFoundComponent', () => {
let component: PageNotFoundComponent;
let fixture: ComponentFixture<PageNotFoundComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [PageNotFoundComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PageNotFoundComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,14 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-page-not-found',
templateUrl: './page-not-found.component.html',
styleUrls: ['./page-not-found.component.scss']
})
export class PageNotFoundComponent implements OnInit {
constructor() {}
ngOnInit(): void {
console.log('PageNotFoundComponent INIT');
}
}

View File

@ -0,0 +1 @@
export * from './webview/webview.directive';

View File

@ -0,0 +1,8 @@
import { WebviewDirective } from './webview.directive';
describe('WebviewDirective', () => {
it('should create an instance', () => {
const directive = new WebviewDirective();
expect(directive).toBeTruthy();
});
});

View File

@ -0,0 +1,8 @@
import { Directive } from '@angular/core';
@Directive({
selector: 'webview'
})
export class WebviewDirective {
constructor() { }
}

View File

@ -0,0 +1,15 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
import { PageNotFoundComponent } from './components/';
import { WebviewDirective } from './directives/';
import { FormsModule } from '@angular/forms';
@NgModule({
declarations: [PageNotFoundComponent, WebviewDirective],
imports: [CommonModule, TranslateModule, FormsModule],
exports: [TranslateModule, WebviewDirective, FormsModule]
})
export class SharedModule {}

0
src/assets/.gitkeep Normal file
View File

BIN
src/assets/background.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

12
src/assets/i18n/en.json Normal file
View File

@ -0,0 +1,12 @@
{
"PAGES": {
"HOME": {
"TITLE": "App works !",
"GO_TO_DETAIL": "Go to Detail"
},
"DETAIL": {
"TITLE": "Detail page !",
"BACK_TO_HOME": "Back to Home"
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 446 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,4 @@
export const APP_CONFIG = {
production: false,
environment: 'DEV'
};

View File

@ -0,0 +1,4 @@
export const APP_CONFIG = {
production: true,
environment: 'PROD'
};

View File

@ -0,0 +1,4 @@
export const APP_CONFIG = {
production: false,
environment: 'LOCAL'
};

View File

@ -0,0 +1,4 @@
export const APP_CONFIG = {
production: true,
environment: 'WEB-PROD'
};

View File

@ -0,0 +1,4 @@
export const APP_CONFIG = {
production: false,
environment: 'WEB'
};

BIN
src/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 948 B

14
src/index.html Normal file
View File

@ -0,0 +1,14 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Angular Electron</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="assets/icons/favicon.ico">
</head>
<body>
<app-root>Loading...</app-root>
</body>
</html>

45
src/karma.conf.js Normal file
View File

@ -0,0 +1,45 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/0.13/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-electron'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client:{
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../coverage'),
reports: [ 'html', 'lcovonly' ],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
browsers: ['AngularElectron'],
customLaunchers: {
AngularElectron: {
base: 'Electron',
flags: [
'--remote-debugging-port=9222'
],
browserWindowOptions: {
webPreferences: {
nodeIntegration: true,
nodeIntegrationInSubFrames: true,
allowRunningInsecureContent: true,
contextIsolation: false
}
}
}
}
});
};

15
src/main.ts Normal file
View File

@ -0,0 +1,15 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { APP_CONFIG } from './environments/environment';
if (APP_CONFIG.production) {
enableProdMode();
}
platformBrowserDynamic()
.bootstrapModule(AppModule, {
preserveWhitespaces: false
})
.catch(err => console.error(err));

1
src/polyfills-test.ts Normal file
View File

@ -0,0 +1 @@
import 'zone.js';

53
src/polyfills.ts Normal file
View File

@ -0,0 +1,53 @@
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
* Learn more in https://angular.io/guide/browser-support
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
* because those flags need to be set before `zone.js` being loaded, and webpack
* will put import in the top of bundle, so user need to create a separate file
* in this directory (for example: zone-flags.ts), and put the following flags
* into that file, and then add the following code before importing zone.js.
* import './zone-flags.ts';
*
* The flags allowed in zone-flags.ts are listed here.
*
* The following flags will work for all browsers.
*
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*
* (window as any).__Zone_enable_cross_context_check = true;
*
*/
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/

50
src/styles.scss Normal file
View File

@ -0,0 +1,50 @@
/* You can add global styles to this file, and also import other style files */
html, body {
margin: 0;
padding: 0;
height: 100%;
font-family: Arial, Helvetica, sans-serif;
}
/* CAN (MUST) BE REMOVED ! Sample Global style */
.container {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: url(./assets/background.jpg) no-repeat center fixed;
-webkit-background-size: cover; /* pour anciens Chrome et Safari */
background-size: cover; /* version standardisée */
.title {
color: white;
margin: 0;
padding: 50px 20px;
}
a {
color: #fff !important;
text-transform: uppercase;
text-decoration: none;
background: #ed3330;
padding: 20px;
border-radius: 5px;
display: inline-block;
border: none;
transition: all 0.4s ease 0s;
&:hover {
background: #fff;
color: #ed3330 !important;
letter-spacing: 1px;
-webkit-box-shadow: 0px 5px 40px -10px rgba(0,0,0,0.57);
-moz-box-shadow: 0px 5px 40px -10px rgba(0,0,0,0.57);
box-shadow: 5px 40px -10px rgba(0,0,0,0.57);
transition: all 0.4s ease 0s;
}
}
}

22
src/test.ts Normal file
View File

@ -0,0 +1,22 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: any;
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting(), {
teardown: { destroyAfterEach: false }
}
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

20
src/tsconfig.app.json Normal file
View File

@ -0,0 +1,20 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"baseUrl": "",
"types": [
"node"
]
},
"files": [
"main.ts",
"polyfills.ts"
],
"include": [
"**/*.d.ts"
],
"exclude": [
"**/*.spec.ts"
]
}

23
src/tsconfig.spec.json Normal file
View File

@ -0,0 +1,23 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/spec",
"types": [
"jasmine",
"node"
]
},
"files": [
"test.ts",
"polyfills-test.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
],
"exclude": [
"dist",
"release",
"node_modules"
]
}

9
src/typings.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
/* SystemJS module definition */
declare const nodeModule: NodeModule;
interface NodeModule {
id: string;
}
interface Window {
process: any;
require: any;
}

36
tsconfig.json Normal file
View File

@ -0,0 +1,36 @@
{
"compileOnSave": false,
"compilerOptions": {
"outDir": "./dist/out-tsc",
"module": "es2020",
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowJs": true,
"target": "es2015",
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2017",
"es2016",
"es2015",
"dom"
]
},
"exclude": [
"node_modules"
],
"angularCompilerOptions": {
"strictTemplates": true,
"fullTemplateTypeCheck": true,
"annotateForClosureCompiler": true,
"strictInjectionParameters": true,
"skipTemplateCodegen": false,
"preserveWhitespaces": true,
"skipMetadataEmit": false,
"disableTypeScriptVersionCheck": true
}
}

27
tsconfig.serve.json Normal file
View File

@ -0,0 +1,27 @@
{
"compilerOptions": {
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"module": "commonjs",
"target": "es2015",
"types": [
"node"
],
"lib": [
"es2017",
"es2016",
"es2015",
"dom"
]
},
"files": [
"app/main.ts"
],
"exclude": [
"node_modules",
"**/*.spec.ts"
]
}