UI: Battery, Light

This commit is contained in:
Patrick Haßel 2025-01-30 10:03:17 +01:00
commit b0038fd7a7
35 changed files with 15212 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target/

18
pom.xml Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.ph87.data</groupId>
<artifactId>Elektro</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>

View File

@ -0,0 +1,18 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
# noinspection EditorConfigKeyCorrectness
quote_type = single
ij_typescript_use_double_quotes = false
[*.md]
max_line_length = off
trim_trailing_whitespace = false

42
src/main/angular/.gitignore vendored Normal file
View File

@ -0,0 +1,42 @@
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
# System files
.DS_Store
Thumbs.db

View File

@ -0,0 +1,59 @@
# Angular
This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 19.0.7.
## Development server
To start a local development server, run:
```bash
ng serve
```
Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files.
## Code scaffolding
Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
```bash
ng generate component component-name
```
For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
```bash
ng generate --help
```
## Building
To build the project run:
```bash
ng build
```
This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed.
## Running unit tests
To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
```bash
ng test
```
## Running end-to-end tests
For end-to-end (e2e) testing, run:
```bash
ng e2e
```
Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
## Additional Resources
For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.

View File

@ -0,0 +1,124 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"angular": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "less",
"skipTests": true
},
"@schematics/angular:class": {
"skipTests": true
},
"@schematics/angular:directive": {
"skipTests": true
},
"@schematics/angular:guard": {
"skipTests": true
},
"@schematics/angular:interceptor": {
"skipTests": true
},
"@schematics/angular:pipe": {
"skipTests": true
},
"@schematics/angular:resolver": {
"skipTests": true
},
"@schematics/angular:service": {
"skipTests": true
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/angular",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": [
"zone.js"
],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "less",
"assets": [
{
"glob": "**/*",
"input": "public"
}
],
"styles": [
"src/styles.less"
],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kB",
"maximumError": "1MB"
},
{
"type": "anyComponentStyle",
"maximumWarning": "4kB",
"maximumError": "8kB"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "angular:build:production"
},
"development": {
"buildTarget": "angular:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n"
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"polyfills": [
"zone.js",
"zone.js/testing"
],
"tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "less",
"assets": [
{
"glob": "**/*",
"input": "public"
}
],
"styles": [
"src/styles.less"
],
"scripts": []
}
}
}
}
}
}

