Dashboard + CrudLiveList
This commit is contained in:
parent
bb2af44542
commit
73926b13e6
@ -27,4 +27,8 @@ export class Device {
|
|||||||
return device.uuid;
|
return device.uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static equals(a: Device, b: Device): boolean {
|
||||||
|
return a.uuid !== b.uuid;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,4 +2,10 @@ export class DeviceFilter {
|
|||||||
|
|
||||||
search: string = "";
|
search: string = "";
|
||||||
|
|
||||||
|
stateTrue: boolean = true;
|
||||||
|
|
||||||
|
stateFalse: boolean = true;
|
||||||
|
|
||||||
|
stateNull: boolean = true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,4 +27,8 @@ export class Shutter {
|
|||||||
return shutter.uuid;
|
return shutter.uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static equals(a: Shutter, b: Shutter): boolean {
|
||||||
|
return a.uuid !== b.uuid;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,4 +2,12 @@ export class ShutterFilter {
|
|||||||
|
|
||||||
search: string = "";
|
search: string = "";
|
||||||
|
|
||||||
|
positionOpen: boolean | null = null;
|
||||||
|
|
||||||
|
positionBetween: boolean | null = null;
|
||||||
|
|
||||||
|
positionClosed: boolean | null = null;
|
||||||
|
|
||||||
|
stateNull: boolean | null = null;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import {Property} from "../Property/Property";
|
|||||||
import {orNull, validateString} from "../common/validators";
|
import {orNull, validateString} from "../common/validators";
|
||||||
|
|
||||||
export class Tunable {
|
export class Tunable {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly uuid: string,
|
readonly uuid: string,
|
||||||
readonly name: string,
|
readonly name: string,
|
||||||
@ -34,4 +35,8 @@ export class Tunable {
|
|||||||
return tunable.uuid;
|
return tunable.uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static equals(a: Tunable, b: Tunable): boolean {
|
||||||
|
return a.uuid !== b.uuid;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,4 +2,10 @@ export class TunableFilter {
|
|||||||
|
|
||||||
search: string = "";
|
search: string = "";
|
||||||
|
|
||||||
|
stateTrue: boolean = true;
|
||||||
|
|
||||||
|
stateFalse: boolean = true;
|
||||||
|
|
||||||
|
stateNull: boolean = true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
42
src/main/angular/src/app/api/common/CrudLiveList.ts
Normal file
42
src/main/angular/src/app/api/common/CrudLiveList.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import {CrudService} from "./CrudService";
|
||||||
|
import {Subscription} from "rxjs";
|
||||||
|
|
||||||
|
export class CrudLiveList<ENTITY> extends Subscription {
|
||||||
|
|
||||||
|
private readonly subs: Subscription[] = [];
|
||||||
|
|
||||||
|
unfiltered: ENTITY[] = [];
|
||||||
|
|
||||||
|
filtered: ENTITY[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
crudService: CrudService<ENTITY>,
|
||||||
|
readonly equals: (a: ENTITY, b: ENTITY) => boolean,
|
||||||
|
readonly filter: (item: ENTITY) => boolean = _ => true,
|
||||||
|
) {
|
||||||
|
super(() => {
|
||||||
|
this.subs.forEach(sub => sub.unsubscribe());
|
||||||
|
});
|
||||||
|
crudService.all(list => this.unfiltered = list);
|
||||||
|
this.subs.push(crudService.subscribe(item => this.update(item)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private update(item: ENTITY) {
|
||||||
|
const index = this.unfiltered.findIndex(i => this.equals(i, item));
|
||||||
|
if (index >= 0) {
|
||||||
|
this.unfiltered.splice(index, 1, item);
|
||||||
|
} else {
|
||||||
|
this.unfiltered.push(item);
|
||||||
|
}
|
||||||
|
this.filtered = this.unfiltered.filter(this.filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasUnfiltered(): boolean {
|
||||||
|
return this.unfiltered.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasFiltered(): boolean {
|
||||||
|
return this.filtered.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -13,6 +13,10 @@ export abstract class CrudService<ENTITY> {
|
|||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
|
all(next: Next<ENTITY[]>) {
|
||||||
|
this.getList(['list'], next);
|
||||||
|
}
|
||||||
|
|
||||||
subscribe(next: Next<ENTITY>): Subscription {
|
subscribe(next: Next<ENTITY>): Subscription {
|
||||||
return this.api.subscribe([...this.path], this.fromJson, next);
|
return this.api.subscribe([...this.path], this.fromJson, next);
|
||||||
}
|
}
|
||||||
@ -48,5 +52,4 @@ export abstract class CrudService<ENTITY> {
|
|||||||
protected postPage(path: any[], data: any, next?: Next<Page<ENTITY>>): void {
|
protected postPage(path: any[], data: any, next?: Next<Page<ENTITY>>): void {
|
||||||
this.api.postPage([...this.path, ...path], data, this.fromJson, next);
|
this.api.postPage([...this.path, ...path], data, this.fromJson, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
<div class="flexBox">
|
<div class="flexBox">
|
||||||
<div class="flexBoxFixed menu">
|
<div class="flexBoxFixed menu">
|
||||||
|
<div class="item itemLeft" routerLink="Dashboard" routerLinkActive="active">Dash</div>
|
||||||
<div class="item itemLeft" routerLink="DeviceList" routerLinkActive="active">Geräte</div>
|
<div class="item itemLeft" routerLink="DeviceList" routerLinkActive="active">Geräte</div>
|
||||||
<div class="item itemLeft" routerLink="TunableList" routerLinkActive="active">Licht</div>
|
<div class="item itemLeft" routerLink="TunableList" routerLinkActive="active">Lichter</div>
|
||||||
<div class="item itemLeft" routerLink="ShutterList" routerLinkActive="active">Rollläden</div>
|
<div class="item itemLeft" routerLink="ShutterList" routerLinkActive="active">Rollläden</div>
|
||||||
<div class="item itemRight" routerLink="GroupList" routerLinkActive="active">KNX</div>
|
<div class="item itemRight" routerLink="GroupList" routerLinkActive="active">KNX</div>
|
||||||
</div>
|
</div>
|
||||||
@ -11,7 +12,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="notConnected" *ngIf="apiService.websocketError">
|
<div id="notConnected" *ngIf="apiService.websocketError">
|
||||||
<div>
|
<div class="text">
|
||||||
Nicht verbunden
|
Nicht verbunden
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -34,7 +34,7 @@
|
|||||||
border: @space solid red;
|
border: @space solid red;
|
||||||
color: red;
|
color: red;
|
||||||
|
|
||||||
div {
|
.text {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
font-size: 200%;
|
font-size: 200%;
|
||||||
|
|||||||
@ -3,11 +3,13 @@ import {KnxGroupListPageComponent} from './pages/knx-group-list-page/knx-group-l
|
|||||||
import {DeviceListPageComponent} from './pages/device-list-page/device-list-page.component';
|
import {DeviceListPageComponent} from './pages/device-list-page/device-list-page.component';
|
||||||
import {ShutterListPageComponent} from './pages/shutter-list-page/shutter-list-page.component';
|
import {ShutterListPageComponent} from './pages/shutter-list-page/shutter-list-page.component';
|
||||||
import {TunableListPageComponent} from './pages/tunable-list-page/tunable-list-page.component';
|
import {TunableListPageComponent} from './pages/tunable-list-page/tunable-list-page.component';
|
||||||
|
import {DashboardComponent} from './pages/dashboard/dashboard.component';
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
|
{path: 'Dashboard', component: DashboardComponent},
|
||||||
{path: 'DeviceList', component: DeviceListPageComponent},
|
{path: 'DeviceList', component: DeviceListPageComponent},
|
||||||
{path: 'TunableList', component: TunableListPageComponent},
|
{path: 'TunableList', component: TunableListPageComponent},
|
||||||
{path: 'ShutterList', component: ShutterListPageComponent},
|
{path: 'ShutterList', component: ShutterListPageComponent},
|
||||||
{path: 'GroupList', component: KnxGroupListPageComponent},
|
{path: 'GroupList', component: KnxGroupListPageComponent},
|
||||||
{path: '**', redirectTo: 'GroupList'},
|
{path: '**', redirectTo: 'Dashboard'},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -0,0 +1,21 @@
|
|||||||
|
<div class="verticalScroll">
|
||||||
|
|
||||||
|
<div class="subheading">
|
||||||
|
Eingeschaltete Geräte:
|
||||||
|
{{ deviceList.filtered.length }}
|
||||||
|
</div>
|
||||||
|
<app-device-list [list]="deviceList.filtered"></app-device-list>
|
||||||
|
|
||||||
|
<div class="subheading">
|
||||||
|
Eingeschaltete Lichter:
|
||||||
|
{{ tunableList.filtered.length }}
|
||||||
|
</div>
|
||||||
|
<app-tunable-list [list]="tunableList.filtered"></app-tunable-list>
|
||||||
|
|
||||||
|
<div class="subheading">
|
||||||
|
{{ shutterSubheading }} Rollläden:
|
||||||
|
{{ shutterList.filtered.length }}
|
||||||
|
</div>
|
||||||
|
<app-shutter-list [list]="shutterList.filtered"></app-shutter-list>
|
||||||
|
|
||||||
|
</div>
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
@import "../../../config";
|
||||||
|
|
||||||
|
.subheading {
|
||||||
|
font-size: 65%;
|
||||||
|
font-style: italic;
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
@ -0,0 +1,76 @@
|
|||||||
|
import {Component, OnDestroy, OnInit} from '@angular/core';
|
||||||
|
import {DeviceService} from '../../api/Device/device.service';
|
||||||
|
import {TunableService} from '../../api/Tunable/tunable.service';
|
||||||
|
import {Device} from '../../api/Device/Device';
|
||||||
|
import {Tunable} from '../../api/Tunable/Tunable';
|
||||||
|
import {Shutter} from '../../api/Shutter/Shutter';
|
||||||
|
import {ShutterService} from '../../api/Shutter/shutter.service';
|
||||||
|
import {DeviceListComponent} from '../../shared/device-list/device-list.component';
|
||||||
|
import {FormsModule} from '@angular/forms';
|
||||||
|
import {CrudLiveList} from '../../api/common/CrudLiveList';
|
||||||
|
import {TunableListComponent} from '../../shared/tunable-list/tunable-list.component';
|
||||||
|
import {ShutterListComponent} from '../../shared/shutter-list/shutter-list.component';
|
||||||
|
import {Subscription, timer} from 'rxjs';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-dashboard',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
DeviceListComponent,
|
||||||
|
FormsModule,
|
||||||
|
TunableListComponent,
|
||||||
|
ShutterListComponent,
|
||||||
|
],
|
||||||
|
templateUrl: './dashboard.component.html',
|
||||||
|
styleUrl: './dashboard.component.less'
|
||||||
|
})
|
||||||
|
export class DashboardComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
protected deviceList!: CrudLiveList<Device>;
|
||||||
|
|
||||||
|
protected tunableList!: CrudLiveList<Tunable>;
|
||||||
|
|
||||||
|
protected shutterList!: CrudLiveList<Shutter>;
|
||||||
|
|
||||||
|
protected now: Date = new Date();
|
||||||
|
|
||||||
|
private subs: Subscription[] = [];
|
||||||
|
|
||||||
|
protected shutterSubheading: string = "";
|
||||||
|
|
||||||
|
protected shuttersShouldBeOpen: boolean = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected readonly deviceService: DeviceService,
|
||||||
|
protected readonly tunableService: TunableService,
|
||||||
|
protected readonly shutterService: ShutterService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.newDate();
|
||||||
|
this.subs.push(timer(5000, 5000).subscribe(() => this.newDate()));
|
||||||
|
this.subs.push(this.deviceList = new CrudLiveList(this.deviceService, Device.equals, device => device.stateProperty?.state?.value === true));
|
||||||
|
this.subs.push(this.tunableList = new CrudLiveList(this.tunableService, Tunable.equals, tunable => tunable.stateProperty?.state?.value === true));
|
||||||
|
this.subs.push(this.shutterList = new CrudLiveList(this.shutterService, Shutter.equals, shutter => this.shutterFilter(shutter)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private newDate() {
|
||||||
|
this.now = new Date();
|
||||||
|
this.shuttersShouldBeOpen = this.now.getHours() >= 7 && this.now.getHours() < 16;
|
||||||
|
this.shutterSubheading = this.shuttersShouldBeOpen ? "Geschlossene" : "Offene";
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.subs.forEach(sub => sub.unsubscribe());
|
||||||
|
}
|
||||||
|
|
||||||
|
private shutterFilter(shutter: Shutter) {
|
||||||
|
if (this.shuttersShouldBeOpen) {
|
||||||
|
return shutter.positionProperty?.state?.value !== 0;
|
||||||
|
} else {
|
||||||
|
return shutter.positionProperty?.state?.value !== 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -2,7 +2,7 @@
|
|||||||
<div class="flexBoxFixed">
|
<div class="flexBoxFixed">
|
||||||
<input type="text" [(ngModel)]="filter.search" (ngModelChange)="fetchDelayed()" placeholder="Suchen...">
|
<input type="text" [(ngModel)]="filter.search" (ngModelChange)="fetchDelayed()" placeholder="Suchen...">
|
||||||
</div>
|
</div>
|
||||||
<div class="flexBoxRest">
|
<div class="flexBoxRest verticalScroll">
|
||||||
<app-device-list [deviceList]="deviceList"></app-device-list>
|
<app-device-list [list]="deviceList"></app-device-list>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
<div class="flexBoxFixed">
|
<div class="flexBoxFixed">
|
||||||
<input type="text" [(ngModel)]="filter.search" (ngModelChange)="fetchDelayed()" placeholder="Suchen...">
|
<input type="text" [(ngModel)]="filter.search" (ngModelChange)="fetchDelayed()" placeholder="Suchen...">
|
||||||
</div>
|
</div>
|
||||||
<div class="flexBoxRest">
|
<div class="flexBoxRest verticalScroll">
|
||||||
<app-knx-group-list [groupList]="groupList"></app-knx-group-list>
|
<app-knx-group-list [groupList]="groupList"></app-knx-group-list>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
<div class="flexBoxFixed">
|
<div class="flexBoxFixed">
|
||||||
<input type="text" [(ngModel)]="filter.search" (ngModelChange)="fetchDelayed()" placeholder="Suchen...">
|
<input type="text" [(ngModel)]="filter.search" (ngModelChange)="fetchDelayed()" placeholder="Suchen...">
|
||||||
</div>
|
</div>
|
||||||
<div class="flexBoxRest">
|
<div class="flexBoxRest verticalScroll">
|
||||||
<app-shutter-list [shutterList]="shutterList"></app-shutter-list>
|
<app-shutter-list [list]="shutterList"></app-shutter-list>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
<div class="flexBoxFixed">
|
<div class="flexBoxFixed">
|
||||||
<input type="text" [(ngModel)]="filter.search" (ngModelChange)="fetchDelayed()" placeholder="Suchen...">
|
<input type="text" [(ngModel)]="filter.search" (ngModelChange)="fetchDelayed()" placeholder="Suchen...">
|
||||||
</div>
|
</div>
|
||||||
<div class="flexBoxRest">
|
<div class="flexBoxRest verticalScroll">
|
||||||
<app-tunable-list [tunableList]="tunableList"></app-tunable-list>
|
<app-tunable-list [list]="tunableList"></app-tunable-list>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -0,0 +1,9 @@
|
|||||||
|
<div class="all" (click)="toggle()">
|
||||||
|
<div class="box">
|
||||||
|
<div class="TRUE" *ngIf="model">{{ labelTrue }}</div>
|
||||||
|
<div class="FALSE" *ngIf="!model">{{ labelFalse }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="label" *ngIf="label">
|
||||||
|
{{ label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
@import '../../../config';
|
||||||
|
|
||||||
|
.all {
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
.box {
|
||||||
|
float: left;
|
||||||
|
width: 1.5em;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
margin-right: @space;
|
||||||
|
text-align: center;
|
||||||
|
border: @border solid gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
import {Component, EventEmitter, Input, Output} from '@angular/core';
|
||||||
|
import {NgIf} from '@angular/common';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-checkbox',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
NgIf
|
||||||
|
],
|
||||||
|
templateUrl: './checkbox.component.html',
|
||||||
|
styleUrl: './checkbox.component.less'
|
||||||
|
})
|
||||||
|
export class CheckboxComponent {
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
label: string = '';
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
labelFalse: string = '';
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
labelTrue: string = 'X';
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
model!: boolean;
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
modelChange: EventEmitter<boolean | null> = new EventEmitter();
|
||||||
|
|
||||||
|
toggle() {
|
||||||
|
this.model = !this.model;
|
||||||
|
this.modelChange.emit(this.model);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
<div class="deviceList tileContainer">
|
<div class="deviceList tileContainer">
|
||||||
|
|
||||||
<div class="tile" *ngFor="let device of deviceList; trackBy: Device.trackBy">
|
<div class="tile" *ngFor="let device of list; trackBy: Device.trackBy">
|
||||||
|
|
||||||
<div class="device tileInner" [ngClass]="ngClass(device)">
|
<div class="device tileInner" [ngClass]="ngClass(device)">
|
||||||
|
|
||||||
@ -9,8 +9,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<div class="switchOn" (click)="deviceService.setState(device, true)"></div>
|
<div class="action switchOn" (click)="deviceService.setState(device, true)"></div>
|
||||||
<div class="switchOff" (click)="deviceService.setState(device, false)"></div>
|
<div class="action switchOff" (click)="deviceService.setState(device, false)"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="timestamp details">
|
<div class="timestamp details">
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
@import "../../../config";
|
@import "../../../config";
|
||||||
|
|
||||||
.deviceList {
|
.deviceList {
|
||||||
overflow-y: auto;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
.device {
|
.device {
|
||||||
|
|
||||||
@ -19,7 +17,7 @@
|
|||||||
.actions {
|
.actions {
|
||||||
float: right;
|
float: right;
|
||||||
|
|
||||||
div {
|
.action {
|
||||||
float: left;
|
float: left;
|
||||||
margin-left: @space;
|
margin-left: @space;
|
||||||
width: 4em;
|
width: 4em;
|
||||||
|
|||||||
@ -25,7 +25,7 @@ export class DeviceListComponent implements OnInit, OnDestroy {
|
|||||||
protected now: Date = new Date();
|
protected now: Date = new Date();
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
deviceList: Device[] = [];
|
list: Device[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected readonly deviceService: DeviceService,
|
protected readonly deviceService: DeviceService,
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
@import "../../../config";
|
@import "../../../config";
|
||||||
|
|
||||||
.groupList {
|
.groupList {
|
||||||
overflow-y: auto;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
.group {
|
.group {
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<div class="shutterList tileContainer">
|
<div class="shutterList tileContainer">
|
||||||
|
|
||||||
<div class="tile" *ngFor="let shutter of shutterList; trackBy: Shutter.trackBy">
|
<div class="tile" *ngFor="let shutter of list; trackBy: Shutter.trackBy">
|
||||||
|
|
||||||
<div class="shutter tileInner">
|
<div class="shutter tileInner">
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
@import "../../../config";
|
@import "../../../config";
|
||||||
|
|
||||||
.shutterList {
|
.shutterList {
|
||||||
overflow-y: auto;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
.shutter {
|
.shutter {
|
||||||
|
|
||||||
@ -26,7 +24,7 @@
|
|||||||
clear: right;
|
clear: right;
|
||||||
float: right;
|
float: right;
|
||||||
|
|
||||||
div {
|
.action {
|
||||||
float: left;
|
float: left;
|
||||||
margin-left: @space;
|
margin-left: @space;
|
||||||
width: 3em;
|
width: 3em;
|
||||||
|
|||||||
@ -26,7 +26,7 @@ export class ShutterListComponent implements OnInit, OnDestroy {
|
|||||||
protected now: Date = new Date();
|
protected now: Date = new Date();
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
shutterList: Shutter[] = [];
|
list: Shutter[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected readonly shutterService: ShutterService,
|
protected readonly shutterService: ShutterService,
|
||||||
|
|||||||
@ -0,0 +1,10 @@
|
|||||||
|
<div class="all" (click)="toggle()">
|
||||||
|
<div class="box">
|
||||||
|
<div class="FALSE" *ngIf="model === false">{{labelFalse}}</div>
|
||||||
|
<div class="TRUE" *ngIf="model === true">{{labelTrue}}</div>
|
||||||
|
<div class="NULL" *ngIf="model === null">{{labelNull}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="label" *ngIf="label">
|
||||||
|
{{ label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
@import '../../../config';
|
||||||
|
|
||||||
|
.all {
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
.box {
|
||||||
|
float: left;
|
||||||
|
width: 1.5em;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
margin-right: @space;
|
||||||
|
text-align: center;
|
||||||
|
border: @border solid gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
import {Component, EventEmitter, Input, Output} from '@angular/core';
|
||||||
|
import {NgIf} from '@angular/common';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-tristate',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
NgIf
|
||||||
|
],
|
||||||
|
templateUrl: './tristate.component.html',
|
||||||
|
styleUrl: './tristate.component.less'
|
||||||
|
})
|
||||||
|
export class TristateComponent {
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
label: string = '';
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
labelFalse: string = 'J';
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
labelTrue: string = 'N';
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
labelNull: string = '';
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
model: boolean | null = null;
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
modelChange: EventEmitter<boolean | null> = new EventEmitter();
|
||||||
|
|
||||||
|
toggle() {
|
||||||
|
if (this.model === true) {
|
||||||
|
this.model = false;
|
||||||
|
} else if (this.model === false) {
|
||||||
|
this.model = null;
|
||||||
|
} else {
|
||||||
|
this.model = true;
|
||||||
|
}
|
||||||
|
this.modelChange.emit(this.model);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
<div class="tunableList tileContainer">
|
<div class="tunableList tileContainer">
|
||||||
|
|
||||||
<div *ngFor="let tunable of tunableList; trackBy: Tunable.trackBy" class="tile">
|
<div *ngFor="let tunable of list; trackBy: Tunable.trackBy" class="tile">
|
||||||
|
|
||||||
<div [ngClass]="ngClass(tunable)" class="tunable tileInner">
|
<div [ngClass]="ngClass(tunable)" class="tunable tileInner">
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
@import "../../../config";
|
@import "../../../config";
|
||||||
|
|
||||||
.tunableList {
|
.tunableList {
|
||||||
overflow-y: auto;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
.tunable {
|
.tunable {
|
||||||
|
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import {FormsModule} from '@angular/forms';
|
|||||||
export class TunableListComponent implements OnInit, OnDestroy {
|
export class TunableListComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
tunableList: Tunable[] = [];
|
list: Tunable[] = [];
|
||||||
|
|
||||||
protected readonly Tunable = Tunable;
|
protected readonly Tunable = Tunable;
|
||||||
|
|
||||||
|
|||||||
@ -61,6 +61,11 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.verticalScroll {
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 1000px) {
|
@media (min-width: 1000px) {
|
||||||
|
|
||||||
.tileContainer {
|
.tileContainer {
|
||||||
|
|||||||
@ -3,7 +3,6 @@ package de.ph87.home.device;
|
|||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import de.ph87.home.common.crud.AbstractSearchFilter;
|
import de.ph87.home.common.crud.AbstractSearchFilter;
|
||||||
import de.ph87.home.property.PropertyTypeMismatch;
|
import de.ph87.home.property.PropertyTypeMismatch;
|
||||||
import jakarta.annotation.Nullable;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
@ -12,27 +11,24 @@ import lombok.ToString;
|
|||||||
@ToString
|
@ToString
|
||||||
public class DeviceFilter extends AbstractSearchFilter {
|
public class DeviceFilter extends AbstractSearchFilter {
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private Boolean stateNull;
|
private boolean stateNull;
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private Boolean stateTrue;
|
private boolean stateTrue;
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private Boolean stateFalse;
|
private boolean stateFalse;
|
||||||
|
|
||||||
public boolean filter(@NonNull final DeviceDto dto) throws PropertyTypeMismatch {
|
public boolean filter(@NonNull final DeviceDto dto) throws PropertyTypeMismatch {
|
||||||
if (stateNull != null && stateNull != (dto.getStateProperty() == null)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
final Boolean value = dto.getStateValue();
|
final Boolean value = dto.getStateValue();
|
||||||
if (stateTrue != null && (value == null || stateTrue != value)) {
|
if (!stateNull && value == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (stateFalse != null && (value == null || stateFalse == value)) {
|
if (!stateTrue && Boolean.TRUE.equals(value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!stateFalse == Boolean.FALSE.equals(value)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return search(dto.getName());
|
return search(dto.getName());
|
||||||
|
|||||||
@ -3,7 +3,6 @@ package de.ph87.home.tunable;
|
|||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import de.ph87.home.common.crud.AbstractSearchFilter;
|
import de.ph87.home.common.crud.AbstractSearchFilter;
|
||||||
import de.ph87.home.property.PropertyTypeMismatch;
|
import de.ph87.home.property.PropertyTypeMismatch;
|
||||||
import jakarta.annotation.Nullable;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
@ -12,27 +11,24 @@ import lombok.ToString;
|
|||||||
@ToString
|
@ToString
|
||||||
public class TunableFilter extends AbstractSearchFilter {
|
public class TunableFilter extends AbstractSearchFilter {
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private Boolean stateNull;
|
private boolean stateNull;
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private Boolean stateTrue;
|
private boolean stateTrue;
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private Boolean stateFalse;
|
private boolean stateFalse;
|
||||||
|
|
||||||
public boolean filter(@NonNull final TunableDto dto) throws PropertyTypeMismatch {
|
public boolean filter(@NonNull final TunableDto dto) throws PropertyTypeMismatch {
|
||||||
if (stateNull != null && stateNull != (dto.getStateProperty() == null)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
final Boolean value = dto.getStateValue();
|
final Boolean value = dto.getStateValue();
|
||||||
if (stateTrue != null && (value == null || stateTrue != value)) {
|
if (!stateNull && value == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (stateFalse != null && (value == null || stateFalse == value)) {
|
if (!stateTrue && Boolean.TRUE.equals(value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!stateFalse == Boolean.FALSE.equals(value)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return search(dto.getName());
|
return search(dto.getName());
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user