diff --git a/src/main/angular/src/app/app.html b/src/main/angular/src/app/app.html index 84cf053..a3bbbc4 100644 --- a/src/main/angular/src/app/app.html +++ b/src/main/angular/src/app/app.html @@ -1,14 +1,22 @@ diff --git a/src/main/angular/src/app/app.routes.ts b/src/main/angular/src/app/app.routes.ts index 41dbeba..b4c2b55 100644 --- a/src/main/angular/src/app/app.routes.ts +++ b/src/main/angular/src/app/app.routes.ts @@ -1,9 +1,10 @@ import {Routes} from '@angular/router'; -import {LocationList} from './location/list/location-list'; import {LocationDetail} from './location/detail/location-detail'; +import {SettingsComponent} from './settings/settings-component'; export const routes: Routes = [ {path: 'Location/:id', component: LocationDetail}, - {path: 'Location', component: LocationList}, + {path: 'Location', component: LocationDetail}, + {path: 'Settings', component: SettingsComponent}, {path: '**', redirectTo: '/Location'}, ]; diff --git a/src/main/angular/src/app/app.ts b/src/main/angular/src/app/app.ts index 5636f9a..92eb718 100644 --- a/src/main/angular/src/app/app.ts +++ b/src/main/angular/src/app/app.ts @@ -1,15 +1,17 @@ import {Component, OnDestroy, OnInit} from '@angular/core'; -import {Router, RouterLinkActive, RouterOutlet} from '@angular/router'; +import {Router, RouterLink, RouterLinkActive, RouterOutlet} from '@angular/router'; import {FaIconComponent} from '@fortawesome/angular-fontawesome'; -import {faBars} from '@fortawesome/free-solid-svg-icons'; +import {faBars, faBookmark as faBookmarkSolid, faGear, faGears} from '@fortawesome/free-solid-svg-icons'; import {MenuService} from './menu-service'; import {Location} from './location/Location'; import {LocationService} from './location/location-service'; import {WebsocketService} from './common'; +import {ConfigService} from './config.service'; +import {faBookmark as faBookmarkRegular} from '@fortawesome/free-regular-svg-icons'; @Component({ selector: 'app-root', - imports: [RouterOutlet, FaIconComponent, RouterLinkActive], + imports: [RouterOutlet, FaIconComponent, RouterLinkActive, RouterLink], templateUrl: './app.html', styleUrl: './app.less' }) @@ -23,6 +25,7 @@ export class App implements OnInit, OnDestroy { constructor( readonly locationService: LocationService, + readonly configService: ConfigService, readonly menuService: MenuService, readonly router: Router, readonly ws: WebsocketService, @@ -45,4 +48,11 @@ export class App implements OnInit, OnDestroy { }) } + protected readonly faBookmarkRegular = faBookmarkRegular; + + protected readonly faBookmarkSolid = faBookmarkSolid; + + protected readonly faGear = faGear; + + protected readonly faGears = faGears; } diff --git a/src/main/angular/src/app/config.service.ts b/src/main/angular/src/app/config.service.ts new file mode 100644 index 0000000..82e6027 --- /dev/null +++ b/src/main/angular/src/app/config.service.ts @@ -0,0 +1,106 @@ +import {Injectable} from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class ConfigService { + + private readonly LOCATION_ID_KEY = "locationId"; + + private readonly ENERGY_PERCENT_KEY = "energyPercent"; + + private readonly ENERGY_PERCENT_FALLBACK = false; + + private readonly LOCATION_CONFIG_KEY = "locationConfig"; + + private readonly LOCATION_CONFIG_FALLBACK = false; + + constructor() { + this._locationId = this.readNumberOrNull(this.LOCATION_ID_KEY); + this._energyPercent = this.readBoolean(this.ENERGY_PERCENT_KEY, this.ENERGY_PERCENT_FALLBACK); + this._locationConfig = this.readBoolean(this.LOCATION_CONFIG_KEY, this.LOCATION_CONFIG_FALLBACK); + } + + private readNumberOrNull(key: string): number | null { + const value = this.read(key); + if (value === null) { + return null; + } + const number = parseInt(value); + return isNaN(number) ? null : number; + } + + private readBoolean(key: string, fallback: boolean): boolean { + const value = this.read(key); + if (value === "true") { + return true; + } + if (value === "false") { + return false; + } + return fallback; + } + + private read(key: string): string | null { + const value = localStorage.getItem(key); + console.log("LOAD", key, value); + return value; + } + + private writeBoolean(key: string, value: boolean | null): void { + console.log("STORE", key, value); + if (value !== null && value !== undefined) { + localStorage.setItem(key, value + ""); + } else { + localStorage.removeItem(key); + } + } + + // locationId ----------------------------------------------------------------------------------- + + private _locationId: number | null = null; + + get locationId(): number | null { + return this._locationId; + } + + set locationId(value: number | null) { + this._locationId = value; + if (value !== null && value !== undefined) { + localStorage.setItem(this.LOCATION_ID_KEY, value + ""); + } else { + localStorage.removeItem(this.LOCATION_ID_KEY); + } + } + + // energyPercent ----------------------------------------------------------------------------------- + + private _energyPercent: boolean = this.ENERGY_PERCENT_FALLBACK; + + get energyPercent(): boolean { + return this._energyPercent; + } + + set energyPercent(value: boolean) { + this._energyPercent = value; + this.writeBoolean(this.ENERGY_PERCENT_KEY, value); + } + +// locationConfig ----------------------------------------------------------------------------------- + + private _locationConfig: boolean = this.LOCATION_CONFIG_FALLBACK; + + get locationConfig(): boolean { + return this._locationConfig; + } + + set locationConfig(value: boolean) { + this._locationConfig = value; + if (value !== null && value !== undefined) { + localStorage.setItem(this.LOCATION_CONFIG_KEY, value + ""); + } else { + localStorage.removeItem(this.LOCATION_CONFIG_KEY); + } + } + +} 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 22f9567..6cffb5c 100644 --- a/src/main/angular/src/app/location/detail/location-detail.html +++ b/src/main/angular/src/app/location/detail/location-detail.html @@ -28,164 +28,167 @@ -
-
-
- Ort -
-
-
-
-
-
- Name -
-
-
- + @if (configService.locationConfig) { +
+
+
+ Ort
-
-
-
- Breitengrad +
+
+
+
+ Name +
+
+
+
-
- -
-
-
-
-
- Längengrad +
+
+
+ Breitengrad +
+
+
+
-
- +
+
+
+ Längengrad +
+
+
+ +
-
-
-
-
- Energie -
-
-
-
-
-
- Bezug -
-
-
- +
+
+
+ Energie
-
-
-
- Einspeisung +
+
+
+
+ Bezug +
+
+
+
-
- -
-
-
-
-
- Erzeugung +
+
+
+ Einspeisung +
+
+
+
-
- +
+
+
+ Erzeugung +
+
+
+ +
-
-
-
-
- Leistung -
-
-
-
-
-
- Bezug -
-
-
- +
+
+
+ Leistung
-
-
-
- Einspeisung +
+
+
+
+ Bezug +
+
+
+
-
- -
-
-
-
-
- Erzeugung +
+
+
+ Einspeisung +
+
+
+
-
- +
+
+
+ Erzeugung +
+
+
+ +
-
-
-
-
- Außen -
-
-
-
-
-
- Temperatur -
-
-
- +
+
+
+ Außen
-
-
-
- Relative Luftfeuchte +
+
+
+
+ Temperatur +
+
+
+
-
- -
-
-
-
-
- Absolute Luftfeuchte +
+
+
+ Relative Luftfeuchte +
+
+
+
-
- +
+
+
+ Absolute Luftfeuchte +
+
+
+ +
-
+ + } } 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 98c6ad6..923c958 100644 --- a/src/main/angular/src/app/location/detail/location-detail.ts +++ b/src/main/angular/src/app/location/detail/location-detail.ts @@ -1,6 +1,6 @@ import {Component, Inject, LOCALE_ID, OnDestroy, OnInit} from '@angular/core'; import {LocationService} from '../location-service'; -import {ActivatedRoute} from '@angular/router'; +import {ActivatedRoute, Params, Router} from '@angular/router'; import {Location} from '../Location'; import {Text} from '../../shared/text/text'; import {Number} from '../../shared/number/number'; @@ -14,6 +14,19 @@ import {Series} from '../../series/Series'; import {SeriesType} from '../../series/SeriesType'; import {DateService} from '../../date.service'; import {LocationPower} from '../power/location-power'; +import {ConfigService} from '../../config.service'; + +export function paramNumberOrNull(params: Params, key: string): number | null { + const param = params[key]; + if (param === null || param === undefined) { + return null; + } + const value = parseInt(param); + if (isNaN(value)) { + return null; + } + return value; +} @Component({ selector: 'app-location-detail', @@ -58,6 +71,8 @@ export class LocationDetail implements OnInit, OnDestroy { readonly activatedRoute: ActivatedRoute, readonly menuService: MenuService, readonly dateService: DateService, + readonly configService: ConfigService, + readonly router: Router, @Inject(LOCALE_ID) readonly locale: string, ) { this.datePipe = new DatePipe(locale); @@ -65,7 +80,14 @@ export class LocationDetail implements OnInit, OnDestroy { ngOnInit(): void { this.locationService.id = null; - this.subs.push(this.activatedRoute.params.subscribe(params => this.locationService.id = params['id'] || null)); + this.subs.push(this.activatedRoute.params.subscribe(params => { + const id = paramNumberOrNull(params, "id"); + if (id === null && this.configService.locationId !== null) { + this.router.navigate(["Location/" + this.configService.locationId]); + return; + } + this.locationService.id = id; + })); this.subs.push(this.locationService.location$.subscribe(this.onLocationChange)); } diff --git a/src/main/angular/src/app/location/energy/location-energy.html b/src/main/angular/src/app/location/energy/location-energy.html index 2fa6d6a..66c175a 100644 --- a/src/main/angular/src/app/location/energy/location-energy.html +++ b/src/main/angular/src/app/location/energy/location-energy.html @@ -14,10 +14,12 @@
{{ purchase.toValueString(null) }}
-
- {{ purchasePercentConsume.toValueString(null) }} - Verbrauch -
+ @if (configService.energyPercent) { +
+ {{ purchasePercentConsume.toValueString(null) }} + Verbrauch +
+ }
@@ -27,10 +29,12 @@
{{ produce.toValueString(null) }}
-
- {{ producePercentConsume.toValueString(null) }} - Verbrauch -
+ @if (configService.energyPercent) { +
+ {{ producePercentConsume.toValueString(null) }} + Verbrauch +
+ }
@@ -40,14 +44,16 @@
{{ self.toValueString(null) }}
-
- {{ selfPercentConsume.toValueString(null) }} - Verbrauch -
-
- {{ selfPercentProduce.toValueString(null) }} - Produktion -
+ @if (configService.energyPercent) { +
+ {{ selfPercentConsume.toValueString(null) }} + Verbrauch +
+
+ {{ selfPercentProduce.toValueString(null) }} + Produktion +
+ }
@@ -66,14 +72,16 @@
{{ deliver.toValueString(null) }}
-
- {{ deliveryPercentConsume.toValueString(null) }} - Verbrauch -
-
- {{ deliveryPercentProduce.toValueString(null) }} - Produktion -
+ @if (configService.energyPercent) { +
+ {{ deliveryPercentConsume.toValueString(null) }} + Verbrauch +
+
+ {{ deliveryPercentProduce.toValueString(null) }} + Produktion +
+ }
diff --git a/src/main/angular/src/app/location/energy/location-energy.ts b/src/main/angular/src/app/location/energy/location-energy.ts index f595d31..9e5679b 100644 --- a/src/main/angular/src/app/location/energy/location-energy.ts +++ b/src/main/angular/src/app/location/energy/location-energy.ts @@ -1,4 +1,4 @@ -import {AfterViewInit, Component, Input, OnDestroy, OnInit, signal} from '@angular/core'; +import {AfterViewInit, Component, Input, OnDestroy, OnInit} from '@angular/core'; import {Location} from '../Location'; import {Series} from '../../series/Series'; import {Next} from '../../common'; @@ -8,6 +8,7 @@ import {SeriesService} from '../../series/series-service'; import {Subscription} from 'rxjs'; import {Value} from '../../series/Value'; import EnergyCharts from './charts/energy-charts'; +import {ConfigService} from '../../config.service'; @Component({ selector: 'app-location-energy', @@ -20,8 +21,6 @@ import EnergyCharts from './charts/energy-charts'; }) export class LocationEnergy implements OnInit, AfterViewInit, OnDestroy { - protected readonly signal = signal; - protected readonly Interval = Interval; private readonly subs: Subscription[] = []; @@ -75,6 +74,7 @@ export class LocationEnergy implements OnInit, AfterViewInit, OnDestroy { constructor( readonly pointService: PointService, readonly serieService: SeriesService, + readonly configService: ConfigService, ) { // } diff --git a/src/main/angular/src/app/location/list/location-list.html b/src/main/angular/src/app/location/list/location-list.html deleted file mode 100644 index 15405e3..0000000 --- a/src/main/angular/src/app/location/list/location-list.html +++ /dev/null @@ -1,7 +0,0 @@ -
- @for (location of list; track location.id) { -
- {{ location.name }} -
- } -
diff --git a/src/main/angular/src/app/location/list/location-list.ts b/src/main/angular/src/app/location/list/location-list.ts deleted file mode 100644 index c84b987..0000000 --- a/src/main/angular/src/app/location/list/location-list.ts +++ /dev/null @@ -1,35 +0,0 @@ -import {Component, OnDestroy, OnInit} from '@angular/core'; -import {LocationService} from '../location-service'; -import {Location} from '../Location'; -import {RouterLink} from '@angular/router'; -import {MenuService} from '../../menu-service'; - -@Component({ - selector: 'app-location-list', - imports: [ - RouterLink - ], - templateUrl: './location-list.html', - styleUrl: './location-list.less', -}) -export class LocationList implements OnInit, OnDestroy { - - protected list: Location[] = []; - - constructor( - readonly locationService: LocationService, - readonly menuService: MenuService, - ) { - // - } - - ngOnInit(): void { - this.locationService.findAll(list => this.list = list); - this.menuService.title = "Orte"; - } - - ngOnDestroy(): void { - this.menuService.title = ""; - } - -} diff --git a/src/main/angular/src/app/location/location-service.ts b/src/main/angular/src/app/location/location-service.ts index 5786155..b4f23ca 100644 --- a/src/main/angular/src/app/location/location-service.ts +++ b/src/main/angular/src/app/location/location-service.ts @@ -25,8 +25,14 @@ export class LocationService extends CrudService { } set id(id: number | null) { - this._id = id; - this.fetch(); + if (this._id !== id) { + this._id = id; + this.fetch(); + } + } + + get id(): number | null { + return this._id; } private readonly fetch = () => { diff --git a/src/main/angular/src/app/location/power/location-power.html b/src/main/angular/src/app/location/power/location-power.html index f6d47c9..d7e2ad1 100644 --- a/src/main/angular/src/app/location/power/location-power.html +++ b/src/main/angular/src/app/location/power/location-power.html @@ -1,7 +1,7 @@
- Aktuelle Leistung + Aktuell
@@ -13,10 +13,12 @@
{{ location.powerPurchase?.value?.toValueString(dateService.now) }}
-
- {{ location.powerPurchasePercentConsume.toValueString(dateService.now) }} - Verbrauch -
+ @if (configService.energyPercent) { +
+ {{ location.powerPurchasePercentConsume.toValueString(dateService.now) }} + Verbrauch +
+ }
@@ -26,10 +28,12 @@
{{ location.powerProduce?.value?.toValueString(dateService.now) }}
-
- {{ location.powerProducePercentConsume.toValueString(dateService.now) }} - Verbrauch -
+ @if (configService.energyPercent) { +
+ {{ location.powerProducePercentConsume.toValueString(dateService.now) }} + Verbrauch +
+ }
@@ -39,14 +43,16 @@
{{ location.powerSelf.toValueString(dateService.now) }}
-
- {{ location.powerSelfPercentConsume.toValueString(dateService.now) }} - Verbrauch -
-
- {{ location.powerSelfPercentProduce.toValueString(dateService.now) }} - Produktion -
+ @if (configService.energyPercent) { +
+ {{ location.powerSelfPercentConsume.toValueString(dateService.now) }} + Verbrauch +
+
+ {{ location.powerSelfPercentProduce.toValueString(dateService.now) }} + Produktion +
+ }
@@ -65,14 +71,16 @@
{{ location.powerDeliver?.value?.toValueString(dateService.now) }}
-
- {{ location.powerDeliveryPercentConsume.toValueString(dateService.now) }} - Verbrauch -
-
- {{ location.powerDeliveryPercentProduce.toValueString(dateService.now) }} - Produktion -
+ @if (configService.energyPercent) { +
+ {{ location.powerDeliveryPercentConsume.toValueString(dateService.now) }} + Verbrauch +
+
+ {{ location.powerDeliveryPercentProduce.toValueString(dateService.now) }} + Produktion +
+ }
diff --git a/src/main/angular/src/app/location/power/location-power.ts b/src/main/angular/src/app/location/power/location-power.ts index d067e77..555de35 100644 --- a/src/main/angular/src/app/location/power/location-power.ts +++ b/src/main/angular/src/app/location/power/location-power.ts @@ -1,6 +1,7 @@ import {Component, Input} from '@angular/core'; import {Location} from '../Location'; import {DateService} from '../../date.service'; +import {ConfigService} from '../../config.service'; @Component({ selector: 'app-location-power', @@ -15,6 +16,7 @@ export class LocationPower { constructor( readonly dateService: DateService, + readonly configService: ConfigService, ) { // } diff --git a/src/main/angular/src/app/location/select/location-select.html b/src/main/angular/src/app/location/select/location-select.html new file mode 100644 index 0000000..02be951 --- /dev/null +++ b/src/main/angular/src/app/location/select/location-select.html @@ -0,0 +1,18 @@ +
+ + @if (showPen) { + + } +
diff --git a/src/main/angular/src/app/location/select/location-select.less b/src/main/angular/src/app/location/select/location-select.less new file mode 100644 index 0000000..2dfd7e5 --- /dev/null +++ b/src/main/angular/src/app/location/select/location-select.less @@ -0,0 +1,25 @@ +.container { + display: flex; + + .value { + flex: 1; + } + +} + +.container:hover { + background-color: #0002; +} + +select { + all: unset; + width: 100%; +} + +.invalid { + background-color: red !important; +} + +.changed { + background-color: yellow !important; +} diff --git a/src/main/angular/src/app/location/select/location-select.ts b/src/main/angular/src/app/location/select/location-select.ts new file mode 100644 index 0000000..68ec004 --- /dev/null +++ b/src/main/angular/src/app/location/select/location-select.ts @@ -0,0 +1,80 @@ +import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; +import {FaIconComponent} from '@fortawesome/angular-fontawesome'; +import {FormsModule} from '@angular/forms'; +import {NgClass} from '@angular/common'; +import {faPen} from '@fortawesome/free-solid-svg-icons'; +import {Location} from '../Location'; +import {LocationService} from '../location-service'; +import {DateService} from '../../date.service'; + +@Component({ + selector: 'app-location-select', + imports: [ + FaIconComponent, + FormsModule, + NgClass + ], + templateUrl: './location-select.html', + styleUrl: './location-select.less', +}) +export class LocationSelect implements OnInit { + + protected readonly faPen = faPen; + + private _initial: number | null = null; + + @Input() + locations!: Location[]; + + @Input() + allowEmpty: boolean = true; + + @Input() + filter: (location: Location) => boolean = () => true; + + @Output() + readonly onChange = new EventEmitter(); + + protected showPen: boolean = false; + + protected model: number | null = null; + + protected readonly Location = Location; + + constructor( + readonly locationService: LocationService, + readonly dateService: DateService, + ) { + // + } + + ngOnInit(): void { + this.locationService.findAll(list => this.locations = list); + } + + @Input() + set initial(value: number | null) { + this._initial = value; + this.reset(); + } + + private readonly reset = (): void => { + this.model = this._initial; + }; + + protected classes(): {} { + return { + "changed": this.model !== this._initial, + "invalid": !this.allowEmpty && this.model === null, + }; + } + + protected changed(id: number | null) { + if (this.allowEmpty || id !== null) { + this.onChange.emit(id); + } else { + this.reset(); + } + } + +} diff --git a/src/main/angular/src/app/series/select/series-select.less b/src/main/angular/src/app/series/select/series-select.less index 5546c96..2dfd7e5 100644 --- a/src/main/angular/src/app/series/select/series-select.less +++ b/src/main/angular/src/app/series/select/series-select.less @@ -16,10 +16,6 @@ select { width: 100%; } -.unchanged { - background-color: lightgreen !important; -} - .invalid { background-color: red !important; } diff --git a/src/main/angular/src/app/series/select/series-select.ts b/src/main/angular/src/app/series/select/series-select.ts index be95bb9..d79b008 100644 --- a/src/main/angular/src/app/series/select/series-select.ts +++ b/src/main/angular/src/app/series/select/series-select.ts @@ -78,7 +78,6 @@ export class SeriesSelect implements OnInit, OnDestroy { protected classes(): {} { return { - "unchanged": this.model === this._initial, "changed": this.model !== or(this._initial, i => i.id, null), "invalid": !this.allowEmpty && this.model === null, }; diff --git a/src/main/angular/src/app/settings/settings-component.html b/src/main/angular/src/app/settings/settings-component.html new file mode 100644 index 0000000..b5c156b --- /dev/null +++ b/src/main/angular/src/app/settings/settings-component.html @@ -0,0 +1,32 @@ +
+
+
+ Favorit +
+
+
+ +
+
+ +
+
+
+ Anzeige +
+
+
+
+ +
+
+ +
+
+
diff --git a/src/main/angular/src/app/location/list/location-list.less b/src/main/angular/src/app/settings/settings-component.less similarity index 100% rename from src/main/angular/src/app/location/list/location-list.less rename to src/main/angular/src/app/settings/settings-component.less diff --git a/src/main/angular/src/app/settings/settings-component.ts b/src/main/angular/src/app/settings/settings-component.ts new file mode 100644 index 0000000..4c71d29 --- /dev/null +++ b/src/main/angular/src/app/settings/settings-component.ts @@ -0,0 +1,30 @@ +import {Component} from '@angular/core'; +import {LocationSelect} from '../location/select/location-select'; +import {LocationService} from '../location/location-service'; +import {Location} from '../location/Location' +import {ConfigService} from '../config.service'; +import {config} from 'rxjs'; +import {FormsModule} from '@angular/forms'; + +@Component({ + selector: 'app-settings-component', + imports: [ + LocationSelect, + FormsModule + ], + templateUrl: './settings-component.html', + styleUrl: './settings-component.less', +}) +export class SettingsComponent { + + protected location: Location | null = null; + + constructor( + readonly locationService: LocationService, + readonly configService: ConfigService, + ) { + // + } + + protected readonly config = config; +} diff --git a/src/main/angular/src/styles.less b/src/main/angular/src/styles.less index d355748..7f3341b 100644 --- a/src/main/angular/src/styles.less +++ b/src/main/angular/src/styles.less @@ -62,7 +62,8 @@ div { } .Section3 { - border: 1px solid gray; + border: 1px solid lightgray; + border-radius: 0.25em; margin: 1em 0.5em 0.5em; padding: 0.5em; overflow: visible;