14401
src/main/angular/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
{
"name": "angular",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"private": true,
"dependencies": {
"@angular/animations": "^19.0.0",
"@angular/common": "^19.0.0",
"@angular/compiler": "^19.0.0",
"@angular/core": "^19.0.0",
"@angular/forms": "^19.0.0",
"@angular/platform-browser": "^19.0.0",
"@angular/platform-browser-dynamic": "^19.0.0",
"@angular/router": "^19.0.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.15.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "^19.0.7",
"@angular/cli": "^19.0.7",
"@angular/compiler-cli": "^19.0.0",
"@types/jasmine": "~5.1.0",
"jasmine-core": "~5.4.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.6.2"
}
}

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 398.502 398.502">
<polygon fill="#3E81C8" points="227.741,360.947 221.728,386.947 176.754,386.947 170.741,360.947"/>
<polygon fill="#3E81C8" points="254.751,325.177 248.067,353.177 150.435,353.177 143.751,325.177"/>
<rect x="167.751" y="254.827" fill="#EB7830" width="63" height="63.74"/>
<path fill="#EB7830" d="M237.161,193.872l-6.44,55.56h-62.94l-6.44-55.56l7.5,3.91c1.57,1.45,8.66,2.2,10.79,2.11c2.13-0.1,4.13-1.04,5.56-2.62l14.06-12.53l14.05,12.53c1.43,1.58,3.44,2.52,5.57,2.62c0.12,0,0.24,0.01,0.36,0.01c2.01,0,8.94-0.75,10.42-2.12L237.161,193.872z"/>
<path fill="#F5C525" d="M297.261,176.372c0,14.23-3.12,27.88-9.29,40.57c-14.55,29.97-28.07,67.25-29.42,101.02h-22.34v-65.53l8.93-65.49c0.62-4.37,3.58-8.42-0.8-9.04c-2.47-0.35-4.84,0.47-6.54,2.04l-18.05,16.61l-14.57-16.1c-1.52-1.67-3.67-2.63-5.93-2.63s-4.42,0.96-5.93,2.63l-14.58,16.1l-18.04-16.62c-3.25-2.99-3.31-2.78-6.31,0.47c-1.69,1.84-2.36,4.25-2.03,6.54l9.93,65.49v65.53h-22.35c-1.14-28.07-10.35-63.35-28.73-99.64c-6.57-12.98-9.93-26.99-9.97-41.65c-0.16-50.74,45.97-97.6,96.68-98.3C249.831,77.662,297.261,124.642,297.261,176.372z"/>
<path fill="#231F20"
d="M199.251,67.362c-0.52,0-1.03,0-1.55,0.01c-59.44,0.82-107.65,49.88-107.46,109.35c0.05,17.18,3.99,33.6,11.7,48.83c18.5,36.55,27.12,66.21,27.12,93.36v2.05c0,0.8,0.13,1.58,0.35,2.31c0,0,0,0,0,0.01v0.01c0.01,0.03,0.02,0.05,0.03,0.08l11.01,36.34c1.02,3.37,4.13,5.68,7.65,5.68h10.23l8.19,27.4c1.01,3.39,4.13,5.71,7.66,5.71h50.14c3.53,0,6.65-2.32,7.66-5.71l8.19-27.4h10.23c3.52,0,6.63-2.31,7.65-5.68l11.02-36.34c0-0.03,0.01-0.05,0.02-0.08v-0.01c0-0.01,0-0.01,0-0.01c0.22-0.73,0.35-1.51,0.35-2.31v-2.51c0-32.53,13.42-64.65,27.92-94.52c7.24-14.89,10.9-30.89,10.9-47.56C308.261,116.262,259.361,67.362,199.251,67.362z M218.361,382.502h-38.22l-5.11-17.11h48.44L218.361,382.502z M244.461,349.392h-90.42l-6.19-20.43h102.8L244.461,349.392z M172.781,244.432l-6.44-45.56l7.5,6.91c1.57,1.45,3.66,2.2,5.79,2.11c2.13-0.1,4.13-1.04,5.56-2.62l14.06-15.53l14.05,15.53c1.43,1.58,3.44,2.52,5.57,2.62c0.12,0,0.24,0.01,0.36,0.01c2.01,0,3.94-0.75,5.42-2.12l7.51-6.91l-6.44,45.56H172.781z M225.211,260.432v52.53h-51.92v-52.53H225.211z M282.971,216.942c-14.55,29.97-28.07,62.25-29.42,96.02h-12.34v-60.53l9.93-73.49c0.62-4.37-2.42-8.42-6.8-9.04c-2.47-0.35-4.84,0.47-6.54,2.04l-18.05,16.61l-14.57-16.1c-1.52-1.67-3.67-2.63-5.93-2.63s-4.42,0.96-5.93,2.63l-14.58,16.1l-18.04-16.62c-3.25-2.99-8.31-2.78-11.31,0.47c-1.69,1.84-2.36,4.25-2.03,6.54l9.93,73.49v60.53h-12.35c-1.14-28.07-10.35-58.35-28.73-94.64c-6.57-12.98-9.93-26.99-9.97-41.65c-0.16-50.74,40.97-92.6,91.68-93.3c51.91-0.71,94.34,41.27,94.34,93C292.261,190.602,289.141,204.252,282.971,216.942z"/>
<path fill="#231F20" d="M67.602,175.614c0-4.418-3.582-8-8-8H31.637c-4.418,0-8,3.582-8,8s3.582,8,8,8h27.965C64.021,183.614,67.602,180.032,67.602,175.614z"/>
<path fill="#231F20" d="M366.865,167.614H338.9c-4.418,0-8,3.582-8,8s3.582,8,8,8h27.965c4.418,0,8-3.582,8-8S371.284,167.614,366.865,167.614z"/>
<path fill="#231F20" d="M82.311,98.861L58.093,84.878c-3.827-2.21-8.719-0.898-10.928,2.928c-2.209,3.826-0.898,8.719,2.928,10.928l24.218,13.983c1.26,0.728,2.635,1.073,3.993,1.073c2.765,0,5.454-1.435,6.936-4.001C87.449,105.963,86.138,101.071,82.311,98.861z"/>
<path fill="#231F20" d="M348.409,252.493L324.19,238.51c-3.827-2.209-8.719-0.898-10.928,2.928c-2.209,3.826-0.898,8.719,2.928,10.928l24.219,13.983c1.26,0.728,2.635,1.073,3.993,1.073c2.765,0,5.454-1.435,6.936-4.001C353.546,259.595,352.235,254.702,348.409,252.493z"/>
<path fill="#231F20" d="M122.498,58.674c1.482,2.566,4.171,4.001,6.936,4.001c1.357,0,2.733-0.346,3.993-1.073c3.826-2.209,5.137-7.102,2.928-10.928l-13.982-24.218c-2.209-3.826-7.103-5.135-10.928-2.928c-3.826,2.209-5.137,7.102-2.928,10.928L122.498,58.674z"/>
<path fill="#231F20" d="M199.251,43.965c4.418,0,8-3.582,8-8V8c0-4.418-3.582-8-8-8s-8,3.582-8,8v27.965C191.251,40.384,194.832,43.965,199.251,43.965z"/>
<path fill="#231F20" d="M265.076,61.603c1.26,0.728,2.635,1.073,3.993,1.073c2.765,0,5.454-1.435,6.936-4.001l13.982-24.218c2.209-3.826,0.898-8.719-2.928-10.928c-3.826-2.21-8.719-0.898-10.928,2.928l-13.982,24.218C259.938,54.5,261.249,59.394,265.076,61.603z"/>
<path fill="#231F20" d="M320.198,113.791c1.357,0,2.733-0.346,3.993-1.073l24.219-13.983c3.826-2.209,5.137-7.102,2.928-10.928c-2.209-3.827-7.103-5.135-10.928-2.928l-24.22,13.982c-3.826,2.209-5.137,7.102-2.928,10.928C314.744,112.356,317.433,113.791,320.198,113.791z"/>
<path fill="#231F20" d="M74.311,238.51l-24.218,13.983c-3.827,2.209-5.137,7.102-2.928,10.928c1.482,2.567,4.171,4.001,6.936,4.001c1.357,0,2.733-0.346,3.993-1.073l24.218-13.983c3.826-2.209,5.137-7.102,2.928-10.928C83.03,237.611,78.137,236.303,74.311,238.51z"/>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

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

