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);
|
||||
}
|
||||
|
||||
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">
|
||||
|
||||
<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>
|
||||
<app-device-tile [now]="now" [device]="device" *ngFor="let device of sorted(); trackBy: Device.trackBy"></app-device-tile>
|
||||
|
||||
</div>
|
||||
|
||||
@ -1,41 +1 @@
|
||||
@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 {NgClass, NgForOf} from '@angular/common';
|
||||
import {NgForOf} from '@angular/common';
|
||||
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 {DeviceTileComponent} from '../device-tile/device-tile.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-device-list',
|
||||
standalone: true,
|
||||
imports: [
|
||||
NgForOf,
|
||||
NgClass,
|
||||
RelativePipe
|
||||
DeviceTileComponent
|
||||
],
|
||||
templateUrl: './device-list.component.html',
|
||||
styleUrl: './device-list.component.less'
|
||||
@ -27,12 +25,6 @@ export class DeviceListComponent implements OnInit, OnDestroy {
|
||||
@Input()
|
||||
list: Device[] = [];
|
||||
|
||||
constructor(
|
||||
protected readonly deviceService: DeviceService,
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
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());
|
||||
}
|
||||
|
||||
ngClass(device: Device) {
|
||||
return {
|
||||
"stateOn": device.stateProperty?.state?.value === true,
|
||||
"stateOff": device.stateProperty?.state?.value === false,
|
||||
};
|
||||
}
|
||||
|
||||
sorted(): Device[] {
|
||||
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">
|
||||
|
||||
<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>
|
||||
<app-knx-group-tile [now]="now" [group]="group" *ngFor="let group of sorted(); trackBy: Group.trackBy"></app-knx-group-tile>
|
||||
|
||||
</div>
|
||||
|
||||
@ -1,13 +1 @@
|
||||
@import "../../../config";
|
||||
|
||||
.groupList {
|
||||
|
||||
.group {
|
||||
|
||||
.name {
|
||||
margin-bottom: @space;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,16 +1,15 @@
|
||||
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 {RelativePipe} from '../../api/common/relative.pipe';
|
||||
import {Subscription, timer} from 'rxjs';
|
||||
import {KnxGroupTileComponent} from '../knx-group-tile/knx-group-tile.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-knx-group-list',
|
||||
standalone: true,
|
||||
imports: [
|
||||
NgForOf,
|
||||
NgClass,
|
||||
RelativePipe
|
||||
KnxGroupTileComponent
|
||||
],
|
||||
templateUrl: './knx-group-list.component.html',
|
||||
styleUrl: './knx-group-list.component.less'
|
||||
@ -26,13 +25,6 @@ export class KnxGroupListComponent implements OnInit, OnDestroy {
|
||||
@Input()
|
||||
groupList: Group[] = [];
|
||||
|
||||
ngClass(group: Group) {
|
||||
return {
|
||||
"stateOn": group.state?.value === true,
|
||||
"stateOff": group.state?.value === false,
|
||||
};
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
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">
|
||||
|
||||
<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>
|
||||
<app-shutter-tile [now]="now" [shutter]="shutter" *ngFor="let shutter of sorted(); trackBy: Shutter.trackBy"></app-shutter-tile>
|
||||
|
||||
</div>
|
||||
|
||||
@ -1,38 +1 @@
|
||||
@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 {NgForOf} from '@angular/common';
|
||||
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 {ShutterIconComponent} from './shutter-icon/shutter-icon.component';
|
||||
import {ShutterTileComponent} from '../shutter-tile/shutter-tile.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-shutter-list',
|
||||
standalone: true,
|
||||
imports: [
|
||||
NgForOf,
|
||||
RelativePipe,
|
||||
ShutterIconComponent
|
||||
ShutterTileComponent
|
||||
],
|
||||
templateUrl: './shutter-list.component.html',
|
||||
styleUrl: './shutter-list.component.less'
|
||||
@ -28,12 +25,6 @@ export class ShutterListComponent implements OnInit, OnDestroy {
|
||||
@Input()
|
||||
list: Shutter[] = [];
|
||||
|
||||
constructor(
|
||||
protected readonly shutterService: ShutterService,
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
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";
|
||||
|
||||
.window {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: lightskyblue;
|
||||
@ -11,4 +12,15 @@
|
||||
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 {NgIf} from '@angular/common';
|
||||
|
||||
import {isSet, isUnset} from '../../../api/common/validators';
|
||||
|
||||
@Component({
|
||||
selector: 'app-shutter-icon',
|
||||
standalone: true,
|
||||
imports: [],
|
||||
imports: [
|
||||
NgIf
|
||||
],
|
||||
templateUrl: './shutter-icon.component.html',
|
||||
styleUrl: './shutter-icon.component.less'
|
||||
})
|
||||
export class ShutterIconComponent {
|
||||
|
||||
protected readonly isUnset = isUnset;
|
||||
|
||||
protected readonly isSet = isSet;
|
||||
|
||||
@Input()
|
||||
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">
|
||||
|
||||
<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>
|
||||
<app-tunable-tile [now]="now" [tunable]="tunable" *ngFor="let tunable of sorted(); trackBy: Tunable.trackBy"></app-tunable-tile>
|
||||
|
||||
</div>
|
||||
|
||||
@ -1,63 +1 @@
|
||||
@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 {NgClass, NgForOf, NgIf} from '@angular/common';
|
||||
import {NgForOf} from '@angular/common';
|
||||
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 {FormsModule} from '@angular/forms';
|
||||
import {TunableTileComponent} from '../tunable-tile/tunable-tile.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tunable-list',
|
||||
standalone: true,
|
||||
imports: [
|
||||
NgForOf,
|
||||
NgClass,
|
||||
RelativePipe,
|
||||
FormsModule,
|
||||
NgIf
|
||||
TunableTileComponent
|
||||
],
|
||||
templateUrl: './tunable-list.component.html',
|
||||
styleUrl: './tunable-list.component.less'
|
||||
@ -30,12 +27,6 @@ export class TunableListComponent implements OnInit, OnDestroy {
|
||||
|
||||
private readonly subs: Subscription[] = [];
|
||||
|
||||
constructor(
|
||||
protected readonly tunableService: TunableService,
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
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());
|
||||
}
|
||||
|
||||
ngClass(tunable: Tunable) {
|
||||
return {
|
||||
"stateOn": tunable.stateProperty?.state?.value === true,
|
||||
"stateOff": tunable.stateProperty?.state?.value === false,
|
||||
};
|
||||
}
|
||||
|
||||
sorted(): Tunable[] {
|
||||
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 {
|
||||
padding: @space;
|
||||
border: @border solid lightgray;
|
||||
border-radius: calc(@space / 2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user