extracted list-item-components out of: DeviceList, ShutterList, TunableList, KnxGroupList
This commit is contained in:
parent
120d6fffdc
commit
ad130fc35e
@ -78,3 +78,11 @@ export function orNull<T, R>(item: T | null | undefined, map: (t: T) => R): R |
|
|||||||
}
|
}
|
||||||
return map(item);
|
return map(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isSet(value: any) {
|
||||||
|
return value !== null && value !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isUnset(value: any) {
|
||||||
|
return value === null || value === undefined;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,24 +1,5 @@
|
|||||||
<div class="deviceList tileContainer">
|
<div class="tileContainer deviceList">
|
||||||
|
|
||||||
<div class="tile" *ngFor="let device of sorted(); trackBy: Device.trackBy">
|
<app-device-tile [now]="now" [device]="device" *ngFor="let device of sorted(); trackBy: Device.trackBy"></app-device-tile>
|
||||||
|
|
||||||
<div class="device tileInner" [ngClass]="ngClass(device)">
|
|
||||||
|
|
||||||
<div class="name">
|
|
||||||
{{ device.nameWithArea }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="actions">
|
|
||||||
<div class="action switchOn" (click)="deviceService.setState(device, true)"></div>
|
|
||||||
<div class="action switchOff" (click)="deviceService.setState(device, false)"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="timestamp details">
|
|
||||||
{{ device.stateProperty?.lastValueChange | relative:now }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,41 +1 @@
|
|||||||
@import "../../../config";
|
@import "../../../config";
|
||||||
|
|
||||||
.deviceList {
|
|
||||||
|
|
||||||
.device {
|
|
||||||
|
|
||||||
.name {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timestamp {
|
|
||||||
clear: left;
|
|
||||||
float: left;
|
|
||||||
font-size: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions {
|
|
||||||
float: right;
|
|
||||||
|
|
||||||
.action {
|
|
||||||
float: left;
|
|
||||||
margin-left: @space;
|
|
||||||
width: 4em;
|
|
||||||
aspect-ratio: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switchOn {
|
|
||||||
//noinspection CssUnknownTarget
|
|
||||||
background-image: url("/switchOn.svg");
|
|
||||||
}
|
|
||||||
|
|
||||||
.switchOff {
|
|
||||||
//noinspection CssUnknownTarget
|
|
||||||
background-image: url("/switchOff.svg");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,17 +1,15 @@
|
|||||||
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
|
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
|
||||||
import {NgClass, NgForOf} from '@angular/common';
|
import {NgForOf} from '@angular/common';
|
||||||
import {Device} from '../../api/Device/Device';
|
import {Device} from '../../api/Device/Device';
|
||||||
import {DeviceService} from '../../api/Device/device.service';
|
|
||||||
import {RelativePipe} from '../../api/common/relative.pipe';
|
|
||||||
import {Subscription, timer} from 'rxjs';
|
import {Subscription, timer} from 'rxjs';
|
||||||
|
import {DeviceTileComponent} from '../device-tile/device-tile.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-device-list',
|
selector: 'app-device-list',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [
|
imports: [
|
||||||
NgForOf,
|
NgForOf,
|
||||||
NgClass,
|
DeviceTileComponent
|
||||||
RelativePipe
|
|
||||||
],
|
],
|
||||||
templateUrl: './device-list.component.html',
|
templateUrl: './device-list.component.html',
|
||||||
styleUrl: './device-list.component.less'
|
styleUrl: './device-list.component.less'
|
||||||
@ -27,12 +25,6 @@ export class DeviceListComponent implements OnInit, OnDestroy {
|
|||||||
@Input()
|
@Input()
|
||||||
list: Device[] = [];
|
list: Device[] = [];
|
||||||
|
|
||||||
constructor(
|
|
||||||
protected readonly deviceService: DeviceService,
|
|
||||||
) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.now = new Date();
|
this.now = new Date();
|
||||||
this.subs.push(timer(1000, 1000).subscribe(() => this.now = new Date()));
|
this.subs.push(timer(1000, 1000).subscribe(() => this.now = new Date()));
|
||||||
@ -42,13 +34,6 @@ export class DeviceListComponent implements OnInit, OnDestroy {
|
|||||||
this.subs.forEach(sub => sub.unsubscribe());
|
this.subs.forEach(sub => sub.unsubscribe());
|
||||||
}
|
}
|
||||||
|
|
||||||
ngClass(device: Device) {
|
|
||||||
return {
|
|
||||||
"stateOn": device.stateProperty?.state?.value === true,
|
|
||||||
"stateOff": device.stateProperty?.state?.value === false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
sorted(): Device[] {
|
sorted(): Device[] {
|
||||||
return this.list.sort(Device.compareByAreaThenName);
|
return this.list.sort(Device.compareByAreaThenName);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,20 @@
|
|||||||
|
<div class="tile">
|
||||||
|
|
||||||
|
<div class="tileInner device" [ngClass]="ngClass(device)">
|
||||||
|
|
||||||
|
<div class="name">
|
||||||
|
{{ device.nameWithArea }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<div class="action switchOn" (click)="deviceService.setState(device, true)"></div>
|
||||||
|
<div class="action switchOff" (click)="deviceService.setState(device, false)"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="timestamp details">
|
||||||
|
{{ device.stateProperty?.lastValueChange | relative:now }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
@import "../../../config";
|
||||||
|
|
||||||
|
.device {
|
||||||
|
|
||||||
|
.name {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timestamp {
|
||||||
|
clear: left;
|
||||||
|
float: left;
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
float: right;
|
||||||
|
|
||||||
|
.action {
|
||||||
|
float: left;
|
||||||
|
margin-left: @space;
|
||||||
|
width: 4em;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switchOn {
|
||||||
|
//noinspection CssUnknownTarget
|
||||||
|
background-image: url("/switchOn.svg");
|
||||||
|
}
|
||||||
|
|
||||||
|
.switchOff {
|
||||||
|
//noinspection CssUnknownTarget
|
||||||
|
background-image: url("/switchOff.svg");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
import {Component, Input} from '@angular/core';
|
||||||
|
import {RelativePipe} from "../../api/common/relative.pipe";
|
||||||
|
import {Device} from "../../api/Device/Device";
|
||||||
|
import {DeviceService} from '../../api/Device/device.service';
|
||||||
|
import {NgClass} from '@angular/common';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-device-tile',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
RelativePipe,
|
||||||
|
NgClass
|
||||||
|
],
|
||||||
|
templateUrl: './device-tile.component.html',
|
||||||
|
styleUrl: './device-tile.component.less'
|
||||||
|
})
|
||||||
|
export class DeviceTileComponent {
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
now!: Date;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
device!: Device;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected readonly deviceService: DeviceService,
|
||||||
|
) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
ngClass(device: Device) {
|
||||||
|
return {
|
||||||
|
"stateOn": device.stateProperty?.state?.value === true,
|
||||||
|
"stateOff": device.stateProperty?.state?.value === false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,35 +1,5 @@
|
|||||||
<div class="groupList tileContainer">
|
<div class="tileContainer groupList">
|
||||||
|
|
||||||
<div class="tile" *ngFor="let group of sorted(); trackBy: Group.trackBy">
|
<app-knx-group-tile [now]="now" [group]="group" *ngFor="let group of sorted(); trackBy: Group.trackBy"></app-knx-group-tile>
|
||||||
|
|
||||||
<div class="group tileInner" [ngClass]="ngClass(group)">
|
|
||||||
|
|
||||||
<div class="name">
|
|
||||||
{{ group.name }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="details">
|
|
||||||
|
|
||||||
<div class="stackLeft address">
|
|
||||||
{{ group.address }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="stackLeft dpt">
|
|
||||||
DPT {{ group.dpt }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="stackRight state">
|
|
||||||
{{ group.state?.string || '-' }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="stackRight timestamp">
|
|
||||||
{{ group.lastValueChange | relative:now }}:
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,13 +1 @@
|
|||||||
@import "../../../config";
|
@import "../../../config";
|
||||||
|
|
||||||
.groupList {
|
|
||||||
|
|
||||||
.group {
|
|
||||||
|
|
||||||
.name {
|
|
||||||
margin-bottom: @space;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,16 +1,15 @@
|
|||||||
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
|
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
|
||||||
import {NgClass, NgForOf} from '@angular/common';
|
import {NgForOf} from '@angular/common';
|
||||||
import {Group} from '../../api/Group/Group';
|
import {Group} from '../../api/Group/Group';
|
||||||
import {RelativePipe} from '../../api/common/relative.pipe';
|
|
||||||
import {Subscription, timer} from 'rxjs';
|
import {Subscription, timer} from 'rxjs';
|
||||||
|
import {KnxGroupTileComponent} from '../knx-group-tile/knx-group-tile.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-knx-group-list',
|
selector: 'app-knx-group-list',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [
|
imports: [
|
||||||
NgForOf,
|
NgForOf,
|
||||||
NgClass,
|
KnxGroupTileComponent
|
||||||
RelativePipe
|
|
||||||
],
|
],
|
||||||
templateUrl: './knx-group-list.component.html',
|
templateUrl: './knx-group-list.component.html',
|
||||||
styleUrl: './knx-group-list.component.less'
|
styleUrl: './knx-group-list.component.less'
|
||||||
@ -26,13 +25,6 @@ export class KnxGroupListComponent implements OnInit, OnDestroy {
|
|||||||
@Input()
|
@Input()
|
||||||
groupList: Group[] = [];
|
groupList: Group[] = [];
|
||||||
|
|
||||||
ngClass(group: Group) {
|
|
||||||
return {
|
|
||||||
"stateOn": group.state?.value === true,
|
|
||||||
"stateOff": group.state?.value === false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.now = new Date();
|
this.now = new Date();
|
||||||
this.subs.push(timer(1000, 1000).subscribe(() => this.now = new Date()));
|
this.subs.push(timer(1000, 1000).subscribe(() => this.now = new Date()));
|
||||||
|
|||||||
@ -0,0 +1,31 @@
|
|||||||
|
<div class="tile">
|
||||||
|
|
||||||
|
<div class="tileInner group" [ngClass]="ngClass(group)">
|
||||||
|
|
||||||
|
<div class="name">
|
||||||
|
{{ group.name }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="details">
|
||||||
|
|
||||||
|
<div class="stackLeft address">
|
||||||
|
{{ group.address }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stackLeft dpt">
|
||||||
|
DPT {{ group.dpt }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stackRight state">
|
||||||
|
{{ group.state?.string || '-' }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stackRight timestamp">
|
||||||
|
{{ group.lastValueChange | relative:now }}:
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
@import "../../../config";
|
||||||
|
|
||||||
|
.group {
|
||||||
|
|
||||||
|
.name {
|
||||||
|
margin-bottom: @space;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
import {Component, Input} from '@angular/core';
|
||||||
|
import {NgClass} from '@angular/common';
|
||||||
|
import {RelativePipe} from '../../api/common/relative.pipe';
|
||||||
|
import {Group} from '../../api/Group/Group';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-knx-group-tile',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
RelativePipe,
|
||||||
|
NgClass
|
||||||
|
],
|
||||||
|
templateUrl: './knx-group-tile.component.html',
|
||||||
|
styleUrl: './knx-group-tile.component.less'
|
||||||
|
})
|
||||||
|
export class KnxGroupTileComponent {
|
||||||
|
|
||||||
|
protected readonly Group = Group;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
now!: Date;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
group!: Group;
|
||||||
|
|
||||||
|
ngClass(group: Group) {
|
||||||
|
return {
|
||||||
|
"stateOn": group.state?.value === true,
|
||||||
|
"stateOff": group.state?.value === false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,5 +0,0 @@
|
|||||||
<div class="window" (click)="activate.emit(position)">
|
|
||||||
<div class="shutter" [style.height]="position + '%'">
|
|
||||||
<!-- -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@ -1,41 +1,5 @@
|
|||||||
<div class="shutterList tileContainer">
|
<div class="tileContainer shutterList">
|
||||||
|
|
||||||
<div class="tile" *ngFor="let shutter of sorted(); trackBy: Shutter.trackBy">
|
<app-shutter-tile [now]="now" [shutter]="shutter" *ngFor="let shutter of sorted(); trackBy: Shutter.trackBy"></app-shutter-tile>
|
||||||
|
|
||||||
<div class="shutter tileInner">
|
|
||||||
|
|
||||||
<div class="name">
|
|
||||||
{{ shutter.nameWithArea }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="icon">
|
|
||||||
<app-shutter-icon [position]="shutter.positionProperty?.state?.value"></app-shutter-icon>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="timestamp details">
|
|
||||||
{{ shutter.positionProperty?.lastValueChange | relative:now }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="actions">
|
|
||||||
<div class="action">
|
|
||||||
<app-shutter-icon [position]="0" (activate)="shutterService.setPosition(shutter, $event)"></app-shutter-icon>
|
|
||||||
</div>
|
|
||||||
<div class="action">
|
|
||||||
<app-shutter-icon [position]="50" (activate)="shutterService.setPosition(shutter, $event)"></app-shutter-icon>
|
|
||||||
</div>
|
|
||||||
<div class="action">
|
|
||||||
<app-shutter-icon [position]="80" (activate)="shutterService.setPosition(shutter, $event)"></app-shutter-icon>
|
|
||||||
</div>
|
|
||||||
<div class="action">
|
|
||||||
<app-shutter-icon [position]="90" (activate)="shutterService.setPosition(shutter, $event)"></app-shutter-icon>
|
|
||||||
</div>
|
|
||||||
<div class="action">
|
|
||||||
<app-shutter-icon [position]="100" (activate)="shutterService.setPosition(shutter, $event)"></app-shutter-icon>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,38 +1 @@
|
|||||||
@import "../../../config";
|
@import "../../../config";
|
||||||
|
|
||||||
.shutterList {
|
|
||||||
|
|
||||||
.shutter {
|
|
||||||
|
|
||||||
.name {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
clear: left;
|
|
||||||
float: left;
|
|
||||||
width: 4em;
|
|
||||||
aspect-ratio: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timestamp {
|
|
||||||
float: right;
|
|
||||||
font-size: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions {
|
|
||||||
clear: right;
|
|
||||||
float: right;
|
|
||||||
|
|
||||||
.action {
|
|
||||||
float: left;
|
|
||||||
margin-left: @space;
|
|
||||||
width: 3em;
|
|
||||||
aspect-ratio: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,18 +1,15 @@
|
|||||||
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
|
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
|
||||||
import {NgForOf} from '@angular/common';
|
import {NgForOf} from '@angular/common';
|
||||||
import {Shutter} from '../../api/Shutter/Shutter';
|
import {Shutter} from '../../api/Shutter/Shutter';
|
||||||
import {ShutterService} from '../../api/Shutter/shutter.service';
|
|
||||||
import {RelativePipe} from '../../api/common/relative.pipe';
|
|
||||||
import {Subscription, timer} from 'rxjs';
|
import {Subscription, timer} from 'rxjs';
|
||||||
import {ShutterIconComponent} from './shutter-icon/shutter-icon.component';
|
import {ShutterTileComponent} from '../shutter-tile/shutter-tile.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-shutter-list',
|
selector: 'app-shutter-list',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [
|
imports: [
|
||||||
NgForOf,
|
NgForOf,
|
||||||
RelativePipe,
|
ShutterTileComponent
|
||||||
ShutterIconComponent
|
|
||||||
],
|
],
|
||||||
templateUrl: './shutter-list.component.html',
|
templateUrl: './shutter-list.component.html',
|
||||||
styleUrl: './shutter-list.component.less'
|
styleUrl: './shutter-list.component.less'
|
||||||
@ -28,12 +25,6 @@ export class ShutterListComponent implements OnInit, OnDestroy {
|
|||||||
@Input()
|
@Input()
|
||||||
list: Shutter[] = [];
|
list: Shutter[] = [];
|
||||||
|
|
||||||
constructor(
|
|
||||||
protected readonly shutterService: ShutterService,
|
|
||||||
) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.now = new Date();
|
this.now = new Date();
|
||||||
this.subs.push(timer(1000, 1000).subscribe(() => this.now = new Date()));
|
this.subs.push(timer(1000, 1000).subscribe(() => this.now = new Date()));
|
||||||
|
|||||||
@ -0,0 +1,4 @@
|
|||||||
|
<div class="window" (click)="activate.emit(position)">
|
||||||
|
<div *ngIf="isSet(position)" class="shutter" [style.height]="position + '%'"></div>
|
||||||
|
<div *ngIf="isUnset(position)" class="unknown">?</div>
|
||||||
|
</div>
|
||||||
@ -1,6 +1,7 @@
|
|||||||
@import "../../../../config";
|
@import "../../../../config";
|
||||||
|
|
||||||
.window {
|
.window {
|
||||||
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: lightskyblue;
|
background-color: lightskyblue;
|
||||||
@ -11,4 +12,15 @@
|
|||||||
border-bottom: @border solid black;
|
border-bottom: @border solid black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.unknown {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 1.25em;
|
||||||
|
color: red;
|
||||||
|
font-size: 260%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1,14 +1,23 @@
|
|||||||
import {Component, EventEmitter, Input, Output} from '@angular/core';
|
import {Component, EventEmitter, Input, Output} from '@angular/core';
|
||||||
|
import {NgIf} from '@angular/common';
|
||||||
|
|
||||||
|
import {isSet, isUnset} from '../../../api/common/validators';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-shutter-icon',
|
selector: 'app-shutter-icon',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [],
|
imports: [
|
||||||
|
NgIf
|
||||||
|
],
|
||||||
templateUrl: './shutter-icon.component.html',
|
templateUrl: './shutter-icon.component.html',
|
||||||
styleUrl: './shutter-icon.component.less'
|
styleUrl: './shutter-icon.component.less'
|
||||||
})
|
})
|
||||||
export class ShutterIconComponent {
|
export class ShutterIconComponent {
|
||||||
|
|
||||||
|
protected readonly isUnset = isUnset;
|
||||||
|
|
||||||
|
protected readonly isSet = isSet;
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
position?: number
|
position?: number
|
||||||
|
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
<div class="tile">
|
||||||
|
|
||||||
|
<div class="tileInner shutter" [class.unknown]="isUnset(shutter.positionProperty?.state?.value)">
|
||||||
|
|
||||||
|
<div class="name">
|
||||||
|
{{ shutter.nameWithArea }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="icon">
|
||||||
|
<app-shutter-icon [position]="shutter.positionProperty?.state?.value"></app-shutter-icon>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="timestamp details">
|
||||||
|
{{ shutter.positionProperty?.lastValueChange | relative:now }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<div class="action">
|
||||||
|
<app-shutter-icon [position]="0" (activate)="shutterService.setPosition(shutter, $event)"></app-shutter-icon>
|
||||||
|
</div>
|
||||||
|
<div class="action">
|
||||||
|
<app-shutter-icon [position]="50" (activate)="shutterService.setPosition(shutter, $event)"></app-shutter-icon>
|
||||||
|
</div>
|
||||||
|
<div class="action">
|
||||||
|
<app-shutter-icon [position]="80" (activate)="shutterService.setPosition(shutter, $event)"></app-shutter-icon>
|
||||||
|
</div>
|
||||||
|
<div class="action">
|
||||||
|
<app-shutter-icon [position]="90" (activate)="shutterService.setPosition(shutter, $event)"></app-shutter-icon>
|
||||||
|
</div>
|
||||||
|
<div class="action">
|
||||||
|
<app-shutter-icon [position]="100" (activate)="shutterService.setPosition(shutter, $event)"></app-shutter-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
@import "../../../config";
|
||||||
|
|
||||||
|
.shutter {
|
||||||
|
background-color: lightgray;
|
||||||
|
border: @border solid gray !important;
|
||||||
|
|
||||||
|
.name {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
clear: left;
|
||||||
|
float: left;
|
||||||
|
width: 4em;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timestamp {
|
||||||
|
float: right;
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
clear: right;
|
||||||
|
float: right;
|
||||||
|
|
||||||
|
.action {
|
||||||
|
float: left;
|
||||||
|
margin-left: @space;
|
||||||
|
width: 3em;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
import {Component, Input} from '@angular/core';
|
||||||
|
import {RelativePipe} from "../../api/common/relative.pipe";
|
||||||
|
import {Shutter} from "../../api/Shutter/Shutter";
|
||||||
|
import {ShutterService} from '../../api/Shutter/shutter.service';
|
||||||
|
import {ShutterIconComponent} from './shutter-icon/shutter-icon.component';
|
||||||
|
import {isUnset} from '../../api/common/validators';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-shutter-tile',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
RelativePipe,
|
||||||
|
ShutterIconComponent
|
||||||
|
],
|
||||||
|
templateUrl: './shutter-tile.component.html',
|
||||||
|
styleUrl: './shutter-tile.component.less'
|
||||||
|
})
|
||||||
|
export class ShutterTileComponent {
|
||||||
|
|
||||||
|
protected readonly isUnset = isUnset;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
now!: Date;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
shutter!: Shutter;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected readonly shutterService: ShutterService,
|
||||||
|
) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,33 +1,5 @@
|
|||||||
<div class="tunableList tileContainer">
|
<div class="tileContainer tunableList">
|
||||||
|
|
||||||
<div *ngFor="let tunable of sorted(); trackBy: Tunable.trackBy" class="tile">
|
<app-tunable-tile [now]="now" [tunable]="tunable" *ngFor="let tunable of sorted(); trackBy: Tunable.trackBy"></app-tunable-tile>
|
||||||
|
|
||||||
<div [ngClass]="ngClass(tunable)" class="tunable tileInner">
|
|
||||||
|
|
||||||
<div class="name">
|
|
||||||
{{ tunable.nameWithArea }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="timestamp details">
|
|
||||||
{{ tunable.stateProperty?.lastValueChange | relative:now }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="sliders">
|
|
||||||
<div class="slider" *ngIf="tunable.brightnessPropertyId !== ''">
|
|
||||||
<input type="range" [ngModel]="tunable.brightnessProperty?.state?.value" (ngModelChange)="tunableService.setBrightness(tunable, $event)">
|
|
||||||
</div>
|
|
||||||
<div class="slider" *ngIf="tunable.coldnessPropertyId !== ''">
|
|
||||||
<input type="range" [ngModel]="tunable.coldnessProperty?.state?.value" (ngModelChange)="tunableService.setColdness(tunable, $event)">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="actions">
|
|
||||||
<div class="switch switchOn" (click)="tunableService.setState(tunable, true)"></div>
|
|
||||||
<div class="switch switchOff" (click)="tunableService.setState(tunable, false)"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,63 +1 @@
|
|||||||
@import "../../../config";
|
@import "../../../config";
|
||||||
|
|
||||||
.tunableList {
|
|
||||||
|
|
||||||
.tunable {
|
|
||||||
|
|
||||||
.name {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timestamp {
|
|
||||||
float: right;
|
|
||||||
font-size: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sliders {
|
|
||||||
float: left;
|
|
||||||
clear: left;
|
|
||||||
width: 60%;
|
|
||||||
overflow: visible;
|
|
||||||
padding-top: 0.4em;
|
|
||||||
|
|
||||||
.slider {
|
|
||||||
float: left;
|
|
||||||
clear: left;
|
|
||||||
margin-left: @space;
|
|
||||||
width: 100%;
|
|
||||||
overflow: visible;
|
|
||||||
|
|
||||||
input {
|
|
||||||
width: 100%;
|
|
||||||
height: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions {
|
|
||||||
float: right;
|
|
||||||
|
|
||||||
.switch {
|
|
||||||
float: left;
|
|
||||||
margin-left: @space;
|
|
||||||
width: 4em;
|
|
||||||
aspect-ratio: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switchOn {
|
|
||||||
//noinspection CssUnknownTarget
|
|
||||||
background-image: url("/switchOn.svg");
|
|
||||||
}
|
|
||||||
|
|
||||||
.switchOff {
|
|
||||||
//noinspection CssUnknownTarget
|
|
||||||
background-image: url("/switchOff.svg");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,20 +1,17 @@
|
|||||||
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
|
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
|
||||||
import {NgClass, NgForOf, NgIf} from '@angular/common';
|
import {NgForOf} from '@angular/common';
|
||||||
import {Tunable} from '../../api/Tunable/Tunable';
|
import {Tunable} from '../../api/Tunable/Tunable';
|
||||||
import {TunableService} from '../../api/Tunable/tunable.service';
|
|
||||||
import {RelativePipe} from '../../api/common/relative.pipe';
|
|
||||||
import {Subscription, timer} from 'rxjs';
|
import {Subscription, timer} from 'rxjs';
|
||||||
import {FormsModule} from '@angular/forms';
|
import {FormsModule} from '@angular/forms';
|
||||||
|
import {TunableTileComponent} from '../tunable-tile/tunable-tile.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-tunable-list',
|
selector: 'app-tunable-list',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [
|
imports: [
|
||||||
NgForOf,
|
NgForOf,
|
||||||
NgClass,
|
|
||||||
RelativePipe,
|
|
||||||
FormsModule,
|
FormsModule,
|
||||||
NgIf
|
TunableTileComponent
|
||||||
],
|
],
|
||||||
templateUrl: './tunable-list.component.html',
|
templateUrl: './tunable-list.component.html',
|
||||||
styleUrl: './tunable-list.component.less'
|
styleUrl: './tunable-list.component.less'
|
||||||
@ -30,12 +27,6 @@ export class TunableListComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
private readonly subs: Subscription[] = [];
|
private readonly subs: Subscription[] = [];
|
||||||
|
|
||||||
constructor(
|
|
||||||
protected readonly tunableService: TunableService,
|
|
||||||
) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.now = new Date();
|
this.now = new Date();
|
||||||
this.subs.push(timer(1000, 1000).subscribe(() => this.now = new Date()));
|
this.subs.push(timer(1000, 1000).subscribe(() => this.now = new Date()));
|
||||||
@ -45,13 +36,6 @@ export class TunableListComponent implements OnInit, OnDestroy {
|
|||||||
this.subs.forEach(sub => sub.unsubscribe());
|
this.subs.forEach(sub => sub.unsubscribe());
|
||||||
}
|
}
|
||||||
|
|
||||||
ngClass(tunable: Tunable) {
|
|
||||||
return {
|
|
||||||
"stateOn": tunable.stateProperty?.state?.value === true,
|
|
||||||
"stateOff": tunable.stateProperty?.state?.value === false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
sorted(): Tunable[] {
|
sorted(): Tunable[] {
|
||||||
return this.list.sort(Tunable.compareByAreaThenName);
|
return this.list.sort(Tunable.compareByAreaThenName);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,29 @@
|
|||||||
|
<div class="tile">
|
||||||
|
|
||||||
|
<div class="tileInner tunable" [ngClass]="ngClass(tunable)">
|
||||||
|
|
||||||
|
<div class="name">
|
||||||
|
{{ tunable.nameWithArea }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="timestamp details">
|
||||||
|
{{ tunable.stateProperty?.lastValueChange | relative:now }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sliders">
|
||||||
|
<div class="slider" *ngIf="tunable.brightnessPropertyId !== ''">
|
||||||
|
<input type="range" [ngModel]="tunable.brightnessProperty?.state?.value" (ngModelChange)="tunableService.setBrightness(tunable, $event)">
|
||||||
|
</div>
|
||||||
|
<div class="slider" *ngIf="tunable.coldnessPropertyId !== ''">
|
||||||
|
<input type="range" [ngModel]="tunable.coldnessProperty?.state?.value" (ngModelChange)="tunableService.setColdness(tunable, $event)">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<div class="switch switchOn" (click)="tunableService.setState(tunable, true)"></div>
|
||||||
|
<div class="switch switchOff" (click)="tunableService.setState(tunable, false)"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
@import "../../../config";
|
||||||
|
|
||||||
|
.tunable {
|
||||||
|
|
||||||
|
.name {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timestamp {
|
||||||
|
float: right;
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sliders {
|
||||||
|
float: left;
|
||||||
|
clear: left;
|
||||||
|
width: 60%;
|
||||||
|
overflow: visible;
|
||||||
|
padding-top: 0.4em;
|
||||||
|
|
||||||
|
.slider {
|
||||||
|
float: left;
|
||||||
|
clear: left;
|
||||||
|
margin-left: @space;
|
||||||
|
width: 100%;
|
||||||
|
overflow: visible;
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
height: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
float: right;
|
||||||
|
|
||||||
|
.switch {
|
||||||
|
float: left;
|
||||||
|
margin-left: @space;
|
||||||
|
width: 4em;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switchOn {
|
||||||
|
//noinspection CssUnknownTarget
|
||||||
|
background-image: url("/switchOn.svg");
|
||||||
|
}
|
||||||
|
|
||||||
|
.switchOff {
|
||||||
|
//noinspection CssUnknownTarget
|
||||||
|
background-image: url("/switchOff.svg");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
import {Component, Input} from '@angular/core';
|
||||||
|
import {NgClass, NgIf} from "@angular/common";
|
||||||
|
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
|
||||||
|
import {RelativePipe} from "../../api/common/relative.pipe";
|
||||||
|
import {Tunable} from "../../api/Tunable/Tunable";
|
||||||
|
import {TunableService} from '../../api/Tunable/tunable.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-tunable-tile',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
NgIf,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
RelativePipe,
|
||||||
|
NgClass,
|
||||||
|
FormsModule
|
||||||
|
],
|
||||||
|
templateUrl: './tunable-tile.component.html',
|
||||||
|
styleUrl: './tunable-tile.component.less'
|
||||||
|
})
|
||||||
|
export class TunableTileComponent {
|
||||||
|
|
||||||
|
protected readonly Tunable = Tunable;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
now!: Date;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
tunable!: Tunable;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected readonly tunableService: TunableService,
|
||||||
|
) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
ngClass(tunable: Tunable) {
|
||||||
|
return {
|
||||||
|
"stateOn": tunable.stateProperty?.state?.value === true,
|
||||||
|
"stateOff": tunable.stateProperty?.state?.value === false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -55,6 +55,7 @@
|
|||||||
.tileInner {
|
.tileInner {
|
||||||
padding: @space;
|
padding: @space;
|
||||||
border: @border solid lightgray;
|
border: @border solid lightgray;
|
||||||
|
border-radius: calc(@space / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user