View File

@ -0,0 +1,11 @@
import {Component} from '@angular/core';
import {RouterOutlet} from '@angular/router';
@Component({
selector: 'app-root',
imports: [RouterOutlet],
templateUrl: './app.component.html',
styleUrl: './app.component.less'
})
export class AppComponent {
}

View File

@ -0,0 +1,8 @@
import {ApplicationConfig, provideZoneChangeDetection} from '@angular/core';
import {provideRouter} from '@angular/router';
import {routes} from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [provideZoneChangeDetection({eventCoalescing: true}), provideRouter(routes)]
};

View File

@ -0,0 +1,7 @@
import {Routes} from '@angular/router';
import {EditorComponent} from './editor/editor.component';
export const routes: Routes = [
{path: 'Editor', component: EditorComponent},
{path: '**', redirectTo: 'Editor'},
];

View File

@ -0,0 +1,32 @@
<svg class="breadboard">
<svg *ngFor="let part of parts" [attr.height]="part.hP" [attr.width]="part.wP" [attr.x]="part.xP" [attr.y]="part.yP" [ngClass]="part.ngClass()">
<rect class="background" height="100%" width="100%" x="0" y="0"></rect>
<ng-container *ngIf="isBattery(part)">
<text dominant-baseline="hanging" text-anchor="middle" x="50%" y="3%">{{ asBattery(part).voltageStr }}</text>
<line class="wire" id="wireMinus" x1="15%" x2="40%" y1="50%" y2="50%"></line>
<line class="wire" id="wirePlus" x1="55%" x2="80%" y1="50%" y2="50%"></line>
<rect height="30%" id="symbolMinus" width="10%" x="35%" y="35%"></rect>
<rect height="60%" id="symbolPlus" width="5%" x="55%" y="20%"></rect>
<text text-anchor="middle" x="50%" y="95%">{{ asBattery(part).currentStr }}</text>
</ng-container>
<ng-container *ngIf="isLight(part)">
<text dominant-baseline="hanging" text-anchor="middle" x="50%" y="3%">{{ asBattery(part).voltageStr }}</text>
<circle [attr.fill]="asLight(part).fill()" [class.defect]="asLight(part).defect" cx="50%" cy="50%" id="circle" r="35%"></circle>
<g class="cross">
<line id="lineLeftRight" x1="15%" x2="85%" y1="50%" y2="50%"></line>
<line id="lineTopDown" x1="50%" x2="50%" y1="15%" y2="85%"></line>
</g>
<text text-anchor="middle" x="50%" y="95%">{{ asBattery(part).currentStr }}</text>
</ng-container>
<svg *ngFor="let j of part.junctions" [attr.height]="j.hP" [attr.width]="j.wP" [attr.x]="j.xP" [attr.y]="j.yP" class="junction">
<circle [attr.id]="'j' + j.name" cx="50%" cy="50%" fill="gray" r="35%"></circle>
</svg>
</svg>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,59 @@
.breadboard {
position: absolute;
width: 100%;
height: 100%;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
.part {
.background {
fill: lightgray;
}
}
.part:not(:has(.junction:hover)):hover {
.background {
fill: lightblue;
}
}
.partLight {
circle {
stroke-width: 1px;
stroke: black;
}
.defect {
filter: drop-shadow(0 0 30px black);
}
.cross {
transform-origin: 50% 50%;
transform: rotate(45deg);
stroke-width: 1px;
stroke: black;
}
}
.wire {
stroke-width: 5px;
stroke: black;
}
.junction {
stroke-width: 1px;
stroke: black;
}
.junction:hover {
circle {
fill: lightblue;
}
}
}

