From 1c0ffc7b64b75c295b3085d7f709c0f25e034511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Ha=C3=9Fel?= Date: Thu, 12 Sep 2024 09:11:58 +0200 Subject: [PATCH] css generalized 'tile' (PropertyList) --- src/main/angular/src/app/api/device/Device.ts | 32 +++++++ .../angular/src/app/api/property/Property.ts | 42 ++++++++- src/main/angular/src/app/api/validators.ts | 4 + .../device/list/device-list.component.html | 6 +- .../device/list/device-list.component.ts | 31 +------ .../list/property-list.component.html | 92 ++++--------------- .../list/property-list.component.less | 31 ++++++- .../property/list/property-list.component.ts | 46 +++++++--- .../editor/schedule-editor.component.html | 2 +- .../editor/schedule-editor.component.ts | 48 +++++----- .../app/shared/search/search.component.html | 5 +- .../app/shared/search/search.component.less | 8 +- .../src/app/shared/search/search.component.ts | 8 +- src/main/angular/src/config.less | 19 ++-- 14 files changed, 207 insertions(+), 167 deletions(-) diff --git a/src/main/angular/src/app/api/device/Device.ts b/src/main/angular/src/app/api/device/Device.ts index 1dc7f21..af594b1 100644 --- a/src/main/angular/src/app/api/device/Device.ts +++ b/src/main/angular/src/app/api/device/Device.ts @@ -72,6 +72,38 @@ export abstract class Device { return d => d.areaId === areaId && d.roomId === roomId; } + getSwitchClassList(): object { + if (!(this instanceof DeviceSwitch)) { + throw Error(); + } + const value: number | null | undefined = (this as DeviceSwitch).stateProperty?.readChannel?.value; + return { + deviceSwitchOnBack: value === 1, + deviceSwitchOffBack: value === 0, + disabledBack: value === null || value === undefined, + }; + } + + getStateSceneClassList(): object { + if (!(this instanceof DeviceStateScene)) { + throw Error(); + } + return this.getSwitchClassList(); + } + + getShutterClassList(): object { + if (!(this instanceof DeviceShutter)) { + throw Error(); + } + const value: number | null | undefined = (this as DeviceShutter).positionProperty?.readChannel?.value; + return { + deviceShutterOpenBack: value === 0, + deviceShutterIntermediateBack: value !== null && value !== undefined && value > 0 && value < 100, + deviceShutterClosedBack: value === 100, + disabledBack: value === null || value === undefined, + }; + } + } export class DeviceSwitch extends Device { diff --git a/src/main/angular/src/app/api/property/Property.ts b/src/main/angular/src/app/api/property/Property.ts index deb0891..31c0a6b 100644 --- a/src/main/angular/src/app/api/property/Property.ts +++ b/src/main/angular/src/app/api/property/Property.ts @@ -1,8 +1,16 @@ -import {validateListOrEmpty, validateNumberNotNull, validateStringEmptyToNull, validateStringNotEmptyNotNull} from "../validators"; +import {undefinedOrNull, validateListOrEmpty, validateNumberNotNull, validateStringEmptyToNull, validateStringNotEmptyNotNull} from "../validators"; import {Channel} from "../channel/Channel"; import {SearchResult} from "../SearchResult"; import {environment} from "../../../environments/environment"; +export enum PropertyType { + BOOLEAN = 'BOOLEAN', + SHUTTER = 'SHUTTER', + BRIGHTNESS_PERCENT = 'BRIGHTNESS_PERCENT', + COLOR_TEMPERATURE = 'COLOR_TEMPERATURE', + LUX = 'LUX', +} + export class Property { readonly hrefId: string | null; @@ -11,7 +19,7 @@ export class Property { constructor( public id: number, - public type: string, + public type: PropertyType, public title: string, public slug: string | null, public readChannel: Channel | null, @@ -36,7 +44,7 @@ export class Property { static fromJson(json: any): Property { return new Property( validateNumberNotNull(json['id']), - validateStringNotEmptyNotNull(json['type']), + validateStringNotEmptyNotNull(json['type']) as PropertyType, validateStringNotEmptyNotNull(json['title']), validateStringEmptyToNull(json['slug']), Channel.fromJsonAllowNull(json['readChannel']), @@ -63,4 +71,32 @@ export class Property { } return a.title.localeCompare(b.title); } + + getStateClassList() { + const value = this.readChannel?.value; + switch (this.type) { + case PropertyType.BOOLEAN: + return { + propertyStateBooleanUnknown: undefinedOrNull(value), + propertyStateBooleanTrue: value > 0, + propertyStateBooleanFalse: value === 0, + }; + case PropertyType.SHUTTER: + return { + propertyStatePercentUnknown: undefinedOrNull(value), + propertyStatePercentActive: value === 0, + propertyStatePercentBetween: value > 0 && value < 100, + propertyStatePercentInactive: value === 100, + }; + case PropertyType.BRIGHTNESS_PERCENT: + return { + propertyStatePercentUnknown: undefinedOrNull(value), + propertyStatePercentActive: value === 100, + propertyStatePercentBetween: value > 0 && value < 100, + propertyStatePercentInactive: value === 0, + }; + } + return {}; + } + } diff --git a/src/main/angular/src/app/api/validators.ts b/src/main/angular/src/app/api/validators.ts index 46ac792..66e530d 100644 --- a/src/main/angular/src/app/api/validators.ts +++ b/src/main/angular/src/app/api/validators.ts @@ -87,3 +87,7 @@ export function validateMap(json: any, valueFromJson: (json: any) => T): Map< } return map; } + +export function undefinedOrNull(value: any) { + return value === undefined || value === null; +} diff --git a/src/main/angular/src/app/pages/device/list/device-list.component.html b/src/main/angular/src/app/pages/device/list/device-list.component.html index 4f538b6..14bdd90 100644 --- a/src/main/angular/src/app/pages/device/list/device-list.component.html +++ b/src/main/angular/src/app/pages/device/list/device-list.component.html @@ -2,7 +2,7 @@ -
+
@@ -17,7 +17,7 @@
-
+
@@ -33,7 +33,7 @@
-
+
diff --git a/src/main/angular/src/app/pages/device/list/device-list.component.ts b/src/main/angular/src/app/pages/device/list/device-list.component.ts index e692885..9460a43 100644 --- a/src/main/angular/src/app/pages/device/list/device-list.component.ts +++ b/src/main/angular/src/app/pages/device/list/device-list.component.ts @@ -1,7 +1,7 @@ import {Component, OnInit} from '@angular/core'; import {DeviceService} from "../../../api/device/device.service"; import {PropertyService} from "../../../api/property/property.service"; -import {Device, DeviceShutter, DeviceStateScene, DeviceSwitch} from "../../../api/device/Device"; +import {Device, DeviceStateScene} from "../../../api/device/Device"; import {faEdit} from '@fortawesome/free-regular-svg-icons'; import {Scene} from "../../../api/scene/Scene"; import {SceneService} from "../../../api/scene/scene.service"; @@ -78,32 +78,9 @@ export class DeviceListComponent implements OnInit { } } - getSwitchClassList(device: Device): object { - const value: number | null | undefined = (device as DeviceSwitch).stateProperty?.readChannel?.value; - return { - deviceSwitchOnBack: value === 1, - deviceSwitchOffBack: value === 0, - disabledBack: value === null || value === undefined, - }; - } - - getStateSceneClassList(device: Device): object { - return this.getSwitchClassList(device); - } - - getShutterClassList(device: Device): object { - const value: number | null | undefined = (device as DeviceShutter).positionProperty?.readChannel?.value; - return { - deviceShutterOpenBack: value === 0, - deviceShutterIntermediateBack: value !== null && value !== undefined && value > 0 && value < 100, - deviceShutterClosedBack: value === 100, - disabledBack: value === null || value === undefined, - }; - } - - getStateScenes(d: Device): Scene[] { - const device: DeviceStateScene = d as DeviceStateScene; - return device.sceneNumbers.map(sceneNumber => this.scenes.find(scene => scene.number === sceneNumber)).filter(scene => scene !== undefined).map(s => s as Scene); + getStateScenes(device: Device): Scene[] { + const casted: DeviceStateScene = device as DeviceStateScene; + return casted.sceneNumbers.map(sceneNumber => this.scenes.find(scene => scene.number === sceneNumber)).filter(scene => scene !== undefined).map(s => s as Scene); } set(device: Device, key: string, value: any): void { diff --git a/src/main/angular/src/app/pages/property/list/property-list.component.html b/src/main/angular/src/app/pages/property/list/property-list.component.html index a97ac45..953b94e 100644 --- a/src/main/angular/src/app/pages/property/list/property-list.component.html +++ b/src/main/angular/src/app/pages/property/list/property-list.component.html @@ -2,49 +2,17 @@ - - - - - - - - - - - - - - +
-
+ - +
-
- - - - - - - - - - - - - - +
+ +
- - -
BezeichnungSlugTypWertZeitstempelLesekanalSchreibkanal - -
+
+
+ +
- - -
- - - - - - {{property.readChannel?.value ? "An" : "Aus"}} - - {{property.readChannel?.value}} % - - {{property.readChannel?.value}} % - - {{property.readChannel?.value}} K - - {{property.readChannel?.value | number:'0.0-0'}} lux - - {{findScene(property)?.title || "Unbekannt: " + property.readChannel?.value}} - - {{property.readChannel?.timestamp | date:'yyyy-MM-dd HH:mm:ss'}} - - + - - -
+
+ +
+
+ +
diff --git a/src/main/angular/src/app/pages/property/list/property-list.component.less b/src/main/angular/src/app/pages/property/list/property-list.component.less index 20f1fc6..de7f70e 100644 --- a/src/main/angular/src/app/pages/property/list/property-list.component.less +++ b/src/main/angular/src/app/pages/property/list/property-list.component.less @@ -1,8 +1,29 @@ -table { - width: 100%; +@import "../../../../config"; + +.propertyStateBooleanUnknown { + background-color: @COLOR_UNKNOWN; } -.links { - color: gray; - font-size: 80%; +.propertyStateBooleanTrue { + background-color: @COLOR_ACTIVE; +} + +.propertyStateBooleanFalse { + background-color: @COLOR_INACTIVE; +} + +.propertyStatePercentUnknown { + background-color: @COLOR_UNKNOWN; +} + +.propertyStatePercentActive { + background-color: @COLOR_ACTIVE; +} + +.propertyStatePercentBetween { + background-color: @COLOR_BETWEEN; +} + +.propertyStatePercentInactive { + background-color: @COLOR_INACTIVE; } diff --git a/src/main/angular/src/app/pages/property/list/property-list.component.ts b/src/main/angular/src/app/pages/property/list/property-list.component.ts index 245531e..d030053 100644 --- a/src/main/angular/src/app/pages/property/list/property-list.component.ts +++ b/src/main/angular/src/app/pages/property/list/property-list.component.ts @@ -1,5 +1,5 @@ import {Component, OnInit} from '@angular/core'; -import {Property} from "../../../api/property/Property"; +import {Property, PropertyType} from "../../../api/property/Property"; import {PropertyService} from "../../../api/property/property.service"; import {Scene} from "../../../api/scene/Scene"; import {SceneService} from "../../../api/scene/scene.service"; @@ -14,16 +14,18 @@ import {environment} from 'src/environments/environment'; }) export class PropertyListComponent implements OnInit { + readonly environment = environment; + readonly faTimes = faTimesCircle; + protected booleans: Property[] = []; + + protected shutters: Property[] = []; + Property = Property; - properties: Property[] = []; - scenes: Scene[] = []; - readonly environment = environment; - constructor( readonly propertyService: PropertyService, readonly sceneService: SceneService, @@ -33,7 +35,10 @@ export class PropertyListComponent implements OnInit { } ngOnInit(): void { - this.propertyService.findAll(properties => this.properties = properties, Property.compareTypeThenTitle); + this.propertyService.findAll(properties => { + this.booleans = properties.filter(p => p.type === PropertyType.BOOLEAN).sort(Property.compareTypeThenTitle); + this.shutters = properties.filter(p => p.type === PropertyType.SHUTTER).sort(Property.compareTypeThenTitle); + }, Property.compareTypeThenTitle); this.propertyService.subscribe(update => this.updateProperty(update.payload, update.existing)); this.sceneService.findAll(scenes => this.scenes = scenes, Scene.compareNumber); @@ -49,15 +54,20 @@ export class PropertyListComponent implements OnInit { } private updateProperty(property: Property, existing: boolean): void { - const index: number = this.properties.findIndex(p => p.id === property.id); + this.updateProperty2(this.booleans, property, existing); + this.updateProperty2(this.shutters, property, existing); + } + + private updateProperty2(properties: Property[], property: Property, existing: boolean) { + const index: number = properties.findIndex(p => p.id === property.id); if (index >= 0) { if (existing) { - this.properties[index] = property; + properties[index] = property; } else { - this.properties.slice(index, 1); + properties.slice(index, 1); } } else if (existing) { - this.properties.push(property); + properties.push(property); } } @@ -88,8 +98,22 @@ export class PropertyListComponent implements OnInit { delete(property: Property): void { if (confirm(`Eigenschaft "${property.title}" wirklich löschen?`)) { - this.propertyService.delete(property, () => this.properties.splice(this.properties.findIndex(p => p.id === property.id), 1)); + this.propertyService.delete(property, () => { + this.delete2(this.booleans, property); + this.delete2(this.shutters, property); + }); } } + private delete2(properties: Property[], property: Property) { + const index = properties.findIndex(p => p.id === property.id); + if (index >= 0) { + properties.splice(index, 1); + } + } + + listLists(): Property[][] { + return [this.shutters, this.booleans]; + } + } diff --git a/src/main/angular/src/app/pages/schedule/editor/schedule-editor.component.html b/src/main/angular/src/app/pages/schedule/editor/schedule-editor.component.html index ff0760a..1b8ea2c 100644 --- a/src/main/angular/src/app/pages/schedule/editor/schedule-editor.component.html +++ b/src/main/angular/src/app/pages/schedule/editor/schedule-editor.component.html @@ -75,7 +75,7 @@
-
+
diff --git a/src/main/angular/src/app/pages/schedule/editor/schedule-editor.component.ts b/src/main/angular/src/app/pages/schedule/editor/schedule-editor.component.ts index 0c969f3..e8c6b84 100644 --- a/src/main/angular/src/app/pages/schedule/editor/schedule-editor.component.ts +++ b/src/main/angular/src/app/pages/schedule/editor/schedule-editor.component.ts @@ -16,6 +16,18 @@ import {faCheckCircle, faCircle, faTimesCircle} from "@fortawesome/free-regular- const DAY_MINUTES: number = 24 * 60; +const ZENITH_ENTRIES: Zenith[] = [ + new Zenith("Astr.", 107, true, true), + new Zenith("Naut.", 102, true, true), + new Zenith("Bürg.", 96, true, true), + new Zenith("Aufg.", 90.8, true, false), + new Zenith("Unterg.", 90.8, false, true), +]; + +const ZENITH_SUNRISE = ZENITH_ENTRIES.filter(zenith => zenith.sunrise); + +const ZENITH_SUNSET = ZENITH_ENTRIES.filter(zenith => zenith.sunset).reverse(); + @Component({ selector: 'app-schedule-editor', templateUrl: './schedule-editor.component.html', @@ -23,6 +35,12 @@ const DAY_MINUTES: number = 24 * 60; }) export class ScheduleEditorComponent implements OnInit { + protected readonly faCheckCircle = faCheckCircle; + + protected readonly faTimesCircle = faTimesCircle; + + protected readonly faCircle = faCircle; + protected readonly ScheduleEntry = ScheduleEntry; protected readonly Schedule = Schedule; @@ -39,14 +57,6 @@ export class ScheduleEditorComponent implements OnInit { protected bulks: Bulk[] = []; - protected readonly ZENITH_ENTRIES: Zenith[] = [ - new Zenith("Astr.", 107, true, true), - new Zenith("Naut.", 102, true, true), - new Zenith("Bürg.", 96, true, true), - new Zenith("Aufg.", 90.8, true, false), - new Zenith("Unterg.", 90.8, false, true), - ]; - constructor( readonly router: Router, readonly activatedRoute: ActivatedRoute, @@ -99,17 +109,6 @@ export class ScheduleEditorComponent implements OnInit { } } - getZenithEntries(type: string): Zenith[] { - if (type === 'SUNRISE') { - return this.ZENITH_ENTRIES.filter(zenith => zenith.sunrise); - } - return this.ZENITH_ENTRIES.reverse().filter(zenith => zenith.sunset); - } - - trackByZenith(index: number, zenith: Zenith) { - return zenith.value; - } - timeFromString(entry: ScheduleEntry, time: string) { const parts = time.split(':'); const hour = parseInt(parts[0]); @@ -119,7 +118,6 @@ export class ScheduleEditorComponent implements OnInit { second = parseInt(parts[2]); } const daySecond = (hour * 24 + minute) * 60 + second; - console.log(hour, minute, second, daySecond); this.entryService.set(entry, 'daySecond', daySecond); } @@ -131,9 +129,11 @@ export class ScheduleEditorComponent implements OnInit { this.entryService.set(entry, 'daySecond', newMinutes * 60); } - protected readonly faCheckCircle = faCheckCircle; + getZenithEntries(type: string) { + if (type === 'SUNRISE') { + return ZENITH_SUNRISE; + } + return ZENITH_SUNSET; + } - protected readonly faCircle = faCircle; - - protected readonly faTimesCircle = faTimesCircle; } diff --git a/src/main/angular/src/app/shared/search/search.component.html b/src/main/angular/src/app/shared/search/search.component.html index 9f346fe..4638b06 100644 --- a/src/main/angular/src/app/shared/search/search.component.html +++ b/src/main/angular/src/app/shared/search/search.component.html @@ -7,9 +7,8 @@ -
- - -
+
+
-
diff --git a/src/main/angular/src/app/shared/search/search.component.less b/src/main/angular/src/app/shared/search/search.component.less index 1465331..70aceff 100644 --- a/src/main/angular/src/app/shared/search/search.component.less +++ b/src/main/angular/src/app/shared/search/search.component.less @@ -1,6 +1,7 @@ @import "../../../config"; .all { + .initial { padding: @padding; height: 100%; @@ -12,7 +13,12 @@ } .resultList { - position: absolute; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: lightgray; min-width: 10em; border: @border solid black; diff --git a/src/main/angular/src/app/shared/search/search.component.ts b/src/main/angular/src/app/shared/search/search.component.ts index 08cc7e0..8f7b951 100644 --- a/src/main/angular/src/app/shared/search/search.component.ts +++ b/src/main/angular/src/app/shared/search/search.component.ts @@ -7,7 +7,7 @@ import {ISearchService} from "../../api/ISearchService"; templateUrl: './search.component.html', styleUrls: ['./search.component.less'] }) -export class SearchComponent implements OnInit { +export class SearchComponent implements OnInit { private changedTimeout: number | undefined; @@ -19,9 +19,6 @@ export class SearchComponent implements OnInit { @ViewChild('input') input?: HTMLInputElement; - @ViewChild('resultList') - resultList?: HTMLDivElement; - @Input() searchService!: ISearchService; @@ -75,9 +72,6 @@ export class SearchComponent implements OnInit { start(): void { this.term = this.selected?.title || ""; - if (this.resultList && this.input) { - this.resultList.style.left = this.input.style.left; - } this.searching = true; setTimeout(() => this.input2?.nativeElement.focus(), 0); this.doSearch(); diff --git a/src/main/angular/src/config.less b/src/main/angular/src/config.less index 4960a7e..0e03364 100644 --- a/src/main/angular/src/config.less +++ b/src/main/angular/src/config.less @@ -3,12 +3,17 @@ @border: 0.05em; @border-radius: 0.2em; +@COLOR_UNKNOWN: gray; +@COLOR_ACTIVE: #8fbc8f; +@COLOR_BETWEEN: #e4db9c; +@COLOR_INACTIVE: #bc8f8f; + .disabledBack { - background-color: gray; + background-color: @COLOR_UNKNOWN; } .enabledBack { - background-color: #8fbc8f; + background-color: @COLOR_ACTIVE; } .skipBack { @@ -28,21 +33,21 @@ } .deviceSwitchOnBack { - background-color: #8fbc8f; + background-color: @COLOR_ACTIVE; } .deviceSwitchOffBack { - background-color: #bc8f8f; + background-color: @COLOR_INACTIVE; } .deviceShutterOpenBack { - background-color: #8fbc8f; + background-color: @COLOR_ACTIVE; } .deviceShutterIntermediateBack { - background-color: #e4db9c; + background-color: @COLOR_BETWEEN; } .deviceShutterClosedBack { - background-color: #bc8f8f; + background-color: @COLOR_INACTIVE; }