dark theme + percent-bar + Value.locale
This commit is contained in:
parent
f5cbf9cf43
commit
f495ad9af1
27
src/main/angular/config.less
Normal file
27
src/main/angular/config.less
Normal file
@ -0,0 +1,27 @@
|
||||
/* bright theme */
|
||||
|
||||
@foreground: gray;
|
||||
@background: white;
|
||||
|
||||
@consumption: orange;
|
||||
@purchase: orangered;
|
||||
@production: dodgerblue;
|
||||
@self: forestgreen;
|
||||
@delivery: magenta;
|
||||
|
||||
@consumptionBack: @consumption;
|
||||
@purchaseBack: @purchase;
|
||||
@productionBack: @production;
|
||||
@selfBack: @self;
|
||||
@deliveryBack: @delivery;
|
||||
|
||||
/* dark theme */
|
||||
|
||||
@foreground: gray;
|
||||
@background: #11171b;
|
||||
|
||||
@consumptionBack: #856938;
|
||||
@purchaseBack: #71361d;
|
||||
@productionBack: #2d4255;
|
||||
@selfBack: #2b4e2b;
|
||||
@deliveryBack: #753475;
|
||||
@ -6,7 +6,7 @@
|
||||
|
||||
<div class="content">
|
||||
<div class="entry consumption" [class.zero]="powerConsumption?.zero">
|
||||
<div class="name">Verbrauch</div>
|
||||
<div class="name">Bedarf</div>
|
||||
<div class="percent"> </div>
|
||||
<div class="value">{{ powerConsumption?.formatted }}</div>
|
||||
</div>
|
||||
@ -15,13 +15,13 @@
|
||||
<div class="percent">{{ powerPurchasePercent?.formatted }}</div>
|
||||
<div class="value">{{ powerPurchase?.formatted }}</div>
|
||||
</div>
|
||||
<div class="entry production" [class.zero]="powerProduced?.zero">
|
||||
<div class="entry production" [class.zero]="powerProduction?.zero">
|
||||
<div class="name">Produktion</div>
|
||||
<div class="percent">{{ powerProducedPercent?.formatted }}</div>
|
||||
<div class="value">{{ powerProduced?.formatted }}</div>
|
||||
<div class="value">{{ powerProduction?.formatted }}</div>
|
||||
</div>
|
||||
<div class="entry self" [class.zero]="powerSelf?.zero">
|
||||
<div class="name">Eigenverbrauch</div>
|
||||
<div class="name">Eigenbedarf</div>
|
||||
<div class="percent">{{ powerSelfPercent?.formatted }}</div>
|
||||
<div class="value">{{ powerSelf?.formatted }}</div>
|
||||
</div>
|
||||
@ -32,14 +32,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<app-percent-bar [produktion]="powerProduction" [self]="powerSelf" [purchase]="powerPurchase" [delivery]="powerDelivery"></app-percent-bar>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
|
||||
<div class="title">
|
||||
Energie
|
||||
<span class="live" *ngIf="interval">Live</span>
|
||||
<span class="archive" *ngIf="!interval">Archiv</span>
|
||||
</div>
|
||||
|
||||
<div class="option">
|
||||
@ -56,7 +56,7 @@
|
||||
|
||||
<div class="content">
|
||||
<div class="entry consumption" [class.zero]="aggregations.energyConsumed?.zero">
|
||||
<div class="name">Verbrauch</div>
|
||||
<div class="name">Bedarf</div>
|
||||
<div class="percent"> </div>
|
||||
<div class="value">{{ aggregations.energyConsumed?.formatted }}</div>
|
||||
</div>
|
||||
@ -71,17 +71,19 @@
|
||||
<div class="value">{{ aggregations.energyProduced?.delta?.formatted }}</div>
|
||||
</div>
|
||||
<div class="entry self" [class.zero]="aggregations.energySelf?.zero">
|
||||
<div class="name">Eigenverbrauch</div>
|
||||
<div class="name">Eigenbedarf</div>
|
||||
<div class="percent">{{ aggregations.energySelfPercent?.formatted }}</div>
|
||||
<div class="value">{{ aggregations.energySelf?.formatted }}</div>
|
||||
</div>
|
||||
<div class="entry delivery" [class.zero]="aggregations.energyDelivered?.delta?.zero">
|
||||
<div class="name">Eingespeist</div>
|
||||
<div class="name">Einspeisung</div>
|
||||
<div class="percent">{{ aggregations.energyDeliveredPercent?.formatted }}</div>
|
||||
<div class="value">{{ aggregations.energyDelivered?.delta?.formatted }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<app-percent-bar [produktion]="aggregations.energyProduced?.delta" [self]="aggregations.energySelf" [purchase]="aggregations.energyPurchased?.delta" [delivery]="aggregations.energyDelivered?.delta"></app-percent-bar>
|
||||
|
||||
</div>
|
||||
|
||||
<router-outlet/>
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
@import "../../config.less";
|
||||
|
||||
.section {
|
||||
margin-bottom: 1em;
|
||||
margin-bottom: 2em;
|
||||
|
||||
.back {
|
||||
float: left;
|
||||
@ -12,7 +14,6 @@
|
||||
.title {
|
||||
clear: left;
|
||||
font-weight: bold;
|
||||
font-size: 120%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@ -28,7 +29,6 @@
|
||||
|
||||
.name {
|
||||
float: left;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.value {
|
||||
@ -39,7 +39,7 @@
|
||||
float: right;
|
||||
padding-top: 0.5em;
|
||||
font-size: 60%;
|
||||
width: 3.5em;
|
||||
width: 4.5em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@ -62,25 +62,25 @@
|
||||
}
|
||||
|
||||
.consumption {
|
||||
color: orange;
|
||||
color: @consumption;
|
||||
}
|
||||
|
||||
.purchase {
|
||||
color: orangered;
|
||||
color: @purchase;
|
||||
}
|
||||
|
||||
.production {
|
||||
color: dodgerblue;
|
||||
color: @production;
|
||||
}
|
||||
|
||||
.self {
|
||||
color: forestgreen;
|
||||
color: @self;
|
||||
}
|
||||
|
||||
.delivery {
|
||||
color: magenta;
|
||||
color: @delivery;
|
||||
}
|
||||
|
||||
.zero {
|
||||
filter: opacity(30%);
|
||||
filter: opacity(20%);
|
||||
}
|
||||
|
||||
@ -5,11 +5,11 @@ import {AggregationWrapperDto} from './series/AggregationWrapperDto';
|
||||
import {SeriesService} from './series/series.service';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {Value} from './value/Value';
|
||||
import {NgIf} from '@angular/common';
|
||||
import {PercentBarComponent} from './percent-bar/percent-bar.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
imports: [RouterOutlet, NgIf],
|
||||
imports: [RouterOutlet, PercentBarComponent],
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.less'
|
||||
})
|
||||
@ -25,7 +25,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
|
||||
private readonly subs: Subscription[] = [];
|
||||
|
||||
get powerProduced(): Value | undefined {
|
||||
get powerProduction(): Value | undefined {
|
||||
return this.seriesService.powerProduced.series?.lastValue;
|
||||
}
|
||||
|
||||
@ -42,19 +42,19 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
get powerSelf(): Value | undefined {
|
||||
return this.powerProduced?.minus(this.powerDelivery);
|
||||
return this.powerProduction?.minus(this.powerDelivery);
|
||||
}
|
||||
|
||||
get powerConsumption(): Value | undefined {
|
||||
return this.powerBalance?.plus(this.powerProduced);
|
||||
return this.powerPurchase?.plus(this.powerProduction);
|
||||
}
|
||||
|
||||
get powerProducedPercent(): Value | undefined {
|
||||
return this.powerProduced?.percent(this.powerConsumption);
|
||||
return this.powerProduction?.percent(this.powerConsumption);
|
||||
}
|
||||
|
||||
get powerSelfPercent(): Value | undefined {
|
||||
return this.powerSelf?.percent(this.powerProduced);
|
||||
return this.powerSelf?.percent(this.powerProduction);
|
||||
}
|
||||
|
||||
get powerPurchasePercent(): Value | undefined {
|
||||
@ -62,7 +62,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
get powerDeliveryPercent(): Value | undefined {
|
||||
return this.powerDelivery?.percent(this.powerProduced);
|
||||
return this.powerDelivery?.percent(this.powerProduction);
|
||||
}
|
||||
|
||||
constructor(
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
<div class="bar">
|
||||
<div class="part purchase" *ngIf="barPurchasePercent" [style.width]="barPurchasePercent.value + '%'">
|
||||
<div class="text">
|
||||
{{ _purchase?.formatted }}<br>
|
||||
{{ purchasePercent?.formatted }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="part self" *ngIf="barSelfPercent" [style.width]="barSelfPercent.value + '%'">
|
||||
<div class="text">
|
||||
{{ _self?.formatted }}<br>
|
||||
{{ selfPercent?.formatted }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="part delivery" *ngIf="barDeliveryPercent" [style.width]="barDeliveryPercent.value + '%'">
|
||||
<div class="text">
|
||||
{{ _delivery?.formatted }}<br>
|
||||
{{ deliveryPercent?.formatted }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,30 @@
|
||||
@import "../../../config.less";
|
||||
|
||||
.bar {
|
||||
position: relative;
|
||||
color: white;
|
||||
|
||||
.part {
|
||||
float: left;
|
||||
white-space: nowrap;
|
||||
font-size: 40%;
|
||||
|
||||
.text {
|
||||
padding-left: 0.25em;
|
||||
}
|
||||
border-radius: 0.5em;
|
||||
}
|
||||
|
||||
.self {
|
||||
background-color: @selfBack;
|
||||
}
|
||||
|
||||
.purchase {
|
||||
background-color: @purchaseBack;
|
||||
}
|
||||
|
||||
.delivery {
|
||||
background-color: @deliveryBack;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
import {Component, Input} from '@angular/core';
|
||||
import {Value} from '../value/Value';
|
||||
import {NgIf} from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'app-percent-bar',
|
||||
imports: [
|
||||
NgIf
|
||||
],
|
||||
templateUrl: './percent-bar.component.html',
|
||||
styleUrl: './percent-bar.component.less'
|
||||
})
|
||||
export class PercentBarComponent {
|
||||
|
||||
protected _produktion: Value | undefined;
|
||||
|
||||
@Input()
|
||||
set produktion(produktion: Value | undefined) {
|
||||
this._produktion = produktion;
|
||||
this.update();
|
||||
}
|
||||
|
||||
protected _self: Value | undefined;
|
||||
|
||||
@Input()
|
||||
set self(self: Value | undefined) {
|
||||
this._self = self;
|
||||
this.update();
|
||||
}
|
||||
|
||||
protected _purchase: Value | undefined;
|
||||
|
||||
@Input()
|
||||
set purchase(purchase: Value | undefined) {
|
||||
this._purchase = purchase;
|
||||
this.update();
|
||||
}
|
||||
|
||||
protected _delivery: Value | undefined;
|
||||
|
||||
@Input()
|
||||
set delivery(delivery: Value | undefined) {
|
||||
this._delivery = delivery;
|
||||
this.update();
|
||||
}
|
||||
|
||||
@Input()
|
||||
percent: boolean = false;
|
||||
|
||||
protected consumption: Value | undefined;
|
||||
|
||||
protected barSum: Value | undefined;
|
||||
|
||||
protected selfPercent: Value | undefined;
|
||||
|
||||
protected purchasePercent: Value | undefined;
|
||||
|
||||
protected deliveryPercent: Value | undefined;
|
||||
|
||||
protected barSelfPercent: Value | undefined;
|
||||
|
||||
protected barPurchasePercent: Value | undefined;
|
||||
|
||||
protected barDeliveryPercent: Value | undefined;
|
||||
|
||||
private update() {
|
||||
this.consumption = this._self?.plus(this._purchase);
|
||||
this.selfPercent = this._self?.percent(this.consumption);
|
||||
this.purchasePercent = this._purchase?.percent(this.consumption);
|
||||
this.deliveryPercent = this._delivery?.percent(this._produktion);
|
||||
|
||||
this.barSum = this.consumption?.plus(this._delivery);
|
||||
this.barSelfPercent = this._self?.percent(this.barSum);
|
||||
this.barPurchasePercent = this._purchase?.percent(this.barSum);
|
||||
this.barDeliveryPercent = this._delivery?.percent(this.barSum);
|
||||
}
|
||||
|
||||
}
|
||||
@ -48,11 +48,11 @@ export class AggregationWrapperDto {
|
||||
this.energySelf = this.energyProduced?.delta.minus(this.energyDelivered?.delta);
|
||||
}
|
||||
|
||||
static fromJson(json: any): AggregationWrapperDto {
|
||||
static fromJson(json: any, locale: string): AggregationWrapperDto {
|
||||
return new AggregationWrapperDto(
|
||||
json['alignment'] as Alignment,
|
||||
new Date(json['date']),
|
||||
(json['aggregations'] as any[]).map(a => a.hasOwnProperty('delta') ? MeterAggregate.fromJson(a) : VaryingAggregate.fromJson(a)),
|
||||
(json['aggregations'] as any[]).map(a => a.hasOwnProperty('delta') ? MeterAggregate.fromJson(a, locale) : VaryingAggregate.fromJson(a, locale)),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@ export class Series {
|
||||
//
|
||||
}
|
||||
|
||||
static fromJson(json: any): Series {
|
||||
static fromJson(json: any, locale: string): Series {
|
||||
const decimals = validateNumber(json['decimals']);
|
||||
const unit = Unit.fromJson(json['unit']);
|
||||
return new Series(
|
||||
@ -24,7 +24,7 @@ export class Series {
|
||||
validateString(json['title']),
|
||||
decimals,
|
||||
unit,
|
||||
Value.fromJson(json['lastValue'], json['unit'], unit, decimals),
|
||||
Value.fromJson(json['lastValue'], unit, decimals, locale),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -11,11 +11,11 @@ export class MeterAggregate extends Aggregate {
|
||||
super(series);
|
||||
}
|
||||
|
||||
static fromJson(json: any): MeterAggregate {
|
||||
const series = Series.fromJson(json['series']);
|
||||
static fromJson(json: any, locale: string): MeterAggregate {
|
||||
const series = Series.fromJson(json['series'], locale);
|
||||
return new MeterAggregate(
|
||||
series,
|
||||
Value.fromJson(json['delta'], series, series.unit, series.decimals),
|
||||
Value.fromJson(json['delta'], series.unit, series.decimals, locale),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {Inject, Injectable, LOCALE_ID} from '@angular/core';
|
||||
import {ApiService} from '../core/api.service';
|
||||
import {Alignment} from './Alignment';
|
||||
import {AggregationWrapperDto} from './AggregationWrapperDto';
|
||||
@ -34,6 +34,7 @@ export class SeriesService {
|
||||
]
|
||||
|
||||
constructor(
|
||||
@Inject(LOCALE_ID) readonly locale: string,
|
||||
protected readonly api: ApiService,
|
||||
) {
|
||||
//
|
||||
@ -57,7 +58,7 @@ export class SeriesService {
|
||||
if (this.subs.length !== 0) {
|
||||
return;
|
||||
}
|
||||
this.subs.push(this.api.subscribe(['Series'], Series.fromJson, series => this.update(series)));
|
||||
this.subs.push(this.api.subscribe(['Series'], j => Series.fromJson(j, this.locale), series => this.update(series)));
|
||||
this.subs.push(this.api.subscribeConnection(connected => {
|
||||
if (connected) {
|
||||
this.all();
|
||||
@ -97,7 +98,7 @@ export class SeriesService {
|
||||
}
|
||||
|
||||
all(next?: Next<Series[]>) {
|
||||
this.api.getList(['Series', 'all'], Series.fromJson, list => {
|
||||
this.api.getList(['Series', 'all'], j => Series.fromJson(j, this.locale), list => {
|
||||
list.forEach(item => this.update(item));
|
||||
if (next) {
|
||||
next(list);
|
||||
@ -106,7 +107,7 @@ export class SeriesService {
|
||||
}
|
||||
|
||||
aggregations(alignment: Alignment, offset: number, next: Next<AggregationWrapperDto>) {
|
||||
this.api.getSingle(['Series', 'agg', 'all', alignment.name, 'offset', offset], AggregationWrapperDto.fromJson, next);
|
||||
this.api.getSingle(['Series', 'agg', 'all', alignment.name, 'offset', offset], j => AggregationWrapperDto.fromJson(j, this.locale), next);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -12,9 +12,9 @@ export class VaryingAggregate extends Aggregate {
|
||||
super(series);
|
||||
}
|
||||
|
||||
static fromJson(json: any): VaryingAggregate {
|
||||
static fromJson(json: any, locale: string): VaryingAggregate {
|
||||
return new VaryingAggregate(
|
||||
Series.fromJson(json['series']),
|
||||
Series.fromJson(json['series'], locale),
|
||||
json['min'] as number,
|
||||
json['avg'] as number,
|
||||
json['max'] as number,
|
||||
|
||||
@ -1,25 +1,18 @@
|
||||
import {Unit} from "./Unit";
|
||||
import {validateNumber} from "../core/validators";
|
||||
import {Series} from "../series/Series";
|
||||
|
||||
export class Value {
|
||||
|
||||
static readonly EMPTY: Value = new Value(0, Unit._UNKNOWN_, 1);
|
||||
|
||||
constructor(
|
||||
readonly value: number,
|
||||
readonly unit: Unit,
|
||||
readonly decimals: number,
|
||||
) {
|
||||
readonly locale: string) {
|
||||
//
|
||||
}
|
||||
|
||||
static fromJson(value: any, series: Series, unit: Unit, decimals: number): Value {
|
||||
return new Value(
|
||||
validateNumber(value),
|
||||
unit,
|
||||
decimals,
|
||||
);
|
||||
static fromJson(value: any, unit: Unit, decimals: number, locale: string): Value {
|
||||
return new Value(validateNumber(value), unit, decimals, locale);
|
||||
}
|
||||
|
||||
get zero(): boolean {
|
||||
@ -27,25 +20,25 @@ export class Value {
|
||||
}
|
||||
|
||||
get formatted(): string {
|
||||
return `${this.value.toFixed(this.decimals)} ${this.unit.unit}`;
|
||||
return `${this.value.toLocaleString(this.locale, {maximumFractionDigits: this.decimals, minimumFractionDigits: this.decimals})} ${this.unit.unit}`;
|
||||
}
|
||||
|
||||
negate() {
|
||||
return new Value(-this.value, this.unit, this.decimals);
|
||||
return new Value(-this.value, this.unit, this.decimals, this.locale);
|
||||
}
|
||||
|
||||
plus(other: Value | undefined): Value | undefined {
|
||||
if (!other) {
|
||||
return undefined;
|
||||
}
|
||||
return new Value(this.value + other.value, this.unit, this.decimals);
|
||||
return new Value(this.value + other.value, this.unit, this.decimals, this.locale);
|
||||
}
|
||||
|
||||
minus(other: Value | undefined): Value | undefined {
|
||||
if (!other) {
|
||||
return undefined;
|
||||
}
|
||||
return new Value(this.value - other.value, this.unit, this.decimals);
|
||||
return new Value(this.value - other.value, this.unit, this.decimals, this.locale);
|
||||
}
|
||||
|
||||
notNegative(): Value {
|
||||
@ -59,14 +52,14 @@ export class Value {
|
||||
if (this.value === 0) {
|
||||
return this;
|
||||
}
|
||||
return new Value(0, this.unit, this.decimals);
|
||||
return new Value(0, this.unit, this.decimals, this.locale);
|
||||
}
|
||||
|
||||
percent(other: Value | undefined): Value | undefined {
|
||||
if (!other || other.value === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return new Value(this.value / other.value * 100, Unit.PERCENT, 0);
|
||||
return new Value(this.value / other.value * 100, Unit.PERCENT, 0, this.locale);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,11 +1,22 @@
|
||||
@import "../config.less";
|
||||
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
font-size: 6vw;
|
||||
user-select: none;
|
||||
background-color: @background;
|
||||
color: @foreground;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
button, input, select {
|
||||
button {
|
||||
all: unset;
|
||||
font-size: inherit;
|
||||
padding: 0 0.25em;
|
||||
background-color: #002433;
|
||||
color: #008fca;
|
||||
border: unset;
|
||||
border-radius: 10%;
|
||||
}
|
||||
|
||||
div {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user