View File

@ -0,0 +1,40 @@
import {Component} from '@angular/core';
import {NgClass, NgForOf, NgIf} from '@angular/common';
import {Part} from '../parts/Part';
import {Battery} from '../parts/battery/Battery';
import {Light} from '../parts/light/Light';
@Component({
selector: 'app-board',
imports: [
NgForOf,
NgIf,
NgClass,
],
templateUrl: './breadboard.component.html',
styleUrl: './breadboard.component.less'
})
export class BreadboardComponent {
parts: Part[] = [
new Battery(1, 1, 3, 0.5),
new Light(1, 5, 3, 3, 0.5),
];
asBattery(part: Part): Battery {
return part as Battery;
}
isBattery(part: Part): boolean {
return part instanceof Battery;
}
asLight(part: Part): Light {
return part as Light;
}
isLight(part: Part): boolean {
return part instanceof Light;
}
}

View File

@ -0,0 +1 @@
<app-board></app-board>

View File

@ -0,0 +1,14 @@
import {Component} from '@angular/core';
import {BreadboardComponent} from './breadboard/breadboard.component';
@Component({
selector: 'app-editor',
imports: [
BreadboardComponent
],
templateUrl: './editor.component.html',
styleUrl: './editor.component.less'
})
export class EditorComponent {
}

View File

@ -0,0 +1,11 @@
import {parseColor} from './parseColor';
export function fadeGrayToYellow(factor: number, fromColor: any, toColor: any): string {
fromColor = parseColor(fromColor);
toColor = parseColor(toColor);
factor = Math.max(0, Math.min(100, factor));
const r = Math.round(fromColor.r + factor * (toColor.r - fromColor.r));
const g = Math.round(fromColor.g + factor * (toColor.g - fromColor.g));
const b = Math.round(fromColor.b + factor * (toColor.b - fromColor.b));
return `rgb(${r}, ${g}, ${b})`;
}

View File

@ -0,0 +1,29 @@
export const JUNCTION_RADIUS_PERCENT = 15;
export class Junction {
constructor(
readonly x: number,
readonly y: number,
readonly name: string,
) {
//
}
get xP(): string {
return (this.x - JUNCTION_RADIUS_PERCENT) + '%';
}
get yP(): string {
return (this.y - JUNCTION_RADIUS_PERCENT) + '%';
}
get wP(): string {
return (JUNCTION_RADIUS_PERCENT * 2) + '%';
}
get hP(): string {
return (JUNCTION_RADIUS_PERCENT * 2) + '%';
}
}

View File

@ -0,0 +1,26 @@
export function parseColor(color: string): { r: number; g: number; b: number } {
const ctx = document.createElement("canvas").getContext("2d");
if (!ctx) {
throw new Error();
}
ctx.fillStyle = color;
const computed = ctx.fillStyle;
if (!computed.startsWith("#")) {
throw new Error();
}
let hex = computed.slice(1);
if (hex.length === 3) {
hex = hex.split("").map(c => c + c).join("");
}
if (hex.length !== 6) {
throw new Error();
}
const r = parseInt(hex.substring(0, 2), 16);
const g = parseInt(hex.substring(2, 4), 16);
const b = parseInt(hex.substring(4, 6), 16);
return {r, g, b};
}

View File

