Compare commits
17 Commits
87a43b0b37
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| f34d8e836a | |||
|
|
c891fdad45 | ||
|
|
128d4da9d9 | ||
|
|
417179a5e2 | ||
|
|
37e6e6d003 | ||
| afa0518f5f | |||
|
|
11bdecc6d3 | ||
|
|
7611f10b1c | ||
|
|
80ffb8b70c | ||
|
|
6422846605 | ||
|
|
f1fce63e24 | ||
|
|
fa6222a1a8 | ||
|
|
b37c040655 | ||
|
|
573b91d224 | ||
|
|
344127b617 | ||
|
|
eed389b59b | ||
|
|
a69186daaa |
@@ -1,11 +1,13 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"es2020": true,
|
||||
"node": true
|
||||
},
|
||||
"plugins": ["@typescript-eslint", "import", "unicorn", "sonarjs"],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:unicorn/recommended",
|
||||
"plugin:sonarjs/recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:import/errors",
|
||||
@@ -15,8 +17,8 @@
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"settings": {
|
||||
"import/resolver": {
|
||||
"node": {
|
||||
"extensions": [".js", ".jsx", ".ts", ".tsx"]
|
||||
"typescript": {
|
||||
"project": "workspaces/*/tsconfig.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -28,11 +30,7 @@
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-explicit-any": [
|
||||
"error",
|
||||
{
|
||||
"ignoreRestArgs": true
|
||||
}
|
||||
]
|
||||
"@typescript-eslint/no-explicit-any": ["off"],
|
||||
"unicorn/prefer-module": "off"
|
||||
}
|
||||
}
|
||||
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macOS-latest, ubuntu-latest, windows-latest]
|
||||
node-version: [12.x, 14.x, 16.x]
|
||||
node-version: [14.x, 16.x, 18.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
64
.gitignore
vendored
64
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
# ---> Node
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
@@ -5,6 +6,7 @@ npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
@@ -26,6 +28,12 @@ coverage
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
@@ -36,6 +44,8 @@ build/Release
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
@@ -48,6 +58,15 @@ typings/
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
@@ -57,6 +76,31 @@ typings/
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
.dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
@@ -73,6 +117,13 @@ typings/
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
@@ -82,6 +133,19 @@ typings/
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
# Webpack
|
||||
.webpack/
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
#!/usr/bin/env sh
|
||||
if [ -z "$husky_skip_init" ]; then
|
||||
debug () {
|
||||
if [ "$HUSKY_DEBUG" = "1" ]; then
|
||||
@@ -6,7 +6,7 @@ if [ -z "$husky_skip_init" ]; then
|
||||
fi
|
||||
}
|
||||
|
||||
readonly hook_name="$(basename "$0")"
|
||||
readonly hook_name="$(basename -- "$0")"
|
||||
debug "starting $hook_name..."
|
||||
|
||||
if [ "$HUSKY" = "0" ]; then
|
||||
@@ -19,7 +19,8 @@ if [ -z "$husky_skip_init" ]; then
|
||||
. ~/.huskyrc
|
||||
fi
|
||||
|
||||
export readonly husky_skip_init=1
|
||||
readonly husky_skip_init=1
|
||||
export husky_skip_init
|
||||
sh -e "$0" "$@"
|
||||
exitCode="$?"
|
||||
|
||||
@@ -27,5 +28,9 @@ if [ -z "$husky_skip_init" ]; then
|
||||
echo "husky - $hook_name hook exited with code $exitCode (error)"
|
||||
fi
|
||||
|
||||
if [ $exitCode = 127 ]; then
|
||||
echo "husky - command not found in PATH=$PATH"
|
||||
fi
|
||||
|
||||
exit $exitCode
|
||||
fi
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
npx --no-install commitlint --edit "$1"
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
npx lint-staged
|
||||
npx pretty-quick --staged
|
||||
17
.vscode/launch.json
vendored
Normal file
17
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch Program",
|
||||
"skipFiles": ["<node_internals>/**"],
|
||||
"program": "${workspaceFolder}\\.webpack\\main",
|
||||
"preLaunchTask": "tsc: build - tsconfig.json",
|
||||
"outFiles": ["${workspaceFolder}/.dist/**/*.js"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -2,6 +2,15 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
## [3.2.0](https://github.com/sourcygen/electron-angular-quick-start/compare/v3.1.0...v3.2.0) (2022-09-24)
|
||||
|
||||
### Features
|
||||
|
||||
- angular upgrade - 13.3.2 => 14.2.3 ([fa6222a](https://github.com/sourcygen/electron-angular-quick-start/commit/fa6222a1a86c0528a09f901a97f61f374933e2a6))
|
||||
- electron upgrade - 18.0.3 => 20.2.0 ([f1fce63](https://github.com/sourcygen/electron-angular-quick-start/commit/f1fce63e24fc8c0ca53a97a83e337ea36332e645))
|
||||
|
||||
## [3.1.0](https://github.com/sourcygen/electron-angular-quick-start/compare/v3.0.0...v3.1.0) (2022-04-07)
|
||||
|
||||
## [3.0.0](https://github.com/sourcygen/electron-angular-quick-start/compare/v2.4.0...v3.0.0) (2022-04-07)
|
||||
|
||||
### Features
|
||||
|
||||
23
LICENSE
23
LICENSE
@@ -1,21 +1,10 @@
|
||||
MIT License
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Copyright (c) 2020 Sourcygen
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law.
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
For more information, please refer to <http://unlicense.org/>
|
||||
|
||||
152
README.md
152
README.md
@@ -1,152 +1,2 @@
|
||||
<a href="https://github.com/sourcygen/electron-angular-quick-start/actions"><img src="https://github.com/sourcygen/electron-angular-quick-start/workflows/CI/badge.svg?branch=master" alt="CI Status"/></a>
|
||||
<a href="https://gitHub.com/sourcygen/electron-angular-quick-start/graphs/commit-activity"><img src="https://img.shields.io/badge/maintained-yes-brightgreen.svg" alt="Maintenance Status"/></a>
|
||||
<a href="https://github.com/sourcygen/electron-angular-quick-start/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-brightgreen.svg" alt="License MIT"></a>
|
||||
<a href="http://makeapullrequest.com"><img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg" alt="PRs Welcome"></a>
|
||||
<a href="https://twitter.com/share?text=Build%20cross%20platform%20desktop%20app%20with%20Electron%20and%20Angular%20%F0%9F%9A%80&url=https://github.com/sourcygen/electron-angular-quick-start"><img src="https://img.shields.io/twitter/url/https/github.com/sourcygen/electron-angular-quick-start.svg?style=social" alt="Tweet" align="right"></a>
|
||||
<a href="https://github.com/sourcygen/electron-angular-quick-start/watchers"><img src="https://img.shields.io/github/watchers/sourcygen/electron-angular-quick-start.svg?style=social" alt="Watch on GitHub" align="right"></a>
|
||||
<a href="https://github.com/sourcygen/electron-angular-quick-start/stargazers"><img src="https://img.shields.io/github/stars/sourcygen/electron-angular-quick-start.svg?style=social" alt="Star on GitHub" align="right"></a>
|
||||
# Pevabo-doors
|
||||
|
||||

|
||||
|
||||
## Overview
|
||||
|
||||
Depending on your need, putting up [Electron](https://www.electronjs.org/) and [Angular](https://angular.io/) may require a lot of setup. Fortunately, this simple project will help you **go fast** and directly start building desktop apps in [Typescript](https://www.typescriptlang.org/) with live reload.
|
||||
|
||||
> Because building a desktop app with Electron and Angular shouldn't be difficult.
|
||||
|
||||
### Main features :
|
||||
|
||||
- This project is based on last [Angular 13](https://angular.io/) version with required dependencies for [Electron 18](https://www.electronjs.org/).
|
||||
- This project is also written in [Typescript 4](https://www.typescriptlang.org/) and includes test samples ([WebdriverIO](https://webdriver.io/) and [Jasmine](https://jasmine.github.io/)).
|
||||
- The app is runnable `on desktop` (with **live-reload** in `development mode`).
|
||||
- The app is also runnable `on browser` but **without Electron features**.
|
||||
- You can generate your platform distributables thanks to [`electron-forge`](https://www.electronforge.io/).
|
||||
- You are also granted a minimal size for your app thanks to the packaging based on its[`webpack-template`](https://www.electronforge.io/templates/typescript-+-webpack-template).
|
||||
|
||||
### Project structure :
|
||||
|
||||
```
|
||||
├── ./CHANGELOG.md
|
||||
├── ./LICENSE
|
||||
├── ./README.md
|
||||
├── ./_config.yml
|
||||
├── ./commitlint.config.js
|
||||
├── ./package-lock.json
|
||||
├── ./package.json
|
||||
├── ./tsconfig.json
|
||||
├── ./webpack.main.config.js
|
||||
├── ./webpack.plugins.js
|
||||
├── ./webpack.renderer.config.js
|
||||
├── ./webpack.rules.js
|
||||
└── ./workspaces
|
||||
├── ./workspaces/angular-app # Angular source directory (web renderer part)
|
||||
├── ./workspaces/electron-app # Electron source directory (main & preload part)
|
||||
├── ./workspaces/electron-e2e # Electron end-to-end test directory
|
||||
└── ./workspaces/shared-lib # Shared source directory (common part)
|
||||
```
|
||||
|
||||
## Getting started
|
||||
|
||||
To clone and run this repository, you'll need installed on your computer at least :
|
||||
|
||||
- [Git](https://git-scm.com)
|
||||
- [Node 14](https://nodejs.org/en/download/)
|
||||
- [Npm 7](https://docs.npmjs.com/about-npm)
|
||||
- [Angular-CLI 12](https://angular.io/cli)
|
||||
|
||||
Then from your command line:
|
||||
|
||||
```bash
|
||||
# Upgrade to the latest version of npm (if necessary)
|
||||
npm install -g npm@latest
|
||||
|
||||
# Upgrade to the latest version of Angular CLI (if necessary)
|
||||
npm install -g @angular/cli@latest
|
||||
|
||||
# Clone this repository
|
||||
git clone https://github.com/sourcygen/electron-angular-quick-start.git
|
||||
|
||||
# Then go into the repository
|
||||
cd electron-angular-quick-start
|
||||
|
||||
# After that, install dependencies
|
||||
npm install
|
||||
|
||||
# And finally run the app (dev mode)
|
||||
npm start
|
||||
```
|
||||
|
||||
## How to use
|
||||
|
||||
| Command | Description |
|
||||
| --------------------------- | ---------------------------------------- |
|
||||
| `npm run install` | Install dependencies |
|
||||
| `npm run start` | Run the app on desktop (dev mode) |
|
||||
| `npm run start:angular-app` | Run the app on browser (dev mode) |
|
||||
| `npm run test:electron-e2e` | Run **electron** end-to-end tests |
|
||||
| `npm run package` | Build and prepare application content |
|
||||
| `npm run make` | Generate platform distributables (./out) |
|
||||
| `npm run clean` | Delete generated outputs |
|
||||
|
||||
### Adding dependencies
|
||||
|
||||
This project architecture is based on [npm workspaces](https://docs.npmjs.com/cli/v7/using-npm/workspaces). This allows having different version of the same dependency depending on your workspace :
|
||||
|
||||
- electron-app
|
||||
`npm install --save <dependency>`
|
||||
- angular-app
|
||||
`npm install --save <dependency> -w angular-app`
|
||||
- shared-lib
|
||||
`npm install --save <dependency> -w shared-lib`
|
||||
|
||||
### Listing outdated dependencies
|
||||
|
||||
- electron-app
|
||||
`npm run outdated-deps:electron-app`
|
||||
- angular-app
|
||||
`npm run outdated-deps:angular-app`
|
||||
- shared-lib
|
||||
`npm run outdated-deps:shared-lib`
|
||||
- all of them
|
||||
`npm run outdated-deps`
|
||||
|
||||
### Updating dependencies
|
||||
|
||||
- electron-app
|
||||
`npm run update-deps:electron-app`
|
||||
- angular-app
|
||||
`npm run update-deps:angular-app`
|
||||
- shared-lib
|
||||
`npm run update-deps:shared-lib`
|
||||
- all of them
|
||||
`npm run update-deps`
|
||||
|
||||
### Customizing app icons
|
||||
|
||||
```bash
|
||||
# Install the icon generator globally
|
||||
npm i -g electron-icon-maker
|
||||
|
||||
# Run following command from anywhere you have your input file (1024px at least) to generate platforms icons
|
||||
electron-icon-maker --input=icon.png --output=./out
|
||||
```
|
||||
|
||||
Rename and move files to match with next config
|
||||
|
||||
- ./workspaces/electron-app/main/assets/icons/icon.png for Linux
|
||||
- ./workspaces/electron-app/main/assets/icons/icon.icns for MacOs
|
||||
- ./workspaces/electron-app/main/assets/icons/icon.ico for Windows
|
||||
|
||||
## Resources
|
||||
|
||||
### Electron
|
||||
|
||||
- [electronjs.org/docs](https://electronjs.org/docs) - Electron's documentation
|
||||
- [electron/simple-samples](https://github.com/electron/simple-samples) - Small applications with ideas to take further
|
||||
- [electron/electron-api-demos](https://github.com/electron/electron-api-demos) - Sample app that teaches you how to use Electron
|
||||
|
||||
### Angular
|
||||
|
||||
- [angular.io/start](https://angular.io/start) - Getting started with Angular
|
||||
- [angular.io/docs](https://angular.io/docs) - Angular's documentation
|
||||
- [cli.angular.io](https://cli.angular.io/) - Angular CLI documentation
|
||||
|
||||
BIN
assets/murmur.xlsx
Normal file
BIN
assets/murmur.xlsx
Normal file
Binary file not shown.
BIN
assets/test-klant-2.xlsx
Normal file
BIN
assets/test-klant-2.xlsx
Normal file
Binary file not shown.
13
config.json
Normal file
13
config.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"filePaths": [
|
||||
"./assets"
|
||||
],
|
||||
"mapping": {
|
||||
"lr": ["L/R"],
|
||||
"krukSlot": ["Kruk/\r\nSLOT"],
|
||||
"pivot": ["SCHARNIER"],
|
||||
"type": ["SOORT"],
|
||||
"modelKruk": ["MODEL\r\nKRUK"],
|
||||
"remark": ["OPMERKING"]
|
||||
}
|
||||
}
|
||||
27688
package-lock.json
generated
27688
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
96
package.json
96
package.json
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"name": "electron-angular-app",
|
||||
"productName": "Electron Angular App",
|
||||
"version": "3.0.0",
|
||||
"description": "An Electron and Angular Quick-starter",
|
||||
"name": "pevabo-deuren",
|
||||
"productName": "Pevabo Deuren",
|
||||
"version": "3.2.0",
|
||||
"description": "Deuren",
|
||||
"repository": "https://github.com/sourcygen/electron-angular-quick-start.git",
|
||||
"author": "Sourcygen",
|
||||
"license": "MIT",
|
||||
"main": ".webpack/main",
|
||||
"keywords": [
|
||||
"angular",
|
||||
"angular 13",
|
||||
"angular 14",
|
||||
"electron",
|
||||
"electron 18",
|
||||
"typescript",
|
||||
@@ -41,8 +41,8 @@
|
||||
"test:angular-e2e": "npm-run-all -p -r start start:angular-e2e",
|
||||
"start:angular-e2e": "wait-on http://localhost:4200 && cd workspaces/angular-app && npm run cypress:run",
|
||||
"test:electron-e2e": "npm run package && npm run test:electron-e2e:wdio-only",
|
||||
"test:electron-e2e:wdio-only": "cross-env X_NODE_ENV=e2e-test wdio run workspaces/electron-e2e/wdio.conf.ts --autoCompileOpts.tsNodeOpts.project=workspaces/electron-e2e/tsconfig.json",
|
||||
"clean": "shx rm -rf .webpack out allure-results node_modules workspaces/shared-lib/.dist workspaces/angular-app/node_modules workspaces/angular-app/.dist",
|
||||
"test:electron-e2e:wdio-only": "cross-env X_NODE_ENV=e2e-test wdio run workspaces/electron-e2e/webdriverio.config.ts --autoCompileOpts.tsNodeOpts.project=workspaces/electron-e2e/tsconfig.json",
|
||||
"clean": "shx rm -rf .webpack out allure-results package-lock.json node_modules workspaces/shared-lib/.dist workspaces/angular-app/node_modules workspaces/angular-app/.angular workspaces/angular-app/.dist",
|
||||
"prepare": "husky install",
|
||||
"postinstall": "husky install && shx rm -rf .git/hooks && shx ln -s ../.husky .git/hooks",
|
||||
"outdated-deps": "npm run outdated-deps:electron-app && npm run outdated-deps:angular-app && npm run outdated-deps:shared-lib",
|
||||
@@ -51,7 +51,7 @@
|
||||
"outdated-deps:shared-lib": "cd workspaces/shared-lib && npx ncu",
|
||||
"update-deps": "npm run update-deps:electron-app && npm run update-deps:angular-app && npm run update-deps:shared-lib",
|
||||
"update-deps:electron-app": "npx ncu -u",
|
||||
"update-deps:angular-app": "cd workspaces/angular-app && ng update @angular/cli @angular/core --force && npx ncu -u",
|
||||
"update-deps:angular-app": "cd workspaces/angular-app && ng update @angular/cli @angular/core --force && npx ncu -u && cd ../.. && npm run clean",
|
||||
"update-deps:shared-lib": "cd workspaces/shared-lib && npx ncu -u",
|
||||
"release:minor": "standard-version --release-as minor",
|
||||
"release:patch": "standard-version --release-as patch",
|
||||
@@ -102,60 +102,64 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^16.2.3",
|
||||
"@commitlint/config-conventional": "^16.2.1",
|
||||
"@electron-forge/cli": "^6.0.0-beta.63",
|
||||
"@electron-forge/maker-deb": "^6.0.0-beta.63",
|
||||
"@electron-forge/maker-dmg": "^6.0.0-beta.63",
|
||||
"@electron-forge/maker-rpm": "^6.0.0-beta.63",
|
||||
"@electron-forge/maker-squirrel": "^6.0.0-beta.63",
|
||||
"@electron-forge/maker-zip": "^6.0.0-beta.63",
|
||||
"@electron-forge/plugin-webpack": "6.0.0-beta.63",
|
||||
"@types/lodash": "^4.14.181",
|
||||
"@types/node": "^17.0.23",
|
||||
"@typescript-eslint/eslint-plugin": "^5.18.0",
|
||||
"@typescript-eslint/parser": "^5.18.0",
|
||||
"@vercel/webpack-asset-relocator-loader": "^1.7.2",
|
||||
"@wdio/allure-reporter": "^7.19.1",
|
||||
"@wdio/cli": "^7.19.3",
|
||||
"@wdio/jasmine-framework": "^7.19.3",
|
||||
"@wdio/local-runner": "^7.19.3",
|
||||
"@wdio/spec-reporter": "^7.19.1",
|
||||
"allure-commandline": "^2.17.2",
|
||||
"@commitlint/cli": "^17.1.2",
|
||||
"@commitlint/config-conventional": "^17.1.0",
|
||||
"@electron-forge/cli": "^6.0.0-beta.66",
|
||||
"@electron-forge/maker-deb": "^6.0.0-beta.66",
|
||||
"@electron-forge/maker-dmg": "^6.0.0-beta.66",
|
||||
"@electron-forge/maker-rpm": "^6.0.0-beta.66",
|
||||
"@electron-forge/maker-squirrel": "^6.0.0-beta.66",
|
||||
"@electron-forge/maker-zip": "^6.0.0-beta.66",
|
||||
"@electron-forge/plugin-webpack": "6.0.0-beta.66",
|
||||
"@types/lodash": "^4.14.185",
|
||||
"@types/node": "^18.7.18",
|
||||
"@typescript-eslint/eslint-plugin": "^5.38.0",
|
||||
"@typescript-eslint/parser": "^5.38.0",
|
||||
"@vercel/webpack-asset-relocator-loader": "^1.7.3",
|
||||
"@wdio/allure-reporter": "^7.24.1",
|
||||
"@wdio/cli": "^7.24.1",
|
||||
"@wdio/jasmine-framework": "^7.24.1",
|
||||
"@wdio/local-runner": "^7.24.1",
|
||||
"@wdio/spec-reporter": "^7.24.1",
|
||||
"allure-commandline": "^2.18.1",
|
||||
"chokidar-cli": "^3.0.0",
|
||||
"chromedriver": "^100.0.0",
|
||||
"copy-webpack-plugin": "^10.2.4",
|
||||
"chromedriver": "^104.0.0",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^6.7.1",
|
||||
"electron": "^18.0.3",
|
||||
"eslint": "^8.12.0",
|
||||
"electron": "^20.2.0",
|
||||
"eslint": "^8.23.1",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-import-resolver-typescript": "^3.5.1",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"fork-ts-checker-webpack-plugin": "^7.2.3",
|
||||
"husky": "^7.0.4",
|
||||
"lint-staged": "^12.3.7",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-sonarjs": "^0.15.0",
|
||||
"eslint-plugin-unicorn": "^43.0.2",
|
||||
"fork-ts-checker-webpack-plugin": "^7.2.13",
|
||||
"husky": "^8.0.1",
|
||||
"lint-staged": "^13.0.3",
|
||||
"node-loader": "^2.0.0",
|
||||
"npm-check-updates": "^12.5.8",
|
||||
"npm-check-updates": "^16.2.1",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^2.6.2",
|
||||
"prettier": "^2.7.1",
|
||||
"shx": "^0.3.4",
|
||||
"standard-version": "^9.3.2",
|
||||
"standard-version": "^9.5.0",
|
||||
"style-loader": "^3.3.1",
|
||||
"tree-kill": "^1.2.2",
|
||||
"ts-loader": "^9.2.8",
|
||||
"ts-node": "^10.7.0",
|
||||
"typescript": "^4.6.3",
|
||||
"ts-loader": "^9.4.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.8.3",
|
||||
"wait-on": "^6.0.1",
|
||||
"wdio-chromedriver-service": "^7.3.2",
|
||||
"wdio-electron-service": "^2.1.0",
|
||||
"wdio-wait-for": "^2.2.5"
|
||||
"wdio-electron-service": "^3.5.0",
|
||||
"wdio-wait-for": "^2.2.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@electron/remote": "^2.0.8",
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
"fs-extra": "^10.0.1",
|
||||
"winston": "^3.7.2"
|
||||
"fs-extra": "^10.1.0",
|
||||
"winston": "^3.8.2",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.ts": "npm run lint"
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
"with": "src/environments/environment.production.ts"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all"
|
||||
@@ -128,5 +128,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultProject": "angular-app"
|
||||
"cli": {
|
||||
"analytics": false
|
||||
}
|
||||
}
|
||||
|
||||
18
workspaces/angular-app/cypress.config.ts
Normal file
18
workspaces/angular-app/cypress.config.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { defineConfig } from 'cypress';
|
||||
import configCypress from './cypress/plugins/index';
|
||||
|
||||
export default defineConfig({
|
||||
videosFolder: 'cypress/videos',
|
||||
screenshotsFolder: 'cypress/screenshots',
|
||||
fixturesFolder: 'cypress/fixtures',
|
||||
screenshotOnRunFailure: false,
|
||||
video: false,
|
||||
e2e: {
|
||||
// We've imported your old cypress plugins here.
|
||||
// You may want to clean this up later by importing these.
|
||||
setupNodeEvents(on, config) {
|
||||
return configCypress(on, config);
|
||||
},
|
||||
baseUrl: 'http://localhost:4200',
|
||||
},
|
||||
});
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"integrationFolder": "cypress/integration",
|
||||
"supportFile": "cypress/support/index.ts",
|
||||
"videosFolder": "cypress/videos",
|
||||
"screenshotsFolder": "cypress/screenshots",
|
||||
"pluginsFile": "cypress/plugins/index.ts",
|
||||
"fixturesFolder": "cypress/fixtures",
|
||||
"baseUrl": "http://localhost:4200",
|
||||
"screenshotOnRunFailure": false,
|
||||
"video": false
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
// Plugins enable you to tap into, modify, or extend the internal behavior of Cypress
|
||||
// For more info, visit https://on.cypress.io/plugins-api
|
||||
module.exports = (_on, _config) => {
|
||||
export default (
|
||||
_on: Cypress.PluginEvents,
|
||||
_config: Cypress.PluginConfigOptions
|
||||
): void => {
|
||||
// configure plugins here
|
||||
};
|
||||
|
||||
@@ -1,23 +1,6 @@
|
||||
/// <reference types="cypress" />
|
||||
// ***********************************************
|
||||
// This example namespace declaration will help
|
||||
// with Intellisense and code completion in your
|
||||
// IDE or Text Editor.
|
||||
// ***********************************************
|
||||
// declare namespace Cypress {
|
||||
// interface Chainable<Subject = any> {
|
||||
// customCommand(param: any): typeof customCommand;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// function customCommand(param: any): void {
|
||||
// console.warn(param);
|
||||
// }
|
||||
//
|
||||
// NOTE: You can use it like so:
|
||||
// Cypress.Commands.add('customCommand', customCommand);
|
||||
//
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// This example commands.ts shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
@@ -28,16 +11,27 @@
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add("login", (email, password) => { ... })
|
||||
// Cypress.Commands.add('login', (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||
//
|
||||
// declare global {
|
||||
// namespace Cypress {
|
||||
// interface Chainable {
|
||||
// login(email: string, password: string): Chainable<void>
|
||||
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
|
||||
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
|
||||
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/* eslint-disable unicorn/prevent-abbreviations */
|
||||
|
||||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// This example support/e2e.ts is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
@@ -13,5 +15,8 @@
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// When a command from ./commands is ready to use, import with `import './commands'` syntax
|
||||
// import './commands';
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands';
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
@@ -10,33 +10,34 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "~13.3.2",
|
||||
"@angular/common": "~13.3.2",
|
||||
"@angular/compiler": "~13.3.2",
|
||||
"@angular/core": "~13.3.2",
|
||||
"@angular/forms": "~13.3.2",
|
||||
"@angular/platform-browser": "~13.3.2",
|
||||
"@angular/platform-browser-dynamic": "~13.3.2",
|
||||
"@angular/router": "~13.3.2",
|
||||
"@angular/animations": "^14.2.3",
|
||||
"@angular/common": "^14.2.3",
|
||||
"@angular/compiler": "^14.2.3",
|
||||
"@angular/core": "^14.2.3",
|
||||
"@angular/forms": "^14.2.3",
|
||||
"@angular/material": "^7.0.0",
|
||||
"@angular/platform-browser": "^14.2.3",
|
||||
"@angular/platform-browser-dynamic": "^14.2.3",
|
||||
"@angular/router": "^14.2.3",
|
||||
"@ngx-translate/core": "^14.0.0",
|
||||
"@ngx-translate/http-loader": "^7.0.0",
|
||||
"electron": "^18.0.3",
|
||||
"rxjs": "~7.5.5",
|
||||
"tslib": "^2.3.1",
|
||||
"zone.js": "~0.11.5"
|
||||
"electron": "^20.2.0",
|
||||
"rxjs": "~7.5.6",
|
||||
"tslib": "^2.4.0",
|
||||
"zone.js": "~0.11.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "~13.3.2",
|
||||
"@angular/cli": "~13.3.2",
|
||||
"@angular/compiler-cli": "~13.3.2",
|
||||
"@cypress/schematic": "^1.6.0",
|
||||
"@types/node": "^17.0.23",
|
||||
"cypress": "9.5.3",
|
||||
"karma": "~6.3.17",
|
||||
"@angular-devkit/build-angular": "^14.2.3",
|
||||
"@angular/cli": "^14.2.3",
|
||||
"@angular/compiler-cli": "^14.2.3",
|
||||
"@cypress/schematic": "^2.1.1",
|
||||
"@types/node": "^18.7.18",
|
||||
"cypress": "10.8.0",
|
||||
"karma": "~6.4.1",
|
||||
"karma-chrome-launcher": "~3.1.1",
|
||||
"karma-coverage": "~2.2.0",
|
||||
"karma-jasmine": "~4.0.2",
|
||||
"karma-jasmine-html-reporter": "~1.7.0",
|
||||
"typescript": "^4.6.3"
|
||||
"karma-jasmine": "~5.1.0",
|
||||
"karma-jasmine-html-reporter": "~2.0.0",
|
||||
"typescript": "^4.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { MutiplesComponent } from './components/mutiples/mutiples.component';
|
||||
import { MultiplesComponent } from './components/multiples/multiples.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: MutiplesComponent,
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: MultiplesComponent,
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes)],
|
||||
exports: [RouterModule],
|
||||
imports: [RouterModule.forRoot(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class AppRoutingModule {}
|
||||
|
||||
@@ -1,501 +1 @@
|
||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * * The content below * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * is only a placeholder * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * and can be replaced. * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * Delete the template below * * * * * * * * * * -->
|
||||
<!-- * * * * * * * to get started with your project! * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
||||
|
||||
<style>
|
||||
:host {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
box-sizing: border-box;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #1976d2;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.toolbar img {
|
||||
margin: 0 16px;
|
||||
}
|
||||
|
||||
.toolbar #twitter-logo {
|
||||
height: 40px;
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
.toolbar #youtube-logo {
|
||||
height: 40px;
|
||||
margin: 0 16px;
|
||||
}
|
||||
|
||||
.toolbar #twitter-logo:hover,
|
||||
.toolbar #youtube-logo:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
margin: 82px auto 32px;
|
||||
padding: 0 16px;
|
||||
max-width: 960px;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
svg.material-icons {
|
||||
height: 24px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
svg.material-icons:not(:last-child) {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.card svg.material-icons path {
|
||||
fill: #888;
|
||||
}
|
||||
|
||||
.card-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.card {
|
||||
all: unset;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #eee;
|
||||
background-color: #fafafa;
|
||||
height: 40px;
|
||||
width: 200px;
|
||||
margin: 0 8px 16px;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: all 0.2s ease-in-out;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.card-container .card:not(:last-child) {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.card.card-small {
|
||||
height: 16px;
|
||||
width: 168px;
|
||||
}
|
||||
|
||||
.card-container .card:not(.highlight-card) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.card-container .card:not(.highlight-card):hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 4px 17px rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
.card-container .card:not(.highlight-card):hover .material-icons path {
|
||||
fill: rgb(105, 103, 103);
|
||||
}
|
||||
|
||||
.card.highlight-card {
|
||||
background-color: #1976d2;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
width: auto;
|
||||
min-width: 30%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.card.card.highlight-card span {
|
||||
margin-left: 60px;
|
||||
}
|
||||
|
||||
svg#rocket {
|
||||
width: 80px;
|
||||
position: absolute;
|
||||
left: -10px;
|
||||
top: -24px;
|
||||
}
|
||||
|
||||
svg#rocket-smoke {
|
||||
height: calc(100vh - 95px);
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 180px;
|
||||
z-index: -10;
|
||||
}
|
||||
|
||||
a,
|
||||
a:visited,
|
||||
a:hover {
|
||||
color: #1976d2;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #125699;
|
||||
}
|
||||
|
||||
.terminal {
|
||||
position: relative;
|
||||
width: 80%;
|
||||
max-width: 600px;
|
||||
border-radius: 6px;
|
||||
padding-top: 45px;
|
||||
margin-top: 8px;
|
||||
overflow: hidden;
|
||||
background-color: rgb(15, 15, 16);
|
||||
}
|
||||
|
||||
.terminal::before {
|
||||
content: "\2022 \2022 \2022";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 4px;
|
||||
background: rgb(58, 58, 58);
|
||||
color: #c2c3c4;
|
||||
width: 100%;
|
||||
font-size: 2rem;
|
||||
line-height: 0;
|
||||
padding: 14px 0;
|
||||
text-indent: 4px;
|
||||
}
|
||||
|
||||
.terminal pre {
|
||||
font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;
|
||||
color: white;
|
||||
padding: 0 1rem 1rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.circle-link {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border-radius: 40px;
|
||||
margin: 8px;
|
||||
background-color: white;
|
||||
border: 1px solid #eeeeee;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
transition: 1s ease-out;
|
||||
}
|
||||
|
||||
.circle-link:hover {
|
||||
transform: translateY(-0.25rem);
|
||||
box-shadow: 0px 3px 15px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
footer a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.github-star-badge {
|
||||
color: #24292e;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
padding: 3px 10px;
|
||||
border: 1px solid rgba(27,31,35,.2);
|
||||
border-radius: 3px;
|
||||
background-image: linear-gradient(-180deg,#fafbfc,#eff3f6 90%);
|
||||
margin-left: 4px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.github-star-badge:hover {
|
||||
background-image: linear-gradient(-180deg,#f0f3f6,#e6ebf1 90%);
|
||||
border-color: rgba(27,31,35,.35);
|
||||
background-position: -.5em;
|
||||
}
|
||||
|
||||
.github-star-badge .material-icons {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
svg#clouds {
|
||||
position: fixed;
|
||||
bottom: -160px;
|
||||
left: -230px;
|
||||
z-index: -10;
|
||||
width: 1920px;
|
||||
}
|
||||
|
||||
/* Responsive Styles */
|
||||
@media screen and (max-width: 767px) {
|
||||
.card-container > *:not(.circle-link) ,
|
||||
.terminal {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card:not(.highlight-card) {
|
||||
height: 16px;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.card.highlight-card span {
|
||||
margin-left: 72px;
|
||||
}
|
||||
|
||||
svg#rocket-smoke {
|
||||
right: 120px;
|
||||
transform: rotate(-5deg);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 575px) {
|
||||
svg#rocket-smoke {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Toolbar -->
|
||||
<div class="toolbar" role="banner">
|
||||
<img
|
||||
width="40"
|
||||
alt="Angular Logo"
|
||||
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg=="
|
||||
/>
|
||||
<span>Welcome</span>
|
||||
<div class="spacer"></div>
|
||||
<a aria-label="Angular on twitter" target="_blank" rel="noopener" href="https://twitter.com/angular" title="Twitter">
|
||||
<svg id="twitter-logo" height="24" data-name="Logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400">
|
||||
<rect width="400" height="400" fill="none"/>
|
||||
<path d="M153.62,301.59c94.34,0,145.94-78.16,145.94-145.94,0-2.22,0-4.43-.15-6.63A104.36,104.36,0,0,0,325,122.47a102.38,102.38,0,0,1-29.46,8.07,51.47,51.47,0,0,0,22.55-28.37,102.79,102.79,0,0,1-32.57,12.45,51.34,51.34,0,0,0-87.41,46.78A145.62,145.62,0,0,1,92.4,107.81a51.33,51.33,0,0,0,15.88,68.47A50.91,50.91,0,0,1,85,169.86c0,.21,0,.43,0,.65a51.31,51.31,0,0,0,41.15,50.28,51.21,51.21,0,0,1-23.16.88,51.35,51.35,0,0,0,47.92,35.62,102.92,102.92,0,0,1-63.7,22A104.41,104.41,0,0,1,75,278.55a145.21,145.21,0,0,0,78.62,23" fill="#fff"/>
|
||||
</svg>
|
||||
</a>
|
||||
<a aria-label="Angular on YouTube" target="_blank" rel="noopener" href="https://youtube.com/angular" title="YouTube">
|
||||
<svg id="youtube-logo" height="24" width="24" data-name="Logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#fff">
|
||||
<path d="M0 0h24v24H0V0z" fill="none"/>
|
||||
<path d="M21.58 7.19c-.23-.86-.91-1.54-1.77-1.77C18.25 5 12 5 12 5s-6.25 0-7.81.42c-.86.23-1.54.91-1.77 1.77C2 8.75 2 12 2 12s0 3.25.42 4.81c.23.86.91 1.54 1.77 1.77C5.75 19 12 19 12 19s6.25 0 7.81-.42c.86-.23 1.54-.91 1.77-1.77C22 15.25 22 12 22 12s0-3.25-.42-4.81zM10 15V9l5.2 3-5.2 3z"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="content" role="main">
|
||||
|
||||
<!-- Highlight Card -->
|
||||
<div class="card highlight-card card-small">
|
||||
|
||||
<svg id="rocket" xmlns="http://www.w3.org/2000/svg" width="101.678" height="101.678" viewBox="0 0 101.678 101.678">
|
||||
<title>Rocket Ship</title>
|
||||
<g id="Group_83" data-name="Group 83" transform="translate(-141 -696)">
|
||||
<circle id="Ellipse_8" data-name="Ellipse 8" cx="50.839" cy="50.839" r="50.839" transform="translate(141 696)" fill="#dd0031"/>
|
||||
<g id="Group_47" data-name="Group 47" transform="translate(165.185 720.185)">
|
||||
<path id="Path_33" data-name="Path 33" d="M3.4,42.615a3.084,3.084,0,0,0,3.553,3.553,21.419,21.419,0,0,0,12.215-6.107L9.511,30.4A21.419,21.419,0,0,0,3.4,42.615Z" transform="translate(0.371 3.363)" fill="#fff"/>
|
||||
<path id="Path_34" data-name="Path 34" d="M53.3,3.221A3.09,3.09,0,0,0,50.081,0,48.227,48.227,0,0,0,18.322,13.437c-6-1.666-14.991-1.221-18.322,7.218A33.892,33.892,0,0,1,9.439,25.1l-.333.666a3.013,3.013,0,0,0,.555,3.553L23.985,43.641a2.9,2.9,0,0,0,3.553.555l.666-.333A33.892,33.892,0,0,1,32.647,53.3c8.55-3.664,8.884-12.326,7.218-18.322A48.227,48.227,0,0,0,53.3,3.221ZM34.424,9.772a6.439,6.439,0,1,1,9.106,9.106,6.368,6.368,0,0,1-9.106,0A6.467,6.467,0,0,1,34.424,9.772Z" transform="translate(0 0.005)" fill="#fff"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<span>{{ title }} app is running!</span>
|
||||
|
||||
<svg id="rocket-smoke" xmlns="http://www.w3.org/2000/svg" width="516.119" height="1083.632" viewBox="0 0 516.119 1083.632">
|
||||
<title>Rocket Ship Smoke</title>
|
||||
<path id="Path_40" data-name="Path 40" d="M644.6,141S143.02,215.537,147.049,870.207s342.774,201.755,342.774,201.755S404.659,847.213,388.815,762.2c-27.116-145.51-11.551-384.124,271.9-609.1C671.15,139.365,644.6,141,644.6,141Z" transform="translate(-147.025 -140.939)" fill="#f5f5f5"/>
|
||||
</svg>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Resources -->
|
||||
<h2>Resources</h2>
|
||||
<p>Here are some links to help you get started:</p>
|
||||
|
||||
<div class="card-container">
|
||||
<a class="card" target="_blank" rel="noopener" href="https://angular.io/tutorial">
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M5 13.18v4L12 21l7-3.82v-4L12 17l-7-3.82zM12 3L1 9l11 6 9-4.91V17h2V9L12 3z"/></svg>
|
||||
<span>Learn Angular</span>
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg> </a>
|
||||
|
||||
<a class="card" target="_blank" rel="noopener" href="https://angular.io/cli">
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z"/></svg>
|
||||
<span>CLI Documentation</span>
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>
|
||||
</a>
|
||||
|
||||
<a class="card" target="_blank" rel="noopener" href="https://blog.angular.io/">
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M13.5.67s.74 2.65.74 4.8c0 2.06-1.35 3.73-3.41 3.73-2.07 0-3.63-1.67-3.63-3.73l.03-.36C5.21 7.51 4 10.62 4 14c0 4.42 3.58 8 8 8s8-3.58 8-8C20 8.61 17.41 3.8 13.5.67zM11.71 19c-1.78 0-3.22-1.4-3.22-3.14 0-1.62 1.05-2.76 2.81-3.12 1.77-.36 3.6-1.21 4.62-2.58.39 1.29.59 2.65.59 4.04 0 2.65-2.15 4.8-4.8 4.8z"/></svg>
|
||||
<span>Angular Blog</span>
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>
|
||||
</a>
|
||||
|
||||
<a class="card" target="_blank" rel="noopener" href="https://angular.io/devtools/">
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><rect fill="none" height="24" width="24"/></g><g><g><path d="M14.73,13.31C15.52,12.24,16,10.93,16,9.5C16,5.91,13.09,3,9.5,3S3,5.91,3,9.5C3,13.09,5.91,16,9.5,16 c1.43,0,2.74-0.48,3.81-1.27L19.59,21L21,19.59L14.73,13.31z M9.5,14C7.01,14,5,11.99,5,9.5S7.01,5,9.5,5S14,7.01,14,9.5 S11.99,14,9.5,14z"/><polygon points="10.29,8.44 9.5,6 8.71,8.44 6.25,8.44 8.26,10.03 7.49,12.5 9.5,10.97 11.51,12.5 10.74,10.03 12.75,8.44"/></g></g></svg>
|
||||
<span>Angular DevTools</span>
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Next Steps -->
|
||||
<h2>Next Steps</h2>
|
||||
<p>What do you want to do next with your app?</p>
|
||||
|
||||
<input type="hidden" #selection>
|
||||
|
||||
<div class="card-container">
|
||||
<button class="card card-small" (click)="selection.value = 'component'" tabindex="0">
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
|
||||
<span>New Component</span>
|
||||
</button>
|
||||
|
||||
<button class="card card-small" (click)="selection.value = 'material'" tabindex="0">
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
|
||||
<span>Angular Material</span>
|
||||
</button>
|
||||
|
||||
<button class="card card-small" (click)="selection.value = 'pwa'" tabindex="0">
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
|
||||
<span>Add PWA Support</span>
|
||||
</button>
|
||||
|
||||
<button class="card card-small" (click)="selection.value = 'dependency'" tabindex="0">
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
|
||||
<span>Add Dependency</span>
|
||||
</button>
|
||||
|
||||
<button class="card card-small" (click)="selection.value = 'test'" tabindex="0">
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
|
||||
<span>Run and Watch Tests</span>
|
||||
</button>
|
||||
|
||||
<button class="card card-small" (click)="selection.value = 'build'" tabindex="0">
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
|
||||
<span>Build for Production</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Terminal -->
|
||||
<div class="terminal" [ngSwitch]="selection.value">
|
||||
<pre *ngSwitchDefault>ng generate component xyz</pre>
|
||||
<pre *ngSwitchCase="'material'">ng add @angular/material</pre>
|
||||
<pre *ngSwitchCase="'pwa'">ng add @angular/pwa</pre>
|
||||
<pre *ngSwitchCase="'dependency'">ng add _____</pre>
|
||||
<pre *ngSwitchCase="'test'">ng test</pre>
|
||||
<pre *ngSwitchCase="'build'">ng build</pre>
|
||||
</div>
|
||||
|
||||
<!-- Links -->
|
||||
<div class="card-container">
|
||||
<a class="circle-link" title="Animations" href="https://angular.io/guide/animations" target="_blank" rel="noopener">
|
||||
<svg id="Group_20" data-name="Group 20" xmlns="http://www.w3.org/2000/svg" width="21.813" height="23.453" viewBox="0 0 21.813 23.453">
|
||||
<path id="Path_15" data-name="Path 15" d="M4099.584,972.736h0l-10.882,3.9,1.637,14.4,9.245,5.153,9.245-5.153,1.686-14.4Z" transform="translate(-4088.702 -972.736)" fill="#ffa726"/>
|
||||
<path id="Path_16" data-name="Path 16" d="M4181.516,972.736v23.453l9.245-5.153,1.686-14.4Z" transform="translate(-4170.633 -972.736)" fill="#fb8c00"/>
|
||||
<path id="Path_17" data-name="Path 17" d="M4137.529,1076.127l-7.7-3.723,4.417-2.721,7.753,3.723Z" transform="translate(-4125.003 -1058.315)" fill="#ffe0b2"/>
|
||||
<path id="Path_18" data-name="Path 18" d="M4137.529,1051.705l-7.7-3.723,4.417-2.721,7.753,3.723Z" transform="translate(-4125.003 -1036.757)" fill="#fff3e0"/>
|
||||
<path id="Path_19" data-name="Path 19" d="M4137.529,1027.283l-7.7-3.723,4.417-2.721,7.753,3.723Z" transform="translate(-4125.003 -1015.199)" fill="#fff"/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<a class="circle-link" title="CLI" href="https://cli.angular.io/" target="_blank" rel="noopener">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="21.762" height="23.447" viewBox="0 0 21.762 23.447">
|
||||
<title>Angular CLI Logo</title>
|
||||
<g id="Group_21" data-name="Group 21" transform="translate(0)">
|
||||
<path id="Path_20" data-name="Path 20" d="M2660.313,313.618h0l-10.833,3.9,1.637,14.4,9.2,5.152,9.244-5.152,1.685-14.4Z" transform="translate(-2649.48 -313.618)" fill="#37474f"/>
|
||||
<path id="Path_21" data-name="Path 21" d="M2741.883,313.618v23.447l9.244-5.152,1.685-14.4Z" transform="translate(-2731.05 -313.618)" fill="#263238"/>
|
||||
<path id="Path_22" data-name="Path 22" d="M2692.293,379.169h11.724V368.618h-11.724Zm11.159-.6h-10.608v-9.345h10.621v9.345Z" transform="translate(-2687.274 -362.17)" fill="#fff"/>
|
||||
<path id="Path_23" data-name="Path 23" d="M2709.331,393.688l.4.416,2.265-2.28-2.294-2.294-.4.4,1.893,1.893Z" transform="translate(-2702.289 -380.631)" fill="#fff"/>
|
||||
<rect id="Rectangle_12" data-name="Rectangle 12" width="3.517" height="0.469" transform="translate(9.709 13.744)" fill="#fff"/>
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<a class="circle-link" title="Find a Local Meetup" href="https://www.meetup.com/find/?keywords=angular" target="_blank" rel="noopener">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24.607" height="23.447" viewBox="0 0 24.607 23.447">
|
||||
<title>Meetup Logo</title>
|
||||
<path id="logo--mSwarm" d="M21.221,14.95A4.393,4.393,0,0,1,17.6,19.281a4.452,4.452,0,0,1-.8.069c-.09,0-.125.035-.154.117a2.939,2.939,0,0,1-2.506,2.091,2.868,2.868,0,0,1-2.248-.624.168.168,0,0,0-.245-.005,3.926,3.926,0,0,1-2.589.741,4.015,4.015,0,0,1-3.7-3.347,2.7,2.7,0,0,1-.043-.38c0-.106-.042-.146-.143-.166a3.524,3.524,0,0,1-1.516-.69A3.623,3.623,0,0,1,2.23,14.557a3.66,3.66,0,0,1,1.077-3.085.138.138,0,0,0,.026-.2,3.348,3.348,0,0,1-.451-1.821,3.46,3.46,0,0,1,2.749-3.28.44.44,0,0,0,.355-.281,5.072,5.072,0,0,1,3.863-3,5.028,5.028,0,0,1,3.555.666.31.31,0,0,0,.271.03A4.5,4.5,0,0,1,18.3,4.7a4.4,4.4,0,0,1,1.334,2.751,3.658,3.658,0,0,1,.022.706.131.131,0,0,0,.1.157,2.432,2.432,0,0,1,1.574,1.645,2.464,2.464,0,0,1-.7,2.616c-.065.064-.051.1-.014.166A4.321,4.321,0,0,1,21.221,14.95ZM13.4,14.607a2.09,2.09,0,0,0,1.409,1.982,4.7,4.7,0,0,0,1.275.221,1.807,1.807,0,0,0,.9-.151.542.542,0,0,0,.321-.545.558.558,0,0,0-.359-.534,1.2,1.2,0,0,0-.254-.078c-.262-.047-.526-.086-.787-.138a.674.674,0,0,1-.617-.75,3.394,3.394,0,0,1,.218-1.109c.217-.658.509-1.286.79-1.918a15.609,15.609,0,0,0,.745-1.86,1.95,1.95,0,0,0,.06-1.073,1.286,1.286,0,0,0-1.051-1.033,1.977,1.977,0,0,0-1.521.2.339.339,0,0,1-.446-.042c-.1-.092-.2-.189-.307-.284a1.214,1.214,0,0,0-1.643-.061,7.563,7.563,0,0,1-.614.512A.588.588,0,0,1,10.883,8c-.215-.115-.437-.215-.659-.316a2.153,2.153,0,0,0-.695-.248A2.091,2.091,0,0,0,7.541,8.562a9.915,9.915,0,0,0-.405.986c-.559,1.545-1.015,3.123-1.487,4.7a1.528,1.528,0,0,0,.634,1.777,1.755,1.755,0,0,0,1.5.211,1.35,1.35,0,0,0,.824-.858c.543-1.281,1.032-2.584,1.55-3.875.142-.355.28-.712.432-1.064a.548.548,0,0,1,.851-.24.622.622,0,0,1,.185.539,2.161,2.161,0,0,1-.181.621c-.337.852-.68,1.7-1.018,2.552a2.564,2.564,0,0,0-.173.528.624.624,0,0,0,.333.71,1.073,1.073,0,0,0,.814.034,1.22,1.22,0,0,0,.657-.655q.758-1.488,1.511-2.978.35-.687.709-1.37a1.073,1.073,0,0,1,.357-.434.43.43,0,0,1,.463-.016.373.373,0,0,1,.153.387.7.7,0,0,1-.057.236c-.065.157-.127.316-.2.469-.42.883-.846,1.763-1.262,2.648A2.463,2.463,0,0,0,13.4,14.607Zm5.888,6.508a1.09,1.09,0,0,0-2.179.006,1.09,1.09,0,0,0,2.179-.006ZM1.028,12.139a1.038,1.038,0,1,0,.01-2.075,1.038,1.038,0,0,0-.01,2.075ZM13.782.528a1.027,1.027,0,1,0-.011,2.055A1.027,1.027,0,0,0,13.782.528ZM22.21,6.95a.882.882,0,0,0-1.763.011A.882.882,0,0,0,22.21,6.95ZM4.153,4.439a.785.785,0,1,0,.787-.78A.766.766,0,0,0,4.153,4.439Zm8.221,18.22a.676.676,0,1,0-.677.666A.671.671,0,0,0,12.374,22.658ZM22.872,12.2a.674.674,0,0,0-.665.665.656.656,0,0,0,.655.643.634.634,0,0,0,.655-.644A.654.654,0,0,0,22.872,12.2ZM7.171-.123A.546.546,0,0,0,6.613.43a.553.553,0,1,0,1.106,0A.539.539,0,0,0,7.171-.123ZM24.119,9.234a.507.507,0,0,0-.493.488.494.494,0,0,0,.494.494.48.48,0,0,0,.487-.483A.491.491,0,0,0,24.119,9.234Zm-19.454,9.7a.5.5,0,0,0-.488-.488.491.491,0,0,0-.487.5.483.483,0,0,0,.491.479A.49.49,0,0,0,4.665,18.936Z" transform="translate(0 0.123)" fill="#f64060"/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<a class="circle-link" title="Join the Conversation on Discord" href="https://discord.gg/angular" target="_blank" rel="noopener">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 245 240">
|
||||
<title>Discord Logo</title>
|
||||
<path d="M104.4 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1.1-6.1-4.5-11.1-10.2-11.1zM140.9 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1s-4.5-11.1-10.2-11.1z"/>
|
||||
<path d="M189.5 20h-134C44.2 20 35 29.2 35 40.6v135.2c0 11.4 9.2 20.6 20.5 20.6h113.4l-5.3-18.5 12.8 11.9 12.1 11.2 21.5 19V40.6c0-11.4-9.2-20.6-20.5-20.6zm-38.6 130.6s-3.6-4.3-6.6-8.1c13.1-3.7 18.1-11.9 18.1-11.9-4.1 2.7-8 4.6-11.5 5.9-5 2.1-9.8 3.5-14.5 4.3-9.6 1.8-18.4 1.3-25.9-.1-5.7-1.1-10.6-2.7-14.7-4.3-2.3-.9-4.8-2-7.3-3.4-.3-.2-.6-.3-.9-.5-.2-.1-.3-.2-.4-.3-1.8-1-2.8-1.7-2.8-1.7s4.8 8 17.5 11.8c-3 3.8-6.7 8.3-6.7 8.3-22.1-.7-30.5-15.2-30.5-15.2 0-32.2 14.4-58.3 14.4-58.3 14.4-10.8 28.1-10.5 28.1-10.5l1 1.2c-18 5.2-26.3 13.1-26.3 13.1s2.2-1.2 5.9-2.9c10.7-4.7 19.2-6 22.7-6.3.6-.1 1.1-.2 1.7-.2 6.1-.8 13-1 20.2-.2 9.5 1.1 19.7 3.9 30.1 9.6 0 0-7.9-7.5-24.9-12.7l1.4-1.6s13.7-.3 28.1 10.5c0 0 14.4 26.1 14.4 58.3 0 0-8.5 14.5-30.6 15.2z"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer>
|
||||
Love Angular?
|
||||
<a href="https://github.com/angular/angular" target="_blank" rel="noopener"> Give our repo a star.
|
||||
<div class="github-star-badge">
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/></svg>
|
||||
Star
|
||||
</div>
|
||||
</a>
|
||||
<a href="https://github.com/angular/angular" target="_blank" rel="noopener">
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" fill="#1976d2"/><path d="M0 0h24v24H0z" fill="none"/></svg>
|
||||
</a>
|
||||
</footer>
|
||||
|
||||
<svg id="clouds" xmlns="http://www.w3.org/2000/svg" width="2611.084" height="485.677" viewBox="0 0 2611.084 485.677">
|
||||
<title>Gray Clouds Background</title>
|
||||
<path id="Path_39" data-name="Path 39" d="M2379.709,863.793c10-93-77-171-168-149-52-114-225-105-264,15-75,3-140,59-152,133-30,2.83-66.725,9.829-93.5,26.25-26.771-16.421-63.5-23.42-93.5-26.25-12-74-77-130-152-133-39-120-212-129-264-15-54.084-13.075-106.753,9.173-138.488,48.9-31.734-39.726-84.4-61.974-138.487-48.9-52-114-225-105-264,15a162.027,162.027,0,0,0-103.147,43.044c-30.633-45.365-87.1-72.091-145.206-58.044-52-114-225-105-264,15-75,3-140,59-152,133-53,5-127,23-130,83-2,42,35,72,70,86,49,20,106,18,157,5a165.625,165.625,0,0,0,120,0c47,94,178,113,251,33,61.112,8.015,113.854-5.72,150.492-29.764a165.62,165.62,0,0,0,110.861-3.236c47,94,178,113,251,33,31.385,4.116,60.563,2.495,86.487-3.311,25.924,5.806,55.1,7.427,86.488,3.311,73,80,204,61,251-33a165.625,165.625,0,0,0,120,0c51,13,108,15,157-5a147.188,147.188,0,0,0,33.5-18.694,147.217,147.217,0,0,0,33.5,18.694c49,20,106,18,157,5a165.625,165.625,0,0,0,120,0c47,94,178,113,251,33C2446.709,1093.793,2554.709,922.793,2379.709,863.793Z" transform="translate(142.69 -634.312)" fill="#eee"/>
|
||||
</svg>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * * The content above * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * is only a placeholder * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * and can be replaced. * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * End of Placeholder * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss'],
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss'],
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'electron-angular-quick-start';
|
||||
title = 'PEVABO - Deuren';
|
||||
}
|
||||
|
||||
@@ -1,33 +1,28 @@
|
||||
import { HttpClient, HttpClientModule } from '@angular/common/http';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
import { MutiplesComponent } from './components/mutiples/mutiples.component';
|
||||
|
||||
// AoT requires an exported function for factories
|
||||
export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
|
||||
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
|
||||
}
|
||||
import { MultiplesComponent } from './components/multiples/multiples.component';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent, MutiplesComponent],
|
||||
declarations: [AppComponent, MultiplesComponent],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
AppRoutingModule,
|
||||
HttpClientModule,
|
||||
ReactiveFormsModule,
|
||||
TranslateModule.forRoot({
|
||||
defaultLanguage: 'en',
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useFactory: HttpLoaderFactory,
|
||||
deps: [HttpClient],
|
||||
},
|
||||
}),
|
||||
MatSelectModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
BrowserAnimationsModule,
|
||||
MatButtonModule,
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent],
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
<mat-form-field appearance="fill" class="projects">
|
||||
<mat-label>Klantenbestand</mat-label>
|
||||
<mat-select [value]="project?.name" (selectionChange)="projectChange($event)">
|
||||
<mat-option *ngFor="let projectOption of projects" [value]="projectOption.name">
|
||||
{{ projectOption.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="doors">
|
||||
<mat-label>Deuren</mat-label>
|
||||
<mat-select [(value)]="door" [disabled]="!project" (selectionChange)="focus()">
|
||||
<mat-option *ngFor="let doorOption of doors" [value]="doorOption">
|
||||
{{ doorOption?.nr }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="fill" class="doors">
|
||||
<mat-label>Deuren</mat-label>
|
||||
<input
|
||||
matInput
|
||||
placeholder="Scanner"
|
||||
(input)="textInput($event)"
|
||||
[disabled]="!project"
|
||||
id="textsearch"
|
||||
/>
|
||||
</mat-form-field>
|
||||
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
class="print-button"
|
||||
(click)="printLabel()"
|
||||
[disabled]="!door"
|
||||
>
|
||||
Print
|
||||
</button>
|
||||
|
||||
<div *ngIf="door?.nr" class="door">
|
||||
<div class="specs">
|
||||
<p class="property">Deurnummer</p>
|
||||
<p class="value">
|
||||
{{ door?.nr }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="specs">
|
||||
<p class="property">L/R</p>
|
||||
<p class="value">
|
||||
{{ door?.lr }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="specs">
|
||||
<p class="property">KRUK/SLOT</p>
|
||||
<p class="value">
|
||||
{{ door?.krukSlot }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="specs">
|
||||
<p class="property">SCHARNIER</p>
|
||||
<p class="value">
|
||||
{{ door?.pivot }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="specs">
|
||||
<p class="property">Soort Deur</p>
|
||||
<p class="value">
|
||||
{{ door?.type }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="specs">
|
||||
<p class="property">Model Kruk</p>
|
||||
<p class="value">
|
||||
{{ door?.modelKruk }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="specs">
|
||||
<p class="property">Opmerkingen</p>
|
||||
<p class="value">
|
||||
{{ door?.remark }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,51 @@
|
||||
.doors {
|
||||
margin-left: 2rem;
|
||||
}
|
||||
|
||||
.specs {
|
||||
flex-direction: column;
|
||||
flex: 1 1 0;
|
||||
|
||||
p {
|
||||
font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.door {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.print-button {
|
||||
margin-left: 2rem;
|
||||
}
|
||||
|
||||
@media print {
|
||||
mat-form-field {
|
||||
display: none;
|
||||
}
|
||||
|
||||
button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
.door {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.specs {
|
||||
flex-direction: row;
|
||||
|
||||
p {
|
||||
display: inline-block;
|
||||
width: 220px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { WindowApiConst, Door } from 'shared-lib';
|
||||
import { ElectronIpcService } from '../../services/electron-ipc.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-multiples',
|
||||
templateUrl: './multiples.component.html',
|
||||
styleUrls: ['./multiples.component.scss'],
|
||||
})
|
||||
export class MultiplesComponent implements OnInit {
|
||||
public projects: any[] = [];
|
||||
public project: {name: string|undefined, path: string|undefined} | undefined = undefined;
|
||||
public doors: Door[] = [];
|
||||
public door: Door | undefined = undefined;
|
||||
public Object = Object;
|
||||
|
||||
constructor(private electronIpc: ElectronIpcService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
// Specifying what to do with received data from main process
|
||||
this.electronIpc.receive<string[][]>(
|
||||
WindowApiConst.FILELIST_OUTPUT,
|
||||
(output: any[]) => {
|
||||
this.projects = output;
|
||||
}
|
||||
);
|
||||
|
||||
this.electronIpc.receive<[]>(
|
||||
WindowApiConst.PROJECT_OUTPUT,
|
||||
(output: Door[]) => {
|
||||
// Update current data
|
||||
this.doors = output;
|
||||
this.focus();
|
||||
}
|
||||
);
|
||||
|
||||
this.electronIpc.receive<void>(WindowApiConst.PRINT_OUTPUT, () => {
|
||||
this.focus();
|
||||
});
|
||||
|
||||
this.refreshProjects();
|
||||
}
|
||||
|
||||
refreshProjects(): void {
|
||||
this.electronIpc.send(WindowApiConst.FILELIST_INPUT, 'refresh');
|
||||
}
|
||||
|
||||
projectChange(event: any): void {
|
||||
this.door = undefined;
|
||||
this.project = this.projects.find(x => x.name === event.value);
|
||||
this.electronIpc.send(WindowApiConst.PROJECT_INPUT, this.project);
|
||||
}
|
||||
|
||||
textInput(event: any): void {
|
||||
const searchValue = event?.target?.value;
|
||||
const door: Door | undefined =
|
||||
this.doors.find((x) => x.nr === searchValue) || undefined;
|
||||
|
||||
if (door) {
|
||||
this.door = door;
|
||||
event.target.value = '';
|
||||
this.printLabel();
|
||||
}
|
||||
}
|
||||
|
||||
printLabel(): void {
|
||||
this.electronIpc.send(WindowApiConst.PRINT_INPUT, {});
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
setTimeout(() => {
|
||||
// eslint-disable-next-line unicorn/prefer-query-selector
|
||||
const input = document.getElementById('textsearch');
|
||||
|
||||
(input as any).value = "";
|
||||
input?.focus();
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<div id="multiples">
|
||||
<div class="container">
|
||||
<button (click)="translateIn('en')">EN</button>
|
||||
<button (click)="translateIn('fr')">FR</button>
|
||||
<h2>{{ 'MULTIPLES.TITLE' | translate }}</h2>
|
||||
<form [formGroup]="timesTableForm" (ngSubmit)="onSubmit()">
|
||||
<div class="form-group">
|
||||
<input
|
||||
id="input"
|
||||
type="number"
|
||||
class="form-control"
|
||||
formControlName="input"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-success"
|
||||
[disabled]="!timesTableForm.valid"
|
||||
>
|
||||
{{ 'MULTIPLES.SUBMIT' | translate }}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<h3 class="results" *ngFor="let multiple of multiples; let i = index">
|
||||
{{ timesTableForm.value.input }} * {{ 1 + i }} = {{ multiple }}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,23 +0,0 @@
|
||||
#multiples {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 210px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
color: #ffffff;
|
||||
font-weight: 900;
|
||||
|
||||
.container {
|
||||
background-color: #d47800e0;
|
||||
border-radius: 4px;
|
||||
padding: 20px;
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
> * {
|
||||
margin: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { MutiplesComponent } from './mutiples.component';
|
||||
|
||||
describe('MutiplesComponent', () => {
|
||||
let component: MutiplesComponent;
|
||||
let fixture: ComponentFixture<MutiplesComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ MutiplesComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(MutiplesComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -1,51 +0,0 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormControl, FormGroup } from '@angular/forms';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { WindowApiConst } from 'shared-lib';
|
||||
import { ElectronIpcService } from '../../services/electron-ipc.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-mutiples',
|
||||
templateUrl: './mutiples.component.html',
|
||||
styleUrls: ['./mutiples.component.scss'],
|
||||
})
|
||||
export class MutiplesComponent implements OnInit {
|
||||
timesTableForm = new FormGroup({
|
||||
input: new FormControl(Math.round(Math.random() * 100) % 10),
|
||||
});
|
||||
|
||||
multiples: number[] = [];
|
||||
|
||||
constructor(
|
||||
private electronIpc: ElectronIpcService,
|
||||
private translate: TranslateService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
// Specifying what to do with received data from main process
|
||||
this.electronIpc.receive<number[]>(
|
||||
WindowApiConst.MULTIPLES_OUTPUT,
|
||||
(output: number[]) => {
|
||||
// Update current data
|
||||
this.multiples = output;
|
||||
}
|
||||
);
|
||||
|
||||
// Reset multiples on form changes
|
||||
this.timesTableForm.valueChanges.subscribe(() => {
|
||||
this.multiples = [];
|
||||
});
|
||||
|
||||
// Init time tables with given random value
|
||||
this.onSubmit();
|
||||
}
|
||||
|
||||
translateIn(lang: string): void {
|
||||
this.translate.use(lang);
|
||||
}
|
||||
|
||||
onSubmit(): void {
|
||||
const input = this.timesTableForm.value.input;
|
||||
this.electronIpc.send(WindowApiConst.MULTIPLES_INPUT, input);
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ export class ElectronIpcService {
|
||||
}
|
||||
}
|
||||
|
||||
public receive<Out>(channel: string, func: (output: Out) => void): void {
|
||||
public receive<Out>(channel: string, callback: (output: Out) => void): void {
|
||||
if (this._api) {
|
||||
this._api.receive<Out>(channel, (output) => {
|
||||
console.log(`Received from main process channel [${channel}]`, output);
|
||||
@@ -25,7 +25,7 @@ export class ElectronIpcService {
|
||||
// doesn't recognize it needs to run change detection
|
||||
// Further details on SO : https://stackoverflow.com/a/49136353/11480016
|
||||
this.zone.run(() => {
|
||||
func(output);
|
||||
callback(output);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export const environment = {
|
||||
production: true
|
||||
production: true,
|
||||
};
|
||||
@@ -1,9 +1,9 @@
|
||||
// This file can be replaced during build by using the `fileReplacements` array.
|
||||
// `ng build` replaces `environment.ts` with `environment.prod.ts`.
|
||||
// `ng build` replaces `environment.ts` with `environment.production.ts`.
|
||||
// The list of file replacements can be found in `angular.json`.
|
||||
|
||||
export const environment = {
|
||||
production: false
|
||||
production: false,
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -5,8 +5,10 @@ import { AppModule } from './app/app.module';
|
||||
import { environment } from './environments/environment';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule)
|
||||
.catch(err => console.error(err));
|
||||
platformBrowserDynamic()
|
||||
.bootstrapModule(AppModule)
|
||||
// eslint-disable-next-line unicorn/prefer-top-level-await
|
||||
.catch((error) => console.error(error));
|
||||
|
||||
@@ -1 +1,11 @@
|
||||
/* You can add global styles to this file, and also import other style files */
|
||||
@import '~@angular/material/prebuilt-themes/indigo-pink.css';
|
||||
|
||||
@media print {
|
||||
html {
|
||||
height: 530px;
|
||||
width: 460px;
|
||||
overflow: hidden;
|
||||
zoom: 0.25;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
// 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';
|
||||
import 'zone.js/testing';
|
||||
|
||||
declare const require: {
|
||||
context(
|
||||
@@ -29,4 +29,6 @@ getTestBed().initTestEnvironment(
|
||||
// Then we find all the tests.
|
||||
const context = require.context('./', true, /\.spec\.ts$/);
|
||||
// And load the modules.
|
||||
context.keys().map(context);
|
||||
for (const key of context.keys()) {
|
||||
context(key);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"target": "es2017",
|
||||
"target": "es2020",
|
||||
"module": "es2020",
|
||||
"lib": ["es2018", "dom"]
|
||||
},
|
||||
|
||||
@@ -1,26 +1,44 @@
|
||||
import { app, BrowserWindow, ipcMain, nativeImage } from 'electron';
|
||||
import * as path from 'path';
|
||||
import { AbstractService } from '../services/abstract-service';
|
||||
import { MultiplesService } from '../services/multiples-service';
|
||||
import { Logger } from '../utils/logger';
|
||||
import { Door } from 'shared-lib';
|
||||
import * as remoteMain from '@electron/remote/main';
|
||||
import { app, BrowserWindow, ipcMain, nativeImage } from 'electron';
|
||||
import * as path from 'node:path';
|
||||
import { AbstractService } from '../services/abstract-service';
|
||||
import { FileListService } from '../services/file-list-service';
|
||||
import { PrintService } from './../services/print-service';
|
||||
import { Logger } from '../utils/logger';
|
||||
import { DoorService } from '../services/door-service';
|
||||
import { readFile } from 'node:fs';
|
||||
|
||||
declare const global: Global;
|
||||
declare const MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY: string;
|
||||
|
||||
export class Window {
|
||||
private _electronWindow: BrowserWindow | undefined;
|
||||
private _printService: PrintService;
|
||||
|
||||
constructor() {
|
||||
this.createWindow();
|
||||
this.loadRenderer();
|
||||
this.registerService<number, number[]>(new MultiplesService());
|
||||
|
||||
const fileListService = new FileListService();
|
||||
this.registerService<void, any[]>(fileListService);
|
||||
const doorService = new DoorService();
|
||||
this.registerService<any, Door[]>(doorService);
|
||||
|
||||
readFile('./config.json', 'utf8', (error, data) => {
|
||||
if(error){
|
||||
console.log(error);
|
||||
return;
|
||||
}
|
||||
const config = JSON.parse(data);
|
||||
fileListService.setPaths(config.filePaths);
|
||||
doorService.setMapping(config.mapping);
|
||||
});
|
||||
}
|
||||
|
||||
private createWindow(): void {
|
||||
this._electronWindow = new BrowserWindow({
|
||||
width: 1280,
|
||||
height: 720,
|
||||
fullscreen: false,
|
||||
backgroundColor: '#FFFFFF',
|
||||
icon: this.loadIcon(),
|
||||
webPreferences: {
|
||||
@@ -31,11 +49,20 @@ export class Window {
|
||||
// Isolate window context to protect against prototype pollution
|
||||
// except in e2e test when that access is required
|
||||
contextIsolation: global.appConfig.isContextIsolation,
|
||||
// Introduced in Electron 20 and enabled by default
|
||||
// Among others security constraints, it prevents from required
|
||||
// CommonJS modules imports into preload script
|
||||
// which is not bundled yet in dev mode
|
||||
sandbox: global.appConfig.isSandbox,
|
||||
// Use a preload script to enhance security
|
||||
preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,
|
||||
},
|
||||
});
|
||||
|
||||
this._printService = new PrintService();
|
||||
this._printService.setWindow(this._electronWindow);
|
||||
this.registerService<string, void>(this._printService);
|
||||
|
||||
// Disable the remote module to enhance security
|
||||
// except in e2e test when that access is required
|
||||
if (global.appConfig.isEnableRemoteModule) {
|
||||
@@ -44,17 +71,17 @@ export class Window {
|
||||
}
|
||||
|
||||
private loadIcon(): Electron.NativeImage | undefined {
|
||||
let iconObj = undefined;
|
||||
let iconObject;
|
||||
if (global.appConfig.isIconAvailable) {
|
||||
const iconPath = path.join(__dirname, 'icons/icon.png');
|
||||
Logger.debug('Icon Path', iconPath);
|
||||
iconObj = nativeImage.createFromPath(iconPath);
|
||||
iconObject = nativeImage.createFromPath(iconPath);
|
||||
// Change dock icon on MacOS
|
||||
if (iconObj && process.platform === 'darwin') {
|
||||
app.dock.setIcon(iconObj);
|
||||
if (iconObject && process.platform === 'darwin') {
|
||||
app.dock.setIcon(iconObject);
|
||||
}
|
||||
}
|
||||
return iconObj;
|
||||
return iconObject;
|
||||
}
|
||||
|
||||
private loadRenderer(): void {
|
||||
@@ -96,20 +123,20 @@ export class Window {
|
||||
private registerService<In, Out>(service: AbstractService<In, Out>) {
|
||||
ipcMain.on(
|
||||
service.receptionChannel(),
|
||||
async (event: Electron.IpcMainEvent, ...args: any[]) => {
|
||||
async (event: Electron.IpcMainEvent, ...parameters: any[]) => {
|
||||
// Handling input
|
||||
const input = args[0];
|
||||
const input = parameters[0];
|
||||
Logger.debug(`[${service.receptionChannel()}] =====> `, input);
|
||||
const output: Out = await service.process(input);
|
||||
|
||||
// Handling output
|
||||
if (service.sendingChannel()) {
|
||||
Logger.debug(`[${service.sendingChannel()}] =====> `, output);
|
||||
this._electronWindow.webContents.send(
|
||||
service.sendingChannel(),
|
||||
output
|
||||
);
|
||||
}
|
||||
service.process(input).then((output: Out) => {
|
||||
// Handling output
|
||||
if (service.sendingChannel()) {
|
||||
Logger.debug(`[${service.sendingChannel()}] =====> `, output);
|
||||
this._electronWindow.webContents.send(
|
||||
service.sendingChannel(),
|
||||
output
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as fs from 'fs-extra';
|
||||
import * as _ from 'lodash';
|
||||
import * as path from 'path';
|
||||
import _ from 'lodash';
|
||||
import * as path from 'node:path';
|
||||
import { AppConfig } from 'shared-lib';
|
||||
import { App } from './components/app';
|
||||
|
||||
@@ -14,14 +14,14 @@ declare global {
|
||||
}
|
||||
|
||||
// Load config
|
||||
const currentEnv = process.env.X_NODE_ENV || process.env.NODE_ENV;
|
||||
const currentEnvironment = process.env.X_NODE_ENV || process.env.NODE_ENV;
|
||||
const appConfigs = fs.readJsonSync(path.join(__dirname, 'config.json'));
|
||||
const defaultConf = appConfigs.development;
|
||||
const currentConf = appConfigs[currentEnv];
|
||||
const defaultConfig = appConfigs.development;
|
||||
const currentConfig = appConfigs[currentEnvironment];
|
||||
global.appConfig =
|
||||
currentEnv === 'development'
|
||||
? defaultConf
|
||||
: _.merge(defaultConf, currentConf);
|
||||
currentEnvironment === 'development'
|
||||
? defaultConfig
|
||||
: _.merge(defaultConfig, currentConfig);
|
||||
|
||||
// Launch app
|
||||
App.launch();
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
const NOT_IMPEMENTED_YET = 'Method not implemented yet.';
|
||||
export class AbstractService<In, Out> {
|
||||
receptionChannel(): string {
|
||||
throw new Error('Method not implemented yet.');
|
||||
throw new Error(NOT_IMPEMENTED_YET);
|
||||
}
|
||||
|
||||
sendingChannel(): string {
|
||||
throw new Error('Method not implemented yet.');
|
||||
throw new Error(NOT_IMPEMENTED_YET);
|
||||
}
|
||||
|
||||
process(_input: In): Out {
|
||||
throw new Error('Method not implemented yet.');
|
||||
process(_input: In): Promise<Out> {
|
||||
throw new Error(NOT_IMPEMENTED_YET);
|
||||
}
|
||||
}
|
||||
|
||||
67
workspaces/electron-app/main/services/door-service.ts
Normal file
67
workspaces/electron-app/main/services/door-service.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { WindowApiConst, Door } from 'shared-lib';
|
||||
import { AbstractService } from './abstract-service';
|
||||
import { readFile, set_fs } from 'xlsx';
|
||||
import * as fs from 'node:fs';
|
||||
set_fs(fs);
|
||||
|
||||
export class DoorService extends AbstractService<any, Door[]> {
|
||||
receptionChannel(): string {
|
||||
return WindowApiConst.PROJECT_INPUT;
|
||||
}
|
||||
|
||||
sendingChannel(): string {
|
||||
return WindowApiConst.PROJECT_OUTPUT;
|
||||
}
|
||||
private _mapping: any = {};
|
||||
|
||||
public setMapping(mapping: any): void {
|
||||
this._mapping = mapping;
|
||||
}
|
||||
|
||||
process(input: {name: string, path: string}): Promise<Door[]> {
|
||||
return new Promise((resolve) => {
|
||||
const workbook = readFile(`${input.path}/${input.name}`);
|
||||
const workingSheet = workbook.Sheets['DEURLIJST'];
|
||||
const doorList: Door[] = [];
|
||||
|
||||
|
||||
const headers: any = {};
|
||||
// get header values
|
||||
for (let index = 65; index <= 90; index++) {
|
||||
const currentValue = workingSheet[`${String.fromCodePoint(index)}3`]?.v;
|
||||
|
||||
for (const [key, value] of Object.entries(this._mapping)) {
|
||||
if ((value as string[]).find((x: string) => x.includes(currentValue)) != undefined) {
|
||||
this.setObjectWithChar(headers, key, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get max records
|
||||
const maxRecords = Number.parseInt(workingSheet['!ref'].split(':')[1].replace(/[A-Z]*/g, ''));
|
||||
|
||||
// stop at 19
|
||||
|
||||
for (let index = 4; index < maxRecords; index++) {
|
||||
const door: Door = {};
|
||||
door.nr = workingSheet[`A${index}`]?.v;
|
||||
if (door.nr) {
|
||||
door.lr = workingSheet[`${headers['lr']}${index}`]?.v;
|
||||
door.krukSlot = workingSheet[`${headers['krukSlot']}${index}`]?.v;
|
||||
door.pivot = workingSheet[`${headers['pivot']}${index}`]?.v;
|
||||
door.type = workingSheet[`${headers['type']}${index}`]?.v;
|
||||
door.modelKruk = workingSheet[`${headers['modelKruk']}${index}`]?.v;
|
||||
door.remark = workingSheet[`${headers['remark']}${index}`]?.v;
|
||||
|
||||
doorList.push(door);
|
||||
}
|
||||
}
|
||||
|
||||
resolve(doorList);
|
||||
});
|
||||
}
|
||||
|
||||
private setObjectWithChar(object: any, key: string, value: number) {
|
||||
object[key] = String.fromCodePoint(value);
|
||||
}
|
||||
}
|
||||
55
workspaces/electron-app/main/services/file-list-service.ts
Normal file
55
workspaces/electron-app/main/services/file-list-service.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { readdir } from 'node:fs';
|
||||
import { WindowApiConst } from 'shared-lib';
|
||||
import { AbstractService } from './abstract-service';
|
||||
|
||||
export class FileListService extends AbstractService<void, any[]> {
|
||||
private _paths: string[] = ['./assets'];
|
||||
|
||||
receptionChannel(): string {
|
||||
return WindowApiConst.FILELIST_INPUT;
|
||||
}
|
||||
|
||||
sendingChannel(): string {
|
||||
return WindowApiConst.FILELIST_OUTPUT;
|
||||
}
|
||||
|
||||
process(): Promise<any[]> {
|
||||
return new Promise((resolve) => {
|
||||
const projects: any[] = [];
|
||||
Promise.all(this._paths.map((path) => this.readPath(path))).then((result: any) => {
|
||||
for (const entry of result.entries()) {
|
||||
for (const file of entry[1].result) {
|
||||
projects.push({
|
||||
path: entry[1].path,
|
||||
name: file,
|
||||
});
|
||||
}
|
||||
}
|
||||
resolve(projects);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
private readPath(path: string): Promise<any> {
|
||||
return new Promise((resolve) => {
|
||||
readdir(path, (error, fileList: string[]) => {
|
||||
if (!error) {
|
||||
const newList: string[] = [];
|
||||
for (const file of fileList) {
|
||||
newList.push(file);
|
||||
}
|
||||
return resolve({
|
||||
path,
|
||||
result: newList,
|
||||
});
|
||||
}
|
||||
|
||||
return {};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public setPaths(inputPaths: string[]) {
|
||||
this._paths = inputPaths;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import { WindowApiConst } from 'shared-lib';
|
||||
import { AbstractService } from './abstract-service';
|
||||
|
||||
export class MultiplesService extends AbstractService<number, number[]> {
|
||||
receptionChannel(): string {
|
||||
return WindowApiConst.MULTIPLES_INPUT;
|
||||
}
|
||||
|
||||
sendingChannel(): string {
|
||||
return WindowApiConst.MULTIPLES_OUTPUT;
|
||||
}
|
||||
|
||||
process(input: number): number[] {
|
||||
// From 1 to 10, return input multiples
|
||||
const multiples = [];
|
||||
for (let n = 1; n <= 10; n++) {
|
||||
multiples.push(n * input);
|
||||
}
|
||||
return multiples;
|
||||
}
|
||||
}
|
||||
27
workspaces/electron-app/main/services/print-service.ts
Normal file
27
workspaces/electron-app/main/services/print-service.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { BrowserWindow } from 'electron';
|
||||
import { WindowApiConst } from 'shared-lib';
|
||||
import { AbstractService } from './abstract-service';
|
||||
|
||||
export class PrintService extends AbstractService<string, void> {
|
||||
private _browserWindow: BrowserWindow;
|
||||
sendingChannel(): string {
|
||||
return WindowApiConst.PRINT_OUTPUT;
|
||||
}
|
||||
|
||||
receptionChannel(): string {
|
||||
return WindowApiConst.PRINT_INPUT;
|
||||
}
|
||||
|
||||
process(): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
if (this._browserWindow) {
|
||||
this._browserWindow.webContents.print({ silent: false });
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
public setWindow(window: BrowserWindow): void {
|
||||
this._browserWindow = window;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { app } from 'electron';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
import * as winston from 'winston';
|
||||
|
||||
declare const global: Global;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// To secure user platform when running renderer process stuff,
|
||||
/*/ To secure user platform when running renderer process stuff,
|
||||
// Node.JS and Electron APIs are only available in this script
|
||||
import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron';
|
||||
import { WindowApi, WindowApiConst } from 'shared-lib';
|
||||
@@ -11,11 +11,13 @@ const windowApi: WindowApi = {
|
||||
ipcRenderer.send(channel, input);
|
||||
}
|
||||
},
|
||||
receive: <Out>(channel: string, func: (output: Out) => void) => {
|
||||
receive: <Out>(channel: string, callback: (output: Out) => void) => {
|
||||
if (WindowApiConst.RECEIVING_SAFE_CHANNELS.includes(channel)) {
|
||||
// Deliberately strip event as it includes `sender`
|
||||
ipcRenderer.on(channel, (event: IpcRendererEvent, ...args: any[]) =>
|
||||
func(args[0])
|
||||
ipcRenderer.on(
|
||||
channel,
|
||||
(_event: IpcRendererEvent, ...parameters: any[]) =>
|
||||
callback(parameters[0])
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -29,6 +31,27 @@ if (process.env.X_NODE_ENV === 'e2e-test') {
|
||||
// ContextBridge API can only be used when contextIsolation is enabled
|
||||
// which is normally the case except in e2e test mode
|
||||
contextBridge.exposeInMainWorld('api', windowApi);
|
||||
}
|
||||
}*/
|
||||
|
||||
import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron';
|
||||
import { WindowApiConst } from 'shared-lib';
|
||||
|
||||
contextBridge.exposeInMainWorld('api', {
|
||||
node: () => process.versions.node,
|
||||
chrome: () => process.versions.chrome,
|
||||
electron: () => process.versions.electron,
|
||||
send: <In>(channel: string, input: In) => {
|
||||
if (WindowApiConst.SENDING_SAFE_CHANNELS.includes(channel)) {
|
||||
ipcRenderer.send(channel, input);
|
||||
}
|
||||
},
|
||||
receive: <Out>(channel: string, callback: (output: Out) => void) => {
|
||||
// Deliberately strip event as it includes `sender`
|
||||
ipcRenderer.on(channel, (_event: IpcRendererEvent, ...parameters: any[]) =>
|
||||
callback(parameters[0])
|
||||
);
|
||||
},
|
||||
// we can also expose variables, not just functions
|
||||
});
|
||||
|
||||
console.log('The preload script has been injected successfully.');
|
||||
|
||||
@@ -35,7 +35,8 @@ describe('A simple test to check if app window is opened, visible and with expec
|
||||
// Checking there is one visible window
|
||||
// expect(await browser.).toEqual(true);
|
||||
// Please note that getWindowHandles() will return 2 if `dev tools` is opened.
|
||||
expect((await browser.getWindowHandles()).length).toEqual(1);
|
||||
const { length } = await browser.getWindowHandles();
|
||||
expect(length).toEqual(1);
|
||||
});
|
||||
|
||||
it('have expected title', async () => {
|
||||
@@ -10,10 +10,10 @@ describe('A simple test to check if a given input matches with computed multiple
|
||||
it(`display expected results on input (${number})`, async () => {
|
||||
await MultiplesPage.enterInput(number);
|
||||
const results = await MultiplesPage.results;
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
const ntimes = 1 + i;
|
||||
for (const index of results.keys()) {
|
||||
const ntimes = 1 + index;
|
||||
const expected = `${number} * ${ntimes} = ${number * ntimes}`;
|
||||
expect(await results[i].getText()).toEqual(expected);
|
||||
expect(await results[index].getText()).toEqual(expected);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Options } from '@wdio/types';
|
||||
import path from 'path';
|
||||
import path from 'node:path';
|
||||
|
||||
// Path to local electron binary
|
||||
let electronPath = path.join(__dirname, '../../node_modules/.bin/electron');
|
||||
@@ -9,7 +9,7 @@ if (process.platform === 'win32') {
|
||||
|
||||
// Starting hook
|
||||
const waitUntilWindowLoaded = async () => {
|
||||
const timeout = 10000;
|
||||
const timeout = 10_000;
|
||||
await browser.waitUntil(async () => (await browser.isLoading()) === false, {
|
||||
timeout: timeout,
|
||||
timeoutMsg: `Expected app to be loaded in less than ${timeout}ms`,
|
||||
@@ -74,7 +74,7 @@ export const config: Options.Testrunner = {
|
||||
// then the current working directory is where your `package.json` resides, so `wdio`
|
||||
// will be called from there.
|
||||
//
|
||||
specs: ['./workspaces/electron-e2e/**/*.e2e-spec.ts'],
|
||||
specs: ['./workspaces/electron-e2e/**/*.spec.ts'],
|
||||
// Patterns to exclude.
|
||||
exclude: [
|
||||
// 'path/to/excluded/files'
|
||||
@@ -154,11 +154,11 @@ export const config: Options.Testrunner = {
|
||||
baseUrl: 'http://localhost',
|
||||
//
|
||||
// Default timeout for all waitFor* commands.
|
||||
waitforTimeout: 10000,
|
||||
waitforTimeout: 10_000,
|
||||
//
|
||||
// Default timeout in milliseconds for request
|
||||
// if browser driver or grid doesn't send response
|
||||
connectionRetryTimeout: 120000,
|
||||
connectionRetryTimeout: 120_000,
|
||||
//
|
||||
// Default request retries count
|
||||
connectionRetryCount: 3,
|
||||
@@ -195,7 +195,7 @@ export const config: Options.Testrunner = {
|
||||
// Options to be passed to Jasmine.
|
||||
jasmineOpts: {
|
||||
// Jasmine default timeout
|
||||
defaultTimeoutInterval: 60000,
|
||||
defaultTimeoutInterval: 60_000,
|
||||
//
|
||||
// The Jasmine framework allows interception of each assertion in order to log the state of the application
|
||||
// or website depending on the result. For example, it is pretty handy to take a screenshot every time
|
||||
@@ -1,17 +1,24 @@
|
||||
export class WindowApiConst {
|
||||
/** Channel used by the renderer process to send data to the main process */
|
||||
public static readonly MULTIPLES_INPUT = "getMultiplesInput";
|
||||
public static readonly FILELIST_INPUT = 'getFileListInput';
|
||||
public static readonly FILELIST_OUTPUT = 'getFileListOutput';
|
||||
|
||||
/** Channel used by the renderer process to receive data from the main process */
|
||||
public static readonly MULTIPLES_OUTPUT = "getMultiplesOutput";
|
||||
public static readonly PROJECT_INPUT = 'getProjectInput';
|
||||
public static readonly PROJECT_OUTPUT = 'getProjectOutput';
|
||||
|
||||
/** Whitelist of the safe channels to use when sending data to the main process */
|
||||
public static readonly SENDING_SAFE_CHANNELS = [
|
||||
WindowApiConst.MULTIPLES_INPUT,
|
||||
];
|
||||
public static readonly PRINT_OUTPUT = 'getPrintOutput';
|
||||
public static readonly PRINT_INPUT = 'getPrintInput';
|
||||
|
||||
/** Whitelist of the safe channels to use when receiving data from the main process */
|
||||
public static readonly RECEIVING_SAFE_CHANNELS = [
|
||||
WindowApiConst.MULTIPLES_OUTPUT,
|
||||
];
|
||||
/** Whitelist of the safe channels to use when sending data to the main process */
|
||||
public static readonly SENDING_SAFE_CHANNELS = [
|
||||
WindowApiConst.FILELIST_INPUT,
|
||||
WindowApiConst.PROJECT_INPUT,
|
||||
WindowApiConst.PRINT_INPUT,
|
||||
];
|
||||
|
||||
/** Whitelist of the safe channels to use when receiving data from the main process */
|
||||
public static readonly RECEIVING_SAFE_CHANNELS = [
|
||||
WindowApiConst.FILELIST_OUTPUT,
|
||||
WindowApiConst.PROJECT_OUTPUT,
|
||||
WindowApiConst.PROJECT_OUTPUT,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ export interface WindowApi {
|
||||
* @param channel used by the renderer to receive data and by the main to send them
|
||||
* @param func the callback function to execute when data are available
|
||||
*/
|
||||
receive<Out>(channel: string, func: (output: Out) => void): void;
|
||||
receive<Out>(channel: string, callback: (output: Out) => void): void;
|
||||
|
||||
/**
|
||||
* This method is used by the renderer process to send data to the main process
|
||||
|
||||
@@ -2,6 +2,7 @@ import { WindowApi } from './apis/window-api';
|
||||
export * from './apis/window-api';
|
||||
export * from './apis/window-api-consts';
|
||||
export * from './models/config/app-config';
|
||||
export * from './models/door';
|
||||
|
||||
declare global {
|
||||
// Global augmentation of the `Window` interface
|
||||
|
||||
@@ -17,6 +17,9 @@ export interface AppConfig {
|
||||
/** Tells if `contextIsolation` and `worldSafeExecuteJavaScript` webPreferences are enabled */
|
||||
isContextIsolation: boolean;
|
||||
|
||||
/** Tells if `isSandbox` webPreference is enabled */
|
||||
isSandbox: boolean;
|
||||
|
||||
/** Tells if `isEnableRemoteModule` webPreference is enabled */
|
||||
isEnableRemoteModule: boolean;
|
||||
|
||||
|
||||
9
workspaces/shared-lib/models/door.ts
Normal file
9
workspaces/shared-lib/models/door.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export interface Door {
|
||||
nr?: string;
|
||||
lr?: string;
|
||||
pivot?: string;
|
||||
type?: string;
|
||||
remark?: string;
|
||||
modelKruk?: string;
|
||||
krukSlot?: string;
|
||||
}
|
||||
Reference in New Issue
Block a user