From 0dff76f59840c29bb0afcd540b8608778113c800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Ha=C3=9Fel?= Date: Thu, 30 Oct 2025 16:33:44 +0100 Subject: [PATCH] Value.NONE not nullable + offset change --- src/main/angular/public/favicon.ico | Bin 15086 -> 0 bytes src/main/angular/public/favicon.svg | 21 ++++++++++ .../detail/history/series-history.html | 9 ++-- .../location/detail/history/series-history.ts | 39 ++++++++++-------- .../app/location/detail/location-detail.html | 13 +++++- .../app/location/detail/location-detail.ts | 27 +++++++++++- src/main/angular/src/app/series/Series.ts | 2 +- src/main/angular/src/app/series/Value.ts | 38 +++++++++-------- .../src/app/series/select/series-select.html | 2 +- src/main/angular/src/index.html | 4 +- src/main/angular/src/styles.less | 2 + 11 files changed, 112 insertions(+), 45 deletions(-) delete mode 100644 src/main/angular/public/favicon.ico create mode 100644 src/main/angular/public/favicon.svg diff --git a/src/main/angular/public/favicon.ico b/src/main/angular/public/favicon.ico deleted file mode 100644 index 57614f9c967596fad0a3989bec2b1deff33034f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 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 Nr__JTFzvMr^FKp4S3v*( diff --git a/src/main/angular/public/favicon.svg b/src/main/angular/public/favicon.svg new file mode 100644 index 0000000..d0e96ff --- /dev/null +++ b/src/main/angular/public/favicon.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/angular/src/app/location/detail/history/series-history.html b/src/main/angular/src/app/location/detail/history/series-history.html index 216fe02..3159e9a 100644 --- a/src/main/angular/src/app/location/detail/history/series-history.html +++ b/src/main/angular/src/app/location/detail/history/series-history.html @@ -2,6 +2,7 @@
{{ heading }} +
@@ -11,7 +12,7 @@ Bezug
- {{ purchase?.toValueString(true, interval ? null : now) }} + {{ purchase.toValueString(true, interval ? null : now) }}
@@ -20,7 +21,7 @@ Solar
- {{ produce?.toValueString(true, interval ? null : now) }} + {{ produce.toValueString(true, interval ? null : now) }}
@@ -29,7 +30,7 @@ Verbrauch
- {{ consume?.toValueString(true, interval ? null : now) }} + {{ consume.toValueString(true, interval ? null : now) }}
@@ -38,7 +39,7 @@ Einspeisung
- {{ deliver?.toValueString(true, interval ? null : now) }} + {{ deliver.toValueString(true, interval ? null : now) }}
diff --git a/src/main/angular/src/app/location/detail/history/series-history.ts b/src/main/angular/src/app/location/detail/history/series-history.ts index 4f5ab18..1567646 100644 --- a/src/main/angular/src/app/location/detail/history/series-history.ts +++ b/src/main/angular/src/app/location/detail/history/series-history.ts @@ -20,19 +20,28 @@ export class SeriesHistory implements OnInit, AfterViewInit, OnDestroy { private readonly subs: Subscription[] = []; - protected purchase: Value | null = null; + protected purchase: Value = Value.NONE; - protected deliver: Value | null = null; + protected deliver: Value = Value.NONE; - protected produce: Value | null = null; + protected produce: Value = Value.NONE; - protected consume: Value | null = null; + protected consume: Value = Value.NONE; @Input() - heading!: string; + heading: string = ""; + + private _o_: number = 0; @Input() - offset: number = 0; + set offset(value: number) { + this._o_ = value; + this.ngAfterViewInit(); + } + + get offset(): number { + return this._o_; + } @Input() interval: Interval | null = null; @@ -85,11 +94,11 @@ export class SeriesHistory implements OnInit, AfterViewInit, OnDestroy { } }; - private history(fresh: Series | null | undefined, series: Series | null | undefined, next: Next) { - const n = (value: Value | null) => { + private history(fresh: Series | null | undefined, series: Series | null | undefined, next: Next): void { + const callNextAndUpdateConsume = (value: Value) => { next(value); - this.consume = this.purchase?.plus(this.produce)?.minus(this.deliver) || null; - } + this.consume = this.purchase.plus(this.produce).minus(this.deliver); + }; if (fresh !== null && fresh !== undefined) { if (fresh.id !== series?.id) { return; @@ -97,18 +106,14 @@ export class SeriesHistory implements OnInit, AfterViewInit, OnDestroy { series = fresh; } if (!series) { - n(null); + callNextAndUpdateConsume(Value.NONE); return } if (this.interval) { - this.pointService.relative([series], this.interval, this.offset, 1, response => n(Value.ofPoint(response, 0, 0, 1))); + this.pointService.relative([series], this.interval, this.offset, 1, response => callNextAndUpdateConsume(Value.ofPoint(response, 0, 0, 1))); } else { - n(series.value); + callNextAndUpdateConsume(series.value); } } - protected nullOrZero(value: Value | null | undefined): boolean { - return value === null || value === undefined || value.value === 0; - } - } diff --git a/src/main/angular/src/app/location/detail/location-detail.html b/src/main/angular/src/app/location/detail/location-detail.html index e5561f7..604d471 100644 --- a/src/main/angular/src/app/location/detail/location-detail.html +++ b/src/main/angular/src/app/location/detail/location-detail.html @@ -4,7 +4,18 @@ - + + +
+   +
+   +
+   +
{{ offsetDayTitle() }}
+
+
+
diff --git a/src/main/angular/src/app/location/detail/location-detail.ts b/src/main/angular/src/app/location/detail/location-detail.ts index 085f0eb..e97bf42 100644 --- a/src/main/angular/src/app/location/detail/location-detail.ts +++ b/src/main/angular/src/app/location/detail/location-detail.ts @@ -1,4 +1,4 @@ -import {Component, OnDestroy, OnInit} from '@angular/core'; +import {Component, Inject, LOCALE_ID, OnDestroy, OnInit} from '@angular/core'; import {LocationService} from '../location-service'; import {ActivatedRoute} from '@angular/router'; import {Location} from '../Location'; @@ -12,6 +12,7 @@ import {Subscription, timer} from 'rxjs'; import {SeriesHistory} from './history/series-history'; import {Interval} from '../../series/Interval'; import {MenuService} from '../../menu-service'; +import {DatePipe} from '@angular/common'; function yesterday(now: any) { const yesterday = new Date(now.getTime()); @@ -34,6 +35,8 @@ export class LocationDetail implements OnInit, OnDestroy { protected readonly Interval = Interval; + protected readonly Math = Math; + protected location: Location | null = null; private readonly subs: Subscription [] = []; @@ -44,13 +47,18 @@ export class LocationDetail implements OnInit, OnDestroy { protected yesterday: Date = yesterday(this.now); + protected offset: number = 1; + + private readonly datePipe: DatePipe; + constructor( readonly locationService: LocationService, readonly seriesService: SeriesService, readonly activatedRoute: ActivatedRoute, readonly menuService: MenuService, + @Inject(LOCALE_ID) readonly locale: string, ) { - // + this.datePipe = new DatePipe(locale); } ngOnInit(): void { @@ -96,4 +104,19 @@ export class LocationDetail implements OnInit, OnDestroy { return this.series.filter(series => series.type === SeriesType.VARYING && series.unit === 'W'); }; + protected offsetDayTitle(): string { + if (this.offset === 1) { + return 'Gestern'; + } else if (this.offset === 2) { + return 'Vorgestern'; + } else { + if (this.offset < 7) { + const d = new Date(this.now); + d.setDate(d.getDate() - this.offset); + return this.datePipe.transform(d, 'EEEE') || ''; + } + return `Vor ${this.offset} Tagen`; + } + } + } diff --git a/src/main/angular/src/app/series/Series.ts b/src/main/angular/src/app/series/Series.ts index f4e0bc1..8d455a8 100644 --- a/src/main/angular/src/app/series/Series.ts +++ b/src/main/angular/src/app/series/Series.ts @@ -5,7 +5,7 @@ import {Value} from './Value'; export class Series { - readonly value: Value | null = null; + readonly value: Value; constructor( readonly id: number, diff --git a/src/main/angular/src/app/series/Value.ts b/src/main/angular/src/app/series/Value.ts index c0bd367..1baee61 100644 --- a/src/main/angular/src/app/series/Value.ts +++ b/src/main/angular/src/app/series/Value.ts @@ -4,8 +4,10 @@ import {Series} from './Series'; export class Value { - constructor( - readonly value: number, + static readonly NONE: Value = new Value(null, 0, 0, "", new Date()); + + private constructor( + readonly value: number | null, readonly precision: number, readonly seconds: number, readonly unit: string, @@ -15,8 +17,8 @@ export class Value { } toValueString(zeroToDash: boolean, now_ageCheckToDash: Date | null): string { - if (this.value === null || this.value === undefined) { - return "[???]"; + if (this.value === null) { + return "-"; } if (this.value === 0) { return zeroToDash ? "-" : `0 ${this.unit}`; @@ -39,20 +41,20 @@ export class Value { return formatNumber(this.value, "de-DE", `0.${-rest}-${-rest}`) + ' ' + this.unit; } - plus(other: Value | null | undefined): Value | null { + plus(other: Value): Value { return this.operateSameUnit("plus", other, (a, b) => a + b); } - minus(other: Value | null | undefined): Value | null { + minus(other: Value): Value { return this.operateSameUnit("minus", other, (a, b) => a - b); } - operateSameUnit(operationName: string, other: Value | null | undefined, operation: (a: number, b: number) => number): Value | null { - if (!other) { - return null; + operateSameUnit(name: string, other: Value, operation: (a: number, b: number) => number): Value { + if (this.value === null || other.value === null) { + return Value.NONE; } if (this.unit !== other.unit) { - throw new Error(`Operation '${operationName} needs units to be the same: this=${this}, other=${other}`); + throw new Error(`Operation '${name} needs units to be the same: this=${this}, other=${other}`); } const decimals = Math.max(this.precision, other.precision); const seconds = Math.max(this.seconds, other.seconds); @@ -60,25 +62,27 @@ export class Value { return new Value(operation(this.value, other.value), decimals, seconds, this.unit, date); } - static of(series: Series, value: number | null | undefined, date: Date | null | undefined): Value | null { - if (value === null || value === undefined) { - return null; + static of(series: Series, value: number | null | undefined, date: Date | null | undefined): Value { + value = value === undefined ? null : value; + date = date === undefined ? null : date; + if (value === null) { + return this.NONE; } - if (date === null || date === undefined) { + if (date === null) { throw new Error("When 'value' is set, 'last' must be set too, but isn't!") } return new Value(value, series.precision, series.seconds, series.unit, date); } - static ofPoint(response: PointResponse, seriesIndex: number, pointIndex: number, valueIndex: number): Value | null { + static ofPoint(response: PointResponse, seriesIndex: number, pointIndex: number, valueIndex: number): Value { const series = response.series[seriesIndex]; if (!series) { - return null; + return this.NONE; } const point = series.points[pointIndex]; if (!point) { - return null; + return this.NONE; } const date = new Date(point[0] * 1000); diff --git a/src/main/angular/src/app/series/select/series-select.html b/src/main/angular/src/app/series/select/series-select.html index 8ed6faa..c7844e1 100644 --- a/src/main/angular/src/app/series/select/series-select.html +++ b/src/main/angular/src/app/series/select/series-select.html @@ -8,7 +8,7 @@ @for (series of series; track series.id) { } diff --git a/src/main/angular/src/index.html b/src/main/angular/src/index.html index d83079f..a1d0c5f 100644 --- a/src/main/angular/src/index.html +++ b/src/main/angular/src/index.html @@ -2,11 +2,11 @@ - Angular + Data2025 - + diff --git a/src/main/angular/src/styles.less b/src/main/angular/src/styles.less index e8cf5cc..0edb031 100644 --- a/src/main/angular/src/styles.less +++ b/src/main/angular/src/styles.less @@ -4,6 +4,7 @@ body { margin: 0; font-family: sans-serif; font-size: 4vw; + user-select: none; } div { @@ -46,6 +47,7 @@ div { > .SectionHeading { color: dimgray; + > .SectionHeadingText { font-size: 70%; font-style: italic;