@ -0,0 +1,59 @@
import {Junction} from '../junction/Junction';
import {PartType} from './PartType';
export const RASTER = 50;
export abstract class Part {
protected constructor(
readonly x: number,
readonly y: number,
readonly type: PartType,
readonly junctions: Junction[],
readonly w: number = 3,
readonly h: number = 3,
) {
//
}
get xR(): number {
return this.x * RASTER;
}
get yR(): number {
return this.y * RASTER;
}
get wR(): number {
return this.w * RASTER;
}
get hR(): number {
return this.h * RASTER;
}
get xP(): string {
return this.xR + 'px';
}
get yP(): string {
return this.yR + 'px';
}
get wP(): string {
return this.wR + 'px';
}
get hP(): string {
return this.hR + 'px';
}
ngClass(): string[] {
return ['part', 'part' + this.type, ...this.ngClass2()];
}
protected ngClass2(): string[] {
return [];
}
}

View File

@ -0,0 +1,4 @@
export enum PartType {
Battery = 'Battery',
Light = 'Light',
}

View File

@ -0,0 +1,33 @@
import {Part} from "../Part";
import {Junction} from '../../junction/Junction';
import {PartType} from '../PartType';
import {siPrefix} from '../../siPrefix';
export class Battery extends Part {
constructor(
x: number,
y: number,
readonly voltage: number,
public current: number,
) {
super(
x,
y,
PartType.Battery,
[
new Junction(15, 50, "-"),
new Junction(85, 50, "+"),
]
);
}
get voltageStr(): string {
return siPrefix(this.voltage, 'V', 2);
}
get currentStr(): string {
return siPrefix(this.current, 'A', 2);
}
}

View File

@ -0,0 +1,46 @@
import {Part} from "../Part";
import {Junction} from '../../junction/Junction';
import {PartType} from '../PartType';
import {siPrefix} from '../../siPrefix';
import {fadeGrayToYellow} from '../../fadeGrayToYellow';
export class Light extends Part {
constructor(
x: number,
y: number,
readonly voltageMax: number,
readonly voltage: number,
public current: number,
) {
super(
x,
y,
PartType.Light,
[
new Junction(15, 50, "a"),
new Junction(85, 50, "b"),
]
);
}
get defect(): boolean {
return this.voltage > this.voltageMax;
}
get voltageStr(): string {
return siPrefix(this.voltage, 'V', 2);
}
get currentStr(): string {
return siPrefix(this.current, 'A', 2);
}
fill(): string {
if (this.defect) {
return "#6e4122"
}
return fadeGrayToYellow(this.voltage / this.voltageMax, "#888", "#FF0");
}
}

View File

@ -0,0 +1,15 @@
export const SI_PREFIXES: string[] = ['f', 'p', 'n', 'µ', 'm', '', 'k', 'M', 'G', 'T', 'E', 'P'];
export function siPrefix(value: number, unit: string, minDigits: number): string {
const exp0 = Math.log10(value);
const group = Math.floor(exp0 / 3);
const index = group + 5;
const prefix = SI_PREFIXES[index];
const exp1 = group * 3;
const factor = Math.pow(10, -exp1);
const newValue = factor * value;
const hasDigits = Math.floor(Math.log10(newValue)) + 1;
const decimals = Math.max(0, minDigits - hasDigits);
const newValueStr2 = newValue.toFixed(decimals);
return `${newValueStr2}${prefix}${unit}`;
}

View File

@ -0,0 +1,14 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Elektro</title>
<base href="/">
<meta content="width=device-width, initial-scale=1" name="viewport">
<!--suppress HtmlUnknownTarget -->
<link rel="icon" href="favicon.svg">
</head>
<body>
<app-root></app-root>
</body>
</html>

View File

@ -0,0 +1,6 @@
import {bootstrapApplication} from '@angular/platform-browser';
import {appConfig} from './app/app.config';
import {AppComponent} from './app/app.component';
bootstrapApplication(AppComponent, appConfig)
.catch((err) => console.error(err));

View File

@ -0,0 +1,10 @@
body {
margin: 0;
font-family: sans-serif;
width: 100vw;
height: 100vh;
}
* {
box-sizing: border-box;
}

View File

@ -0,0 +1,15 @@
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": [
"src/main.ts"
],
"include": [
"src/**/*.d.ts"
]
}

View File

@ -0,0 +1,27 @@
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{
"compileOnSave": false,
"compilerOptions": {
"outDir": "./dist/out-tsc",
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"isolatedModules": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"moduleResolution": "bundler",
"importHelpers": true,
"target": "ES2022",
"module": "ES2022"
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}

View File

@ -0,0 +1,15 @@
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [
"jasmine"
]
},
"include": [
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}

View File

@ -0,0 +1,9 @@
package de.ph87.data;
public class Main {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}