Tag, Taggable
This commit is contained in:
parent
ad130fc35e
commit
61ffab50ba
@ -44,10 +44,6 @@ export class Device {
|
|||||||
return device.uuid;
|
return device.uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
static equals(a: Device, b: Device): boolean {
|
|
||||||
return a.uuid === b.uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
static compareByAreaThenName(a: Device, b: Device): number {
|
static compareByAreaThenName(a: Device, b: Device): number {
|
||||||
const area = Area.compareByName(a.area, b.area);
|
const area = Area.compareByName(a.area, b.area);
|
||||||
if (area !== 0) {
|
if (area !== 0) {
|
||||||
|
|||||||
@ -4,8 +4,6 @@ import {Device} from './Device';
|
|||||||
import {ApiService} from '../common/api.service';
|
import {ApiService} from '../common/api.service';
|
||||||
import {Next} from '../common/types';
|
import {Next} from '../common/types';
|
||||||
|
|
||||||
import {DeviceFilter} from './DeviceFilter';
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
@ -21,10 +19,6 @@ export class DeviceService extends CrudService<Device> {
|
|||||||
this.getSingle(['getByUuid', uuid], next);
|
this.getSingle(['getByUuid', uuid], next);
|
||||||
}
|
}
|
||||||
|
|
||||||
list(filter: DeviceFilter | null, next: Next<Device[]>): void {
|
|
||||||
this.postList(['list'], filter, next);
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(device: Device, state: boolean, next?: Next<void>): void {
|
setState(device: Device, state: boolean, next?: Next<void>): void {
|
||||||
this.getNone(['setState', device.uuid, state], next);
|
this.getNone(['setState', device.uuid, state], next);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,8 +4,6 @@ import {Group} from './Group';
|
|||||||
import {ApiService} from '../common/api.service';
|
import {ApiService} from '../common/api.service';
|
||||||
import {Next} from '../common/types';
|
import {Next} from '../common/types';
|
||||||
|
|
||||||
import {GroupFilter} from './GroupFilter';
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
@ -21,8 +19,4 @@ export class GroupService extends CrudService<Group> {
|
|||||||
this.getSingle(['getByAddress', address], next);
|
this.getSingle(['getByAddress', address], next);
|
||||||
}
|
}
|
||||||
|
|
||||||
list(filter: GroupFilter | null, next: Next<Group[]>): void {
|
|
||||||
this.postList(['list'], filter, next);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -45,10 +45,6 @@ export class Shutter {
|
|||||||
return shutter.uuid;
|
return shutter.uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
static equals(a: Shutter, b: Shutter): boolean {
|
|
||||||
return a.uuid === b.uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
static compareByAreaThenName(a: Shutter, b: Shutter): number {
|
static compareByAreaThenName(a: Shutter, b: Shutter): number {
|
||||||
const area = Area.compareByName(a.area, b.area);
|
const area = Area.compareByName(a.area, b.area);
|
||||||
if (area !== 0) {
|
if (area !== 0) {
|
||||||
|
|||||||
@ -4,8 +4,6 @@ import {Shutter} from './Shutter';
|
|||||||
import {ApiService} from '../common/api.service';
|
import {ApiService} from '../common/api.service';
|
||||||
import {Next} from '../common/types';
|
import {Next} from '../common/types';
|
||||||
|
|
||||||
import {ShutterFilter} from './ShutterFilter';
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
@ -21,10 +19,6 @@ export class ShutterService extends CrudService<Shutter> {
|
|||||||
this.getSingle(['getByUuid', uuid], next);
|
this.getSingle(['getByUuid', uuid], next);
|
||||||
}
|
}
|
||||||
|
|
||||||
list(filter: ShutterFilter | null, next: Next<Shutter[]>): void {
|
|
||||||
this.postList(['list'], filter, next);
|
|
||||||
}
|
|
||||||
|
|
||||||
setPosition(shutter: Shutter, position: number, next?: Next<void>): void {
|
setPosition(shutter: Shutter, position: number, next?: Next<void>): void {
|
||||||
this.getNone(['setPosition', shutter.uuid, position], next);
|
this.getNone(['setPosition', shutter.uuid, position], next);
|
||||||
}
|
}
|
||||||
|
|||||||
19
src/main/angular/src/app/api/Tag/Tag.ts
Normal file
19
src/main/angular/src/app/api/Tag/Tag.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import {validateString} from '../common/validators';
|
||||||
|
|
||||||
|
export class Tag {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
readonly uuid: string,
|
||||||
|
readonly name: string,
|
||||||
|
) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json: any): Tag {
|
||||||
|
return new Tag(
|
||||||
|
validateString(json.uuid),
|
||||||
|
validateString(json.name),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
17
src/main/angular/src/app/api/Tag/tag.service.ts
Normal file
17
src/main/angular/src/app/api/Tag/tag.service.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
import {ApiService} from '../common/api.service';
|
||||||
|
import {CrudService} from '../common/CrudService';
|
||||||
|
import {Tag} from './Tag';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class TagService extends CrudService<Tag> {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
apiService: ApiService,
|
||||||
|
) {
|
||||||
|
super(apiService, ['Tag'], Tag.fromJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
19
src/main/angular/src/app/api/Taggable/Taggable.ts
Normal file
19
src/main/angular/src/app/api/Taggable/Taggable.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import {Device} from "../Device/Device";
|
||||||
|
import {Shutter} from "../Shutter/Shutter";
|
||||||
|
import {Tunable} from "../Tunable/Tunable";
|
||||||
|
import {validateAndRemoveDtoSuffix} from "../common/validators";
|
||||||
|
|
||||||
|
export type Taggable = Device | Shutter | Tunable;
|
||||||
|
|
||||||
|
export function taggableFromJson(json: any): Taggable {
|
||||||
|
const _type_ = validateAndRemoveDtoSuffix(json._type_);
|
||||||
|
switch (_type_) {
|
||||||
|
case 'Device':
|
||||||
|
return Device.fromJson(json.payload);
|
||||||
|
case 'Shutter':
|
||||||
|
return Shutter.fromJson(json.payload);
|
||||||
|
case 'Tunable':
|
||||||
|
return Tunable.fromJson(json.payload);
|
||||||
|
}
|
||||||
|
throw new Error("Type not implemented: " + _type_);
|
||||||
|
}
|
||||||
7
src/main/angular/src/app/api/Taggable/TaggableFilter.ts
Normal file
7
src/main/angular/src/app/api/Taggable/TaggableFilter.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export class TaggableFilter {
|
||||||
|
|
||||||
|
tag: string = "";
|
||||||
|
|
||||||
|
search: string = "";
|
||||||
|
|
||||||
|
}
|
||||||
33
src/main/angular/src/app/api/Taggable/taggable.service.ts
Normal file
33
src/main/angular/src/app/api/Taggable/taggable.service.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
import {ApiService} from '../common/api.service';
|
||||||
|
import {CrudService} from '../common/CrudService';
|
||||||
|
import {Taggable, taggableFromJson} from './Taggable';
|
||||||
|
import {Next} from '../common/types';
|
||||||
|
import {Subject, Subscription} from 'rxjs';
|
||||||
|
import {DeviceService} from '../Device/device.service';
|
||||||
|
import {ShutterService} from '../Shutter/shutter.service';
|
||||||
|
import {TunableService} from '../Tunable/tunable.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class TaggableService extends CrudService<Taggable> {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
apiService: ApiService,
|
||||||
|
protected readonly deviceService: DeviceService,
|
||||||
|
protected readonly shutterService: ShutterService,
|
||||||
|
protected readonly tunableService: TunableService,
|
||||||
|
) {
|
||||||
|
super(apiService, ['Taggable'], taggableFromJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
override subscribe(next: Next<Taggable>): Subscription {
|
||||||
|
const subject = new Subject<Taggable>();
|
||||||
|
this.deviceService.subscribe(next => subject.next(next));
|
||||||
|
this.shutterService.subscribe(next => subject.next(next));
|
||||||
|
this.tunableService.subscribe(next => subject.next(next));
|
||||||
|
return subject.subscribe(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -52,10 +52,6 @@ export class Tunable {
|
|||||||
return tunable.uuid;
|
return tunable.uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
static equals(a: Tunable, b: Tunable): boolean {
|
|
||||||
return a.uuid === b.uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
static compareByAreaThenName(a: Tunable, b: Tunable): number {
|
static compareByAreaThenName(a: Tunable, b: Tunable): number {
|
||||||
const area = Area.compareByName(a.area, b.area);
|
const area = Area.compareByName(a.area, b.area);
|
||||||
if (area !== 0) {
|
if (area !== 0) {
|
||||||
|
|||||||
@ -4,8 +4,6 @@ import {Tunable} from './Tunable';
|
|||||||
import {ApiService} from '../common/api.service';
|
import {ApiService} from '../common/api.service';
|
||||||
import {Next} from '../common/types';
|
import {Next} from '../common/types';
|
||||||
|
|
||||||
import {TunableFilter} from './TunableFilter';
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
@ -21,10 +19,6 @@ export class TunableService extends CrudService<Tunable> {
|
|||||||
this.getSingle(['getByUuid', uuid], next);
|
this.getSingle(['getByUuid', uuid], next);
|
||||||
}
|
}
|
||||||
|
|
||||||
list(filter: TunableFilter | null, next: Next<Tunable[]>): void {
|
|
||||||
this.postList(['list'], filter, next);
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(tunable: Tunable, state: boolean, next?: Next<void>): void {
|
setState(tunable: Tunable, state: boolean, next?: Next<void>): void {
|
||||||
this.getNone(['setState', tunable.uuid, state], next);
|
this.getNone(['setState', tunable.uuid, state], next);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,46 +1,62 @@
|
|||||||
import {CrudService} from "./CrudService";
|
import {CrudService} from "./CrudService";
|
||||||
import {Subscription} from "rxjs";
|
import {Subscription} from "rxjs";
|
||||||
|
import {Next} from './types';
|
||||||
|
|
||||||
export class CrudLiveList<ENTITY> extends Subscription {
|
export interface UUID {
|
||||||
|
|
||||||
|
uuid: string;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CrudLiveList<ENTITY extends UUID> extends Subscription {
|
||||||
|
|
||||||
private readonly subs: Subscription[] = [];
|
private readonly subs: Subscription[] = [];
|
||||||
|
|
||||||
unfiltered: ENTITY[] = [];
|
private unfiltered: ENTITY[] = [];
|
||||||
|
|
||||||
filtered: ENTITY[] = [];
|
list: ENTITY[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly crudService: CrudService<ENTITY>,
|
readonly crudService: CrudService<ENTITY>,
|
||||||
readonly equals: (a: ENTITY, b: ENTITY) => boolean,
|
readonly allowAppending: boolean,
|
||||||
readonly filter: (item: ENTITY) => boolean = _ => true,
|
readonly filter: (item: ENTITY) => boolean = _ => true,
|
||||||
|
readonly all: (next: Next<ENTITY[]>) => any = next => this.crudService.list(next),
|
||||||
|
readonly equals: (a: ENTITY, b: ENTITY) => boolean = (a, b) => a.uuid === b.uuid,
|
||||||
) {
|
) {
|
||||||
super(() => {
|
super(() => {
|
||||||
this.subs.forEach(sub => sub.unsubscribe());
|
this.subs.forEach(sub => sub.unsubscribe());
|
||||||
});
|
});
|
||||||
this.fetchAll();
|
this.subs.push(crudService.api.connected(_ => this.refresh()));
|
||||||
this.subs.push(crudService.api.connected(_ => this.fetchAll()));
|
|
||||||
this.subs.push(crudService.subscribe(item => this.update(item)));
|
this.subs.push(crudService.subscribe(item => this.update(item)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private fetchAll() {
|
refresh() {
|
||||||
this.crudService.all(list => {
|
this.all(list => {
|
||||||
this.unfiltered = list;
|
this.unfiltered = list;
|
||||||
this.updateFiltered();
|
this.updateFiltered();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this.unfiltered = [];
|
||||||
|
this.updateFiltered();
|
||||||
|
}
|
||||||
|
|
||||||
private update(item: ENTITY) {
|
private update(item: ENTITY) {
|
||||||
const index = this.unfiltered.findIndex(i => this.equals(i, item));
|
const index = this.unfiltered.findIndex(i => this.equals(i, item));
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
this.unfiltered[index] = item;
|
this.unfiltered[index] = item;
|
||||||
} else {
|
} else {
|
||||||
|
if (!this.allowAppending) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.unfiltered.push(item);
|
this.unfiltered.push(item);
|
||||||
}
|
}
|
||||||
this.updateFiltered();
|
this.updateFiltered();
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateFiltered() {
|
private updateFiltered() {
|
||||||
this.filtered = this.unfiltered.filter(this.filter);
|
this.list = this.unfiltered.filter(this.filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,10 +13,14 @@ export abstract class CrudService<ENTITY> {
|
|||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
all(next: Next<ENTITY[]>) {
|
list(next: Next<ENTITY[]>): void {
|
||||||
this.getList(['list'], next);
|
this.getList(['list'], next);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filter<FILTER>(filter: FILTER, next: Next<ENTITY[]>): void {
|
||||||
|
this.postList(['list'], filter, 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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -79,6 +79,14 @@ export function orNull<T, R>(item: T | null | undefined, map: (t: T) => R): R |
|
|||||||
return map(item);
|
return map(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function validateAndRemoveDtoSuffix(json: any): string {
|
||||||
|
const type = validateString(json);
|
||||||
|
if (!type.endsWith('Dto')) {
|
||||||
|
throw Error("Type name does not end with Dto: " + type);
|
||||||
|
}
|
||||||
|
return type.substring(0, type.length - 3);
|
||||||
|
}
|
||||||
|
|
||||||
export function isSet(value: any) {
|
export function isSet(value: any) {
|
||||||
return value !== null && value !== undefined;
|
return value !== null && value !== undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
<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="Dashboard" routerLinkActive="active">Dash</div>
|
||||||
<div class="item itemLeft" routerLink="DeviceList" routerLinkActive="active">Geräte</div>
|
<div class="item itemLeft" routerLink="TaggableList/device" routerLinkActive="active">Geräte</div>
|
||||||
<div class="item itemLeft" routerLink="TunableList" routerLinkActive="active">Lichter</div>
|
<div class="item itemLeft" routerLink="TaggableList/light" routerLinkActive="active">Licht</div>
|
||||||
<div class="item itemLeft" routerLink="ShutterList" routerLinkActive="active">Rollläden</div>
|
<div class="item itemLeft" routerLink="TaggableList/shutter" routerLinkActive="active">Rollladen</div>
|
||||||
<div class="item itemRight" routerLink="GroupList" routerLinkActive="active">KNX</div>
|
<div class="item itemRight" routerLink="GroupList" routerLinkActive="active">KNX</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flexBoxRest">
|
<div class="flexBoxRest">
|
||||||
|
|||||||
@ -1,15 +1,12 @@
|
|||||||
import {Routes} from '@angular/router';
|
import {Routes} from '@angular/router';
|
||||||
import {KnxGroupListPageComponent} from './pages/knx-group-list-page/knx-group-list-page.component';
|
import {KnxGroupListPageComponent} from './pages/knx-group-list-page/knx-group-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 {TunableListPageComponent} from './pages/tunable-list-page/tunable-list-page.component';
|
|
||||||
import {DashboardComponent} from './pages/dashboard/dashboard.component';
|
import {DashboardComponent} from './pages/dashboard/dashboard.component';
|
||||||
|
import {TaggableListPageComponent} from './pages/taggable-list-page/taggable-list-page.component';
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{path: 'Dashboard', component: DashboardComponent},
|
{path: 'Dashboard', component: DashboardComponent},
|
||||||
{path: 'DeviceList', component: DeviceListPageComponent},
|
|
||||||
{path: 'TunableList', component: TunableListPageComponent},
|
|
||||||
{path: 'ShutterList', component: ShutterListPageComponent},
|
|
||||||
{path: 'GroupList', component: KnxGroupListPageComponent},
|
{path: 'GroupList', component: KnxGroupListPageComponent},
|
||||||
|
{path: 'TaggableList', component: TaggableListPageComponent},
|
||||||
|
{path: 'TaggableList/:tag', component: TaggableListPageComponent},
|
||||||
{path: '**', redirectTo: 'Dashboard'},
|
{path: '**', redirectTo: 'Dashboard'},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<div class="verticalScroll">
|
<div class="verticalScroll">
|
||||||
<app-device-list [list]="deviceList.filtered"></app-device-list>
|
<app-device-list [list]="deviceList.list"></app-device-list>
|
||||||
<app-tunable-list [list]="tunableList.filtered"></app-tunable-list>
|
<app-tunable-list [list]="tunableList.list"></app-tunable-list>
|
||||||
<app-shutter-list [list]="shutterList.filtered"></app-shutter-list>
|
<app-shutter-list [list]="shutterList.list"></app-shutter-list>
|
||||||
<div class="emptyBox" *ngIf="deviceList.filtered.length === 0 && tunableList.filtered.length === 0 && shutterList.filtered.length === 0">- Nichts -</div>
|
<div class="emptyBox" *ngIf="deviceList.list.length === 0 && tunableList.list.length === 0 && shutterList.list.length === 0">- Nichts -</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -52,9 +52,9 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
|||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.newDate();
|
this.newDate();
|
||||||
this.subs.push(timer(5000, 5000).subscribe(() => 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.deviceList = new CrudLiveList(this.deviceService, true, 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.tunableList = new CrudLiveList(this.tunableService, true, tunable => tunable.stateProperty?.state?.value === true));
|
||||||
this.subs.push(this.shutterList = new CrudLiveList(this.shutterService, Shutter.equals, shutter => this.shutterFilter(shutter)));
|
this.subs.push(this.shutterList = new CrudLiveList(this.shutterService, true, shutter => this.shutterFilter(shutter)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private newDate() {
|
private newDate() {
|
||||||
|
|||||||
@ -1,8 +0,0 @@
|
|||||||
<div class="flexBox">
|
|
||||||
<div class="flexBoxFixed">
|
|
||||||
<input type="text" [(ngModel)]="filter.search" (ngModelChange)="fetchDelayed()" placeholder="Suchen...">
|
|
||||||
</div>
|
|
||||||
<div class="flexBoxRest verticalScroll">
|
|
||||||
<app-device-list [list]="deviceList"></app-device-list>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
@import "../../../config";
|
|
||||||
|
|
||||||
input {
|
|
||||||
border-bottom: @border solid lightgray;
|
|
||||||
}
|
|
||||||
@ -1,68 +0,0 @@
|
|||||||
import {Component, OnDestroy, OnInit} from '@angular/core';
|
|
||||||
import {DeviceListComponent} from '../../shared/device-list/device-list.component';
|
|
||||||
import {Device} from '../../api/Device/Device';
|
|
||||||
import {DeviceService} from '../../api/Device/device.service';
|
|
||||||
import {FormsModule} from '@angular/forms';
|
|
||||||
import {DeviceFilter} from '../../api/Device/DeviceFilter';
|
|
||||||
import {Subscription} from 'rxjs';
|
|
||||||
import {ApiService} from '../../api/common/api.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-device-list-page',
|
|
||||||
standalone: true,
|
|
||||||
imports: [
|
|
||||||
DeviceListComponent,
|
|
||||||
FormsModule
|
|
||||||
],
|
|
||||||
templateUrl: './device-list-page.component.html',
|
|
||||||
styleUrl: './device-list-page.component.less'
|
|
||||||
})
|
|
||||||
export class DeviceListPageComponent implements OnInit, OnDestroy {
|
|
||||||
|
|
||||||
private readonly subs: Subscription[] = [];
|
|
||||||
|
|
||||||
protected deviceList: Device[] = [];
|
|
||||||
|
|
||||||
protected filter: DeviceFilter = new DeviceFilter();
|
|
||||||
|
|
||||||
private fetchTimeout: any;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
protected readonly deviceService: DeviceService,
|
|
||||||
protected readonly apiService: ApiService,
|
|
||||||
) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.fetch();
|
|
||||||
this.subs.push(this.deviceService.subscribe(device => this.updateDevice(device)));
|
|
||||||
this.apiService.connected(() => this.fetch());
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
this.subs.forEach(sub => sub.unsubscribe());
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchDelayed() {
|
|
||||||
if (this.fetchTimeout) {
|
|
||||||
clearTimeout(this.fetchTimeout);
|
|
||||||
this.fetchTimeout = undefined;
|
|
||||||
}
|
|
||||||
this.fetchTimeout = setTimeout(() => this.fetch(), 300)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fetch() {
|
|
||||||
this.deviceService.list(this.filter, list => this.deviceList = list)
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateDevice(device: Device) {
|
|
||||||
const index = this.deviceList.findIndex(d => d.uuid === device.uuid);
|
|
||||||
if (index >= 0) {
|
|
||||||
this.deviceList.splice(index, 1, device);
|
|
||||||
} else {
|
|
||||||
this.fetch();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
<div class="flexBox">
|
<div class="flexBox">
|
||||||
<div class="flexBoxFixed">
|
<div class="flexBoxFixed">
|
||||||
<input type="text" [(ngModel)]="filter.search" (ngModelChange)="fetchDelayed()" placeholder="Suchen...">
|
<app-search [(search)]="filter.search" (doSearch)="fetch()"></app-search>
|
||||||
</div>
|
</div>
|
||||||
<div class="flexBoxRest verticalScroll">
|
<div class="flexBoxRest verticalScroll">
|
||||||
<app-knx-group-list [groupList]="groupList"></app-knx-group-list>
|
<app-knx-group-list [groupList]="groupList"></app-knx-group-list>
|
||||||
|
|||||||
@ -6,13 +6,15 @@ import {FormsModule} from '@angular/forms';
|
|||||||
import {GroupFilter} from '../../api/Group/GroupFilter';
|
import {GroupFilter} from '../../api/Group/GroupFilter';
|
||||||
import {Subscription} from 'rxjs';
|
import {Subscription} from 'rxjs';
|
||||||
import {ApiService} from '../../api/common/api.service';
|
import {ApiService} from '../../api/common/api.service';
|
||||||
|
import {SearchComponent} from '../../shared/search/search.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-knx-group-list-page',
|
selector: 'app-knx-group-list-page',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [
|
imports: [
|
||||||
KnxGroupListComponent,
|
KnxGroupListComponent,
|
||||||
FormsModule
|
FormsModule,
|
||||||
|
SearchComponent
|
||||||
],
|
],
|
||||||
templateUrl: './knx-group-list-page.component.html',
|
templateUrl: './knx-group-list-page.component.html',
|
||||||
styleUrl: './knx-group-list-page.component.less'
|
styleUrl: './knx-group-list-page.component.less'
|
||||||
@ -25,8 +27,6 @@ export class KnxGroupListPageComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
protected filter: GroupFilter = new GroupFilter();
|
protected filter: GroupFilter = new GroupFilter();
|
||||||
|
|
||||||
private fetchTimeout: any;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected readonly groupService: GroupService,
|
protected readonly groupService: GroupService,
|
||||||
protected readonly apiService: ApiService,
|
protected readonly apiService: ApiService,
|
||||||
@ -44,16 +44,8 @@ export class KnxGroupListPageComponent implements OnInit, OnDestroy {
|
|||||||
this.subs.forEach(sub => sub.unsubscribe());
|
this.subs.forEach(sub => sub.unsubscribe());
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchDelayed() {
|
protected fetch() {
|
||||||
if (this.fetchTimeout) {
|
this.groupService.filter(this.filter, list => this.groupList = list)
|
||||||
clearTimeout(this.fetchTimeout);
|
|
||||||
this.fetchTimeout = undefined;
|
|
||||||
}
|
|
||||||
this.fetchTimeout = setTimeout(() => this.fetch(), 300)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fetch() {
|
|
||||||
this.groupService.list(this.filter, list => this.groupList = list)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateGroup(group: Group) {
|
private updateGroup(group: Group) {
|
||||||
|
|||||||
@ -1,8 +0,0 @@
|
|||||||
<div class="flexBox">
|
|
||||||
<div class="flexBoxFixed">
|
|
||||||
<input type="text" [(ngModel)]="filter.search" (ngModelChange)="fetchDelayed()" placeholder="Suchen...">
|
|
||||||
</div>
|
|
||||||
<div class="flexBoxRest verticalScroll">
|
|
||||||
<app-shutter-list [list]="shutterList"></app-shutter-list>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
@import "../../../config";
|
|
||||||
|
|
||||||
input {
|
|
||||||
border-bottom: @border solid lightgray;
|
|
||||||
}
|
|
||||||
@ -1,68 +0,0 @@
|
|||||||
import {Component, OnDestroy, OnInit} from '@angular/core';
|
|
||||||
import {ShutterListComponent} from '../../shared/shutter-list/shutter-list.component';
|
|
||||||
import {Shutter} from '../../api/Shutter/Shutter';
|
|
||||||
import {ShutterService} from '../../api/Shutter/shutter.service';
|
|
||||||
import {FormsModule} from '@angular/forms';
|
|
||||||
import {ShutterFilter} from '../../api/Shutter/ShutterFilter';
|
|
||||||
import {Subscription} from 'rxjs';
|
|
||||||
import {ApiService} from '../../api/common/api.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-shutter-list-page',
|
|
||||||
standalone: true,
|
|
||||||
imports: [
|
|
||||||
ShutterListComponent,
|
|
||||||
FormsModule
|
|
||||||
],
|
|
||||||
templateUrl: './shutter-list-page.component.html',
|
|
||||||
styleUrl: './shutter-list-page.component.less'
|
|
||||||
})
|
|
||||||
export class ShutterListPageComponent implements OnInit, OnDestroy {
|
|
||||||
|
|
||||||
private readonly subs: Subscription[] = [];
|
|
||||||
|
|
||||||
protected shutterList: Shutter[] = [];
|
|
||||||
|
|
||||||
protected filter: ShutterFilter = new ShutterFilter();
|
|
||||||
|
|
||||||
private fetchTimeout: any;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
protected readonly shutterService: ShutterService,
|
|
||||||
protected readonly apiService: ApiService,
|
|
||||||
) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.fetch();
|
|
||||||
this.subs.push(this.shutterService.subscribe(shutter => this.updateShutter(shutter)));
|
|
||||||
this.apiService.connected(() => this.fetch());
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
this.subs.forEach(sub => sub.unsubscribe());
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchDelayed() {
|
|
||||||
if (this.fetchTimeout) {
|
|
||||||
clearTimeout(this.fetchTimeout);
|
|
||||||
this.fetchTimeout = undefined;
|
|
||||||
}
|
|
||||||
this.fetchTimeout = setTimeout(() => this.fetch(), 300)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fetch() {
|
|
||||||
this.shutterService.list(this.filter, list => this.shutterList = list)
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateShutter(shutter: Shutter) {
|
|
||||||
const index = this.shutterList.findIndex(d => d.uuid === shutter.uuid);
|
|
||||||
if (index >= 0) {
|
|
||||||
this.shutterList.splice(index, 1, shutter);
|
|
||||||
} else {
|
|
||||||
this.fetch();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
<div class="flexBox">
|
||||||
|
<div class="flexBoxFixed">
|
||||||
|
<app-search [(search)]="filter.search" (doSearch)="liveList.refresh()"></app-search>
|
||||||
|
</div>
|
||||||
|
<div class="flexBoxRest verticalScroll">
|
||||||
|
<app-taggable-list [list]="liveList.list"></app-taggable-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,68 @@
|
|||||||
|
import {Component, OnDestroy, OnInit} from '@angular/core';
|
||||||
|
import {TaggableListComponent} from '../../shared/taggable-list/taggable-list.component';
|
||||||
|
import {Taggable} from '../../api/Taggable/Taggable';
|
||||||
|
import {TaggableService} from '../../api/Taggable/taggable.service';
|
||||||
|
import {FormsModule} from '@angular/forms';
|
||||||
|
import {Subscription} from 'rxjs';
|
||||||
|
import {TaggableFilter} from '../../api/Taggable/TaggableFilter';
|
||||||
|
import {ActivatedRoute} from '@angular/router';
|
||||||
|
import {CrudLiveList} from '../../api/common/CrudLiveList';
|
||||||
|
import {SearchComponent} from '../../shared/search/search.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-taggable-list-page',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
TaggableListComponent,
|
||||||
|
FormsModule,
|
||||||
|
SearchComponent
|
||||||
|
],
|
||||||
|
templateUrl: './taggable-list-page.component.html',
|
||||||
|
styleUrl: './taggable-list-page.component.less'
|
||||||
|
})
|
||||||
|
export class TaggableListPageComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
private readonly subs: Subscription[] = [];
|
||||||
|
|
||||||
|
protected readonly filter: TaggableFilter = new TaggableFilter();
|
||||||
|
|
||||||
|
protected readonly liveList: CrudLiveList<Taggable>;
|
||||||
|
|
||||||
|
private tagSet: boolean = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected readonly taggableService: TaggableService,
|
||||||
|
protected readonly activatedRoute: ActivatedRoute,
|
||||||
|
) {
|
||||||
|
this.subs.push(this.liveList = new CrudLiveList(
|
||||||
|
this.taggableService,
|
||||||
|
false,
|
||||||
|
undefined,
|
||||||
|
next => {
|
||||||
|
if (this.tagSet) {
|
||||||
|
this.taggableService.filter(this.filter, next);
|
||||||
|
} else {
|
||||||
|
next([]);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.subs.push(this.activatedRoute.params.subscribe(params => {
|
||||||
|
this.tagSet = 'tag' in params;
|
||||||
|
if (this.tagSet) {
|
||||||
|
this.filter.tag = params['tag'] || '';
|
||||||
|
console.log(this.filter.tag);
|
||||||
|
this.liveList.refresh();
|
||||||
|
} else {
|
||||||
|
this.liveList.clear();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.subs.forEach(sub => sub.unsubscribe());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,8 +0,0 @@
|
|||||||
<div class="flexBox">
|
|
||||||
<div class="flexBoxFixed">
|
|
||||||
<input type="text" [(ngModel)]="filter.search" (ngModelChange)="fetchDelayed()" placeholder="Suchen...">
|
|
||||||
</div>
|
|
||||||
<div class="flexBoxRest verticalScroll">
|
|
||||||
<app-tunable-list [list]="tunableList"></app-tunable-list>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
@import "../../../config";
|
|
||||||
|
|
||||||
input {
|
|
||||||
border-bottom: @border solid lightgray;
|
|
||||||
}
|
|
||||||
@ -1,68 +0,0 @@
|
|||||||
import {Component, OnDestroy, OnInit} from '@angular/core';
|
|
||||||
import {TunableListComponent} from '../../shared/tunable-list/tunable-list.component';
|
|
||||||
import {Tunable} from '../../api/Tunable/Tunable';
|
|
||||||
import {TunableService} from '../../api/Tunable/tunable.service';
|
|
||||||
import {FormsModule} from '@angular/forms';
|
|
||||||
import {TunableFilter} from '../../api/Tunable/TunableFilter';
|
|
||||||
import {Subscription} from 'rxjs';
|
|
||||||
import {ApiService} from '../../api/common/api.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-tunable-list-page',
|
|
||||||
standalone: true,
|
|
||||||
imports: [
|
|
||||||
TunableListComponent,
|
|
||||||
FormsModule
|
|
||||||
],
|
|
||||||
templateUrl: './tunable-list-page.component.html',
|
|
||||||
styleUrl: './tunable-list-page.component.less'
|
|
||||||
})
|
|
||||||
export class TunableListPageComponent implements OnInit, OnDestroy {
|
|
||||||
|
|
||||||
private readonly subs: Subscription[] = [];
|
|
||||||
|
|
||||||
protected tunableList: Tunable[] = [];
|
|
||||||
|
|
||||||
protected filter: TunableFilter = new TunableFilter();
|
|
||||||
|
|
||||||
private fetchTimeout: any;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
protected readonly tunableService: TunableService,
|
|
||||||
protected readonly apiService: ApiService,
|
|
||||||
) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.fetch();
|
|
||||||
this.apiService.connected(() => this.fetch());
|
|
||||||
this.subs.push(this.tunableService.subscribe(tunable => this.updateTunable(tunable)));
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
this.subs.forEach(sub => sub.unsubscribe());
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchDelayed() {
|
|
||||||
if (this.fetchTimeout) {
|
|
||||||
clearTimeout(this.fetchTimeout);
|
|
||||||
this.fetchTimeout = undefined;
|
|
||||||
}
|
|
||||||
this.fetchTimeout = setTimeout(() => this.fetch(), 300)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fetch() {
|
|
||||||
this.tunableService.list(this.filter, list => this.tunableList = list)
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateTunable(tunable: Tunable) {
|
|
||||||
const index = this.tunableList.findIndex(d => d.uuid === tunable.uuid);
|
|
||||||
if (index >= 0) {
|
|
||||||
this.tunableList.splice(index, 1, tunable);
|
|
||||||
} else {
|
|
||||||
this.fetch();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -3,7 +3,6 @@
|
|||||||
.group {
|
.group {
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
margin-bottom: @space;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,3 @@
|
|||||||
|
<div class="box">
|
||||||
|
<input type="text" [(ngModel)]="search" (ngModelChange)="fetchDelayed()" placeholder="Filter ...">
|
||||||
|
</div>
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
@import "../../../config";
|
||||||
|
|
||||||
|
.box {
|
||||||
|
width: 100%;
|
||||||
|
padding: @space @space 0 @space;
|
||||||
|
}
|
||||||
35
src/main/angular/src/app/shared/search/search.component.ts
Normal file
35
src/main/angular/src/app/shared/search/search.component.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import {Component, EventEmitter, Input, Output} from '@angular/core';
|
||||||
|
import {FormsModule} from '@angular/forms';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-search',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
FormsModule
|
||||||
|
],
|
||||||
|
templateUrl: './search.component.html',
|
||||||
|
styleUrl: './search.component.less'
|
||||||
|
})
|
||||||
|
export class SearchComponent {
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
search: string = '';
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
searchChange: EventEmitter<string> = new EventEmitter();
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
doSearch: EventEmitter<string> = new EventEmitter();
|
||||||
|
|
||||||
|
private fetchTimeout: any;
|
||||||
|
|
||||||
|
fetchDelayed() {
|
||||||
|
if (this.fetchTimeout) {
|
||||||
|
clearTimeout(this.fetchTimeout);
|
||||||
|
this.fetchTimeout = undefined;
|
||||||
|
}
|
||||||
|
this.searchChange.emit(this.search);
|
||||||
|
this.fetchTimeout = setTimeout(() => this.doSearch.emit(this.search), 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
<div class="tileContainer taggableList">
|
||||||
|
|
||||||
|
<app-taggable-tile [now]="now" [taggable]="taggable" *ngFor="let taggable of list"></app-taggable-tile>
|
||||||
|
|
||||||
|
</div>
|
||||||
@ -0,0 +1 @@
|
|||||||
|
@import "../../../config";
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
|
||||||
|
import {NgForOf} from '@angular/common';
|
||||||
|
import {Subscription, timer} from 'rxjs';
|
||||||
|
import {TaggableTileComponent} from '../taggable-tile/taggable-tile.component';
|
||||||
|
import {Taggable} from '../../api/Taggable/Taggable';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-taggable-list',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
NgForOf,
|
||||||
|
TaggableTileComponent
|
||||||
|
],
|
||||||
|
templateUrl: './taggable-list.component.html',
|
||||||
|
styleUrl: './taggable-list.component.less'
|
||||||
|
})
|
||||||
|
export class TaggableListComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
private readonly subs: Subscription[] = [];
|
||||||
|
|
||||||
|
protected now: Date = new Date();
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
list: Taggable[] = [];
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.now = new Date();
|
||||||
|
this.subs.push(timer(1000, 1000).subscribe(() => this.now = new Date()));
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.subs.forEach(sub => sub.unsubscribe());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
<app-device-tile *ngIf="isDevice()" [now]="now" [device]="asDevice()"></app-device-tile>
|
||||||
|
|
||||||
|
<app-shutter-tile *ngIf="isShutter()" [now]="now" [shutter]="asShutter()"></app-shutter-tile>
|
||||||
|
|
||||||
|
<app-tunable-tile *ngIf="isTunable()" [now]="now" [tunable]="asTunable()"></app-tunable-tile>
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
import {Component, Input} from '@angular/core';
|
||||||
|
import {Device} from '../../api/Device/Device';
|
||||||
|
import {Tunable} from '../../api/Tunable/Tunable';
|
||||||
|
import {Shutter} from '../../api/Shutter/Shutter';
|
||||||
|
import {DeviceTileComponent} from '../device-tile/device-tile.component';
|
||||||
|
import {NgIf} from '@angular/common';
|
||||||
|
import {ShutterTileComponent} from '../shutter-tile/shutter-tile.component';
|
||||||
|
import {TunableTileComponent} from '../tunable-tile/tunable-tile.component';
|
||||||
|
import {Taggable} from '../../api/Taggable/Taggable';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-taggable-tile',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
DeviceTileComponent,
|
||||||
|
NgIf,
|
||||||
|
ShutterTileComponent,
|
||||||
|
TunableTileComponent
|
||||||
|
],
|
||||||
|
templateUrl: './taggable-tile.component.html',
|
||||||
|
styleUrl: './taggable-tile.component.less'
|
||||||
|
})
|
||||||
|
export class TaggableTileComponent {
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
now!: Date;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
taggable!: Taggable;
|
||||||
|
|
||||||
|
asDevice(): Device {
|
||||||
|
return this.taggable as Device;
|
||||||
|
}
|
||||||
|
|
||||||
|
isDevice(): boolean {
|
||||||
|
return this.taggable instanceof Device;
|
||||||
|
}
|
||||||
|
|
||||||
|
asShutter(): Shutter {
|
||||||
|
return this.taggable as Shutter;
|
||||||
|
}
|
||||||
|
|
||||||
|
isShutter(): boolean {
|
||||||
|
return this.taggable instanceof Shutter;
|
||||||
|
}
|
||||||
|
|
||||||
|
asTunable(): Tunable {
|
||||||
|
return this.taggable as Tunable;
|
||||||
|
}
|
||||||
|
|
||||||
|
isTunable(): boolean {
|
||||||
|
return this.taggable instanceof Tunable;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -19,9 +19,8 @@ div {
|
|||||||
|
|
||||||
input[type=text] {
|
input[type=text] {
|
||||||
all: unset;
|
all: unset;
|
||||||
width: calc(100% - 2 * 0.2em - @border);
|
width: 100%;
|
||||||
padding-left: 0.2em;
|
border: @border solid lightgray;
|
||||||
padding-right: 0.2em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.emptyBox{
|
.emptyBox{
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package de.ph87.home.area;
|
package de.ph87.home.area;
|
||||||
|
|
||||||
|
import de.ph87.home.search.ISearchable;
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
import jakarta.persistence.Id;
|
import jakarta.persistence.Id;
|
||||||
@ -8,13 +9,14 @@ import lombok.NoArgsConstructor;
|
|||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Getter
|
@Getter
|
||||||
@ToString
|
@ToString
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
public class Area {
|
public class Area implements ISearchable {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -28,6 +30,11 @@ public class Area {
|
|||||||
@Column(nullable = false, unique = true)
|
@Column(nullable = false, unique = true)
|
||||||
private String slug;
|
private String slug;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<?> getSearchableValues() {
|
||||||
|
return List.of(slug, name);
|
||||||
|
}
|
||||||
|
|
||||||
public Area(@NonNull final String name, @NonNull final String slug) {
|
public Area(@NonNull final String name, @NonNull final String slug) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.slug = slug;
|
this.slug = slug;
|
||||||
|
|||||||
@ -6,12 +6,14 @@ import lombok.Getter;
|
|||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import static de.ph87.home.common.crud.SearchHelper.search;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@ToString
|
@ToString
|
||||||
public class AreaFilter extends AbstractSearchFilter {
|
public class AreaFilter extends AbstractSearchFilter {
|
||||||
|
|
||||||
public boolean filter(@NonNull final AreaDto dto) throws PropertyTypeMismatch {
|
public boolean filter(@NonNull final AreaDto dto) throws PropertyTypeMismatch {
|
||||||
return search(dto.getName());
|
return search(search, dto.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
21
src/main/java/de/ph87/home/common/ListHelpers.java
Normal file
21
src/main/java/de/ph87/home/common/ListHelpers.java
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package de.ph87.home.common;
|
||||||
|
|
||||||
|
import lombok.NonNull;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ListHelpers {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@SafeVarargs
|
||||||
|
public static <T> List<T> merge(@NonNull final List<T>... listList) {
|
||||||
|
final List<T> merged = new ArrayList<>(Arrays.stream(listList).reduce(0, (total, subList) -> total + subList.size(), Integer::sum));
|
||||||
|
for (final List<T> ts : listList) {
|
||||||
|
merged.addAll(ts);
|
||||||
|
}
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -11,6 +11,6 @@ public abstract class AbstractSearchFilter implements ISearch {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private String search;
|
protected String search;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,22 +13,4 @@ public interface ISearch {
|
|||||||
@Nullable
|
@Nullable
|
||||||
String getSearch();
|
String getSearch();
|
||||||
|
|
||||||
default boolean search(@NonNull final String... fields) {
|
|
||||||
final String term = getSearch();
|
|
||||||
if (term == null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
final List<String> haystack = Arrays.stream(fields).map(String::toString).map(String::toLowerCase).toList();
|
|
||||||
return splitWords(term).allMatch(word -> anyMatch(word, haystack));
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
default Stream<String> splitWords(@NonNull final String term) {
|
|
||||||
return Arrays.stream(term.toLowerCase(Locale.ROOT).replaceAll("^\\s|\\s$", "").split("\\s+"));
|
|
||||||
}
|
|
||||||
|
|
||||||
default boolean anyMatch(@NonNull final String needle, @NonNull final List<String> haystack) {
|
|
||||||
return haystack.stream().anyMatch(lowerCaseHayStack -> lowerCaseHayStack.contains(needle));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
30
src/main/java/de/ph87/home/common/crud/SearchHelper.java
Normal file
30
src/main/java/de/ph87/home/common/crud/SearchHelper.java
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package de.ph87.home.common.crud;
|
||||||
|
|
||||||
|
import jakarta.annotation.Nullable;
|
||||||
|
import lombok.NonNull;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public class SearchHelper {
|
||||||
|
|
||||||
|
public static boolean search(@Nullable final String term, @NonNull final Object... haystack) {
|
||||||
|
if (term == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
final List<String> haystackValues = Arrays.stream(haystack).map(Object::toString).map(String::toLowerCase).toList();
|
||||||
|
return splitWords(term).allMatch(word -> anyMatch(word, haystackValues));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static Stream<String> splitWords(@NonNull final String term) {
|
||||||
|
return Arrays.stream(term.toLowerCase(Locale.ROOT).replaceAll("^\\s|\\s$", "").split("\\s+"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean anyMatch(@NonNull final String needle, @NonNull final List<String> haystack) {
|
||||||
|
return haystack.stream().anyMatch(lowerCaseHayStack -> lowerCaseHayStack.contains(needle));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -6,6 +6,8 @@ import de.ph87.home.device.DeviceService;
|
|||||||
import de.ph87.home.knx.property.KnxPropertyService;
|
import de.ph87.home.knx.property.KnxPropertyService;
|
||||||
import de.ph87.home.knx.property.KnxPropertyType;
|
import de.ph87.home.knx.property.KnxPropertyType;
|
||||||
import de.ph87.home.shutter.ShutterService;
|
import de.ph87.home.shutter.ShutterService;
|
||||||
|
import de.ph87.home.tag.TagDto;
|
||||||
|
import de.ph87.home.tag.TagService;
|
||||||
import de.ph87.home.tunable.TunableService;
|
import de.ph87.home.tunable.TunableService;
|
||||||
import jakarta.annotation.Nullable;
|
import jakarta.annotation.Nullable;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
@ -17,6 +19,8 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import tuwien.auto.calimero.GroupAddress;
|
import tuwien.auto.calimero.GroupAddress;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@Transactional
|
@Transactional
|
||||||
@ -34,32 +38,48 @@ public class DemoService {
|
|||||||
|
|
||||||
private final AreaService areaService;
|
private final AreaService areaService;
|
||||||
|
|
||||||
|
private final TagService tagService;
|
||||||
|
|
||||||
@EventListener(ApplicationStartedEvent.class)
|
@EventListener(ApplicationStartedEvent.class)
|
||||||
public void startup() {
|
public void startup() {
|
||||||
|
final TagDto tagLight = tagService.create("light", "Licht");
|
||||||
|
final TagDto tagDevice = tagService.create("device", "Gerät");
|
||||||
|
final TagDto tagDecoration = tagService.create("decoration", "Dekoration");
|
||||||
|
final TagDto tagDecorationInside = tagService.create("decoration_inside", "Dekoration Innen");
|
||||||
|
final TagDto tagDecorationWindow = tagService.create("decoration_window", "Dekoration Fenster");
|
||||||
|
final TagDto tagDecorationOutside = tagService.create("decoration_outside", "Dekoration Außen");
|
||||||
|
final TagDto tagAudio = tagService.create("media_audio", "Audio");
|
||||||
|
final TagDto tagVideo = tagService.create("media_video", "Video");
|
||||||
|
final TagDto tagMedia = tagService.create("media", "Media");
|
||||||
|
final TagDto tagShutter = tagService.create("shutter", "Rollladen");
|
||||||
|
final TagDto tagFront = tagService.create("house_front", "Haus Vorne");
|
||||||
|
final TagDto tagSide = tagService.create("house_side", "Haus Seite");
|
||||||
|
final TagDto tagHinten = tagService.create("house_backside", "Haus Hinten");
|
||||||
|
|
||||||
final AreaDto eg = area("eg", "EG");
|
final AreaDto eg = area("eg", "EG");
|
||||||
|
|
||||||
final AreaDto wohnzimmer = area("wohnzimmer", "Wohnzimmer");
|
final AreaDto wohnzimmer = area("wohnzimmer", "Wohnzimmer");
|
||||||
device(wohnzimmer, "fernseher", "Fernseher", 20, 4);
|
device(wohnzimmer, "fernseher", "Fernseher", 20, 4, tagDevice, tagMedia, tagVideo);
|
||||||
device(wohnzimmer, "verstaerker", "Verstärker", 825, 824);
|
device(wohnzimmer, "verstaerker", "Verstärker", 825, 824, tagDevice, tagMedia, tagAudio);
|
||||||
tunable(wohnzimmer, "haengelampe", "Hängelampe", 1794, 1799, null, null, null, null);
|
device(wohnzimmer, "haengelampe", "Hängelampe", 1794, 1799, tagLight);
|
||||||
tunable(wohnzimmer, "fensterdeko", "Fenster", 1823, 1822, null, null, null, null);
|
device(wohnzimmer, "fensterdeko", "Fenster", 1823, 1822, tagDecoration, tagDecorationWindow);
|
||||||
tunable(wohnzimmer, "spots", "", 28, 828, 2344, 2343, 1825, 1824);
|
tunable(wohnzimmer, "spots", "", 28, 828, 2344, 2343, 1825, 1824, tagLight);
|
||||||
shutter(wohnzimmer, "links", "Links", 1048);
|
shutter(wohnzimmer, "links", "Links", 1048, tagShutter, tagFront);
|
||||||
shutter(wohnzimmer, "rechts", "Rechts", 1811);
|
shutter(wohnzimmer, "rechts", "Rechts", 1811, tagShutter, tagFront);
|
||||||
|
|
||||||
final AreaDto kueche = area("kueche", "Küche");
|
final AreaDto kueche = area("kueche", "Küche");
|
||||||
tunable(kueche, "kueche_spots", "", 2311, 2304, 2342, 2341, 2321, 2317);
|
tunable(kueche, "kueche_spots", "", 2311, 2304, 2342, 2341, 2321, 2317, tagLight);
|
||||||
shutter(kueche, "kueche_seite", "Seite", 2316);
|
shutter(kueche, "kueche_seite", "Seite", 2316, tagShutter, tagSide);
|
||||||
shutter(kueche, "kueche_theke", "Theke", 2320);
|
shutter(kueche, "kueche_theke", "Theke", 2320, tagShutter, tagHinten);
|
||||||
shutter(kueche, "kueche_tuer", "Tür", 2324);
|
shutter(kueche, "kueche_tuer", "Tür", 2324, tagShutter, tagHinten);
|
||||||
|
|
||||||
tunable(eg, "eg_ambiente", "Ambiente", 849, 848, null, null, null, null);
|
device(eg, "eg_ambiente", "Ambiente", 849, 848, tagLight);
|
||||||
|
|
||||||
final AreaDto arbeitszimmer = area("arbeitszimmer", "Arbeitszimmer");
|
final AreaDto arbeitszimmer = area("arbeitszimmer", "Arbeitszimmer");
|
||||||
tunable(arbeitszimmer, "spots", "", 2058, 2057, 2067, 2069, 2049, 2054);
|
tunable(arbeitszimmer, "spots", "", 2058, 2057, 2067, 2069, 2049, 2054, tagLight);
|
||||||
|
|
||||||
final AreaDto keller = area("keller", "Keller");
|
final AreaDto keller = area("keller", "Keller");
|
||||||
device(keller, "receiver", "Receiver", 2561, 2560);
|
device(keller, "receiver", "Receiver", 2561, 2560, tagDevice);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -72,23 +92,26 @@ public class DemoService {
|
|||||||
@NonNull final String subSlug,
|
@NonNull final String subSlug,
|
||||||
@NonNull final String name,
|
@NonNull final String name,
|
||||||
@Nullable final Integer stateRead,
|
@Nullable final Integer stateRead,
|
||||||
@Nullable final Integer stateWrite) {
|
@Nullable final Integer stateWrite,
|
||||||
|
@NonNull final TagDto... tagList
|
||||||
|
) {
|
||||||
final String slug = area.getSlug() + "_" + subSlug;
|
final String slug = area.getSlug() + "_" + subSlug;
|
||||||
final String statePropertyId = slug + "_state";
|
final String statePropertyId = slug + "_state";
|
||||||
knxPropertyService.create(statePropertyId, KnxPropertyType.BOOLEAN, adr(stateRead), adr(stateWrite));
|
knxPropertyService.create(statePropertyId, KnxPropertyType.BOOLEAN, adr(stateRead), adr(stateWrite));
|
||||||
deviceService.create(area.getUuid(), name, slug, statePropertyId);
|
deviceService.create(area.getUuid(), name, slug, statePropertyId, Arrays.stream(tagList).map(TagDto::getUuid).toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void shutter(
|
private void shutter(
|
||||||
@NonNull final AreaDto area,
|
@NonNull final AreaDto area,
|
||||||
@NonNull final String subSlug,
|
@NonNull final String subSlug,
|
||||||
@NonNull final String name,
|
@NonNull final String name,
|
||||||
@Nullable final Integer positionReadWrite
|
@Nullable final Integer positionReadWrite,
|
||||||
|
@NonNull final TagDto... tagList
|
||||||
) {
|
) {
|
||||||
final String slug = area.getSlug() + "_" + subSlug;
|
final String slug = area.getSlug() + "_" + subSlug;
|
||||||
final String statePropertyId = slug + "_state";
|
final String statePropertyId = slug + "_state";
|
||||||
knxPropertyService.create(statePropertyId, KnxPropertyType.DOUBLE, adr(positionReadWrite), adr(positionReadWrite));
|
knxPropertyService.create(statePropertyId, KnxPropertyType.DOUBLE, adr(positionReadWrite), adr(positionReadWrite));
|
||||||
shutterService.create(area.getUuid(), name, slug, statePropertyId);
|
shutterService.create(area.getUuid(), name, slug, statePropertyId, Arrays.stream(tagList).map(TagDto::getUuid).toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tunable(
|
private void tunable(
|
||||||
@ -100,13 +123,14 @@ public class DemoService {
|
|||||||
@Nullable final Integer brightnessRead,
|
@Nullable final Integer brightnessRead,
|
||||||
@Nullable final Integer brightnessWrite,
|
@Nullable final Integer brightnessWrite,
|
||||||
@Nullable final Integer coldnessRead,
|
@Nullable final Integer coldnessRead,
|
||||||
@Nullable final Integer coldnessWrite
|
@Nullable final Integer coldnessWrite,
|
||||||
|
@NonNull final TagDto... tagList
|
||||||
) {
|
) {
|
||||||
final String slug = area.getSlug() + "_" + subSlug;
|
final String slug = area.getSlug() + "_" + subSlug;
|
||||||
final String stateProperty = knxProperty(slug + "_state", KnxPropertyType.BOOLEAN, stateRead, stateWrite);
|
final String stateProperty = knxProperty(slug + "_state", KnxPropertyType.BOOLEAN, stateRead, stateWrite);
|
||||||
final String brightnessProperty = knxProperty(slug + "_brightness", KnxPropertyType.DOUBLE, brightnessRead, brightnessWrite);
|
final String brightnessProperty = knxProperty(slug + "_brightness", KnxPropertyType.DOUBLE, brightnessRead, brightnessWrite);
|
||||||
final String coldnessProperty = knxProperty(slug + "_coldness", KnxPropertyType.DOUBLE, coldnessRead, coldnessWrite);
|
final String coldnessProperty = knxProperty(slug + "_coldness", KnxPropertyType.DOUBLE, coldnessRead, coldnessWrite);
|
||||||
tunableService.create(area.getUuid(), name, slug, stateProperty, brightnessProperty, coldnessProperty);
|
tunableService.create(area.getUuid(), name, slug, stateProperty, brightnessProperty, coldnessProperty, Arrays.stream(tagList).map(TagDto::getUuid).toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
|||||||
@ -1,19 +1,22 @@
|
|||||||
package de.ph87.home.device;
|
package de.ph87.home.device;
|
||||||
|
|
||||||
import de.ph87.home.area.Area;
|
import de.ph87.home.area.Area;
|
||||||
import jakarta.persistence.Column;
|
import de.ph87.home.tag.taggable.ITaggable;
|
||||||
import jakarta.persistence.Entity;
|
import de.ph87.home.tag.Tag;
|
||||||
import jakarta.persistence.Id;
|
import jakarta.persistence.*;
|
||||||
import jakarta.persistence.ManyToOne;
|
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static de.ph87.home.common.ListHelpers.merge;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Getter
|
@Getter
|
||||||
@ToString
|
@ToString
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
public class Device {
|
public class Device implements ITaggable {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -36,11 +39,22 @@ public class Device {
|
|||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private String statePropertyId;
|
private String statePropertyId;
|
||||||
|
|
||||||
public Device(final Area area, @NonNull final String name, @NonNull final String slug, @NonNull final String statePropertyId) {
|
@Getter
|
||||||
|
@NonNull
|
||||||
|
@ManyToMany
|
||||||
|
private List<Tag> tagList = new ArrayList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<?> getSearchableValues() {
|
||||||
|
return merge(List.of(area.getSlug(), area.getName(), slug, name), tagList.stream().map(Tag::getSlug).toList(), tagList.stream().map(Tag::getName).toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Device(final Area area, @NonNull final String name, @NonNull final String slug, @NonNull final String statePropertyId, @NonNull final List<Tag> tagList) {
|
||||||
this.area = area;
|
this.area = area;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.slug = slug;
|
this.slug = slug;
|
||||||
this.statePropertyId = statePropertyId;
|
this.statePropertyId = statePropertyId;
|
||||||
|
this.tagList = new ArrayList<>(tagList);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package de.ph87.home.device;
|
|||||||
import de.ph87.home.area.AreaDto;
|
import de.ph87.home.area.AreaDto;
|
||||||
import de.ph87.home.property.PropertyDto;
|
import de.ph87.home.property.PropertyDto;
|
||||||
import de.ph87.home.property.PropertyTypeMismatch;
|
import de.ph87.home.property.PropertyTypeMismatch;
|
||||||
|
import de.ph87.home.tag.Tag;
|
||||||
import de.ph87.home.web.IWebSocketMessage;
|
import de.ph87.home.web.IWebSocketMessage;
|
||||||
import jakarta.annotation.Nullable;
|
import jakarta.annotation.Nullable;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@ -37,6 +38,9 @@ public class DeviceDto implements IWebSocketMessage {
|
|||||||
@ToString.Exclude
|
@ToString.Exclude
|
||||||
private final PropertyDto<Boolean> stateProperty;
|
private final PropertyDto<Boolean> stateProperty;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final List<String> tagList;
|
||||||
|
|
||||||
public DeviceDto(@NonNull final Device device, @Nullable final PropertyDto<Boolean> stateProperty) {
|
public DeviceDto(@NonNull final Device device, @Nullable final PropertyDto<Boolean> stateProperty) {
|
||||||
this.area = new AreaDto(device.getArea());
|
this.area = new AreaDto(device.getArea());
|
||||||
this.uuid = device.getUuid();
|
this.uuid = device.getUuid();
|
||||||
@ -44,6 +48,7 @@ public class DeviceDto implements IWebSocketMessage {
|
|||||||
this.slug = device.getSlug();
|
this.slug = device.getSlug();
|
||||||
this.statePropertyId = device.getStatePropertyId();
|
this.statePropertyId = device.getStatePropertyId();
|
||||||
this.stateProperty = stateProperty;
|
this.stateProperty = stateProperty;
|
||||||
|
this.tagList = device.getTagList().stream().map(Tag::getName).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|||||||
@ -7,6 +7,8 @@ import lombok.Getter;
|
|||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import static de.ph87.home.common.crud.SearchHelper.search;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@ToString
|
@ToString
|
||||||
public class DeviceFilter extends AbstractSearchFilter {
|
public class DeviceFilter extends AbstractSearchFilter {
|
||||||
@ -31,7 +33,7 @@ public class DeviceFilter extends AbstractSearchFilter {
|
|||||||
if (!stateFalse && Boolean.FALSE.equals(value)) {
|
if (!stateFalse && Boolean.FALSE.equals(value)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return search(dto.getName());
|
return search(search, dto.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,12 @@ import de.ph87.home.area.AreaService;
|
|||||||
import de.ph87.home.common.crud.CrudAction;
|
import de.ph87.home.common.crud.CrudAction;
|
||||||
import de.ph87.home.common.crud.EntityNotFound;
|
import de.ph87.home.common.crud.EntityNotFound;
|
||||||
import de.ph87.home.property.*;
|
import de.ph87.home.property.*;
|
||||||
|
import de.ph87.home.search.SearchableDto;
|
||||||
|
import de.ph87.home.tag.Tag;
|
||||||
|
import de.ph87.home.tag.TagReader;
|
||||||
|
import de.ph87.home.tag.TaggableDto;
|
||||||
|
import de.ph87.home.tag.taggable.ITaggableService;
|
||||||
|
import de.ph87.home.tag.taggable.TaggableFilter;
|
||||||
import jakarta.annotation.Nullable;
|
import jakarta.annotation.Nullable;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@ -14,14 +20,15 @@ import org.springframework.context.event.EventListener;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static de.ph87.home.common.crud.SearchHelper.search;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@Transactional
|
@Transactional
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class DeviceService {
|
public class DeviceService implements ITaggableService<DeviceDto> {
|
||||||
|
|
||||||
private final PropertyService propertyService;
|
private final PropertyService propertyService;
|
||||||
|
|
||||||
@ -31,10 +38,13 @@ public class DeviceService {
|
|||||||
|
|
||||||
private final AreaService areaService;
|
private final AreaService areaService;
|
||||||
|
|
||||||
|
private final TagReader tagReader;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public DeviceDto create(@NonNull final String areaUuid, @NonNull final String name, @NonNull final String slug, @NonNull final String stateProperty) {
|
public DeviceDto create(@NonNull final String areaUuid, @NonNull final String name, @NonNull final String slug, @NonNull final String stateProperty, @NonNull final List<String> tagUuidList) {
|
||||||
final Area area = areaService.getByUuid(areaUuid);
|
final Area area = areaService.getByUuid(areaUuid);
|
||||||
return publish(deviceRepository.save(new Device(area, name, slug, stateProperty)), CrudAction.UPDATED);
|
final List<Tag> tagList = tagUuidList.stream().map(tagReader::getByUuid).toList();
|
||||||
|
return publish(deviceRepository.save(new Device(area, name, slug, stateProperty, tagList)), CrudAction.UPDATED);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setState(@NonNull final String uuidOrSlug, final boolean state) throws PropertyNotFound, PropertyNotWritable, PropertyTypeMismatch {
|
public void setState(@NonNull final String uuidOrSlug, final boolean state) throws PropertyNotFound, PropertyNotWritable, PropertyTypeMismatch {
|
||||||
@ -66,17 +76,13 @@ public class DeviceService {
|
|||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public List<DeviceDto> list(@Nullable final DeviceFilter filter) throws PropertyTypeMismatch {
|
public List<DeviceDto> list(@Nullable final DeviceFilter filter) throws PropertyTypeMismatch {
|
||||||
final List<DeviceDto> all = deviceRepository.findAll().stream().map(this::toDto).toList();
|
|
||||||
if (filter == null) {
|
if (filter == null) {
|
||||||
return all;
|
return deviceRepository.findAll().stream().map(this::toDto).toList();
|
||||||
}
|
}
|
||||||
final List<DeviceDto> results = new ArrayList<>();
|
return deviceRepository.findAll().stream()
|
||||||
for (final DeviceDto dto : all) {
|
.filter(device -> search(filter.getSearch(), device.getSearchableValues()))
|
||||||
if (filter.filter(dto)) {
|
.map(this::toDto)
|
||||||
results.add(dto);
|
.toList();
|
||||||
}
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventListener(PropertyDto.class)
|
@EventListener(PropertyDto.class)
|
||||||
@ -97,4 +103,23 @@ public class DeviceService {
|
|||||||
return toDto(getByUuid(uuid));
|
return toDto(getByUuid(uuid));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<TaggableDto<DeviceDto>> findTaggables(final @NonNull TaggableFilter filter) {
|
||||||
|
return deviceRepository.findAll().stream()
|
||||||
|
.filter(device -> search(filter.getSearch(), device.getSearchableValues()))
|
||||||
|
.filter(device -> device.tagListAnyMatch(filter.getTag()))
|
||||||
|
.map(this::toDto)
|
||||||
|
.map(TaggableDto::new)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SearchableDto<DeviceDto>> findSearchables(@NonNull final String term) {
|
||||||
|
return deviceRepository.findAll().stream()
|
||||||
|
.filter(device -> device.search(term))
|
||||||
|
.map(this::toDto)
|
||||||
|
.map(SearchableDto::new)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,12 +5,14 @@ import lombok.Getter;
|
|||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import static de.ph87.home.common.crud.SearchHelper.search;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@ToString
|
@ToString
|
||||||
public class GroupFilter extends AbstractSearchFilter {
|
public class GroupFilter extends AbstractSearchFilter {
|
||||||
|
|
||||||
public boolean filter(@NonNull final Group group) {
|
public boolean filter(@NonNull final Group group) {
|
||||||
return search(group.getName(), group.getAddress().toString());
|
return search(search, group.getName(), group.getAddress().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
16
src/main/java/de/ph87/home/search/ISearchable.java
Normal file
16
src/main/java/de/ph87/home/search/ISearchable.java
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package de.ph87.home.search;
|
||||||
|
|
||||||
|
import de.ph87.home.common.crud.SearchHelper;
|
||||||
|
import lombok.NonNull;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface ISearchable {
|
||||||
|
|
||||||
|
List<?> getSearchableValues();
|
||||||
|
|
||||||
|
default boolean search(@NonNull String term) {
|
||||||
|
return SearchHelper.search(term, getSearchableValues());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
11
src/main/java/de/ph87/home/search/ISearchableService.java
Normal file
11
src/main/java/de/ph87/home/search/ISearchableService.java
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package de.ph87.home.search;
|
||||||
|
|
||||||
|
import lombok.NonNull;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface ISearchableService<T> {
|
||||||
|
|
||||||
|
List<SearchableDto<T>> findSearchables(@NonNull final String term);
|
||||||
|
|
||||||
|
}
|
||||||
24
src/main/java/de/ph87/home/search/SearchController.java
Normal file
24
src/main/java/de/ph87/home/search/SearchController.java
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package de.ph87.home.search;
|
||||||
|
|
||||||
|
import jakarta.annotation.Nullable;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RequestMapping("search")
|
||||||
|
public class SearchController {
|
||||||
|
|
||||||
|
private final SearchService searchService;
|
||||||
|
|
||||||
|
@PostMapping(consumes = "text/plain")
|
||||||
|
public List<? extends SearchableDto<?>> searchSearchables(@RequestBody(required = false) @Nullable final String term) {
|
||||||
|
return searchService.findSearchables(term == null ? "" : term);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
29
src/main/java/de/ph87/home/search/SearchService.java
Normal file
29
src/main/java/de/ph87/home/search/SearchService.java
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package de.ph87.home.search;
|
||||||
|
|
||||||
|
import de.ph87.home.common.ListHelpers;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@Transactional
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SearchService {
|
||||||
|
|
||||||
|
private final List<ISearchableService<?>> searchableServices;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public List<? extends SearchableDto<?>> findSearchables(@NonNull final String term) {
|
||||||
|
return searchableServices.stream()
|
||||||
|
.map(iSearchableService -> iSearchableService.findSearchables(term))
|
||||||
|
.reduce(ListHelpers::merge)
|
||||||
|
.orElse(new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
17
src/main/java/de/ph87/home/search/SearchableDto.java
Normal file
17
src/main/java/de/ph87/home/search/SearchableDto.java
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package de.ph87.home.search;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class SearchableDto<T> {
|
||||||
|
|
||||||
|
private final String _type_;
|
||||||
|
|
||||||
|
private final T payload;
|
||||||
|
|
||||||
|
public SearchableDto(final T payload) {
|
||||||
|
this.payload = payload;
|
||||||
|
this._type_ = payload.getClass().getSimpleName();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,19 +1,22 @@
|
|||||||
package de.ph87.home.shutter;
|
package de.ph87.home.shutter;
|
||||||
|
|
||||||
import de.ph87.home.area.Area;
|
import de.ph87.home.area.Area;
|
||||||
import jakarta.persistence.Column;
|
import de.ph87.home.tag.taggable.ITaggable;
|
||||||
import jakarta.persistence.Entity;
|
import de.ph87.home.tag.Tag;
|
||||||
import jakarta.persistence.Id;
|
import jakarta.persistence.*;
|
||||||
import jakarta.persistence.ManyToOne;
|
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static de.ph87.home.common.ListHelpers.merge;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Getter
|
@Getter
|
||||||
@ToString
|
@ToString
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
public class Shutter {
|
public class Shutter implements ITaggable {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -36,11 +39,22 @@ public class Shutter {
|
|||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private String positionPropertyId;
|
private String positionPropertyId;
|
||||||
|
|
||||||
public Shutter(@NonNull final Area area, @NonNull final String name, @NonNull final String slug, @NonNull final String positionPropertyId) {
|
@Getter
|
||||||
|
@NonNull
|
||||||
|
@ManyToMany
|
||||||
|
private List<Tag> tagList = new ArrayList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<?> getSearchableValues() {
|
||||||
|
return merge(List.of(area.getSlug(), area.getName(), slug, name), tagList.stream().map(Tag::getSlug).toList(), tagList.stream().map(Tag::getName).toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Shutter(@NonNull final Area area, @NonNull final String name, @NonNull final String slug, @NonNull final String positionPropertyId, final List<Tag> tagList) {
|
||||||
this.area = area;
|
this.area = area;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.slug = slug;
|
this.slug = slug;
|
||||||
this.positionPropertyId = positionPropertyId;
|
this.positionPropertyId = positionPropertyId;
|
||||||
|
this.tagList = new ArrayList<>(tagList);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package de.ph87.home.shutter;
|
|||||||
import de.ph87.home.area.AreaDto;
|
import de.ph87.home.area.AreaDto;
|
||||||
import de.ph87.home.property.PropertyDto;
|
import de.ph87.home.property.PropertyDto;
|
||||||
import de.ph87.home.property.PropertyTypeMismatch;
|
import de.ph87.home.property.PropertyTypeMismatch;
|
||||||
|
import de.ph87.home.tag.Tag;
|
||||||
import de.ph87.home.web.IWebSocketMessage;
|
import de.ph87.home.web.IWebSocketMessage;
|
||||||
import jakarta.annotation.Nullable;
|
import jakarta.annotation.Nullable;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@ -20,6 +21,7 @@ public class ShutterDto implements IWebSocketMessage {
|
|||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private final AreaDto area;
|
private final AreaDto area;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private final String uuid;
|
private final String uuid;
|
||||||
|
|
||||||
@ -36,6 +38,9 @@ public class ShutterDto implements IWebSocketMessage {
|
|||||||
@ToString.Exclude
|
@ToString.Exclude
|
||||||
private final PropertyDto<Double> positionProperty;
|
private final PropertyDto<Double> positionProperty;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final List<String> tagList;
|
||||||
|
|
||||||
public ShutterDto(@NonNull final Shutter shutter, @Nullable final PropertyDto<Double> positionProperty) {
|
public ShutterDto(@NonNull final Shutter shutter, @Nullable final PropertyDto<Double> positionProperty) {
|
||||||
this.area = new AreaDto(shutter.getArea());
|
this.area = new AreaDto(shutter.getArea());
|
||||||
this.uuid = shutter.getUuid();
|
this.uuid = shutter.getUuid();
|
||||||
@ -43,6 +48,7 @@ public class ShutterDto implements IWebSocketMessage {
|
|||||||
this.slug = shutter.getSlug();
|
this.slug = shutter.getSlug();
|
||||||
this.positionPropertyId = shutter.getPositionPropertyId();
|
this.positionPropertyId = shutter.getPositionPropertyId();
|
||||||
this.positionProperty = positionProperty;
|
this.positionProperty = positionProperty;
|
||||||
|
this.tagList = shutter.getTagList().stream().map(Tag::getName).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|||||||
@ -9,6 +9,8 @@ import lombok.ToString;
|
|||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static de.ph87.home.common.crud.SearchHelper.search;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@ToString
|
@ToString
|
||||||
public class ShutterFilter extends AbstractSearchFilter {
|
public class ShutterFilter extends AbstractSearchFilter {
|
||||||
@ -39,7 +41,7 @@ public class ShutterFilter extends AbstractSearchFilter {
|
|||||||
if (!positionClosed && Objects.equals(value, 100.0)) {
|
if (!positionClosed && Objects.equals(value, 100.0)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return search(dto.getName());
|
return search(search, dto.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,12 @@ import de.ph87.home.area.AreaService;
|
|||||||
import de.ph87.home.common.crud.CrudAction;
|
import de.ph87.home.common.crud.CrudAction;
|
||||||
import de.ph87.home.common.crud.EntityNotFound;
|
import de.ph87.home.common.crud.EntityNotFound;
|
||||||
import de.ph87.home.property.*;
|
import de.ph87.home.property.*;
|
||||||
|
import de.ph87.home.search.SearchableDto;
|
||||||
|
import de.ph87.home.tag.Tag;
|
||||||
|
import de.ph87.home.tag.TagReader;
|
||||||
|
import de.ph87.home.tag.TaggableDto;
|
||||||
|
import de.ph87.home.tag.taggable.ITaggableService;
|
||||||
|
import de.ph87.home.tag.taggable.TaggableFilter;
|
||||||
import jakarta.annotation.Nullable;
|
import jakarta.annotation.Nullable;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@ -14,14 +20,15 @@ import org.springframework.context.event.EventListener;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static de.ph87.home.common.crud.SearchHelper.search;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@Transactional
|
@Transactional
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class ShutterService {
|
public class ShutterService implements ITaggableService<ShutterDto> {
|
||||||
|
|
||||||
private final AreaService areaService;
|
private final AreaService areaService;
|
||||||
|
|
||||||
@ -31,10 +38,19 @@ public class ShutterService {
|
|||||||
|
|
||||||
private final ApplicationEventPublisher applicationEventPublisher;
|
private final ApplicationEventPublisher applicationEventPublisher;
|
||||||
|
|
||||||
|
private final TagReader tagReader;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public ShutterDto create(@NonNull final String areaUuid, @NonNull final String name, @NonNull final String slug, @NonNull final String positionProperty) {
|
public ShutterDto create(
|
||||||
|
@NonNull final String areaUuid,
|
||||||
|
@NonNull final String name,
|
||||||
|
@NonNull final String slug,
|
||||||
|
@NonNull final String positionProperty,
|
||||||
|
@NonNull final List<String> tagUuidList
|
||||||
|
) {
|
||||||
final Area area = areaService.getByUuid(areaUuid);
|
final Area area = areaService.getByUuid(areaUuid);
|
||||||
return publish(shutterRepository.save(new Shutter(area, name, slug, positionProperty)), CrudAction.CREATED);
|
final List<Tag> tagList = tagUuidList.stream().map(tagReader::getByUuid).toList();
|
||||||
|
return publish(shutterRepository.save(new Shutter(area, name, slug, positionProperty, tagList)), CrudAction.CREATED);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPosition(@NonNull final String uuidOrSlug, final double position) throws PropertyNotFound, PropertyNotWritable, PropertyTypeMismatch {
|
public void setPosition(@NonNull final String uuidOrSlug, final double position) throws PropertyNotFound, PropertyNotWritable, PropertyTypeMismatch {
|
||||||
@ -66,17 +82,13 @@ public class ShutterService {
|
|||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public List<ShutterDto> list(@Nullable final ShutterFilter filter) throws PropertyTypeMismatch {
|
public List<ShutterDto> list(@Nullable final ShutterFilter filter) throws PropertyTypeMismatch {
|
||||||
final List<ShutterDto> all = shutterRepository.findAll().stream().map(this::toDto).toList();
|
|
||||||
if (filter == null) {
|
if (filter == null) {
|
||||||
return all;
|
return shutterRepository.findAll().stream().map(this::toDto).toList();
|
||||||
}
|
}
|
||||||
final List<ShutterDto> results = new ArrayList<>();
|
return shutterRepository.findAll().stream()
|
||||||
for (final ShutterDto dto : all) {
|
.filter(shutter -> search(filter.getSearch(), shutter.getSearchableValues()))
|
||||||
if (filter.filter(dto)) {
|
.map(this::toDto)
|
||||||
results.add(dto);
|
.toList();
|
||||||
}
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventListener(PropertyDto.class)
|
@EventListener(PropertyDto.class)
|
||||||
@ -97,4 +109,23 @@ public class ShutterService {
|
|||||||
return toDto(getByUuid(uuid));
|
return toDto(getByUuid(uuid));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<TaggableDto<ShutterDto>> findTaggables(final @NonNull TaggableFilter filter) {
|
||||||
|
return shutterRepository.findAll().stream()
|
||||||
|
.filter(shutter -> shutter.tagListAnyMatch(filter.getTag()))
|
||||||
|
.filter(device -> search(filter.getSearch(), device.getSearchableValues()))
|
||||||
|
.map(this::toDto)
|
||||||
|
.map(TaggableDto::new)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SearchableDto<ShutterDto>> findSearchables(@NonNull final String term) {
|
||||||
|
return shutterRepository.findAll().stream()
|
||||||
|
.filter(shutter -> shutter.search(term))
|
||||||
|
.map(this::toDto)
|
||||||
|
.map(SearchableDto::new)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
36
src/main/java/de/ph87/home/tag/Tag.java
Normal file
36
src/main/java/de/ph87/home/tag/Tag.java
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package de.ph87.home.tag;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Getter
|
||||||
|
@ToString
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class Tag {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@NonNull
|
||||||
|
private String uuid = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Column(nullable = false, unique = true)
|
||||||
|
private String slug;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Column(nullable = false, unique = true)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public Tag(@NonNull final String slug, @NonNull final String name) {
|
||||||
|
this.slug = slug;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
12
src/main/java/de/ph87/home/tag/TagController.java
Normal file
12
src/main/java/de/ph87/home/tag/TagController.java
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package de.ph87.home.tag;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RequestMapping("Tag")
|
||||||
|
public class TagController {
|
||||||
|
|
||||||
|
}
|
||||||
20
src/main/java/de/ph87/home/tag/TagDto.java
Normal file
20
src/main/java/de/ph87/home/tag/TagDto.java
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package de.ph87.home.tag;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NonNull;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class TagDto {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final String uuid;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
public TagDto(@NonNull final Tag tag) {
|
||||||
|
this.uuid = tag.getUuid();
|
||||||
|
this.name = tag.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
22
src/main/java/de/ph87/home/tag/TagReader.java
Normal file
22
src/main/java/de/ph87/home/tag/TagReader.java
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package de.ph87.home.tag;
|
||||||
|
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@Transactional
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class TagReader {
|
||||||
|
|
||||||
|
private final TagRepository tagRepository;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Tag getByUuid(@NonNull final String uuid) {
|
||||||
|
return tagRepository.findById(uuid).orElseThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
7
src/main/java/de/ph87/home/tag/TagRepository.java
Normal file
7
src/main/java/de/ph87/home/tag/TagRepository.java
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package de.ph87.home.tag;
|
||||||
|
|
||||||
|
import org.springframework.data.repository.ListCrudRepository;
|
||||||
|
|
||||||
|
public interface TagRepository extends ListCrudRepository<Tag, String> {
|
||||||
|
|
||||||
|
}
|
||||||
22
src/main/java/de/ph87/home/tag/TagService.java
Normal file
22
src/main/java/de/ph87/home/tag/TagService.java
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package de.ph87.home.tag;
|
||||||
|
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@Transactional
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class TagService {
|
||||||
|
|
||||||
|
private final TagRepository tagRepository;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public TagDto create(final @NonNull String slug, @NonNull final String name) {
|
||||||
|
return new TagDto(tagRepository.save(new Tag(slug, name)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
17
src/main/java/de/ph87/home/tag/TaggableDto.java
Normal file
17
src/main/java/de/ph87/home/tag/TaggableDto.java
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package de.ph87.home.tag;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class TaggableDto<T> {
|
||||||
|
|
||||||
|
private final String _type_;
|
||||||
|
|
||||||
|
private final T payload;
|
||||||
|
|
||||||
|
public TaggableDto(final T payload) {
|
||||||
|
this.payload = payload;
|
||||||
|
this._type_ = payload.getClass().getSimpleName();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
20
src/main/java/de/ph87/home/tag/taggable/ITaggable.java
Normal file
20
src/main/java/de/ph87/home/tag/taggable/ITaggable.java
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package de.ph87.home.tag.taggable;
|
||||||
|
|
||||||
|
import de.ph87.home.search.ISearchable;
|
||||||
|
import de.ph87.home.tag.Tag;
|
||||||
|
import lombok.NonNull;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface ITaggable extends ISearchable {
|
||||||
|
|
||||||
|
List<Tag> getTagList();
|
||||||
|
|
||||||
|
default boolean tagListAnyMatch(@NonNull final String term) {
|
||||||
|
if (term.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return getTagList().stream().anyMatch(tag -> tag.getSlug().equalsIgnoreCase(term));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
package de.ph87.home.tag.taggable;
|
||||||
|
|
||||||
|
import de.ph87.home.search.ISearchableService;
|
||||||
|
import de.ph87.home.tag.TaggableDto;
|
||||||
|
import lombok.NonNull;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface ITaggableService<T> extends ISearchableService<T> {
|
||||||
|
|
||||||
|
List<TaggableDto<T>> findTaggables(final @NonNull TaggableFilter filter);
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package de.ph87.home.tag.taggable;
|
||||||
|
|
||||||
|
import de.ph87.home.tag.TaggableDto;
|
||||||
|
import jakarta.annotation.Nullable;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RequestMapping("Taggable")
|
||||||
|
public class TaggableController {
|
||||||
|
|
||||||
|
private final TaggableService taggableService;
|
||||||
|
|
||||||
|
@PostMapping(value = "list")
|
||||||
|
public List<? extends TaggableDto<?>> list(@RequestBody @NonNull final TaggableFilter filter) {
|
||||||
|
return taggableService.list(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
15
src/main/java/de/ph87/home/tag/taggable/TaggableFilter.java
Normal file
15
src/main/java/de/ph87/home/tag/taggable/TaggableFilter.java
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package de.ph87.home.tag.taggable;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NonNull;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class TaggableFilter {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final String tag;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final String search;
|
||||||
|
|
||||||
|
}
|
||||||
30
src/main/java/de/ph87/home/tag/taggable/TaggableService.java
Normal file
30
src/main/java/de/ph87/home/tag/taggable/TaggableService.java
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package de.ph87.home.tag.taggable;
|
||||||
|
|
||||||
|
import de.ph87.home.common.ListHelpers;
|
||||||
|
import de.ph87.home.tag.TaggableDto;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@Transactional
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class TaggableService {
|
||||||
|
|
||||||
|
private final List<ITaggableService<?>> taggableServices;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public List<? extends TaggableDto<?>> list(@NonNull final TaggableFilter filter) {
|
||||||
|
return taggableServices.stream()
|
||||||
|
.map(iTaggableService -> iTaggableService.findTaggables(filter))
|
||||||
|
.reduce(ListHelpers::merge)
|
||||||
|
.orElse(new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,19 +1,22 @@
|
|||||||
package de.ph87.home.tunable;
|
package de.ph87.home.tunable;
|
||||||
|
|
||||||
import de.ph87.home.area.Area;
|
import de.ph87.home.area.Area;
|
||||||
import jakarta.persistence.Column;
|
import de.ph87.home.tag.taggable.ITaggable;
|
||||||
import jakarta.persistence.Entity;
|
import de.ph87.home.tag.Tag;
|
||||||
import jakarta.persistence.Id;
|
import jakarta.persistence.*;
|
||||||
import jakarta.persistence.ManyToOne;
|
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static de.ph87.home.common.ListHelpers.merge;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Getter
|
@Getter
|
||||||
@ToString
|
@ToString
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
public class Tunable {
|
public class Tunable implements ITaggable {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -46,13 +49,24 @@ public class Tunable {
|
|||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private String coldnessPropertyId;
|
private String coldnessPropertyId;
|
||||||
|
|
||||||
public Tunable(@NonNull final Area area, @NonNull final String name, @NonNull final String slug, @NonNull final String statePropertyId, @NonNull final String brightnessPropertyId, @NonNull final String coldnessPropertyId) {
|
@Getter
|
||||||
|
@NonNull
|
||||||
|
@ManyToMany
|
||||||
|
private List<Tag> tagList = new ArrayList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<?> getSearchableValues() {
|
||||||
|
return merge(List.of(area.getSlug(), area.getName(), slug, name), tagList.stream().map(Tag::getSlug).toList(), tagList.stream().map(Tag::getName).toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Tunable(@NonNull final Area area, @NonNull final String name, @NonNull final String slug, @NonNull final String statePropertyId, @NonNull final String brightnessPropertyId, @NonNull final String coldnessPropertyId, final List<Tag> tagList) {
|
||||||
this.area = area;
|
this.area = area;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.slug = slug;
|
this.slug = slug;
|
||||||
this.statePropertyId = statePropertyId;
|
this.statePropertyId = statePropertyId;
|
||||||
this.brightnessPropertyId = brightnessPropertyId;
|
this.brightnessPropertyId = brightnessPropertyId;
|
||||||
this.coldnessPropertyId = coldnessPropertyId;
|
this.coldnessPropertyId = coldnessPropertyId;
|
||||||
|
this.tagList = new ArrayList<>(tagList);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package de.ph87.home.tunable;
|
|||||||
import de.ph87.home.area.AreaDto;
|
import de.ph87.home.area.AreaDto;
|
||||||
import de.ph87.home.property.PropertyDto;
|
import de.ph87.home.property.PropertyDto;
|
||||||
import de.ph87.home.property.PropertyTypeMismatch;
|
import de.ph87.home.property.PropertyTypeMismatch;
|
||||||
|
import de.ph87.home.tag.Tag;
|
||||||
import de.ph87.home.web.IWebSocketMessage;
|
import de.ph87.home.web.IWebSocketMessage;
|
||||||
import jakarta.annotation.Nullable;
|
import jakarta.annotation.Nullable;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@ -51,6 +52,9 @@ public class TunableDto implements IWebSocketMessage {
|
|||||||
@ToString.Exclude
|
@ToString.Exclude
|
||||||
private final PropertyDto<Double> coldnessProperty;
|
private final PropertyDto<Double> coldnessProperty;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final List<String> tagList;
|
||||||
|
|
||||||
public TunableDto(@NonNull final Tunable tunable, @Nullable final PropertyDto<Boolean> stateProperty, @Nullable final PropertyDto<Double> brightnessProperty, @Nullable final PropertyDto<Double> coldnessProperty) {
|
public TunableDto(@NonNull final Tunable tunable, @Nullable final PropertyDto<Boolean> stateProperty, @Nullable final PropertyDto<Double> brightnessProperty, @Nullable final PropertyDto<Double> coldnessProperty) {
|
||||||
this.area = new AreaDto(tunable.getArea());
|
this.area = new AreaDto(tunable.getArea());
|
||||||
this.uuid = tunable.getUuid();
|
this.uuid = tunable.getUuid();
|
||||||
@ -62,6 +66,7 @@ public class TunableDto implements IWebSocketMessage {
|
|||||||
this.stateProperty = stateProperty;
|
this.stateProperty = stateProperty;
|
||||||
this.brightnessProperty = brightnessProperty;
|
this.brightnessProperty = brightnessProperty;
|
||||||
this.coldnessProperty = coldnessProperty;
|
this.coldnessProperty = coldnessProperty;
|
||||||
|
this.tagList = tunable.getTagList().stream().map(Tag::getName).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|||||||
@ -7,6 +7,8 @@ import lombok.Getter;
|
|||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import static de.ph87.home.common.crud.SearchHelper.search;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@ToString
|
@ToString
|
||||||
public class TunableFilter extends AbstractSearchFilter {
|
public class TunableFilter extends AbstractSearchFilter {
|
||||||
@ -31,7 +33,7 @@ public class TunableFilter extends AbstractSearchFilter {
|
|||||||
if (!stateFalse && Boolean.FALSE.equals(value)) {
|
if (!stateFalse && Boolean.FALSE.equals(value)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return search(dto.getName());
|
return search(search, dto.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,12 @@ import de.ph87.home.area.AreaService;
|
|||||||
import de.ph87.home.common.crud.CrudAction;
|
import de.ph87.home.common.crud.CrudAction;
|
||||||
import de.ph87.home.common.crud.EntityNotFound;
|
import de.ph87.home.common.crud.EntityNotFound;
|
||||||
import de.ph87.home.property.*;
|
import de.ph87.home.property.*;
|
||||||
|
import de.ph87.home.search.SearchableDto;
|
||||||
|
import de.ph87.home.tag.Tag;
|
||||||
|
import de.ph87.home.tag.TagReader;
|
||||||
|
import de.ph87.home.tag.TaggableDto;
|
||||||
|
import de.ph87.home.tag.taggable.ITaggableService;
|
||||||
|
import de.ph87.home.tag.taggable.TaggableFilter;
|
||||||
import jakarta.annotation.Nullable;
|
import jakarta.annotation.Nullable;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@ -14,14 +20,15 @@ import org.springframework.context.event.EventListener;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static de.ph87.home.common.crud.SearchHelper.search;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@Transactional
|
@Transactional
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class TunableService {
|
public class TunableService implements ITaggableService<TunableDto> {
|
||||||
|
|
||||||
private final PropertyService propertyService;
|
private final PropertyService propertyService;
|
||||||
|
|
||||||
@ -31,10 +38,21 @@ public class TunableService {
|
|||||||
|
|
||||||
private final AreaService areaService;
|
private final AreaService areaService;
|
||||||
|
|
||||||
|
private final TagReader tagReader;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public TunableDto create(@NonNull final String areaUuid, @NonNull final String name, @NonNull final String slug, @NonNull final String stateProperty, @NonNull final String brightnessProperty, @NonNull final String coldnessProperty) {
|
public TunableDto create(
|
||||||
|
@NonNull final String areaUuid,
|
||||||
|
@NonNull final String name,
|
||||||
|
@NonNull final String slug,
|
||||||
|
@NonNull final String stateProperty,
|
||||||
|
@NonNull final String brightnessProperty,
|
||||||
|
@NonNull final String coldnessProperty,
|
||||||
|
@NonNull final List<String> tagUuidList
|
||||||
|
) {
|
||||||
final Area area = areaService.getByUuid(areaUuid);
|
final Area area = areaService.getByUuid(areaUuid);
|
||||||
return publish(tunableRepository.save(new Tunable(area, name, slug, stateProperty, brightnessProperty, coldnessProperty)), CrudAction.UPDATED);
|
final List<Tag> tagList = tagUuidList.stream().map(tagReader::getByUuid).toList();
|
||||||
|
return publish(tunableRepository.save(new Tunable(area, name, slug, stateProperty, brightnessProperty, coldnessProperty, tagList)), CrudAction.UPDATED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -86,17 +104,13 @@ public class TunableService {
|
|||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public List<TunableDto> list(@Nullable final TunableFilter filter) throws PropertyTypeMismatch {
|
public List<TunableDto> list(@Nullable final TunableFilter filter) throws PropertyTypeMismatch {
|
||||||
final List<TunableDto> all = tunableRepository.findAll().stream().map(this::toDto).toList();
|
|
||||||
if (filter == null) {
|
if (filter == null) {
|
||||||
return all;
|
return tunableRepository.findAll().stream().map(this::toDto).toList();
|
||||||
}
|
}
|
||||||
final List<TunableDto> results = new ArrayList<>();
|
return tunableRepository.findAll().stream()
|
||||||
for (final TunableDto dto : all) {
|
.filter(tunable -> search(filter.getSearch(), tunable.getSearchableValues()))
|
||||||
if (filter.filter(dto)) {
|
.map(this::toDto)
|
||||||
results.add(dto);
|
.toList();
|
||||||
}
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventListener(PropertyDto.class)
|
@EventListener(PropertyDto.class)
|
||||||
@ -117,4 +131,23 @@ public class TunableService {
|
|||||||
return toDto(getByUuid(uuid));
|
return toDto(getByUuid(uuid));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<TaggableDto<TunableDto>> findTaggables(final @NonNull TaggableFilter filter) {
|
||||||
|
return tunableRepository.findAll().stream()
|
||||||
|
.filter(tunable -> tunable.tagListAnyMatch(filter.getTag()))
|
||||||
|
.filter(device -> search(filter.getSearch(), device.getSearchableValues()))
|
||||||
|
.map(this::toDto)
|
||||||
|
.map(TaggableDto::new)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SearchableDto<TunableDto>> findSearchables(@NonNull final String term) {
|
||||||
|
return tunableRepository.findAll().stream()
|
||||||
|
.filter(tunable -> tunable.search(term))
|
||||||
|
.map(this::toDto)
|
||||||
|
.map(SearchableDto::new)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user