Compare commits
No commits in common. "master" and "deploy---2022-10-25---15-35-40" have entirely different histories.
master
...
deploy---2
@ -4,8 +4,7 @@ spring.datasource.url=jdbc:h2:./Homeautomation;AUTO_SERVER=TRUE
|
|||||||
spring.datasource.driverClassName=org.h2.Driver
|
spring.datasource.driverClassName=org.h2.Driver
|
||||||
spring.datasource.username=sa
|
spring.datasource.username=sa
|
||||||
spring.datasource.password=password
|
spring.datasource.password=password
|
||||||
#-
|
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
|
||||||
spring.jackson.serialization.indent_output=true
|
|
||||||
#-
|
#-
|
||||||
spring.jpa.hibernate.ddl-auto=create
|
spring.jpa.hibernate.ddl-auto=create
|
||||||
#-
|
#-
|
||||||
|
|||||||
18
pom.xml
@ -9,14 +9,14 @@
|
|||||||
<version>1.0-SNAPSHOT</version>
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<maven.compiler.source>21</maven.compiler.source>
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
<maven.compiler.target>21</maven.compiler.target>
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-parent</artifactId>
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
<version>3.3.3</version>
|
<version>2.7.5</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@ -41,10 +41,6 @@
|
|||||||
<groupId>com.h2database</groupId>
|
<groupId>com.h2database</groupId>
|
||||||
<artifactId>h2</artifactId>
|
<artifactId>h2</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.postgresql</groupId>
|
|
||||||
<artifactId>postgresql</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
@ -58,7 +54,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.calimero</groupId>
|
<groupId>com.github.calimero</groupId>
|
||||||
<artifactId>calimero-core</artifactId>
|
<artifactId>calimero-core</artifactId>
|
||||||
<version>2.5.1</version>
|
<version>2.5</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.luckycatlabs</groupId>
|
<groupId>com.luckycatlabs</groupId>
|
||||||
@ -66,12 +62,6 @@
|
|||||||
<version>1.2</version>
|
<version>1.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-test</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
|||||||
@ -125,8 +125,5 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"cli": {
|
|
||||||
"analytics": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,82 +0,0 @@
|
|||||||
export class Duration {
|
|
||||||
|
|
||||||
static readonly ZERO: Duration = new Duration('0');
|
|
||||||
|
|
||||||
readonly totalSeconds: number;
|
|
||||||
|
|
||||||
readonly days: number;
|
|
||||||
|
|
||||||
readonly hours: number;
|
|
||||||
|
|
||||||
readonly minutes: number;
|
|
||||||
|
|
||||||
readonly seconds: number;
|
|
||||||
|
|
||||||
readonly code: string;
|
|
||||||
|
|
||||||
readonly zero: boolean;
|
|
||||||
|
|
||||||
private constructor(input: string) {
|
|
||||||
this.totalSeconds = Duration.parse(input);
|
|
||||||
const sign = Math.sign(this.totalSeconds);
|
|
||||||
const abs = Math.abs(this.totalSeconds);
|
|
||||||
this.days = sign * Math.floor(abs / (24 * 60 * 60));
|
|
||||||
this.hours = sign * Math.floor(abs / (60 * 60)) % 24;
|
|
||||||
this.minutes = sign * Math.floor(abs / 60) % 60;
|
|
||||||
this.seconds = sign * Math.floor(abs) % 60;
|
|
||||||
this.zero = this.totalSeconds === 0;
|
|
||||||
this.code = this.buildCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
static ofCode(code: string) {
|
|
||||||
return new Duration(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static parse(input: string) {
|
|
||||||
const regex = /(?<signs>[+-]*)(?<value>\d+(?:[.,]\d+)?)(?<unit>d|h|m|s|ms)/g;
|
|
||||||
let totalSeconds = 0;
|
|
||||||
for (const match of input.matchAll(regex)) {
|
|
||||||
if (!match?.groups) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const sign = ((match.groups["signs"].replace(/\++/g, '').length % 2) === 1) ? -1 : 1;
|
|
||||||
const value = sign * parseInt(match.groups["value"]);
|
|
||||||
switch (match.groups["unit"]) {
|
|
||||||
case 'd':
|
|
||||||
totalSeconds += value * 24 * 60 * 60;
|
|
||||||
break;
|
|
||||||
case 'h':
|
|
||||||
totalSeconds += value * 60 * 60;
|
|
||||||
break;
|
|
||||||
case 'm':
|
|
||||||
totalSeconds += value * 60;
|
|
||||||
break;
|
|
||||||
case 's':
|
|
||||||
totalSeconds += value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return totalSeconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
private buildCode() {
|
|
||||||
if (this.zero) {
|
|
||||||
return '0';
|
|
||||||
}
|
|
||||||
let code = "";
|
|
||||||
if (this.days != 0) {
|
|
||||||
code += this.days + "d";
|
|
||||||
}
|
|
||||||
if (this.hours != 0) {
|
|
||||||
code += this.hours + "h";
|
|
||||||
}
|
|
||||||
if (this.minutes != 0) {
|
|
||||||
code += this.minutes + "m";
|
|
||||||
}
|
|
||||||
if (this.seconds != 0) {
|
|
||||||
code += this.seconds + "s";
|
|
||||||
}
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,9 +1,28 @@
|
|||||||
|
import {prefix} from "../helpers";
|
||||||
|
|
||||||
export class Timestamp {
|
export class Timestamp {
|
||||||
|
|
||||||
|
public readonly WEEKDAY: string[] = ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"];
|
||||||
|
|
||||||
|
public readonly dayName;
|
||||||
|
|
||||||
|
public readonly timeString;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
readonly date: Date,
|
readonly date: Date,
|
||||||
) {
|
) {
|
||||||
// -
|
const now = new Date();
|
||||||
|
const minutes: string = prefix(this.date.getMinutes(), '0', 2);
|
||||||
|
if (date.getDate() === now.getDate()) {
|
||||||
|
this.dayName = "Heute";
|
||||||
|
this.timeString = date.getHours() + ":" + minutes;
|
||||||
|
} else if (date.getDate() === now.getDate() + 1) {
|
||||||
|
this.dayName = "Morgen";
|
||||||
|
this.timeString = date.getHours() + ":" + minutes;
|
||||||
|
} else {
|
||||||
|
this.dayName = this.WEEKDAY[date.getDay()];
|
||||||
|
this.timeString = date.getHours() + ":" + minutes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static fromDateOrNull(date: Date | null): Timestamp | null {
|
public static fromDateOrNull(date: Date | null): Timestamp | null {
|
||||||
|
|||||||
@ -1,25 +0,0 @@
|
|||||||
import {validateDateAllowNull, validateNumberNotNull, validateStringNullToEmpty} from "../validators";
|
|
||||||
|
|
||||||
export class Astro {
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
readonly zenith: number,
|
|
||||||
readonly sunrise: Date,
|
|
||||||
readonly sunset: Date,
|
|
||||||
readonly sunriseName: string,
|
|
||||||
readonly sunsetName: string,
|
|
||||||
) {
|
|
||||||
// nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromJson(json: any): Astro {
|
|
||||||
return new Astro(
|
|
||||||
validateNumberNotNull(json['zenith']),
|
|
||||||
validateDateAllowNull(json['sunrise']),
|
|
||||||
validateDateAllowNull(json['sunset']),
|
|
||||||
validateStringNullToEmpty(json['sunriseName']),
|
|
||||||
validateStringNullToEmpty(json['sunsetName']),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -25,14 +25,4 @@ export class BulkEntry {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static compareName(a: BulkEntry, b: BulkEntry) {
|
|
||||||
if (!a.property) {
|
|
||||||
return +1;
|
|
||||||
}
|
|
||||||
if (!b.property) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return a.property.title.localeCompare(b.property.title);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,11 +38,11 @@ export class BulkService implements ISearchService {
|
|||||||
this.api.subscribe("BulkDto", Bulk.fromJson, next);
|
this.api.subscribe("BulkDto", Bulk.fromJson, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
getById(id: number, next: (results: Bulk) => void): void {
|
getById(id: number, next: (results: Bulk) => void, error?: (error: any) => void): void {
|
||||||
this.api.getReturnItem("bulk/getById/" + id, Bulk.fromJson, next);
|
this.api.getReturnItem("bulk/getById/" + id, Bulk.fromJson, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
searchById(id: number, next: (results: SearchResult) => void): void {
|
searchById(id: number, next: (results: SearchResult) => void, error: (error: any) => void): void {
|
||||||
this.api.getReturnItem("bulk/searchById/" + id, SearchResult.fromJson, next);
|
this.api.getReturnItem("bulk/searchById/" + id, SearchResult.fromJson, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,21 +55,13 @@ export class BulkService implements ISearchService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
create(next: (item: Bulk) => void): void {
|
create(next: (item: Bulk) => void): void {
|
||||||
this.api.postReturnItem("bulk/create", new BulkCreate("Neu", true), Bulk.fromJson, next);
|
this.api.postReturnItem("bulk/create/", new BulkCreate("Neu", true), Bulk.fromJson, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(bulk: Bulk, next: () => void): void {
|
delete(bulk: Bulk, next: () => void): void {
|
||||||
this.api.getReturnItem("bulk/delete/" + bulk.id, _ => _, next);
|
this.api.getReturnItem("bulk/delete/" + bulk.id, _ => _, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
run(bulk: Bulk, next?: () => void): void {
|
|
||||||
this.api.getReturnItem("bulk/run/" + bulk.id, _ => _, next);
|
|
||||||
}
|
|
||||||
|
|
||||||
duplicate(bulk: Bulk, next: (item: Bulk) => void): void {
|
|
||||||
this.api.getReturnItem("bulk/duplicate/" + bulk.id, _ => _, next);
|
|
||||||
}
|
|
||||||
|
|
||||||
setEntry(entry: BulkEntry, key: string, value: any, next: (result: BulkEntry) => void = NO_OP): void {
|
setEntry(entry: BulkEntry, key: string, value: any, next: (result: BulkEntry) => void = NO_OP): void {
|
||||||
this.api.putReturnItem("BulkEntry/" + entry.id + "/set/" + key, value, next);
|
this.api.putReturnItem("BulkEntry/" + entry.id + "/set/" + key, value, next);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -62,48 +62,12 @@ export abstract class Device {
|
|||||||
return a.position - b.position;
|
return a.position - b.position;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static compareTitle(a: Device, b: Device): number {
|
|
||||||
return a.title.localeCompare(b.title);
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract updateProperty(property: Property): void;
|
abstract updateProperty(property: Property): void;
|
||||||
|
|
||||||
static filterByAreaIdAndRoomId(areaId: number | null, roomId: number | null): (_: Device) => boolean {
|
static filterByAreaIdAndRoomId(areaId: number | null, roomId: number | null): (_: Device) => boolean {
|
||||||
return d => d.areaId === areaId && d.roomId === roomId;
|
return d => d.areaId === areaId && d.roomId === roomId;
|
||||||
}
|
}
|
||||||
|
|
||||||
getSwitchClassList(): object {
|
|
||||||
if (!(this instanceof DeviceSwitch)) {
|
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
const value: number | null | undefined = (this as DeviceSwitch).stateProperty?.readChannel?.value;
|
|
||||||
return {
|
|
||||||
deviceSwitchOnBack: value === 1,
|
|
||||||
deviceSwitchOffBack: value === 0,
|
|
||||||
disabledBack: value === null || value === undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
getStateSceneClassList(): object {
|
|
||||||
if (!(this instanceof DeviceStateScene)) {
|
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
return this.getSwitchClassList();
|
|
||||||
}
|
|
||||||
|
|
||||||
getShutterClassList(): object {
|
|
||||||
if (!(this instanceof DeviceShutter)) {
|
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
const value: number | null | undefined = (this as DeviceShutter).positionProperty?.readChannel?.value;
|
|
||||||
return {
|
|
||||||
deviceShutterOpenBack: value === 0,
|
|
||||||
deviceShutterIntermediateBack: value !== null && value !== undefined && value > 0 && value < 100,
|
|
||||||
deviceShutterClosedBack: value === 100,
|
|
||||||
disabledBack: value === null || value === undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DeviceSwitch extends Device {
|
export class DeviceSwitch extends Device {
|
||||||
|
|||||||
@ -24,7 +24,7 @@ export class DeviceService {
|
|||||||
this.api.getList("device/findAll", Device.fromJson, compare, next);
|
this.api.getList("device/findAll", Device.fromJson, compare, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
set(device: Device, key: string, value: any, next?: (item: Device) => void): void {
|
set(device: Device, key: string, value: any, next: (item: Device) => void): void {
|
||||||
this.api.postReturnItem("device/set/" + device.id + "/" + key, value, Device.fromJson, next);
|
this.api.postReturnItem("device/set/" + device.id + "/" + key, value, Device.fromJson, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ export class DeviceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
create(type: string, next: (item: Device) => void): void {
|
create(type: string, next: (item: Device) => void): void {
|
||||||
this.api.postReturnItem("device/create", type, Device.fromJson, next);
|
this.api.postReturnItem("device/create/", type, Device.fromJson, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(device: Device, next: () => void): void {
|
delete(device: Device, next: () => void): void {
|
||||||
|
|||||||
@ -1,37 +1,20 @@
|
|||||||
import {undefinedOrNull, validateListOrEmpty, validateNumberNotNull, validateStringEmptyToNull, validateStringNotEmptyNotNull} from "../validators";
|
import {validateDateAllowNull, validateListOrEmpty, validateNumberAllowNull, validateNumberNotNull, validateStringNotEmptyNotNull} from "../validators";
|
||||||
import {Channel} from "../channel/Channel";
|
import {Channel} from "../channel/Channel";
|
||||||
import {SearchResult} from "../SearchResult";
|
import {SearchResult} from "../SearchResult";
|
||||||
import {environment} from "../../../environments/environment";
|
|
||||||
|
|
||||||
export enum PropertyType {
|
|
||||||
BOOLEAN = 'BOOLEAN',
|
|
||||||
SHUTTER = 'SHUTTER',
|
|
||||||
BRIGHTNESS_PERCENT = 'BRIGHTNESS_PERCENT',
|
|
||||||
COLOR_TEMPERATURE = 'COLOR_TEMPERATURE',
|
|
||||||
LUX = 'LUX',
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Property {
|
export class Property {
|
||||||
|
|
||||||
readonly hrefId: string | null;
|
|
||||||
|
|
||||||
readonly hrefSlug: string | null;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public id: number,
|
public id: number,
|
||||||
public type: PropertyType,
|
public type: string,
|
||||||
public title: string,
|
public title: string,
|
||||||
public slug: string | null,
|
public value: number | null,
|
||||||
|
public timestamp: Date | null,
|
||||||
public readChannel: Channel | null,
|
public readChannel: Channel | null,
|
||||||
public writeChannel: Channel | null,
|
public writeChannel: Channel | null,
|
||||||
public usages: SearchResult[],
|
public usages: SearchResult[],
|
||||||
) {
|
) {
|
||||||
if (writeChannel || true) {
|
// nothing
|
||||||
this.hrefId = environment.restBase + '/property/write/id/' + id + '/'
|
|
||||||
if (slug) {
|
|
||||||
this.hrefSlug = environment.restBase + '/property/write/slug/' + slug + '/';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJsonAllowNull(json: any): Property | null {
|
static fromJsonAllowNull(json: any): Property | null {
|
||||||
@ -44,9 +27,10 @@ export class Property {
|
|||||||
static fromJson(json: any): Property {
|
static fromJson(json: any): Property {
|
||||||
return new Property(
|
return new Property(
|
||||||
validateNumberNotNull(json['id']),
|
validateNumberNotNull(json['id']),
|
||||||
validateStringNotEmptyNotNull(json['type']) as PropertyType,
|
validateStringNotEmptyNotNull(json['type']),
|
||||||
validateStringNotEmptyNotNull(json['title']),
|
validateStringNotEmptyNotNull(json['title']),
|
||||||
validateStringEmptyToNull(json['slug']),
|
validateNumberAllowNull(json['value']),
|
||||||
|
validateDateAllowNull(json['timestamp']),
|
||||||
Channel.fromJsonAllowNull(json['readChannel']),
|
Channel.fromJsonAllowNull(json['readChannel']),
|
||||||
Channel.fromJsonAllowNull(json['writeChannel']),
|
Channel.fromJsonAllowNull(json['writeChannel']),
|
||||||
validateListOrEmpty(json['usages'], SearchResult.fromJson),
|
validateListOrEmpty(json['usages'], SearchResult.fromJson),
|
||||||
@ -71,32 +55,4 @@ export class Property {
|
|||||||
}
|
}
|
||||||
return a.title.localeCompare(b.title);
|
return a.title.localeCompare(b.title);
|
||||||
}
|
}
|
||||||
|
|
||||||
getStateClassList() {
|
|
||||||
const value = this.readChannel?.value;
|
|
||||||
switch (this.type) {
|
|
||||||
case PropertyType.BOOLEAN:
|
|
||||||
return {
|
|
||||||
propertyStateBooleanUnknown: undefinedOrNull(value),
|
|
||||||
propertyStateBooleanTrue: value > 0,
|
|
||||||
propertyStateBooleanFalse: value === 0,
|
|
||||||
};
|
|
||||||
case PropertyType.SHUTTER:
|
|
||||||
return {
|
|
||||||
propertyStatePercentUnknown: undefinedOrNull(value),
|
|
||||||
propertyStatePercentActive: value === 0,
|
|
||||||
propertyStatePercentBetween: value > 0 && value < 100,
|
|
||||||
propertyStatePercentInactive: value === 100,
|
|
||||||
};
|
|
||||||
case PropertyType.BRIGHTNESS_PERCENT:
|
|
||||||
return {
|
|
||||||
propertyStatePercentUnknown: undefinedOrNull(value),
|
|
||||||
propertyStatePercentActive: value === 100,
|
|
||||||
propertyStatePercentBetween: value > 0 && value < 100,
|
|
||||||
propertyStatePercentInactive: value === 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,7 +38,7 @@ export class PropertyService implements ISearchService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
create(next: (item: Property) => void): void {
|
create(next: (item: Property) => void): void {
|
||||||
this.api.getReturnItem("property/create", Property.fromJson, next);
|
this.api.getReturnItem("property/create/", Property.fromJson, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(property: Property, next: Next<void>): void {
|
delete(property: Property, next: Next<void>): void {
|
||||||
|
|||||||
@ -5,16 +5,13 @@ export class Schedule {
|
|||||||
|
|
||||||
readonly next?: ScheduleEntry;
|
readonly next?: ScheduleEntry;
|
||||||
|
|
||||||
readonly last?: ScheduleEntry;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly id: number,
|
readonly id: number,
|
||||||
readonly enabled: boolean,
|
readonly enabled: boolean,
|
||||||
readonly title: string,
|
readonly title: string,
|
||||||
readonly entries: ScheduleEntry[],
|
readonly entries: ScheduleEntry[],
|
||||||
) {
|
) {
|
||||||
this.next = entries.filter(e => e.nextFuzzyTimestamp).sort((a, b) => a.nextFuzzyTimestamp.date.getTime() - b.nextFuzzyTimestamp.date.getTime())[0];
|
this.next = entries.filter(e => e.nextFuzzyTimestamp)[0];
|
||||||
this.last = entries.filter(e => e.lastFuzzyTimestamp).sort((a, b) => b.lastFuzzyTimestamp.date.getTime() - a.lastFuzzyTimestamp.date.getTime())[0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json: any): Schedule {
|
static fromJson(json: any): Schedule {
|
||||||
|
|||||||
@ -1,19 +1,14 @@
|
|||||||
import {validateBooleanNotNull, validateDateAllowNull, validateNumberAllowNull, validateNumberNotNull, validateStringNotEmptyNotNull} from "../../validators";
|
import {validateBooleanNotNull, validateDateAllowNull, validateNumberNotNull, validateStringNotEmptyNotNull} from "../../validators";
|
||||||
import {Timestamp} from "../../Timestamp";
|
import {Timestamp} from "../../Timestamp";
|
||||||
import {Property} from "../../property/Property";
|
import {Property} from "../../property/Property";
|
||||||
import {Bulk} from "../../bulk/Bulk";
|
import {Bulk} from "../../bulk/Bulk";
|
||||||
import {formatNumber} from "@angular/common";
|
|
||||||
|
function getDaySeconds(date: Date): number {
|
||||||
|
return date.getHours() * 3600 + date.getMinutes() * 60 + date.getSeconds();
|
||||||
|
}
|
||||||
|
|
||||||
export class ScheduleEntry {
|
export class ScheduleEntry {
|
||||||
|
|
||||||
readonly todo: string;
|
|
||||||
|
|
||||||
readonly dayMinute: number;
|
|
||||||
|
|
||||||
readonly anyWeekday: boolean;
|
|
||||||
|
|
||||||
readonly executable: boolean;
|
|
||||||
|
|
||||||
private constructor(
|
private constructor(
|
||||||
readonly id: number,
|
readonly id: number,
|
||||||
readonly position: number,
|
readonly position: number,
|
||||||
@ -31,33 +26,14 @@ export class ScheduleEntry {
|
|||||||
readonly minute: number,
|
readonly minute: number,
|
||||||
readonly second: number,
|
readonly second: number,
|
||||||
readonly fuzzySeconds: number,
|
readonly fuzzySeconds: number,
|
||||||
readonly skip: number,
|
|
||||||
readonly lastClearTimestamp: Timestamp | null,
|
readonly lastClearTimestamp: Timestamp | null,
|
||||||
readonly nextClearTimestamp: Timestamp | null,
|
readonly nextClearTimestamp: Timestamp | null,
|
||||||
readonly nextFuzzyTimestamp: Timestamp | null,
|
readonly nextFuzzyTimestamp: Timestamp | null,
|
||||||
readonly lastFuzzyTimestamp: Timestamp | null,
|
|
||||||
readonly property: Property | null,
|
readonly property: Property | null,
|
||||||
readonly value: number,
|
readonly value: number,
|
||||||
readonly bulk: Bulk | null,
|
readonly bulk: Bulk | null,
|
||||||
) {
|
) {
|
||||||
this.dayMinute = hour * 60 + minute;
|
// nothing
|
||||||
this.anyWeekday = this.monday || this.tuesday || this.wednesday || this.thursday || this.friday || this.saturday || this.sunday;
|
|
||||||
this.executable = this.enabled && this.anyWeekday && !!this.bulk;
|
|
||||||
this.todo = this.getToDo();
|
|
||||||
}
|
|
||||||
|
|
||||||
getToDo(): string {
|
|
||||||
let result: string[] = [];
|
|
||||||
if (!this.enabled) {
|
|
||||||
result.push("inaktiv");
|
|
||||||
}
|
|
||||||
if (!this.bulk) {
|
|
||||||
result.push("keine Aktion");
|
|
||||||
}
|
|
||||||
if (!this.anyWeekday) {
|
|
||||||
result.push("kein Wochentag");
|
|
||||||
}
|
|
||||||
return result.join(", ");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json: any): ScheduleEntry {
|
static fromJson(json: any): ScheduleEntry {
|
||||||
@ -78,11 +54,9 @@ export class ScheduleEntry {
|
|||||||
validateNumberNotNull(json['minute']),
|
validateNumberNotNull(json['minute']),
|
||||||
validateNumberNotNull(json['second']),
|
validateNumberNotNull(json['second']),
|
||||||
validateNumberNotNull(json['fuzzySeconds']),
|
validateNumberNotNull(json['fuzzySeconds']),
|
||||||
validateNumberAllowNull(json['skip']) || 0,
|
|
||||||
Timestamp.fromDateOrNull(validateDateAllowNull(json['lastClearTimestamp'])),
|
Timestamp.fromDateOrNull(validateDateAllowNull(json['lastClearTimestamp'])),
|
||||||
Timestamp.fromDateOrNull(validateDateAllowNull(json['nextClearTimestamp'])),
|
Timestamp.fromDateOrNull(validateDateAllowNull(json['nextClearTimestamp'])),
|
||||||
Timestamp.fromDateOrNull(validateDateAllowNull(json['nextFuzzyTimestamp'])),
|
Timestamp.fromDateOrNull(validateDateAllowNull(json['nextFuzzyTimestamp'])),
|
||||||
Timestamp.fromDateOrNull(validateDateAllowNull(json['lastFuzzyTimestamp'])),
|
|
||||||
Property.fromJsonAllowNull(json['property']),
|
Property.fromJsonAllowNull(json['property']),
|
||||||
validateNumberNotNull(json['value']),
|
validateNumberNotNull(json['value']),
|
||||||
Bulk.fromJsonOrNull(json['bulk']),
|
Bulk.fromJsonOrNull(json['bulk']),
|
||||||
@ -97,8 +71,4 @@ export class ScheduleEntry {
|
|||||||
return a.position - b.position;
|
return a.position - b.position;
|
||||||
}
|
}
|
||||||
|
|
||||||
get time(): string {
|
|
||||||
return formatNumber(this.hour, 'de-DE', '2.0-0') + ':' + formatNumber(this.minute, 'de-DE', '2.0-0');
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +0,0 @@
|
|||||||
export class Zenith {
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
readonly title: string,
|
|
||||||
readonly value: number,
|
|
||||||
readonly sunrise: boolean,
|
|
||||||
readonly sunset: boolean,
|
|
||||||
) {
|
|
||||||
// -
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -28,7 +28,7 @@ export class ScheduleService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
create(next: Next<Schedule> = NO_OP): void {
|
create(next: Next<Schedule> = NO_OP): void {
|
||||||
this.api.getReturnItem("schedule/create", Schedule.fromJson, next);
|
this.api.getReturnItem("schedule/create/", Schedule.fromJson, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
set(schedule: Schedule, key: string, value: any, next: Next<Schedule> = NO_OP): void {
|
set(schedule: Schedule, key: string, value: any, next: Next<Schedule> = NO_OP): void {
|
||||||
|
|||||||
@ -1,65 +0,0 @@
|
|||||||
import {Inject, Injectable, LOCALE_ID} from '@angular/core';
|
|
||||||
import {DatePipe} from "@angular/common";
|
|
||||||
import {Timestamp} from "./Timestamp";
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class TimeService {
|
|
||||||
|
|
||||||
readonly datePipe: DatePipe = new DatePipe(this.locale);
|
|
||||||
|
|
||||||
private _now: Date = new Date();
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@Inject(LOCALE_ID) readonly locale: string,
|
|
||||||
) {
|
|
||||||
// -
|
|
||||||
}
|
|
||||||
|
|
||||||
get now(): Date {
|
|
||||||
return this._now;
|
|
||||||
}
|
|
||||||
|
|
||||||
relativeDate(timestamp: Timestamp | undefined) {
|
|
||||||
const date: Date = timestamp?.date;
|
|
||||||
if (date === undefined || date === null) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
const relativeName = this.relativeCalendarDaysName(date);
|
|
||||||
return relativeName + " " + this.datePipe.transform(date, 'HH:mm');
|
|
||||||
}
|
|
||||||
|
|
||||||
relativeCalendarDaysName(date: Date): string {
|
|
||||||
const prefix = this.relativeCalendarDaysPrefix(date);
|
|
||||||
const weekday = date.toLocaleDateString(this.locale, {weekday: 'long'});
|
|
||||||
return prefix + ", " + weekday;
|
|
||||||
}
|
|
||||||
|
|
||||||
private relativeCalendarDaysPrefix(date: Date): string {
|
|
||||||
const days = this.calendarDays(date);
|
|
||||||
if (days < -2) {
|
|
||||||
return "Vor " + -days + " Tagen";
|
|
||||||
} else if (days === -2) {
|
|
||||||
return "Vorgestern";
|
|
||||||
} else if (days === -1) {
|
|
||||||
return "Gestern";
|
|
||||||
} else if (days === 1) {
|
|
||||||
return "Morgen";
|
|
||||||
} else if (days === 2) {
|
|
||||||
return "Übermorgen";
|
|
||||||
} else if (days > 2) {
|
|
||||||
return "In " + days + " Tagen";
|
|
||||||
}
|
|
||||||
return "Heute";
|
|
||||||
}
|
|
||||||
|
|
||||||
private calendarDays(date: Date) {
|
|
||||||
const DAY_MS = 1000 * 60 * 60 * 24;
|
|
||||||
const aMidnight = new Date(this._now.getFullYear(), this._now.getMonth(), this._now.getDate());
|
|
||||||
const bMidnight = new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
||||||
const milliseconds = bMidnight.getTime() - aMidnight.getTime();
|
|
||||||
return Math.floor(milliseconds / DAY_MS);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -87,7 +87,3 @@ export function validateMap<T>(json: any, valueFromJson: (json: any) => T): Map<
|
|||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function undefinedOrNull(value: any) {
|
|
||||||
return value === undefined || value === null;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -25,7 +25,7 @@ const routes: Routes = [
|
|||||||
{path: 'Bulk', component: BulkEditorComponent},
|
{path: 'Bulk', component: BulkEditorComponent},
|
||||||
{path: 'BulkList', component: BulkListComponent},
|
{path: 'BulkList', component: BulkListComponent},
|
||||||
|
|
||||||
{path: '**', redirectTo: '/DeviceList;type=DeviceSwitch'},
|
{path: '**', redirectTo: '/ScheduleList'},
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|||||||
@ -1,27 +1,22 @@
|
|||||||
<div class="menubar">
|
<div class="menubar">
|
||||||
|
|
||||||
<div class="item" [routerLink]="['/DeviceList', {'type': 'DeviceSwitch'}]" [class.itemActive]="isRouteActive('/DeviceList', 'DeviceSwitch')">
|
|
||||||
Geräte
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item" [routerLink]="['/DeviceList', {'type': 'DeviceShutter'}]" [class.itemActive]="isRouteActive('/DeviceList', 'DeviceShutter')">
|
|
||||||
Rollläden
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item" routerLink="/BulkList" routerLinkActive="itemActive">
|
|
||||||
Stapel
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item" routerLink="/ScheduleList" routerLinkActive="itemActive">
|
<div class="item" routerLink="/ScheduleList" routerLinkActive="itemActive">
|
||||||
Zeitpläne
|
Zeitpläne
|
||||||
</div>
|
</div>
|
||||||
|
<div class="item" routerLink="/DeviceList" routerLinkActive="itemActive">
|
||||||
|
Geräte
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item" routerLink="/BulkList" routerLinkActive="itemActive">
|
||||||
|
Massenausführungen
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="item itemSecondary" routerLink="/PropertyList" routerLinkActive="itemActive">
|
<div class="item itemSecondary" routerLink="/PropertyList" routerLinkActive="itemActive">
|
||||||
P
|
Eigenschaften
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="item itemSecondary" routerLink="/ChannelList" routerLinkActive="itemActive">
|
<div class="item itemSecondary" routerLink="/ChannelList" routerLinkActive="itemActive">
|
||||||
C
|
Kanäle
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,17 +1,15 @@
|
|||||||
@import "../config";
|
|
||||||
|
|
||||||
.menubar {
|
.menubar {
|
||||||
border-bottom: @border solid black;
|
border-bottom: 1px solid black;
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
float: left;
|
float: left;
|
||||||
padding: @padding;
|
padding: 10px;
|
||||||
border-right: @border solid black;
|
border-right: 1px solid black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.itemSecondary {
|
.itemSecondary {
|
||||||
float: right;
|
float: right;
|
||||||
border-left: @border solid black;
|
border-left: 1px solid black;
|
||||||
border-right: none;
|
border-right: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import {Component} from '@angular/core';
|
import {Component} from '@angular/core';
|
||||||
import {Params, Router, UrlSegment} from "@angular/router";
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
@ -9,27 +8,10 @@ import {Params, Router, UrlSegment} from "@angular/router";
|
|||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
title = 'angular';
|
title = 'angular';
|
||||||
|
|
||||||
private url: UrlSegment[];
|
|
||||||
|
|
||||||
private params: Params;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly router: Router,
|
// nothing
|
||||||
) {
|
) {
|
||||||
// nothing
|
// nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
isRouteActive(base: string, type: string): boolean {
|
|
||||||
const parts = this.router.url.split(';');
|
|
||||||
if (parts[0] === base) {
|
|
||||||
for (let i = 1; i < parts.length; i++) {
|
|
||||||
const param = parts[i].split('=');
|
|
||||||
if (param[0] === 'type' && param[1] === type) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {LOCALE_ID, NgModule} from '@angular/core';
|
import {NgModule} from '@angular/core';
|
||||||
import {BrowserModule} from '@angular/platform-browser';
|
import {BrowserModule} from '@angular/platform-browser';
|
||||||
|
|
||||||
import {AppRoutingModule} from './app-routing.module';
|
import {AppRoutingModule} from './app-routing.module';
|
||||||
@ -17,16 +17,6 @@ import {PropertyListComponent} from './pages/property/list/property-list.compone
|
|||||||
import {ChannelListComponent} from './pages/channel/list/channel-list.component';
|
import {ChannelListComponent} from './pages/channel/list/channel-list.component';
|
||||||
import {BulkListComponent} from './pages/bulk/list/bulk-list.component';
|
import {BulkListComponent} from './pages/bulk/list/bulk-list.component';
|
||||||
import {BulkEditorComponent} from './pages/bulk/editor/bulk-editor.component';
|
import {BulkEditorComponent} from './pages/bulk/editor/bulk-editor.component';
|
||||||
import {LeftPadDirective} from './pipes/left-pad.directive';
|
|
||||||
import {EntryValueComponent} from './shared/entry-value/entry-value.component';
|
|
||||||
|
|
||||||
import {registerLocaleData} from '@angular/common';
|
|
||||||
import localeDe from '@angular/common/locales/de';
|
|
||||||
import localeDeExtra from '@angular/common/locales/extra/de';
|
|
||||||
import {BoolComponent} from './shared/bool/bool.component';
|
|
||||||
import {DurationComponent} from './shared/duration/duration.component';
|
|
||||||
|
|
||||||
registerLocaleData(localeDe, 'de-DE', localeDeExtra);
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@ -43,10 +33,6 @@ registerLocaleData(localeDe, 'de-DE', localeDeExtra);
|
|||||||
DeviceListComponent,
|
DeviceListComponent,
|
||||||
BulkListComponent,
|
BulkListComponent,
|
||||||
BulkEditorComponent,
|
BulkEditorComponent,
|
||||||
LeftPadDirective,
|
|
||||||
EntryValueComponent,
|
|
||||||
BoolComponent,
|
|
||||||
DurationComponent,
|
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
@ -55,9 +41,7 @@ registerLocaleData(localeDe, 'de-DE', localeDeExtra);
|
|||||||
FormsModule,
|
FormsModule,
|
||||||
FontAwesomeModule,
|
FontAwesomeModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [],
|
||||||
{provide: LOCALE_ID, useValue: 'de-DE'}
|
|
||||||
],
|
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
})
|
})
|
||||||
export class AppModule {
|
export class AppModule {
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
<ng-container *ngIf="bulk">
|
<ng-container *ngIf="bulk">
|
||||||
|
|
||||||
<h1>
|
<h1>
|
||||||
<app-text [initial]="bulk.name" (valueChange)="set(bulk, 'name', $event)"></app-text>
|
<app-text [initial]="bulk.name" (valueChange)="set(bulk, 'name', $event)"></app-text>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Eigenschaft</th>
|
<th>Eigenschaft</th>
|
||||||
@ -11,7 +9,7 @@
|
|||||||
<th> </th>
|
<th> </th>
|
||||||
</tr>
|
</tr>
|
||||||
<ng-container *ngIf="bulk">
|
<ng-container *ngIf="bulk">
|
||||||
<tr *ngFor="let entry of bulk.entries.sort(BulkEntry.compareName)">
|
<tr *ngFor="let entry of bulk.entries">
|
||||||
<td>
|
<td>
|
||||||
<app-search [searchService]="propertyService" [allowEmpty]="false" [initial]="entry.property?.id" (valueChange)="setEntry(entry, 'property', $event)"></app-search>
|
<app-search [searchService]="propertyService" [allowEmpty]="false" [initial]="entry.property?.id" (valueChange)="setEntry(entry, 'property', $event)"></app-search>
|
||||||
</td>
|
</td>
|
||||||
@ -22,7 +20,12 @@
|
|||||||
</td>
|
</td>
|
||||||
<td *ngSwitchCase="'SHUTTER'" [class.true]="entry.value === 0" [class.false]="entry.value === 100" [class.tristate]="0 < entry.value && entry.value < 100">
|
<td *ngSwitchCase="'SHUTTER'" [class.true]="entry.value === 0" [class.false]="entry.value === 100" [class.tristate]="0 < entry.value && entry.value < 100">
|
||||||
<select [ngModel]="entry.value" (ngModelChange)="setEntry(entry, 'value', $event)">
|
<select [ngModel]="entry.value" (ngModelChange)="setEntry(entry, 'value', $event)">
|
||||||
<option *ngFor="let _ of [].constructor(21); let value = index" [ngValue]="value * 5">{{value * 5}}%</option>
|
<option [ngValue]="0">100% Offen</option>
|
||||||
|
<option [ngValue]="35"> 50%</option>
|
||||||
|
<option [ngValue]="55"> 75%</option>
|
||||||
|
<option [ngValue]="75"> 90% Sonnenschutz</option>
|
||||||
|
<option [ngValue]="85">100% Schlitze</option>
|
||||||
|
<option [ngValue]="100">100% Geschlossen</option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
<td *ngSwitchCase="'BRIGHTNESS_PERCENT'" [class.true]="entry.value" [class.false]="!entry.value" [class.tristate]="0 < entry.value && entry.value < 100">
|
<td *ngSwitchCase="'BRIGHTNESS_PERCENT'" [class.true]="entry.value" [class.false]="!entry.value" [class.tristate]="0 < entry.value && entry.value < 100">
|
||||||
@ -53,6 +56,7 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<button (click)="create()">+ Hinzufügen</button>
|
<p>
|
||||||
|
<button (click)="create()">+ Hinzufügen</button>
|
||||||
|
</p>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@ -1,3 +0,0 @@
|
|||||||
table {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
@ -13,8 +13,6 @@ import {faTimesCircle} from "@fortawesome/free-regular-svg-icons";
|
|||||||
})
|
})
|
||||||
export class BulkEditorComponent implements OnInit {
|
export class BulkEditorComponent implements OnInit {
|
||||||
|
|
||||||
protected readonly BulkEntry = BulkEntry;
|
|
||||||
|
|
||||||
readonly faTimes = faTimesCircle;
|
readonly faTimes = faTimesCircle;
|
||||||
|
|
||||||
bulk!: Bulk;
|
bulk!: Bulk;
|
||||||
|
|||||||
@ -1,32 +1,27 @@
|
|||||||
<div class="tiles">
|
<table>
|
||||||
<div class="tile" *ngFor="let bulk of bulks.sort(Bulk.compareName); trackBy: Bulk.trackBy">
|
<tr>
|
||||||
<div class="tileHead disabledBack" [class.enabledBack]="bulk.enabled">
|
<th> </th>
|
||||||
|
<th>Bezeichnung</th>
|
||||||
|
<th> </th>
|
||||||
|
</tr>
|
||||||
|
<tr *ngFor="let bulk of bulks; trackBy: Bulk.trackBy">
|
||||||
|
|
||||||
<div (click)="set(bulk, 'enabled', !bulk.enabled)">
|
<td class="boolean" (click)="set(bulk, 'enabled', !bulk.enabled)" [class.true]="bulk.enabled" [class.false]="!bulk.enabled">
|
||||||
<fa-icon *ngIf="bulk.enabled" [icon]="faCheckCircle"></fa-icon>
|
<fa-icon *ngIf="bulk.enabled" [icon]="faCheckCircle"></fa-icon>
|
||||||
<fa-icon *ngIf="!bulk.enabled" [icon]="faCircle"></fa-icon>
|
<fa-icon *ngIf="!bulk.enabled" [icon]="faCircle"></fa-icon>
|
||||||
</div>
|
</td>
|
||||||
|
|
||||||
<div class="flexGrow" [routerLink]="['/Bulk', {id: bulk.id}]">
|
<td [routerLink]="['/Bulk', {id: bulk.id}]">
|
||||||
{{ bulk.name }}
|
{{bulk.name}}
|
||||||
</div>
|
</td>
|
||||||
|
|
||||||
<div class="tileHeadRight" (click)="run(bulk)">
|
<td class="delete" (click)="delete(bulk)">
|
||||||
<fa-icon title="Ausführen" [icon]="faPlay"></fa-icon>
|
<fa-icon title="Löschen" [icon]="faTimes"></fa-icon>
|
||||||
</div>
|
</td>
|
||||||
|
|
||||||
<div class="tileHeadRight" (click)="duplicate(bulk)">
|
</tr>
|
||||||
<fa-icon title="Duplizieren" [icon]="faCopy"></fa-icon>
|
</table>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tileHeadDelete" (click)="delete(bulk)">
|
<p>
|
||||||
<fa-icon title="Löschen" [icon]="faTimes"></fa-icon>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="config">
|
|
||||||
<button (click)="create()">+ Hinzufügen</button>
|
<button (click)="create()">+ Hinzufügen</button>
|
||||||
</div>
|
</p>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import {BulkService} from "../../../api/bulk/BulkService";
|
import {BulkService} from "../../../api/bulk/BulkService";
|
||||||
import {Bulk} from "../../../api/bulk/Bulk";
|
import {Bulk} from "../../../api/bulk/Bulk";
|
||||||
import {faCheckCircle, faCircle, faCopy, faPlayCircle, faTimesCircle} from "@fortawesome/free-regular-svg-icons";
|
import {faCheckCircle, faCircle, faTimesCircle} from "@fortawesome/free-regular-svg-icons";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-bulk-list',
|
selector: 'app-bulk-list',
|
||||||
@ -16,8 +16,6 @@ export class BulkListComponent implements OnInit {
|
|||||||
|
|
||||||
readonly faTimes = faTimesCircle;
|
readonly faTimes = faTimesCircle;
|
||||||
|
|
||||||
readonly faPlay = faPlayCircle;
|
|
||||||
|
|
||||||
readonly Bulk = Bulk;
|
readonly Bulk = Bulk;
|
||||||
|
|
||||||
bulks: Bulk[] = [];
|
bulks: Bulk[] = [];
|
||||||
@ -54,21 +52,10 @@ export class BulkListComponent implements OnInit {
|
|||||||
this.bulkService.create(bulk => this.addOrReplace(bulk));
|
this.bulkService.create(bulk => this.addOrReplace(bulk));
|
||||||
}
|
}
|
||||||
|
|
||||||
run(bulk: Bulk): void {
|
|
||||||
if (confirm("Zeitplan \"" + bulk.name + "\" wirklich ausführen?")) {
|
|
||||||
this.bulkService.run(bulk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
duplicate(bulk: Bulk): void {
|
|
||||||
this.bulkService.duplicate(bulk, bulk => this.addOrReplace(bulk));
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(bulk: Bulk): void {
|
delete(bulk: Bulk): void {
|
||||||
if (confirm("Zeitplan \"" + bulk.name + "\" wirklich löschen?")) {
|
if (confirm("Zeitplan \"" + bulk.name + "\" wirklich löschen?")) {
|
||||||
this.bulkService.delete(bulk, () => this.remove(bulk));
|
this.bulkService.delete(bulk, () => this.remove(bulk));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected readonly faCopy = faCopy;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,24 +1,24 @@
|
|||||||
<div class="tiles">
|
<table>
|
||||||
<div class="tile" *ngFor="let channel of channels">
|
<tr>
|
||||||
<div>
|
<th>Titel</th>
|
||||||
{{ channel.title }}
|
<th>Typ</th>
|
||||||
</div>
|
<th colspan="3">Adresse</th>
|
||||||
<div class="left">
|
<th>DPT</th>
|
||||||
{{ channel.type }}
|
<th colspan="2">Wert</th>
|
||||||
</div>
|
</tr>
|
||||||
|
<tr *ngFor="let channel of channels">
|
||||||
|
<td>{{channel.title}}</td>
|
||||||
|
<td>{{channel.type}}</td>
|
||||||
<ng-container *ngIf="channel.type === 'KnxGroup'">
|
<ng-container *ngIf="channel.type === 'KnxGroup'">
|
||||||
<div class="left">
|
<td class="first number">{{asKnxGroup(channel).addresMain}} / </td>
|
||||||
{{ asKnxGroup(channel).addresMain }} / {{ asKnxGroup(channel).addresMid }} / {{ asKnxGroup(channel).addresSub }}
|
<td class="middle number">{{asKnxGroup(channel).addresMid}} / </td>
|
||||||
</div>
|
<td class="last number">{{asKnxGroup(channel).addresSub}}</td>
|
||||||
<div class="right">
|
<td class="number">{{asKnxGroup(channel).dpt}}</td>
|
||||||
DPT {{ asKnxGroup(channel).dpt }}
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<div class="timestamp">
|
<ng-container *ngIf="channel.type === 'Logic'">
|
||||||
{{ channel.timestamp | date:'yyyy-MM-dd HH:mm:ss' || '-' }}
|
<td colspan="4">{{asLogic(channel).operator}}</td>
|
||||||
</div>
|
</ng-container>
|
||||||
<div class="value">
|
<td class="number">{{channel.value}}</td>
|
||||||
{{ channel.value || '-' }}
|
<td>{{channel.timestamp | date:'yyyy-MM-dd HH:mm:ss'}}</td>
|
||||||
</div>
|
</tr>
|
||||||
</div>
|
</table>
|
||||||
</div>
|
|
||||||
|
|||||||
@ -1,23 +1,3 @@
|
|||||||
@import "../../../../config";
|
table {
|
||||||
|
width: 100%;
|
||||||
div {
|
|
||||||
padding: @padding;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timestamp {
|
|
||||||
clear: left;
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.value {
|
|
||||||
clear: right;
|
|
||||||
float: right;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +0,0 @@
|
|||||||
table {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
@ -53,7 +53,7 @@ export class DeviceEditorComponent implements OnInit {
|
|||||||
|
|
||||||
delete(): void {
|
delete(): void {
|
||||||
if (confirm(this.getDeviceTypeTitle() + " \"" + this.device.title + "\" wirklich löschen?")) {
|
if (confirm(this.getDeviceTypeTitle() + " \"" + this.device.title + "\" wirklich löschen?")) {
|
||||||
this.deviceService.delete(this.device, () => this.router.navigate(["/DeviceList", {type: this.device.type}]));
|
this.deviceService.delete(this.device, () => this.router.navigate(["/DeviceList"]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,85 +1,66 @@
|
|||||||
<div class="tiles">
|
<div class="config">
|
||||||
|
<select [(ngModel)]="createType">
|
||||||
|
<option ngValue="DeviceSwitch">Schalter</option>
|
||||||
|
<option ngValue="DeviceShutter">Rollladen</option>
|
||||||
|
</select>
|
||||||
|
<button (click)="create()">+ Hinzufügen</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ng-container *ngFor="let device of devices; trackBy: Device.trackBy">
|
<ng-container *ngFor="let device of devices.sort(Device.comparePosition); trackBy: Device.trackBy">
|
||||||
|
<ng-container [ngSwitch]="device.type">
|
||||||
|
|
||||||
<div class="tile" *ngIf="device.type == 'DeviceSwitch'" [ngClass]="device.getSwitchClassList()">
|
<div class="device" *ngSwitchCase="'DeviceSwitch'" [ngClass]="getSwitchClassList(device)">
|
||||||
<div class="tileHead">
|
<div class="title">
|
||||||
<div class="flexGrow">
|
{{device.title}}
|
||||||
<app-text [initial]="device.title" (valueChange)="set(device, 'title', $event)"></app-text>
|
|
||||||
</div>
|
|
||||||
<div class="tileHeadRight" [routerLink]="['/Device', {id: device.id}]">
|
|
||||||
<fa-icon [icon]="faEdit"></fa-icon>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="tileBody">
|
<div class="edit" [routerLink]="['/Device', {id: device.id}]">
|
||||||
<img alt="An" class="control" src="assets/switch-on.svg" (click)="deviceService.setSwitchState(device, true)"/>
|
<fa-icon [icon]="faEdit"></fa-icon>
|
||||||
<img alt="Aus" class="control" src="assets/switch-off.svg" (click)="deviceService.setSwitchState(device, false)"/>
|
</div>
|
||||||
|
<div class="controls">
|
||||||
|
<img alt="An" class="control" src="../../../../assets/switch-on.svg" (click)="deviceService.setSwitchState(device, true)"/>
|
||||||
|
<img alt="Aus" class="control" src="../../../../assets/switch-off.svg" (click)="deviceService.setSwitchState(device, false)"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tile" *ngIf="device.type === 'DeviceStateScene'" [ngClass]="device.getStateSceneClassList()">
|
<div class="device" *ngSwitchCase="'DeviceStateScene'" [ngClass]="getStateSceneClassList(device)">
|
||||||
<div class="tileHead">
|
<div class="title">
|
||||||
<div class="flexGrow">
|
{{device.title}}
|
||||||
<app-text [initial]="device.title" (valueChange)="set(device, 'title', $event)"></app-text>
|
|
||||||
</div>
|
|
||||||
<div class="tileHeadRight" [routerLink]="['/Device', {id: device.id}]">
|
|
||||||
<fa-icon [icon]="faEdit"></fa-icon>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="tileBody">
|
<div class="edit" [routerLink]="['/Device', {id: device.id}]">
|
||||||
<div class="control" *ngFor="let scene of getStateScenes(device)" (click)="deviceService.setStateScene(device, scene.number)">
|
<fa-icon [icon]="faEdit"></fa-icon>
|
||||||
{{ scene.title }}
|
</div>
|
||||||
|
<div class="controls">
|
||||||
|
<div *ngFor="let scene of getStateScenes(device)" class="control button" (click)="deviceService.setStateScene(device, scene.number)">
|
||||||
|
<span class="center">{{scene.title}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tile" *ngIf="device.type === 'DeviceShutter'" [ngClass]="device.getShutterClassList()">
|
<div class="device" *ngSwitchCase="'DeviceShutter'" [ngClass]="getShutterClassList(device)">
|
||||||
<div class="tileHead">
|
<div class="title">
|
||||||
<div class="flexGrow">
|
{{device.title}}
|
||||||
<app-text [initial]="device.title" (valueChange)="set(device, 'title', $event)"></app-text>
|
|
||||||
</div>
|
|
||||||
<div class="tileHeadRight" [routerLink]="['/Device', {id: device.id}]">
|
|
||||||
<fa-icon [icon]="faEdit"></fa-icon>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="tileBody">
|
<div class="edit" [routerLink]="['/Device', {id: device.id}]">
|
||||||
<div class="control controlShutter" (click)="deviceService.setShutterPosition(device, 0)">
|
<fa-icon [icon]="faEdit"></fa-icon>
|
||||||
Auf
|
</div>
|
||||||
|
<div class="controls">
|
||||||
|
<div class="control button" (click)="deviceService.setShutterPosition(device, 0)">
|
||||||
|
<span class="center">Auf</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="control controlShutter" (click)="deviceService.setShutterPosition(device, 20)">
|
<div class="control button" (click)="deviceService.setShutterPosition(device, 40)">
|
||||||
20%
|
<span class="center">50%</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="control controlShutter" (click)="deviceService.setShutterPosition(device, 30)">
|
<div class="control button" (click)="deviceService.setShutterPosition(device, 75)">
|
||||||
30%
|
<span class="center">90%</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="control controlShutter" (click)="deviceService.setShutterPosition(device, 40)">
|
<div class="control button" (click)="deviceService.setShutterPosition(device, 85)">
|
||||||
40%
|
<span class="center">Schlitze</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="control controlShutter" (click)="deviceService.setShutterPosition(device, 50)">
|
<div class="control button" (click)="deviceService.setShutterPosition(device, 100)">
|
||||||
50%
|
<span class="center">Zu</span>
|
||||||
</div>
|
|
||||||
<div class="control controlShutter" (click)="deviceService.setShutterPosition(device, 60)">
|
|
||||||
60%
|
|
||||||
</div>
|
|
||||||
<div class="control controlShutter" (click)="deviceService.setShutterPosition(device, 70)">
|
|
||||||
70%
|
|
||||||
</div>
|
|
||||||
<div class="control controlShutter" (click)="deviceService.setShutterPosition(device, 80)">
|
|
||||||
80%
|
|
||||||
</div>
|
|
||||||
<div class="control controlShutter" (click)="deviceService.setShutterPosition(device, 90)">
|
|
||||||
90%
|
|
||||||
</div>
|
|
||||||
<div class="control controlShutter" (click)="deviceService.setShutterPosition(device, 100)">
|
|
||||||
Zu
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="config">
|
|
||||||
<button (click)="create()">+ Hinzufügen</button>
|
|
||||||
</div>
|
|
||||||
|
|||||||
@ -1,17 +1,71 @@
|
|||||||
@import "../../../../config";
|
.device {
|
||||||
|
padding: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
border-radius: 10px;
|
||||||
|
|
||||||
.control {
|
@media (min-width: 1000px) {
|
||||||
float: left;
|
float: left;
|
||||||
width: 4em;
|
width: 400px;
|
||||||
aspect-ratio: 1;
|
margin-right: 5px;
|
||||||
margin: @margin;
|
}
|
||||||
border-radius: 25%;
|
|
||||||
|
.title {
|
||||||
|
float: left;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
clear: both;
|
||||||
|
|
||||||
|
.control {
|
||||||
|
position: relative;
|
||||||
|
float: left;
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
padding: 5px;
|
||||||
|
margin: 5px;
|
||||||
|
border-radius: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
background-color: lightblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control:hover {
|
||||||
|
background-color: lightskyblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.controlShutter {
|
.switchOn {
|
||||||
width: 17.3%;
|
background-color: palegreen;
|
||||||
padding-top: 1.1em;
|
}
|
||||||
text-align: center;
|
|
||||||
margin: calc(@margin / 2);
|
.switchOff {
|
||||||
background-color: lightsteelblue;
|
background-color: indianred;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switchUnknown {
|
||||||
|
background-color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shutterOpen {
|
||||||
|
background-color: palegreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shutterBetween {
|
||||||
|
background-color: yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shutterClosed {
|
||||||
|
background-color: indianred;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shutterUnknown {
|
||||||
|
background-color: gray;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import {DeviceService} from "../../../api/device/device.service";
|
import {DeviceService} from "../../../api/device/device.service";
|
||||||
import {PropertyService} from "../../../api/property/property.service";
|
import {PropertyService} from "../../../api/property/property.service";
|
||||||
import {Device, DeviceStateScene} from "../../../api/device/Device";
|
import {Device, DeviceShutter, DeviceStateScene, DeviceSwitch} from "../../../api/device/Device";
|
||||||
import {faEdit} from '@fortawesome/free-regular-svg-icons';
|
import {faEdit} from '@fortawesome/free-regular-svg-icons';
|
||||||
import {Scene} from "../../../api/scene/Scene";
|
import {Scene} from "../../../api/scene/Scene";
|
||||||
import {SceneService} from "../../../api/scene/scene.service";
|
import {SceneService} from "../../../api/scene/scene.service";
|
||||||
import {ActivatedRoute} from "@angular/router";
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-device-list',
|
selector: 'app-device-list',
|
||||||
@ -20,34 +19,30 @@ export class DeviceListComponent implements OnInit {
|
|||||||
|
|
||||||
devices: Device[] = [];
|
devices: Device[] = [];
|
||||||
|
|
||||||
typeFilter: string;
|
|
||||||
|
|
||||||
scenes: Scene[] = [];
|
scenes: Scene[] = [];
|
||||||
|
|
||||||
|
createType: string = "DeviceSwitch";
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly deviceService: DeviceService,
|
readonly deviceService: DeviceService,
|
||||||
readonly sceneService: SceneService,
|
readonly sceneService: SceneService,
|
||||||
readonly propertyService: PropertyService,
|
readonly propertyService: PropertyService,
|
||||||
readonly activatedRoute: ActivatedRoute,
|
|
||||||
) {
|
) {
|
||||||
// nothing
|
// nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.activatedRoute.params.subscribe(params => {
|
this.propertyService.subscribe(update => this.devices.forEach(d => d.updateProperty(update.payload)));
|
||||||
this.typeFilter = params['type'];
|
|
||||||
this.propertyService.subscribe(update => this.devices.forEach(d => d.updateProperty(update.payload)));
|
|
||||||
|
|
||||||
this.deviceService.subscribe(update => this.updateDevice(update.payload, update.existing));
|
this.deviceService.subscribe(update => this.updateDevice(update.payload, update.existing));
|
||||||
this.deviceService.findAll(devices => this.devices = devices.filter(d => !this.typeFilter || this.typeFilter == d.type).sort(Device.compareTitle));
|
this.deviceService.findAll(devices => this.devices = devices);
|
||||||
|
|
||||||
this.sceneService.subscribe(update => this.updateScene(update.payload, update.existing));
|
this.sceneService.subscribe(update => this.updateScene(update.payload, update.existing));
|
||||||
this.sceneService.findAll(scenes => this.scenes = scenes);
|
this.sceneService.findAll(scenes => this.scenes = scenes);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
create(): void {
|
create(): void {
|
||||||
this.deviceService.create(this.typeFilter, device => this.updateDevice(device, true));
|
this.deviceService.create(this.createType, device => this.updateDevice(device, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateDevice(device: Device, existing: boolean): void {
|
private updateDevice(device: Device, existing: boolean): void {
|
||||||
@ -78,13 +73,32 @@ export class DeviceListComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getStateScenes(device: Device): Scene[] {
|
getSwitchClassList(device: Device): object {
|
||||||
const casted: DeviceStateScene = device as DeviceStateScene;
|
const value: number | null | undefined = (device as DeviceSwitch).stateProperty?.value;
|
||||||
return casted.sceneNumbers.map(sceneNumber => this.scenes.find(scene => scene.number === sceneNumber)).filter(scene => scene !== undefined).map(s => s as Scene);
|
return {
|
||||||
|
switchOn: value === 1,
|
||||||
|
switchOff: value === 0,
|
||||||
|
switchUnknown: value === null || value === undefined,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
set(device: Device, key: string, value: any): void {
|
getStateSceneClassList(device: Device): object {
|
||||||
this.deviceService.set(device, key, value);
|
return this.getSwitchClassList(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
getShutterClassList(device: Device): object {
|
||||||
|
const value: number | null | undefined = (device as DeviceShutter).positionProperty?.value;
|
||||||
|
return {
|
||||||
|
shutterOpen: value === 0,
|
||||||
|
shutterBetween: value !== null && value !== undefined && value > 0 && value < 100,
|
||||||
|
shutterClosed: value === 100,
|
||||||
|
shutterUnknown: value === null || value === undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getStateScenes(d: Device): Scene[] {
|
||||||
|
const device: DeviceStateScene = d as DeviceStateScene;
|
||||||
|
return device.sceneNumbers.map(sceneNumber => this.scenes.find(scene => scene.number === sceneNumber)).filter(scene => scene !== undefined).map(s => s as Scene);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,17 +2,30 @@
|
|||||||
<td class="empty">-</td>
|
<td class="empty">-</td>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<div class="tiles">
|
<div class="config">
|
||||||
|
<button (click)="create()">+ Hinzufügen</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div *ngFor="let list of listLists()">
|
<table>
|
||||||
<div class="tile" *ngFor="let property of list">
|
<tr>
|
||||||
|
<th>Bezeichnung</th>
|
||||||
|
<th>Typ</th>
|
||||||
|
<th>Wert</th>
|
||||||
|
<th>Zeitstempel</th>
|
||||||
|
<th>Lesekanal</th>
|
||||||
|
<th>Schreibkanal</th>
|
||||||
|
<th>
|
||||||
|
<fa-icon title="Löschen" [icon]="faTimes"></fa-icon>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<ng-container *ngFor="let property of properties.sort(Property.compareTypeThenTitle)">
|
||||||
|
<tr>
|
||||||
|
|
||||||
<div class="tileHead" [ngClass]="property.getStateClassList()">
|
<td>
|
||||||
<app-text [initial]="property.title" (valueChange)="edit(property, 'title', $event)"></app-text>
|
<app-text [initial]="property.title" (valueChange)="edit(property, 'title', $event)"></app-text>
|
||||||
</div>
|
</td>
|
||||||
|
|
||||||
<div class="tileBody">
|
|
||||||
|
|
||||||
|
<td>
|
||||||
<select [(ngModel)]="property.type" (ngModelChange)="edit(property, 'type', property.type)">
|
<select [(ngModel)]="property.type" (ngModelChange)="edit(property, 'type', property.type)">
|
||||||
<option value="BOOLEAN">Schalter</option>
|
<option value="BOOLEAN">Schalter</option>
|
||||||
<option value="SHUTTER">Rollladen</option>
|
<option value="SHUTTER">Rollladen</option>
|
||||||
@ -21,22 +34,44 @@
|
|||||||
<option value="LUX">Helligkeit [lux]</option>
|
<option value="LUX">Helligkeit [lux]</option>
|
||||||
<option value="SCENE">Szene</option>
|
<option value="SCENE">Szene</option>
|
||||||
</select>
|
</select>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<ng-container *ngIf="property.value !== null else empty">
|
||||||
|
<td *ngIf="property.type === 'BOOLEAN'" class="boolean" [class.true]="property.value" [class.false]="!property.value" (click)="edit(property, 'value', property.value > 0 ? 0 : 1)">
|
||||||
|
{{property.value ? "An" : "Aus"}}
|
||||||
|
</td>
|
||||||
|
<td *ngIf="property.type === 'SHUTTER'" class="number" [class.true]="property.value === 0" [class.false]="property.value === 100" [class.tristate]="0 < property.value && property.value < 100">
|
||||||
|
{{property.value}} %
|
||||||
|
</td>
|
||||||
|
<td *ngIf="property.type === 'BRIGHTNESS_PERCENT'" class="number">
|
||||||
|
{{property.value}} %
|
||||||
|
</td>
|
||||||
|
<td *ngIf="property.type === 'COLOR_TEMPERATURE'" class="number">
|
||||||
|
{{property.value}} K
|
||||||
|
</td>
|
||||||
|
<td *ngIf="property.type === 'LUX'" class="number">
|
||||||
|
{{property.value | number:'0.0-0'}} lux
|
||||||
|
</td>
|
||||||
|
<td *ngIf="property.type === 'SCENE'">
|
||||||
|
{{findScene(property)?.title || "Unbekannt: " + property.value}}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<td *ngIf="property.timestamp !== null else empty">
|
||||||
|
{{property.timestamp | date:'yyyy-MM-dd HH:mm:ss'}}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="full">
|
||||||
<app-search [searchService]="channelService" [initial]="property.readChannel?.id" (valueChange)="set(property, 'readChannel', $event)"></app-search>
|
<app-search [searchService]="channelService" [initial]="property.readChannel?.id" (valueChange)="set(property, 'readChannel', $event)"></app-search>
|
||||||
|
</td>
|
||||||
|
<td class="full">
|
||||||
<app-search [searchService]="channelService" [initial]="property.writeChannel?.id" (valueChange)="set(property, 'writeChannel', $event)"></app-search>
|
<app-search [searchService]="channelService" [initial]="property.writeChannel?.id" (valueChange)="set(property, 'writeChannel', $event)"></app-search>
|
||||||
|
</td>
|
||||||
|
|
||||||
<div class="delete">
|
<td class="delete">
|
||||||
<fa-icon title="Löschen" *ngIf="property.usages.length === 0" [icon]="faTimes" (click)="delete(property)"></fa-icon>
|
<fa-icon title="Löschen" *ngIf="property.usages.length === 0" [icon]="faTimes" (click)="delete(property)"></fa-icon>
|
||||||
</div>
|
</td>
|
||||||
|
|
||||||
</div>
|
</tr>
|
||||||
|
</ng-container>
|
||||||
</div>
|
</table>
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="config">
|
|
||||||
<button (click)="create()">+ Hinzufügen</button>
|
|
||||||
</div>
|
|
||||||
|
|||||||
@ -1,29 +1,3 @@
|
|||||||
@import "../../../../config";
|
table {
|
||||||
|
width: 100%;
|
||||||
.propertyStateBooleanUnknown {
|
|
||||||
background-color: @COLOR_UNKNOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
.propertyStateBooleanTrue {
|
|
||||||
background-color: @COLOR_ACTIVE;
|
|
||||||
}
|
|
||||||
|
|
||||||
.propertyStateBooleanFalse {
|
|
||||||
background-color: @COLOR_INACTIVE;
|
|
||||||
}
|
|
||||||
|
|
||||||
.propertyStatePercentUnknown {
|
|
||||||
background-color: @COLOR_UNKNOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
.propertyStatePercentActive {
|
|
||||||
background-color: @COLOR_ACTIVE;
|
|
||||||
}
|
|
||||||
|
|
||||||
.propertyStatePercentBetween {
|
|
||||||
background-color: @COLOR_BETWEEN;
|
|
||||||
}
|
|
||||||
|
|
||||||
.propertyStatePercentInactive {
|
|
||||||
background-color: @COLOR_INACTIVE;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import {Property, PropertyType} from "../../../api/property/Property";
|
import {Property} from "../../../api/property/Property";
|
||||||
import {PropertyService} from "../../../api/property/property.service";
|
import {PropertyService} from "../../../api/property/property.service";
|
||||||
import {Scene} from "../../../api/scene/Scene";
|
import {Scene} from "../../../api/scene/Scene";
|
||||||
import {SceneService} from "../../../api/scene/scene.service";
|
import {SceneService} from "../../../api/scene/scene.service";
|
||||||
import {ChannelService} from "../../../api/channel/channel.service";
|
import {ChannelService} from "../../../api/channel/channel.service";
|
||||||
import {faTimesCircle} from "@fortawesome/free-regular-svg-icons";
|
import {faTimesCircle} from "@fortawesome/free-regular-svg-icons";
|
||||||
import {environment} from 'src/environments/environment';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-property-list',
|
selector: 'app-property-list',
|
||||||
@ -14,16 +13,12 @@ import {environment} from 'src/environments/environment';
|
|||||||
})
|
})
|
||||||
export class PropertyListComponent implements OnInit {
|
export class PropertyListComponent implements OnInit {
|
||||||
|
|
||||||
readonly environment = environment;
|
|
||||||
|
|
||||||
readonly faTimes = faTimesCircle;
|
readonly faTimes = faTimesCircle;
|
||||||
|
|
||||||
protected booleans: Property[] = [];
|
|
||||||
|
|
||||||
protected shutters: Property[] = [];
|
|
||||||
|
|
||||||
Property = Property;
|
Property = Property;
|
||||||
|
|
||||||
|
properties: Property[] = [];
|
||||||
|
|
||||||
scenes: Scene[] = [];
|
scenes: Scene[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -35,10 +30,7 @@ export class PropertyListComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.propertyService.findAll(properties => {
|
this.propertyService.findAll(properties => this.properties = properties, Property.compareTypeThenTitle);
|
||||||
this.booleans = properties.filter(p => p.type === PropertyType.BOOLEAN).sort(Property.compareTypeThenTitle);
|
|
||||||
this.shutters = properties.filter(p => p.type === PropertyType.SHUTTER).sort(Property.compareTypeThenTitle);
|
|
||||||
}, Property.compareTypeThenTitle);
|
|
||||||
this.propertyService.subscribe(update => this.updateProperty(update.payload, update.existing));
|
this.propertyService.subscribe(update => this.updateProperty(update.payload, update.existing));
|
||||||
|
|
||||||
this.sceneService.findAll(scenes => this.scenes = scenes, Scene.compareNumber);
|
this.sceneService.findAll(scenes => this.scenes = scenes, Scene.compareNumber);
|
||||||
@ -54,23 +46,15 @@ export class PropertyListComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private updateProperty(property: Property, existing: boolean): void {
|
private updateProperty(property: Property, existing: boolean): void {
|
||||||
if (property.type === PropertyType.BOOLEAN) {
|
const index: number = this.properties.findIndex(p => p.id === property.id);
|
||||||
this.updateProperty2(this.booleans, property, existing);
|
|
||||||
} else if (property.type === PropertyType.SHUTTER) {
|
|
||||||
this.updateProperty2(this.shutters, property, existing);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateProperty2(properties: Property[], property: Property, existing: boolean) {
|
|
||||||
const index: number = properties.findIndex(p => p.id === property.id);
|
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
if (existing) {
|
if (existing) {
|
||||||
properties[index] = property;
|
this.properties[index] = property;
|
||||||
} else {
|
} else {
|
||||||
properties.slice(index, 1);
|
this.properties.slice(index, 1);
|
||||||
}
|
}
|
||||||
} else if (existing) {
|
} else if (existing) {
|
||||||
properties.push(property);
|
this.properties.push(property);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,7 +72,7 @@ export class PropertyListComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
findScene(property: Property): Scene | undefined {
|
findScene(property: Property): Scene | undefined {
|
||||||
return this.scenes.find(s => s.id === property.readChannel?.value);
|
return this.scenes.find(s => s.id === property.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
set(property: Property, key: string, value: any): void {
|
set(property: Property, key: string, value: any): void {
|
||||||
@ -101,22 +85,8 @@ export class PropertyListComponent implements OnInit {
|
|||||||
|
|
||||||
delete(property: Property): void {
|
delete(property: Property): void {
|
||||||
if (confirm(`Eigenschaft "${property.title}" wirklich löschen?`)) {
|
if (confirm(`Eigenschaft "${property.title}" wirklich löschen?`)) {
|
||||||
this.propertyService.delete(property, () => {
|
this.propertyService.delete(property, () => this.properties.splice(this.properties.findIndex(p => p.id === property.id), 1));
|
||||||
this.delete2(this.booleans, property);
|
|
||||||
this.delete2(this.shutters, property);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private delete2(properties: Property[], property: Property) {
|
|
||||||
const index = properties.findIndex(p => p.id === property.id);
|
|
||||||
if (index >= 0) {
|
|
||||||
properties.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
listLists(): Property[][] {
|
|
||||||
return [this.shutters, this.booleans];
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,126 +1,197 @@
|
|||||||
<ng-container *ngIf="schedule">
|
<ng-container *ngIf="schedule">
|
||||||
|
|
||||||
<div id="title">
|
<ng-template #boolean let-entry="entry" let-value="value" let-key="key">
|
||||||
<app-text [initial]="schedule.title" (valueChange)="scheduleService.set(schedule, 'title', $event)"></app-text>
|
<td class="boolean" (click)="set(entry, key, !value)" [class.true]="value" [class.false]="!value">
|
||||||
</div>
|
<fa-icon *ngIf="value" [icon]="faCheckCircle"></fa-icon>
|
||||||
|
<fa-icon *ngIf="!value" [icon]="faCircle"></fa-icon>
|
||||||
|
</td>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
<div class="tiles">
|
<table>
|
||||||
|
<tr class="header">
|
||||||
|
<ng-container *ngTemplateOutlet="boolean;context:{schedule: schedule, value: schedule.enabled, key:'enabled'}"></ng-container>
|
||||||
|
<td colspan="24">
|
||||||
|
<app-text [initial]="schedule.title" (valueChange)="set(null, 'title', $event)"></app-text>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr [class.disabled]="!schedule.enabled">
|
||||||
|
<th> </th>
|
||||||
|
<th>Mo</th>
|
||||||
|
<th>Di</th>
|
||||||
|
<th>Mi</th>
|
||||||
|
<th>Do</th>
|
||||||
|
<th>Fr</th>
|
||||||
|
<th>Sa</th>
|
||||||
|
<th>So</th>
|
||||||
|
<th>Typ</th>
|
||||||
|
<th>Sonnenstand</th>
|
||||||
|
<th colspan="3">Uhrzeit</th>
|
||||||
|
<th>Unschärfe</th>
|
||||||
|
<th colspan="6">Nächste Ausführung</th>
|
||||||
|
<th colspan="2">Eingeschaft setzen</th>
|
||||||
|
<th>Massenausführung</th>
|
||||||
|
<th class="noBorderFirst"> </th>
|
||||||
|
<th class="noBorderMiddle"> </th>
|
||||||
|
<th class="noBorderLast"> </th>
|
||||||
|
</tr>
|
||||||
|
<tr *ngFor="let entry of schedule.entries; trackBy: ScheduleEntry.trackBy" [class.disabled]="entry.nextClearTimestamp === null">
|
||||||
|
<ng-container *ngTemplateOutlet="boolean;context:{entry: entry, value: entry.enabled, key:'enabled'}"></ng-container>
|
||||||
|
<ng-container *ngTemplateOutlet="boolean;context:{entry: entry, value: entry.monday, key:'monday'}"></ng-container>
|
||||||
|
<ng-container *ngTemplateOutlet="boolean;context:{entry: entry, value: entry.tuesday, key:'tuesday'}"></ng-container>
|
||||||
|
<ng-container *ngTemplateOutlet="boolean;context:{entry: entry, value: entry.wednesday, key:'wednesday'}"></ng-container>
|
||||||
|
<ng-container *ngTemplateOutlet="boolean;context:{entry: entry, value: entry.thursday, key:'thursday'}"></ng-container>
|
||||||
|
<ng-container *ngTemplateOutlet="boolean;context:{entry: entry, value: entry.friday, key:'friday'}"></ng-container>
|
||||||
|
<ng-container *ngTemplateOutlet="boolean;context:{entry: entry, value: entry.saturday, key:'saturday'}"></ng-container>
|
||||||
|
<ng-container *ngTemplateOutlet="boolean;context:{entry: entry, value: entry.sunday, key:'sunday'}"></ng-container>
|
||||||
|
|
||||||
<div class="tile" *ngFor="let entry of schedule.entries; trackBy: ScheduleEntry.trackBy">
|
<td>
|
||||||
|
<select [ngModel]="entry.type" (ngModelChange)="set(entry, 'type', entry.type)">
|
||||||
|
<option value="TIME">Uhrzeit</option>
|
||||||
|
<option value="SUNRISE">Sonnenaufgang</option>
|
||||||
|
<option value="SUNSET">Sonnenuntergang</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
|
||||||
<div class="tileHead disabledBack" [class.enabledBack]="entry.executable" [class.skipBack]="entry.skip">
|
<ng-container *ngIf="entry.type === 'SUNRISE' || entry.type === 'SUNSET'">
|
||||||
<div class="enabled" (click)="set(entry, 'enabled', !entry.enabled)">
|
<td>
|
||||||
<fa-icon *ngIf="entry.enabled" [icon]="faCheckCircle"></fa-icon>
|
<select [ngModel]="entry.zenith" (ngModelChange)="set(entry, 'zenith', entry.zenith)">
|
||||||
<fa-icon *ngIf="!entry.enabled" [icon]="faCircle"></fa-icon>
|
<option value="87">
|
||||||
</div>
|
[ 87°]
|
||||||
<div class="flexGrow">
|
<ng-container *ngIf="entry.type === 'SUNRISE'">Nach Sonnenaufgang</ng-container>
|
||||||
<select [ngModel]="entry.bulk?.id" (ngModelChange)="entryService.set(entry, 'bulk', $event)">
|
<ng-container *ngIf="entry.type === 'SUNSET'">Vor Sonnenuntergang</ng-container>
|
||||||
<option [ngValue]="null">-</option>
|
</option>
|
||||||
<option [ngValue]="bulk.id" *ngFor="let bulk of bulks.sort(Bulk.compareName)">{{ bulk.name }}</option>
|
<option value="90.8333">
|
||||||
|
[ 90°]
|
||||||
|
<ng-container *ngIf="entry.type === 'SUNRISE'">Sonnenaufgang</ng-container>
|
||||||
|
<ng-container *ngIf="entry.type === 'SUNSET'">Sonnenuntergang</ng-container>
|
||||||
|
</option>
|
||||||
|
<option value="93">[ 93°]</option>
|
||||||
|
<option value="96">[ 96°] Bürgerliche Dämmerung</option>
|
||||||
|
<option value="99">[ 99°]</option>
|
||||||
|
<option value="102">[102°] Nautische Dämmerung</option>
|
||||||
|
<option value="105">[105°]</option>
|
||||||
|
<option value="108">[108°] Astronomische Dämmerung</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</td>
|
||||||
<div class="tileHeadDelete">
|
</ng-container>
|
||||||
<fa-icon [icon]="faTimesCircle" (click)="delete(entry)"></fa-icon>
|
<td *ngIf="entry.type !== 'SUNRISE' && entry.type !== 'SUNSET'" class="empty"></td>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tileBodyFlex">
|
<ng-container *ngIf="entry.type === 'TIME'">
|
||||||
<div class="weekdays">
|
<td class="first">
|
||||||
<div>
|
<select [ngModel]="entry.hour" (ngModelChange)="set(entry, 'hour', entry.hour)">
|
||||||
<app-bool label="Mo" [value]="entry.monday" (onChange)="entryService.set(entry, 'monday', $event)"></app-bool>
|
<option *ngFor="let _ of [].constructor(24); let value = index" [ngValue]="value">{{value}}</option>
|
||||||
</div>
|
</select>
|
||||||
<div>
|
</td>
|
||||||
<app-bool label="Di" [value]="entry.tuesday" (onChange)="entryService.set(entry, 'tuesday', $event)"></app-bool>
|
<td class="middle">:</td>
|
||||||
</div>
|
<td class="last">
|
||||||
<div>
|
<select [ngModel]="entry.minute" (ngModelChange)="set(entry, 'minute', entry.minute)">
|
||||||
<app-bool label="Mi" [value]="entry.wednesday" (onChange)="entryService.set(entry, 'wednesday', $event)"></app-bool>
|
<option *ngFor="let _ of [].constructor(60); let value = index" [ngValue]="value">{{value | number:'2.0'}}</option>
|
||||||
</div>
|
</select>
|
||||||
<div>
|
</td>
|
||||||
<app-bool label="Do" [value]="entry.thursday" (onChange)="entryService.set(entry, 'thursday', $event)"></app-bool>
|
</ng-container>
|
||||||
</div>
|
<td *ngIf="entry.type !== 'TIME'" colspan="3" class="empty"></td>
|
||||||
<div>
|
|
||||||
<app-bool label="Fr" [value]="entry.friday" (onChange)="entryService.set(entry, 'friday', $event)"></app-bool>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<app-bool label="Sa" [value]="entry.saturday" (onChange)="entryService.set(entry, 'saturday', $event)"></app-bool>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<app-bool label="So" [value]="entry.sunday" (onChange)="entryService.set(entry, 'sunday', $event)"></app-bool>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modes">
|
|
||||||
<div class="_inner_">
|
|
||||||
<div>
|
|
||||||
<app-bool icon="faClock" [value]="entry.type === 'TIME'" (onChange)="entryService.set(entry, 'type', 'TIME')"></app-bool>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<app-bool icon="faArrowAltCircleUp" [value]="entry.type === 'SUNRISE'" (onChange)="entryService.set(entry, 'type', 'SUNRISE')"></app-bool>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<app-bool icon="faArrowAltCircleDown" [value]="entry.type === 'SUNSET'" (onChange)="entryService.set(entry, 'type', 'SUNSET')"></app-bool>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tileBodyFlex" *ngIf="entry.type === 'TIME'">
|
<td>
|
||||||
<div class="flexGrow time">
|
<select [ngModel]="entry.fuzzySeconds" (ngModelChange)="set(entry, 'fuzzySeconds', entry.fuzzySeconds)">
|
||||||
<button class="buttonPlus" (click)="dayMinuteAdd(entry, +60)">+</button>
|
<option [ngValue]="0">Keine</option>
|
||||||
<button class="buttonMinus" (click)="dayMinuteAdd(entry, -60)">-</button>
|
<option [ngValue]="60">1 Minute</option>
|
||||||
<input type="time" [ngModel]="entry.time" (ngModelChange)="timeFromString(entry, $event)">
|
<option [ngValue]="300">5 Minuten</option>
|
||||||
<button class="buttonPlus" (click)="dayMinuteAdd(entry, +1)">+</button>
|
<option [ngValue]="600">10 Minuten</option>
|
||||||
<button class="buttonMinus" (click)="dayMinuteAdd(entry, -1)">-</button>
|
<option [ngValue]="1800">30 Minuten</option>
|
||||||
</div>
|
<option [ngValue]="3600">1 Stunde</option>
|
||||||
</div>
|
<option [ngValue]="7200">2 Stunden</option>
|
||||||
|
<option [ngValue]="10800">3 Stunden</option>
|
||||||
|
<option [ngValue]="21600">6 Stunden</option>
|
||||||
|
<option [ngValue]="43200">12 Stunden</option>
|
||||||
|
<option [ngValue]="86400">1 Tag</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
|
||||||
<div class="tileBodyFlex" *ngIf="entry.type === 'SUNRISE' || entry.type === 'SUNSET'">
|
<ng-container *ngIf="entry.nextClearTimestamp">
|
||||||
<div class="flexGrow sun">
|
<td class="number first" [class.empty]="entry.fuzzySeconds > 0">{{entry.nextClearTimestamp.dayName}}</td>
|
||||||
<div *ngFor="let zenith of getZenithEntries(entry.type)">
|
<td class="number middle" [class.empty]="entry.fuzzySeconds > 0">: </td>
|
||||||
<app-bool [label]="zenith.title" [value]="entry.zenith === zenith.value" (onChange)="entryService.set(entry, 'zenith', zenith.value)"></app-bool>
|
<td class="number last" [class.empty]="entry.fuzzySeconds > 0">{{entry.nextClearTimestamp.timeString}}</td>
|
||||||
</div>
|
</ng-container>
|
||||||
<div>
|
<ng-container *ngIf="!entry.nextClearTimestamp">
|
||||||
<input type="number" min="45" max="120" [ngModel]="entry.zenith" (ngModelChange)="entryService.set(entry, 'zenith', $event || 0)">
|
<td class="empty first"></td>
|
||||||
</div>
|
<td class="empty middle"></td>
|
||||||
</div>
|
<td class="empty last"></td>
|
||||||
</div>
|
</ng-container>
|
||||||
|
|
||||||
<div class="tileBodyFlex">
|
<ng-container *ngIf="entry.nextFuzzyTimestamp && entry.fuzzySeconds > 0">
|
||||||
<div class="flexHalf">
|
<td class="number first">{{entry.nextFuzzyTimestamp.dayName}}</td>
|
||||||
<div class="flexIcon">
|
<td class="number middle">: </td>
|
||||||
<img class="icon" src="assets/dice.svg" alt="+/-" title="Zufallsabweichung +/-">
|
<td class="number last">{{entry.nextFuzzyTimestamp.timeString}}</td>
|
||||||
</div>
|
</ng-container>
|
||||||
<div class="flexIconInput">
|
<ng-container *ngIf="!entry.nextFuzzyTimestamp || entry.fuzzySeconds <= 0">
|
||||||
<app-duration [duration]="Duration.ofCode(entry.fuzzySeconds + 's')" [inputClass]="entry.fuzzySeconds ? 'fuzzyBack' : ''" [min]="Duration.ofCode('')" (onChange)="entryService.set(entry, 'fuzzySeconds', $event.totalSeconds)"></app-duration>
|
<td class="empty first"></td>
|
||||||
</div>
|
<td class="empty middle"></td>
|
||||||
</div>
|
<td class="empty last"></td>
|
||||||
<div class="flexHalf">
|
</ng-container>
|
||||||
<div class="flexIcon">
|
|
||||||
<img class="icon" src="assets/skip.svg" alt="Überspringen">
|
|
||||||
</div>
|
|
||||||
<div class="flexIconInput flexInputLast">
|
|
||||||
<input type="number" min="0" [class.skipBack]="entry.skip" [ngModel]="entry.skip" (ngModelChange)="entryService.set(entry, 'skip', $event || 0)">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tileBodyFlex">
|
<td>
|
||||||
<div class="flexGrow timestamp" [class.skipFont]="entry.skip" *ngIf="entry.executable">
|
<app-search [searchService]="propertyService" [initial]="entry.property?.id" (valueChange)="set(entry, 'property', $event)"></app-search>
|
||||||
{{ timeService.relativeDate(entry.nextFuzzyTimestamp) }}
|
</td>
|
||||||
<span [class.fuzzyFont]="entry.fuzzySeconds" *ngIf="entry.fuzzySeconds">
|
<ng-container [ngSwitch]="entry.property?.type">
|
||||||
(eig: {{ entry.nextClearTimestamp.date | date:'HH:mm' }})
|
<td *ngSwitchCase="'BOOLEAN'" [class.true]="entry.value" [class.false]="!entry.value" (click)="set(entry, 'value', entry.value > 0 ? 0 : 1)">
|
||||||
</span>
|
{{entry.value ? "An" : "Aus"}}
|
||||||
</div>
|
</td>
|
||||||
<div class="flexGrow inactive" *ngIf="entry.todo">
|
<td *ngSwitchCase="'SHUTTER'" [class.true]="entry.value === 0" [class.false]="entry.value === 100" [class.tristate]="0 < entry.value && entry.value < 100">
|
||||||
{{ entry.todo }}
|
<select [ngModel]="entry.value" (ngModelChange)="set(entry, 'value', entry.value)">
|
||||||
</div>
|
<option [ngValue]="0">100% Offen</option>
|
||||||
</div>
|
<option [ngValue]="35"> 50%</option>
|
||||||
|
<option [ngValue]="55"> 75%</option>
|
||||||
|
<option [ngValue]="75"> 90% Sonnenschutz</option>
|
||||||
|
<option [ngValue]="85">100% Schlitze</option>
|
||||||
|
<option [ngValue]="100">100% Geschlossen</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
<td *ngSwitchCase="'BRIGHTNESS_PERCENT'" [class.true]="entry.value" [class.false]="!entry.value" [class.tristate]="0 < entry.value && entry.value < 100">
|
||||||
|
<select [ngModel]="entry.value" (ngModelChange)="set(entry, 'value', entry.value)">
|
||||||
|
<option *ngFor="let _ of [].constructor(21); let value = index" [ngValue]="value * 5">{{value * 5}}%</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
<td *ngSwitchCase="'COLOR_TEMPERATURE'" [class.true]="entry.value" [class.false]="!entry.value" [class.tristate]="0 < entry.value && entry.value < 100">
|
||||||
|
<select [ngModel]="entry.value" (ngModelChange)="set(entry, 'value', entry.value)">
|
||||||
|
<option *ngFor="let _ of [].constructor(21); let value = index" [ngValue]="value * 5">{{value * 5}}%</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
<td *ngSwitchCase="'LUX'" [class.true]="entry.value" [class.false]="!entry.value" [class.tristate]="0 < entry.value && entry.value < 100">
|
||||||
|
<select [ngModel]="entry.value" (ngModelChange)="set(entry, 'value', entry.value)">
|
||||||
|
<option *ngFor="let _ of [].constructor(21); let value = index" [ngValue]="value * 5">{{value * 5}}%</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
<td *ngSwitchCase="'SCENE'">
|
||||||
|
<select [ngModel]="entry.value" (ngModelChange)="set(entry, 'value', entry.value)">
|
||||||
|
<option *ngFor="let scene of scenes" [ngValue]="scene.number">#{{scene.number | number:'2.0-0'}} {{scene.title}}</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
<td *ngSwitchDefault class="empty">
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
</div>
|
<td>
|
||||||
|
<app-search [searchService]="bulkService" [initial]="entry.bulk?.id" (valueChange)="set(entry, 'bulk', $event)"></app-search>
|
||||||
|
</td>
|
||||||
|
|
||||||
</div>
|
<td class="delete noBorderFirst" (click)="delete(entry)">
|
||||||
|
<fa-icon [icon]="faTimes"></fa-icon>
|
||||||
|
</td>
|
||||||
|
|
||||||
<div class="config">
|
<td class="noBorderMiddle" (click)="set(entry, 'position', entry.position - 1)">
|
||||||
|
<fa-icon *ngIf="entry.position > 0" [icon]="faUp"></fa-icon>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="noBorderLast" (click)="set(entry, 'position', entry.position + 1)">
|
||||||
|
<fa-icon *ngIf="entry.position < schedule.entries.length - 1" [icon]="faDown"></fa-icon>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p>
|
||||||
<button (click)="create()">+ Hinzufügen</button>
|
<button (click)="create()">+ Hinzufügen</button>
|
||||||
</div>
|
</p>
|
||||||
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@ -1,73 +1,19 @@
|
|||||||
@import "../../../../config";
|
select {
|
||||||
|
background-color: transparent;
|
||||||
@time_input_width: 35%;
|
border-width: 0;
|
||||||
|
|
||||||
#title {
|
|
||||||
margin: @margin;
|
|
||||||
}
|
|
||||||
|
|
||||||
.weekdays {
|
|
||||||
float: left;
|
|
||||||
width: 75%;
|
|
||||||
border-radius: @border-radius;
|
|
||||||
|
|
||||||
div {
|
|
||||||
float: left;
|
|
||||||
width: 14.2857%;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.modes {
|
|
||||||
float: left;
|
|
||||||
width: 25%;
|
|
||||||
|
|
||||||
._inner_ {
|
|
||||||
margin-left: @margin;
|
|
||||||
border-radius: @border-radius;
|
|
||||||
|
|
||||||
div {
|
|
||||||
float: left;
|
|
||||||
width: 33.3333%;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.time {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
outline: none;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
input {
|
th {
|
||||||
width: @time_input_width;
|
background-color: lightblue;
|
||||||
text-align: center;
|
}
|
||||||
margin-right: @margin;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
tr.header {
|
||||||
width: calc(((100% - @time_input_width) - 4 * @margin) / 4);
|
|
||||||
margin-right: @margin;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:last-child {
|
th:not(:first-child), td:not(:first-child) {
|
||||||
margin-right: 0;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sun {
|
|
||||||
|
|
||||||
div {
|
|
||||||
float: left;
|
|
||||||
width: 20%;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.timestamp {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inactive {
|
|
||||||
color: gray;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -3,31 +3,15 @@ import {ScheduleService} from "../../../api/schedule/schedule.service";
|
|||||||
import {Schedule} from "../../../api/schedule/Schedule";
|
import {Schedule} from "../../../api/schedule/Schedule";
|
||||||
import {ScheduleEntry} from "../../../api/schedule/entry/ScheduleEntry";
|
import {ScheduleEntry} from "../../../api/schedule/entry/ScheduleEntry";
|
||||||
import {ScheduleEntryService} from "../../../api/schedule/entry/schedule-entry.service";
|
import {ScheduleEntryService} from "../../../api/schedule/entry/schedule-entry.service";
|
||||||
|
import {faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faCircle, faTimesCircle} from '@fortawesome/free-regular-svg-icons';
|
||||||
import {ActivatedRoute, Router} from "@angular/router";
|
import {ActivatedRoute, Router} from "@angular/router";
|
||||||
|
import {PropertyService} from "../../../api/property/property.service";
|
||||||
|
import {Scene} from "../../../api/scene/Scene";
|
||||||
|
import {SceneService} from "../../../api/scene/scene.service";
|
||||||
|
import {BulkService} from "../../../api/bulk/BulkService";
|
||||||
import {Update} from "../../../api/Update";
|
import {Update} from "../../../api/Update";
|
||||||
import {NO_OP} from "../../../api/api.service";
|
import {NO_OP} from "../../../api/api.service";
|
||||||
|
|
||||||
import {Duration} from "../../../api/Duration";
|
|
||||||
import {Bulk} from "../../../api/bulk/Bulk";
|
|
||||||
import {BulkService} from "../../../api/bulk/BulkService";
|
|
||||||
import {Zenith} from "../../../api/schedule/entry/Zenith";
|
|
||||||
import {TimeService} from "../../../api/time.service";
|
|
||||||
import {faCheckCircle, faCircle, faTimesCircle} from "@fortawesome/free-regular-svg-icons";
|
|
||||||
|
|
||||||
const DAY_MINUTES: number = 24 * 60;
|
|
||||||
|
|
||||||
const ZENITH_ENTRIES: Zenith[] = [
|
|
||||||
new Zenith("Astr.", 107, true, true),
|
|
||||||
new Zenith("Naut.", 102, true, true),
|
|
||||||
new Zenith("Bürg.", 96, true, true),
|
|
||||||
new Zenith("Aufg.", 90.8, true, false),
|
|
||||||
new Zenith("Unterg.", 90.8, false, true),
|
|
||||||
];
|
|
||||||
|
|
||||||
const ZENITH_SUNRISE = ZENITH_ENTRIES.filter(zenith => zenith.sunrise);
|
|
||||||
|
|
||||||
const ZENITH_SUNSET = ZENITH_ENTRIES.filter(zenith => zenith.sunset).reverse();
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-schedule-editor',
|
selector: 'app-schedule-editor',
|
||||||
templateUrl: './schedule-editor.component.html',
|
templateUrl: './schedule-editor.component.html',
|
||||||
@ -35,43 +19,40 @@ const ZENITH_SUNSET = ZENITH_ENTRIES.filter(zenith => zenith.sunset).reverse();
|
|||||||
})
|
})
|
||||||
export class ScheduleEditorComponent implements OnInit {
|
export class ScheduleEditorComponent implements OnInit {
|
||||||
|
|
||||||
protected readonly faCheckCircle = faCheckCircle;
|
readonly ScheduleEntry = ScheduleEntry;
|
||||||
|
|
||||||
protected readonly faTimesCircle = faTimesCircle;
|
readonly faCheckCircle = faCheckCircle;
|
||||||
|
|
||||||
protected readonly faCircle = faCircle;
|
readonly faCircle = faCircle;
|
||||||
|
|
||||||
protected readonly ScheduleEntry = ScheduleEntry;
|
readonly faTimes = faTimesCircle;
|
||||||
|
|
||||||
protected readonly Schedule = Schedule;
|
readonly faUp = faArrowAltCircleUp;
|
||||||
|
|
||||||
protected readonly Duration = Duration;
|
readonly faDown = faArrowAltCircleDown;
|
||||||
|
|
||||||
protected readonly Bulk = Bulk;
|
readonly Schedule = Schedule;
|
||||||
|
|
||||||
protected readonly expanded: number[] = [];
|
schedule!: Schedule;
|
||||||
|
|
||||||
protected now: Date = new Date(Date.now());
|
scenes: Scene[] = [];
|
||||||
|
|
||||||
protected schedule!: Schedule;
|
|
||||||
|
|
||||||
protected bulks: Bulk[] = [];
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly router: Router,
|
readonly router: Router,
|
||||||
readonly activatedRoute: ActivatedRoute,
|
readonly activatedRoute: ActivatedRoute,
|
||||||
readonly scheduleService: ScheduleService,
|
readonly scheduleService: ScheduleService,
|
||||||
readonly entryService: ScheduleEntryService,
|
readonly scheduleEntryService: ScheduleEntryService,
|
||||||
|
readonly propertyService: PropertyService,
|
||||||
readonly bulkService: BulkService,
|
readonly bulkService: BulkService,
|
||||||
readonly timeService: TimeService,
|
readonly sceneService: SceneService,
|
||||||
) {
|
) {
|
||||||
// -
|
// nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.scheduleService.subscribe(update => this.update(update));
|
this.scheduleService.subscribe(update => this.update(update));
|
||||||
this.bulkService.findAll(list => this.bulks = list);
|
this.sceneService.findAll(scenes => this.scenes = scenes);
|
||||||
this.activatedRoute.params.subscribe(params => this.scheduleService.getById(params['id'], schedule => this.setSchedule(schedule)));
|
this.activatedRoute.params.subscribe(params => this.scheduleService.getById(params['id'], schedule => this.schedule = schedule));
|
||||||
}
|
}
|
||||||
|
|
||||||
private update(update: Update<Schedule>): void {
|
private update(update: Update<Schedule>): void {
|
||||||
@ -82,58 +63,25 @@ export class ScheduleEditorComponent implements OnInit {
|
|||||||
this.router.navigate(['/ScheduleList']);
|
this.router.navigate(['/ScheduleList']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.setSchedule(update.payload);
|
this.schedule = update.payload;
|
||||||
}
|
|
||||||
|
|
||||||
private setSchedule(schedule: Schedule) {
|
|
||||||
this.schedule = schedule;
|
|
||||||
this.expanded.length = 0;
|
|
||||||
this.expanded.push(...schedule.entries.map(e => e.id));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
create(): void {
|
create(): void {
|
||||||
this.entryService.create(this.schedule, NO_OP);
|
this.scheduleEntryService.create(this.schedule, NO_OP);
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(entry: ScheduleEntry): void {
|
delete(entry: ScheduleEntry): void {
|
||||||
if (confirm("Eintrag \"" + this.timeService.relativeDate(entry.nextClearTimestamp) + " +/-" + entry.fuzzySeconds + "\" wirklich löschen?")) {
|
if (confirm("Eintrag \"" + entry.nextClearTimestamp?.timeString + " +/-" + entry.fuzzySeconds + "\" wirklich löschen?")) {
|
||||||
this.entryService.delete(entry, NO_OP);
|
this.scheduleEntryService.delete(entry, NO_OP);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
set(entry: ScheduleEntry | null, key: string, value: any): void {
|
set(entry: ScheduleEntry | null, key: string, value: any): void {
|
||||||
if (entry) {
|
if (entry) {
|
||||||
this.entryService.set(entry, key, value, NO_OP);
|
this.scheduleEntryService.set(entry, key, value, NO_OP);
|
||||||
} else {
|
} else {
|
||||||
this.scheduleService.set(this.schedule, key, value, NO_OP);
|
this.scheduleService.set(this.schedule, key, value, NO_OP);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
timeFromString(entry: ScheduleEntry, time: string) {
|
|
||||||
const parts = time.split(':');
|
|
||||||
const hour = parseInt(parts[0]);
|
|
||||||
const minute = parseInt(parts[1]);
|
|
||||||
let second = 0;
|
|
||||||
if (parts.length === 3) {
|
|
||||||
second = parseInt(parts[2]);
|
|
||||||
}
|
|
||||||
const daySecond = (hour * 24 + minute) * 60 + second;
|
|
||||||
this.entryService.set(entry, 'daySecond', daySecond);
|
|
||||||
}
|
|
||||||
|
|
||||||
dayMinuteAdd(entry: ScheduleEntry, minutes: number) {
|
|
||||||
let newMinutes = entry.dayMinute + minutes;
|
|
||||||
while (newMinutes < 0 || newMinutes >= DAY_MINUTES) {
|
|
||||||
newMinutes = (newMinutes + DAY_MINUTES) % DAY_MINUTES;
|
|
||||||
}
|
|
||||||
this.entryService.set(entry, 'daySecond', newMinutes * 60);
|
|
||||||
}
|
|
||||||
|
|
||||||
getZenithEntries(type: string) {
|
|
||||||
if (type === 'SUNRISE') {
|
|
||||||
return ZENITH_SUNRISE;
|
|
||||||
}
|
|
||||||
return ZENITH_SUNSET;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,53 +1,42 @@
|
|||||||
<div class="tiles">
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th> </th>
|
||||||
|
<th>Bezeichnung</th>
|
||||||
|
<th colspan="3">Zeitpunkt</th>
|
||||||
|
<th colspan="3">Eigenschaft</th>
|
||||||
|
<th>Massenausführung</th>
|
||||||
|
<th> </th>
|
||||||
|
</tr>
|
||||||
|
<tr *ngFor="let schedule of schedules; trackBy: Schedule.trackBy">
|
||||||
|
|
||||||
<div class="tile" *ngFor="let schedule of schedules; trackBy: Schedule.trackBy">
|
<td class="boolean" (click)="set(schedule, 'enabled', !schedule.enabled)" [class.true]="schedule.enabled" [class.false]="!schedule.enabled">
|
||||||
|
<fa-icon *ngIf="schedule.enabled" [icon]="faCheckCircle"></fa-icon>
|
||||||
|
<fa-icon *ngIf="!schedule.enabled" [icon]="faCircle"></fa-icon>
|
||||||
|
</td>
|
||||||
|
|
||||||
<div class="tileHead disabledBack" [class.enabledBack]="schedule.next" [class.skipBack]="schedule.next?.skip">
|
<td [routerLink]="['/Schedule', {id: schedule.id}]">
|
||||||
|
{{schedule.title}}
|
||||||
|
</td>
|
||||||
|
|
||||||
<div class="enabled" (click)="set(schedule, 'enabled', !schedule.enabled)">
|
<td class="number first" [class.empty]="!schedule.next?.nextClearTimestamp">{{schedule.next?.nextClearTimestamp.dayName}}</td>
|
||||||
<fa-icon *ngIf="schedule.enabled" [icon]="faCheckCircle"></fa-icon>
|
<td class="number middle" [class.empty]="!schedule.next?.nextClearTimestamp">: </td>
|
||||||
<fa-icon *ngIf="!schedule.enabled" [icon]="faCircle"></fa-icon>
|
<td class="number last" [class.empty]="!schedule.next?.nextClearTimestamp">{{schedule.next?.nextClearTimestamp.timeString}}</td>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flexGrow" [routerLink]="['/Schedule', {id: schedule.id}]">
|
<td class="number first" [class.empty]="!schedule.next?.property">{{schedule.next?.property?.title}}</td>
|
||||||
{{ schedule.title }}
|
<td class="number middle" [class.empty]="!schedule.next?.property"> = </td>
|
||||||
</div>
|
<td class="number last" [class.empty]="!schedule.next?.property">{{schedule.next?.value}}</td>
|
||||||
|
|
||||||
<div class="tileHeadRight" (click)="skip(schedule.next)" *ngIf="schedule.next">
|
<td [class.empty]="!schedule.next?.bulk">
|
||||||
<img class="icon" src="assets/skip.svg" [alt]="'Über.'">{{ schedule.next?.skip }}
|
{{schedule.next?.bulk?.name}}
|
||||||
</div>
|
</td>
|
||||||
|
|
||||||
<div class="tileHeadRight" (click)="delete(schedule)">
|
<td class="delete" (click)="delete(schedule)">
|
||||||
<fa-icon title="Löschen" [icon]="faTimes"></fa-icon>
|
<fa-icon title="Löschen" [icon]="faTimes"></fa-icon>
|
||||||
</div>
|
</td>
|
||||||
|
|
||||||
</div>
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
<div class="tileBody next">
|
<p>
|
||||||
<div class="timestamp">
|
|
||||||
<ng-container *ngIf="schedule.next">{{ timeService.relativeDate(schedule.next?.nextFuzzyTimestamp) }}</ng-container>
|
|
||||||
</div>
|
|
||||||
<div class="bulk" [class.timestampBulkEmpty]="!schedule.next?.bulk" *ngIf="schedule.next?.bulk">
|
|
||||||
{{ schedule.next?.bulk?.name }}
|
|
||||||
<fa-icon [icon]="faPlayCircle" (click)="execute(schedule.next?.bulk)"></fa-icon>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tileBody last">
|
|
||||||
<div class="timestamp">
|
|
||||||
<ng-container *ngIf="schedule.last">{{ timeService.relativeDate(schedule.last?.lastFuzzyTimestamp) }}</ng-container>
|
|
||||||
<ng-container *ngIf="!schedule.last">- - -</ng-container>
|
|
||||||
</div>
|
|
||||||
<div class="bulk" [class.timestampBulkEmpty]="!schedule.last?.bulk" *ngIf="schedule.last?.bulk">
|
|
||||||
{{ schedule.last?.bulk?.name }}
|
|
||||||
<fa-icon [icon]="faPlayCircle" (click)="execute(schedule.last?.bulk)"></fa-icon>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="config">
|
|
||||||
<button (click)="create()">+ Hinzufügen</button>
|
<button (click)="create()">+ Hinzufügen</button>
|
||||||
</div>
|
</p>
|
||||||
|
|||||||
@ -1,15 +1,11 @@
|
|||||||
@import "../../../../config";
|
select {
|
||||||
|
background-color: transparent;
|
||||||
.last {
|
border-width: 0;
|
||||||
color: gray;
|
width: 100%;
|
||||||
font-size: 60%;
|
outline: none;
|
||||||
border-top: @border solid gray;
|
font-family: monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timestamp {
|
th {
|
||||||
float: left;
|
background-color: lightblue;
|
||||||
}
|
|
||||||
|
|
||||||
.bulk {
|
|
||||||
float: right;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,9 @@
|
|||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import {ScheduleService} from "../../../api/schedule/schedule.service";
|
import {ScheduleService} from "../../../api/schedule/schedule.service";
|
||||||
import {Schedule} from "../../../api/schedule/Schedule";
|
import {Schedule} from "../../../api/schedule/Schedule";
|
||||||
import {faCheckCircle, faCircle, faPlayCircle, faTimesCircle} from '@fortawesome/free-regular-svg-icons';
|
import {faCheckCircle, faCircle, faTimesCircle} from '@fortawesome/free-regular-svg-icons';
|
||||||
import {NO_OP} from "../../../api/api.service";
|
import {NO_OP} from "../../../api/api.service";
|
||||||
import {Update} from "../../../api/Update";
|
import {Update} from "../../../api/Update";
|
||||||
import {ScheduleEntryService} from "../../../api/schedule/entry/schedule-entry.service";
|
|
||||||
import {ScheduleEntry} from "../../../api/schedule/entry/ScheduleEntry";
|
|
||||||
import {Bulk} from "../../../api/bulk/Bulk";
|
|
||||||
import {BulkService} from "../../../api/bulk/BulkService";
|
|
||||||
import {TimeService} from "../../../api/time.service";
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-schedule-list',
|
selector: 'app-schedule-list',
|
||||||
@ -29,9 +24,6 @@ export class ScheduleListComponent implements OnInit {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly scheduleService: ScheduleService,
|
readonly scheduleService: ScheduleService,
|
||||||
readonly entryService: ScheduleEntryService,
|
|
||||||
readonly bulkService: BulkService,
|
|
||||||
readonly timeService: TimeService,
|
|
||||||
) {
|
) {
|
||||||
// nothing
|
// nothing
|
||||||
}
|
}
|
||||||
@ -71,19 +63,4 @@ export class ScheduleListComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
skip(entry: ScheduleEntry) {
|
|
||||||
let skip = entry.skip + 1;
|
|
||||||
if (skip > 7) {
|
|
||||||
skip = 0;
|
|
||||||
}
|
|
||||||
this.entryService.set(entry, 'skip', skip);
|
|
||||||
}
|
|
||||||
|
|
||||||
execute(bulk: Bulk | undefined) {
|
|
||||||
if (bulk && confirm("Stapel Ausführen?\n\n" + bulk.name)) {
|
|
||||||
this.bulkService.run(bulk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected readonly faPlayCircle = faPlayCircle;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,20 +0,0 @@
|
|||||||
import {Pipe, PipeTransform} from '@angular/core';
|
|
||||||
|
|
||||||
@Pipe({
|
|
||||||
name: 'leftPad',
|
|
||||||
})
|
|
||||||
export class LeftPadDirective implements PipeTransform {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
}
|
|
||||||
|
|
||||||
transform(value: any, count: number): any {
|
|
||||||
let result = "" + value;
|
|
||||||
const rest: number = count - result.length;
|
|
||||||
if (rest > 0) {
|
|
||||||
result = " ".repeat(rest) + result;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
<div type="checkbox" [style.background-color]="color()" (click)="onChange.emit(!value)">
|
|
||||||
<ng-container *ngIf="label">
|
|
||||||
{{ label }}
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="!label">
|
|
||||||
<ng-container *ngIf="icon">
|
|
||||||
<fa-icon *ngIf="icon === 'faCircle'" [icon]="faCircle"></fa-icon>
|
|
||||||
<fa-icon *ngIf="icon === 'faCheckCircle'" [icon]="faCheckCircle"></fa-icon>
|
|
||||||
<fa-icon *ngIf="icon === 'faClock'" [icon]="faClock"></fa-icon>
|
|
||||||
<fa-icon *ngIf="icon === 'faArrowAltCircleUp'" [icon]="faArrowAltCircleUp"></fa-icon>
|
|
||||||
<fa-icon *ngIf="icon === 'faArrowAltCircleDown'" [icon]="faArrowAltCircleDown"></fa-icon>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="!icon">
|
|
||||||
<fa-icon *ngIf="value" [icon]="faCheckCircle"></fa-icon>
|
|
||||||
<fa-icon *ngIf="!value" [icon]="faCircle"></fa-icon>
|
|
||||||
</ng-container>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
div {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
padding-top: 0.1em;
|
|
||||||
padding-bottom: 0.1em;
|
|
||||||
text-align: center;
|
|
||||||
background-color: gray;
|
|
||||||
}
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
|
|
||||||
import {faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faCircle, faClock} from "@fortawesome/free-regular-svg-icons";
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-bool',
|
|
||||||
templateUrl: './bool.component.html',
|
|
||||||
styleUrls: ['./bool.component.less']
|
|
||||||
})
|
|
||||||
export class BoolComponent implements OnInit {
|
|
||||||
|
|
||||||
protected readonly faCheckCircle = faCheckCircle;
|
|
||||||
|
|
||||||
protected readonly faCircle = faCircle;
|
|
||||||
|
|
||||||
protected readonly faClock = faClock;
|
|
||||||
|
|
||||||
protected readonly faArrowAltCircleUp = faArrowAltCircleUp;
|
|
||||||
|
|
||||||
protected readonly faArrowAltCircleDown = faArrowAltCircleDown;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
icon: string | undefined = undefined;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
colorActive: string = '#8FBC8FFF';
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
colorInactive: string = '#8c8c8c';
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
label: string | undefined = undefined;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
value: boolean | undefined = undefined;
|
|
||||||
|
|
||||||
@Output()
|
|
||||||
onChange: EventEmitter<boolean> = new EventEmitter();
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
}
|
|
||||||
|
|
||||||
toggle() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
color(): string {
|
|
||||||
if (this.value === true) {
|
|
||||||
return this.colorActive;
|
|
||||||
} else if (this.value === false) {
|
|
||||||
return this.colorInactive;
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
<input type="text" [class]="inputClass" [(ngModel)]="code" (focus)="focus = true" (blur)="apply()" (keydown.enter)="apply()" (keydown.escape)="cancel()">
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
input {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
@ -1,63 +0,0 @@
|
|||||||
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
|
|
||||||
import {Duration} from "../../api/Duration";
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-duration',
|
|
||||||
templateUrl: './duration.component.html',
|
|
||||||
styleUrls: ['./duration.component.less']
|
|
||||||
})
|
|
||||||
export class DurationComponent implements OnInit {
|
|
||||||
|
|
||||||
private _duration: Duration = Duration.ZERO;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
set duration(duration: Duration) {
|
|
||||||
this._duration = duration;
|
|
||||||
if (!this.focus) {
|
|
||||||
this.code = this.duration.code;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get duration(): Duration {
|
|
||||||
return this._duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
inputClass: string = '';
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
min: Duration | undefined = undefined;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
max: Duration | undefined = undefined;
|
|
||||||
|
|
||||||
@Output()
|
|
||||||
readonly onChange: EventEmitter<Duration> = new EventEmitter();
|
|
||||||
|
|
||||||
protected code: string = '';
|
|
||||||
|
|
||||||
protected focus: boolean = false;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
}
|
|
||||||
|
|
||||||
apply() {
|
|
||||||
let duration = Duration.ofCode(this.code);
|
|
||||||
if (this.min !== undefined && duration.totalSeconds < this.min.totalSeconds) {
|
|
||||||
duration = this.min;
|
|
||||||
}
|
|
||||||
if (this.max !== undefined && duration.totalSeconds > this.max.totalSeconds) {
|
|
||||||
duration = this.max;
|
|
||||||
}
|
|
||||||
this.code = duration.code;
|
|
||||||
this.onChange.emit(duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
cancel() {
|
|
||||||
this.code = this.duration.code;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
<ng-container [ngSwitch]="entry.property?.type">
|
|
||||||
|
|
||||||
<div *ngSwitchCase="'BOOLEAN'" [class.true]="entry.value" [class.false]="!entry.value" (click)="set('value', entry.value > 0 ? 0 : 1)">
|
|
||||||
{{entry.value ? "An" : "Aus"}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngSwitchCase="'SHUTTER'" [class.true]="entry.value === 0" [class.false]="entry.value === 100" [class.tristate]="0 < entry.value && entry.value < 100">
|
|
||||||
<select [ngModel]="entry.value" (ngModelChange)="set('value', $event)" [disabled]="!allowChange">
|
|
||||||
<option *ngFor="let _ of [].constructor(21); let value = index" [ngValue]="value * 5">{{value * 5}}%</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngSwitchCase="'BRIGHTNESS_PERCENT'" [class.true]="entry.value" [class.false]="!entry.value" [class.tristate]="0 < entry.value && entry.value < 100">
|
|
||||||
<select [ngModel]="entry.value" (ngModelChange)="set('value', $event)" [disabled]="!allowChange">
|
|
||||||
<option *ngFor="let _ of [].constructor(21); let value = index" [ngValue]="value * 5">{{value * 5}}%</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngSwitchCase="'COLOR_TEMPERATURE'" [class.true]="entry.value" [class.false]="!entry.value" [class.tristate]="0 < entry.value && entry.value < 100">
|
|
||||||
<select [ngModel]="entry.value" (ngModelChange)="set('value', $event)" [disabled]="!allowChange">
|
|
||||||
<option *ngFor="let _ of [].constructor(21); let value = index" [ngValue]="value * 5">{{value * 5}}%</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngSwitchCase="'LUX'" [class.true]="entry.value" [class.false]="!entry.value" [class.tristate]="0 < entry.value && entry.value < 100">
|
|
||||||
<select [ngModel]="entry.value" (ngModelChange)="set('value', $event)" [disabled]="!allowChange">
|
|
||||||
<option *ngFor="let _ of [].constructor(21); let value = index" [ngValue]="value * 5">{{value * 5}}%</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngSwitchCase="'SCENE'">
|
|
||||||
<select [ngModel]="entry.value" (ngModelChange)="set('value', $event)" [disabled]="!allowChange">
|
|
||||||
<option *ngFor="let scene of scenes" [ngValue]="scene.number">#{{scene.number | number:'2.0-0'}} {{scene.title}}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div *ngSwi
|
|
||||||
tchDefault class="empty">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</ng-container>
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
|
|
||||||
import {ScheduleEntry} from "../../api/schedule/entry/ScheduleEntry";
|
|
||||||
import {Scene} from "../../api/scene/Scene";
|
|
||||||
import {SceneService} from "../../api/scene/scene.service";
|
|
||||||
|
|
||||||
export class OnSet {
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
readonly key: string,
|
|
||||||
readonly value: any,
|
|
||||||
) {
|
|
||||||
// nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-entry-value',
|
|
||||||
templateUrl: './entry-value.component.html',
|
|
||||||
styleUrls: ['./entry-value.component.less']
|
|
||||||
})
|
|
||||||
export class EntryValueComponent implements OnInit {
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
entry!: ScheduleEntry;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
allowChange: boolean = false;
|
|
||||||
|
|
||||||
@Output()
|
|
||||||
onSet: EventEmitter<OnSet> = new EventEmitter<OnSet>();
|
|
||||||
|
|
||||||
scenes: Scene[] = [];
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
readonly sceneService: SceneService,) {
|
|
||||||
// nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.sceneService.findAll(scenes => this.scenes = scenes);
|
|
||||||
}
|
|
||||||
|
|
||||||
set(key: string, value: any): void {
|
|
||||||
if (!this.allowChange) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.onSet.emit(new OnSet(key, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,9 +1,7 @@
|
|||||||
@import "../../../config";
|
|
||||||
|
|
||||||
input {
|
input {
|
||||||
outline: none;
|
outline: none;
|
||||||
border: none;
|
border: none;
|
||||||
padding: @padding;
|
padding: 5px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
|||||||
@ -7,8 +7,9 @@
|
|||||||
<ng-container *ngIf="!selected">-</ng-container>
|
<ng-container *ngIf="!selected">-</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="searching" class="resultList">
|
<input #input type="text" *ngIf="searching" [(ngModel)]="term" (ngModelChange)="changed()" (keydown.enter)="doSearch()" (keydown.escape)="cancelSearch()" (focus)="cancelOnBlur=true" (blur)="blur()">
|
||||||
<input #input type="text" *ngIf="searching" [(ngModel)]="term" (ngModelChange)="changed()" (keydown.enter)="doSearch()" (keydown.escape)="cancelSearch()" (focus)="cancelOnBlur=true" (blur)="blur()">
|
|
||||||
|
<div #resultList *ngIf="searching" class="resultList">
|
||||||
<div *ngIf="allowEmpty" class="result" (mousedown)="dontCancelOnBlur()" (click)="select(undefined)">
|
<div *ngIf="allowEmpty" class="result" (mousedown)="dontCancelOnBlur()" (click)="select(undefined)">
|
||||||
-
|
-
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,30 +1,22 @@
|
|||||||
@import "../../../config";
|
|
||||||
|
|
||||||
.all {
|
.all {
|
||||||
|
|
||||||
.initial {
|
.initial {
|
||||||
padding: @padding;
|
padding: 5px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected {
|
.selected {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
border-bottom: @border solid black;
|
border-bottom: 1px solid black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.resultList {
|
.resultList {
|
||||||
position: fixed;
|
position: absolute;
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
background-color: lightgray;
|
background-color: lightgray;
|
||||||
min-width: 10em;
|
min-width: 200px;
|
||||||
border: @border solid black;
|
border: 1px solid black;
|
||||||
|
|
||||||
.result {
|
.result {
|
||||||
padding: @padding;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.result:hover {
|
.result:hover {
|
||||||
|
|||||||
@ -7,21 +7,25 @@ import {ISearchService} from "../../api/ISearchService";
|
|||||||
templateUrl: './search.component.html',
|
templateUrl: './search.component.html',
|
||||||
styleUrls: ['./search.component.less']
|
styleUrls: ['./search.component.less']
|
||||||
})
|
})
|
||||||
export class SearchComponent implements OnInit {
|
export class SearchComponent<T> implements OnInit {
|
||||||
|
|
||||||
private changedTimeout: number | undefined;
|
private changedTimeout: number | undefined;
|
||||||
|
|
||||||
private initial_?: number;
|
|
||||||
|
|
||||||
@ViewChild('input')
|
@ViewChild('input')
|
||||||
input2?: ElementRef;
|
input2?: ElementRef;
|
||||||
|
|
||||||
@ViewChild('input')
|
@ViewChild('input')
|
||||||
input?: HTMLInputElement;
|
input?: HTMLInputElement;
|
||||||
|
|
||||||
|
@ViewChild('resultList')
|
||||||
|
resultList?: HTMLDivElement;
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
searchService!: ISearchService;
|
searchService!: ISearchService;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
initial?: number;
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
allowEmpty: boolean = true;
|
allowEmpty: boolean = true;
|
||||||
|
|
||||||
@ -38,24 +42,13 @@ export class SearchComponent implements OnInit {
|
|||||||
|
|
||||||
cancelOnBlur: boolean = true;
|
cancelOnBlur: boolean = true;
|
||||||
|
|
||||||
@Input()
|
|
||||||
set initial(value: number | undefined) {
|
|
||||||
this.initial_ = value;
|
|
||||||
if (this.initial) {
|
|
||||||
this.searchService.searchById(this.initial, result => this.selected = result, _ => _);
|
|
||||||
} else {
|
|
||||||
this.selected = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get initial(): number {
|
|
||||||
return this.initial_;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
if (this.initial) {
|
||||||
|
this.searchService.searchById(this.initial, result => this.selected = result, _ => _);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
changed(): void {
|
changed(): void {
|
||||||
@ -72,6 +65,9 @@ export class SearchComponent implements OnInit {
|
|||||||
|
|
||||||
start(): void {
|
start(): void {
|
||||||
this.term = this.selected?.title || "";
|
this.term = this.selected?.title || "";
|
||||||
|
if (this.resultList && this.input) {
|
||||||
|
this.resultList.style.left = this.input.style.left;
|
||||||
|
}
|
||||||
this.searching = true;
|
this.searching = true;
|
||||||
setTimeout(() => this.input2?.nativeElement.focus(), 0);
|
setTimeout(() => this.input2?.nativeElement.focus(), 0);
|
||||||
this.doSearch();
|
this.doSearch();
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<input #input *ngIf="editing" type="text" [(ngModel)]="value" (keydown.enter)="finish()" (blur)="finish()">
|
<input #input *ngIf="editing" type="text" [(ngModel)]="value" (keydown.enter)="finish()" (blur)="finish()">
|
||||||
<div *ngIf="!editing" [class.empty]="initial == ''" (click)="start()">
|
<div *ngIf="!editing" [class.empty]="initial == ''" (click)="start()">
|
||||||
<ng-container *ngIf="initial != ''">{{initial}}</ng-container>
|
<ng-container *ngIf="initial != ''">{{initial}}</ng-container>
|
||||||
<ng-container *ngIf="!initial">-</ng-container>
|
<ng-container *ngIf="initial == ''">-</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -28,9 +28,6 @@ export class TextComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
finish(): void {
|
finish(): void {
|
||||||
if (!this.editing) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.editing = false;
|
this.editing = false;
|
||||||
if (this.value != this.initial) {
|
if (this.value != this.initial) {
|
||||||
this.valueChange.emit(this.value);
|
this.valueChange.emit(this.value);
|
||||||
|
|||||||
@ -1,3 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
||||||
<path d="M243.8 339.8C232.9 350.7 215.1 350.7 204.2 339.8L140.2 275.8C129.3 264.9 129.3 247.1 140.2 236.2C151.1 225.3 168.9 225.3 179.8 236.2L224 280.4L332.2 172.2C343.1 161.3 360.9 161.3 371.8 172.2C382.7 183.1 382.7 200.9 371.8 211.8L243.8 339.8zM512 256C512 397.4 397.4 512 256 512C114.6 512 0 397.4 0 256C0 114.6 114.6 0 256 0C397.4 0 512 114.6 512 256zM256 48C141.1 48 48 141.1 48 256C48 370.9 141.1 464 256 464C370.9 464 464 370.9 464 256C464 141.1 370.9 48 256 48z"></path>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 553 B |
@ -1,3 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
||||||
<path d="M175 175C184.4 165.7 199.6 165.7 208.1 175L255.1 222.1L303 175C312.4 165.7 327.6 165.7 336.1 175C346.3 184.4 346.3 199.6 336.1 208.1L289.9 255.1L336.1 303C346.3 312.4 346.3 327.6 336.1 336.1C327.6 346.3 312.4 346.3 303 336.1L255.1 289.9L208.1 336.1C199.6 346.3 184.4 346.3 175 336.1C165.7 327.6 165.7 312.4 175 303L222.1 255.1L175 208.1C165.7 199.6 165.7 184.4 175 175V175zM512 256C512 397.4 397.4 512 256 512C114.6 512 0 397.4 0 256C0 114.6 114.6 0 256 0C397.4 0 512 114.6 512 256zM256 48C141.1 48 48 141.1 48 256C48 370.9 141.1 464 256 464C370.9 464 464 370.9 464 256C464 141.1 370.9 48 256 48z"></path>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 687 B |
@ -1,9 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<svg width="800px" height="800px" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g transform="translate(-220.000000, -8079.000000)">
|
|
||||||
<g transform="translate(56.000000, 160.000000)">
|
|
||||||
<path
|
|
||||||
d="M174,7927.1047 C172.896,7927.1047 172,7927.9997 172,7929.1047 C172,7930.2097 172.896,7931.1047 174,7931.1047 C175.104,7931.1047 176,7930.2097 176,7929.1047 C176,7927.9997 175.104,7927.1047 174,7927.1047 L174,7927.1047 Z M182,7921.9997 C182,7921.4477 181.552,7920.9997 181,7920.9997 L167,7920.9997 C166.448,7920.9997 166,7921.4477 166,7921.9997 L166,7935.9997 C166,7936.5527 166.448,7936.9997 167,7936.9997 L181,7936.9997 C181.552,7936.9997 182,7936.5527 182,7935.9997 L182,7921.9997 Z M184,7920.9997 L184,7936.9997 C184,7938.1047 183.105,7938.9997 182,7938.9997 L166,7938.9997 C164.896,7938.9997 164,7938.1047 164,7936.9997 L164,7920.9997 C164,7919.8957 164.896,7918.9997 166,7918.9997 L182,7918.9997 C183.105,7918.9997 184,7919.8957 184,7920.9997 L184,7920.9997 Z M170,7927.1047 C171.104,7927.1047 172,7926.2097 172,7925.1047 C172,7923.9997 171.104,7923.1047 170,7923.1047 C168.896,7923.1047 168,7923.9997 168,7925.1047 C168,7926.2097 168.896,7927.1047 170,7927.1047 L170,7927.1047 Z M170,7931.1047 C168.896,7931.1047 168,7931.9997 168,7933.1047 C168,7934.2097 168.896,7935.1047 170,7935.1047 C171.104,7935.1047 172,7934.2097 172,7933.1047 C172,7931.9997 171.104,7931.1047 170,7931.1047 L170,7931.1047 Z M178,7923.1047 C176.896,7923.1047 176,7923.9997 176,7925.1047 C176,7926.2097 176.896,7927.1047 178,7927.1047 C179.104,7927.1047 180,7926.2097 180,7925.1047 C180,7923.9997 179.104,7923.1047 178,7923.1047 L178,7923.1047 Z M180,7933.1047 C180,7934.2097 179.104,7935.1047 178,7935.1047 C176.896,7935.1047 176,7934.2097 176,7933.1047 C176,7931.9997 176.896,7931.1047 178,7931.1047 C179.104,7931.1047 180,7931.9997 180,7933.1047 L180,7933.1047 Z"></path>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.9 KiB |
@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
|
||||||
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M18 9L12 15L6 9" stroke="#33363F" stroke-width="2"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 292 B |
@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
|
||||||
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M9 6L15 12L9 18" stroke="#33363F" stroke-width="2"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 292 B |
@ -1,7 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<svg width="800px" height="800px" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<circle cx="8" cy="37" r="6"/>
|
|
||||||
<path d="M24,31a6,6,0,1,0,6,6A6,6,0,0,0,24,31Zm0,8a2,2,0,1,1,2-2A2,2,0,0,1,24,39Z"/>
|
|
||||||
<circle cx="40" cy="37" r="6"/>
|
|
||||||
<path d="M37.4,19.6a1.9,1.9,0,0,0-3,.2,2.1,2.1,0,0,0,.2,2.7l4,3.9a1.9,1.9,0,0,0,2.8,0l4-3.9a2.3,2.3,0,0,0,.3-2.7,2,2,0,0,0-3.1-.2l-.6.6A18,18,0,0,0,6,21v2a2,2,0,0,0,4,0V21a14,14,0,0,1,28-.9Z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 486 B |
@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<circle cx="12" cy="12" r="5"/>
|
|
||||||
<path d="M12 2V4" stroke-linecap="round"/>
|
|
||||||
<path d="M12 20V22" stroke-linecap="round"/>
|
|
||||||
<path d="M4 12L2 12" stroke-linecap="round"/>
|
|
||||||
<path d="M22 12L20 12" stroke-linecap="round"/>
|
|
||||||
<path d="M19.7778 4.22266L17.5558 6.25424" stroke-linecap="round"/>
|
|
||||||
<path d="M4.22217 4.22266L6.44418 6.25424" stroke-linecap="round"/>
|
|
||||||
<path d="M6.44434 17.5557L4.22211 19.7779" stroke-linecap="round"/>
|
|
||||||
<path d="M19.7778 19.7773L17.5558 17.5551" stroke-linecap="round"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 684 B |
@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M12 7V12L10.5 14.5M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z" stroke-linecap="round"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 349 B |
@ -1,3 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
||||||
<path d="M512 256C512 397.4 397.4 512 256 512C114.6 512 0 397.4 0 256C0 114.6 114.6 0 256 0C397.4 0 512 114.6 512 256zM256 48C141.1 48 48 141.1 48 256C48 370.9 141.1 464 256 464C370.9 464 464 370.9 464 256C464 141.1 370.9 48 256 48z"></path>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 314 B |
@ -1,53 +0,0 @@
|
|||||||
@margin: 0.5em;
|
|
||||||
@padding: 0.2em;
|
|
||||||
@border: 0.05em;
|
|
||||||
@border-radius: 0.2em;
|
|
||||||
|
|
||||||
@COLOR_UNKNOWN: gray;
|
|
||||||
@COLOR_ACTIVE: #8fbc8f;
|
|
||||||
@COLOR_BETWEEN: #e4db9c;
|
|
||||||
@COLOR_INACTIVE: #bc8f8f;
|
|
||||||
|
|
||||||
.disabledBack {
|
|
||||||
background-color: @COLOR_UNKNOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
.enabledBack {
|
|
||||||
background-color: @COLOR_ACTIVE;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skipBack {
|
|
||||||
background-color: #ffc059;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skipFont {
|
|
||||||
color: #ff9a00;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fuzzyFont {
|
|
||||||
color: #489dff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fuzzyBack {
|
|
||||||
background-color: #88c0ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deviceSwitchOnBack {
|
|
||||||
background-color: @COLOR_ACTIVE;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deviceSwitchOffBack {
|
|
||||||
background-color: @COLOR_INACTIVE;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deviceShutterOpenBack {
|
|
||||||
background-color: @COLOR_ACTIVE;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deviceShutterIntermediateBack {
|
|
||||||
background-color: @COLOR_BETWEEN;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deviceShutterClosedBack {
|
|
||||||
background-color: @COLOR_INACTIVE;
|
|
||||||
}
|
|
||||||
@ -1,9 +1,20 @@
|
|||||||
import {getBaseUrl} from "./UrlHelper";
|
// This file can be replaced during build by using the `fileReplacements` array.
|
||||||
|
// `ng build` replaces `environment.ts` with `environment.prod.ts`.
|
||||||
|
// The list of file replacements can be found in `angular.json`.
|
||||||
|
|
||||||
const PROD: boolean = false;
|
import {getBaseUrl} from "./UrlHelper";
|
||||||
|
|
||||||
export const environment = {
|
export const environment = {
|
||||||
production: false,
|
production: false,
|
||||||
restBase: PROD ? 'http://10.0.0.50:8082' : getBaseUrl('http', 8080),
|
restBase: getBaseUrl('http', 8080),
|
||||||
websocketBase: PROD ? 'ws://10.0.0.50:8082' : getBaseUrl('ws', 8080),
|
websocketBase: getBaseUrl('ws', 8080),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For easier debugging in development mode, you can import the following file
|
||||||
|
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
|
||||||
|
*
|
||||||
|
* This import should be commented out in production mode because it will have a negative impact
|
||||||
|
* on performance if an error is thrown.
|
||||||
|
*/
|
||||||
|
// import 'zone.js/plugins/zone-error'; // Included with Angular CLI.
|
||||||
|
|||||||
@ -1,35 +1,6 @@
|
|||||||
@import "config";
|
|
||||||
|
|
||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
// font
|
|
||||||
body, input, select, button {
|
|
||||||
font-size: 5vw;
|
|
||||||
font-family: arial, sans-serif;
|
|
||||||
@media (min-width: 1001px) {
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// forms
|
|
||||||
input, select, button {
|
|
||||||
width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
padding: 0.1em;
|
|
||||||
border-radius: @border-radius;
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
margin-top: -0.1em;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
font-family: arial, sans-serif;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,8 +10,6 @@ a {
|
|||||||
|
|
||||||
div {
|
div {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
@ -56,11 +25,11 @@ table {
|
|||||||
|
|
||||||
td, th {
|
td, th {
|
||||||
height: 0; // (=> auto growth) enables use of height percent for children
|
height: 0; // (=> auto growth) enables use of height percent for children
|
||||||
padding: @padding;
|
padding: 5px;
|
||||||
border: @border solid black;
|
border: 1px solid black;
|
||||||
|
|
||||||
img.fullCell {
|
img.fullCell {
|
||||||
margin: calc(-@margin);
|
margin: -5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,6 +41,14 @@ table.vertical {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
position: absolute;
|
||||||
|
margin: 0;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
.empty {
|
.empty {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: gray;
|
color: gray;
|
||||||
@ -145,19 +122,5 @@ table.vertical {
|
|||||||
|
|
||||||
.config {
|
.config {
|
||||||
clear: both;
|
clear: both;
|
||||||
margin: 0 @margin @margin;
|
margin-bottom: 5px;
|
||||||
|
|
||||||
@media (min-width: 1001px) {
|
|
||||||
width: 400px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttonPlus {
|
|
||||||
background-color: #8fbc8f;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttonMinus {
|
|
||||||
background-color: #ef8787;
|
|
||||||
}
|
|
||||||
|
|
||||||
@import "tile";
|
|
||||||
|
|||||||
@ -1,77 +0,0 @@
|
|||||||
@import "config";
|
|
||||||
|
|
||||||
.tiles {
|
|
||||||
margin-top: @margin;
|
|
||||||
margin-left: @margin;
|
|
||||||
|
|
||||||
.tile {
|
|
||||||
border-radius: @border-radius;
|
|
||||||
margin-right: @margin;
|
|
||||||
margin-bottom: @margin;
|
|
||||||
background-color: #ececec;
|
|
||||||
|
|
||||||
@media (min-width: 1001px) {
|
|
||||||
width: 400px;
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tileHead {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
div {
|
|
||||||
float: left;
|
|
||||||
padding: @padding;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tileHeadTitle {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tileHeadRight {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tileHeadDelete {
|
|
||||||
float: right;
|
|
||||||
color: darkred;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.tileBody {
|
|
||||||
padding: @padding;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tileBodyFlex {
|
|
||||||
display: flex;
|
|
||||||
padding: @padding;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
display: inline;
|
|
||||||
vertical-align: bottom;
|
|
||||||
height: 1.1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.flexGrow {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flexHalf {
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flexIcon {
|
|
||||||
float: left;
|
|
||||||
width: 1.5em;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flexIconInput {
|
|
||||||
float: left;
|
|
||||||
width: calc(100% - 1.5em);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -13,6 +13,8 @@ public class Config {
|
|||||||
|
|
||||||
private double longitude = 6.9645334;
|
private double longitude = 6.9645334;
|
||||||
|
|
||||||
|
private String timezone = "Europe/Berlin";
|
||||||
|
|
||||||
private boolean insertDemoData = false;
|
private boolean insertDemoData = false;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import de.ph87.homeautomation.bulk.BulkDto;
|
|||||||
import de.ph87.homeautomation.bulk.entry.BulkEntryController;
|
import de.ph87.homeautomation.bulk.entry.BulkEntryController;
|
||||||
import de.ph87.homeautomation.bulk.entry.BulkEntryCreateDto;
|
import de.ph87.homeautomation.bulk.entry.BulkEntryCreateDto;
|
||||||
import de.ph87.homeautomation.channel.Channel;
|
import de.ph87.homeautomation.channel.Channel;
|
||||||
import de.ph87.homeautomation.device.DeviceController;
|
|
||||||
import de.ph87.homeautomation.property.Property;
|
import de.ph87.homeautomation.property.Property;
|
||||||
import de.ph87.homeautomation.property.PropertyRepository;
|
import de.ph87.homeautomation.property.PropertyRepository;
|
||||||
import de.ph87.homeautomation.property.PropertyType;
|
import de.ph87.homeautomation.property.PropertyType;
|
||||||
@ -40,37 +39,27 @@ public class DemoDataService {
|
|||||||
|
|
||||||
private final ScheduleEntryController scheduleEntryController;
|
private final ScheduleEntryController scheduleEntryController;
|
||||||
|
|
||||||
private final DeviceController deviceController;
|
|
||||||
|
|
||||||
public void insertDemoData() {
|
public void insertDemoData() {
|
||||||
if (!config.isInsertDemoData()) {
|
if (!config.isInsertDemoData()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
final Property propertyDirect = createProperty("propertyDirect", PropertyType.BOOLEAN, null, null);
|
||||||
final long schedule = createSchedule(true, "Schedule");
|
final Property propertyBulkBoolean = createProperty("propertyBulkBoolean", PropertyType.BOOLEAN, null, null);
|
||||||
|
final Property propertyBulkShutter = createProperty("propertyBulkShutter", PropertyType.SHUTTER, null, null);
|
||||||
deviceController.create("DeviceSwitch");
|
final Property propertyBulkBrightness = createProperty("propertyBulkBrightness", PropertyType.BRIGHTNESS_PERCENT, null, null);
|
||||||
deviceController.create("DeviceSwitch");
|
final Property propertyBulkColorTemperature = createProperty("propertyBulkColorTemperature", PropertyType.COLOR_TEMPERATURE, null, null);
|
||||||
deviceController.create("DeviceSwitch");
|
final BulkDto bulk = bulkController.create(new BulkCreateDto("bulk", true));
|
||||||
deviceController.create("DeviceShutter");
|
bulkEntryController.create(new BulkEntryCreateDto(bulk.getId(), propertyBulkBoolean.getId(), 1, 0));
|
||||||
deviceController.create("DeviceShutter");
|
bulkEntryController.create(new BulkEntryCreateDto(bulk.getId(), propertyBulkShutter.getId(), 35, 0));
|
||||||
deviceController.create("DeviceShutter");
|
bulkEntryController.create(new BulkEntryCreateDto(bulk.getId(), propertyBulkBrightness.getId(), 40, 0));
|
||||||
|
bulkEntryController.create(new BulkEntryCreateDto(bulk.getId(), propertyBulkColorTemperature.getId(), 55, 0));
|
||||||
final Property propWeihnachtsbeleuchtung = createProperty("Weihnachtsbeleuchtung", null, PropertyType.BOOLEAN, null, null);
|
final long scheduleId = createSchedule(true, "schedule");
|
||||||
final BulkDto bulkWeihnachtsbeleuchtungAn = bulkController.create(new BulkCreateDto("Weihnachtsbeleuchtung An", true));
|
|
||||||
bulkEntryController.create(new BulkEntryCreateDto(bulkWeihnachtsbeleuchtungAn.getId(), propWeihnachtsbeleuchtung.getId(), 35, 0));
|
|
||||||
final ZonedDateTime now = ZonedDateTime.now().plusSeconds(3);
|
final ZonedDateTime now = ZonedDateTime.now().plusSeconds(3);
|
||||||
createTime(schedule, true, now.getHour(), now.getMinute(), now.getSecond(), 0, 1, bulkWeihnachtsbeleuchtungAn);
|
createTime(scheduleId, true, now.getHour(), now.getMinute(), now.getSecond(), 0, propertyDirect, 1, bulk);
|
||||||
|
|
||||||
final Property propRollladenSchlafzimmer = createProperty("Schlafzimmer", null, PropertyType.SHUTTER, null, null);
|
|
||||||
final BulkDto bulkRollladenSchlafzimmerAufstehen = bulkController.create(new BulkCreateDto("Rollladen Schlafzimmer Aufstehen", true));
|
|
||||||
bulkEntryController.create(new BulkEntryCreateDto(bulkRollladenSchlafzimmerAufstehen.getId(), propRollladenSchlafzimmer.getId(), 1, 0));
|
|
||||||
createSunrise(schedule, true, Zenith.CIVIL, 0, 1, bulkRollladenSchlafzimmerAufstehen);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Property createProperty(final String title, final String slug, final PropertyType type, final Channel readChannel, final Channel writeChannel) {
|
private Property createProperty(final String title, final PropertyType type, final Channel readChannel, final Channel writeChannel) {
|
||||||
final Property property = propertyRepository.findByTitle(title).orElseGet(() -> propertyRepository.save(new Property(title, type)));
|
final Property property = propertyRepository.findByTitle(title).orElseGet(() -> propertyRepository.save(new Property(title, type)));
|
||||||
property.setSlug(slug == null || slug.isEmpty() ? null : slug);
|
|
||||||
property.setReadChannel(readChannel);
|
property.setReadChannel(readChannel);
|
||||||
property.setWriteChannel(writeChannel);
|
property.setWriteChannel(writeChannel);
|
||||||
return property;
|
return property;
|
||||||
@ -83,26 +72,24 @@ public class DemoDataService {
|
|||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createTime(final long scheduleId, final boolean enabled, final int hour, final int minute, final int second, final int fuzzySeconds, final double value, final BulkDto bulk) {
|
private void createTime(final long scheduleId, final boolean enabled, final int hour, final int minute, final int second, final int fuzzySeconds, final Property property, final double value, final BulkDto bulk) {
|
||||||
newScheduleEntry(scheduleId, enabled, ScheduleEntryType.TIME, null, hour, minute, second, fuzzySeconds, value, bulk);
|
newScheduleEntry(scheduleId, enabled, ScheduleEntryType.TIME, null, hour, minute, second, fuzzySeconds, property, value, bulk);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createSunrise(final long scheduleId, final boolean enabled, final Zenith zenith, final int fuzzySeconds, final double value, final BulkDto bulk) {
|
private void newScheduleEntry(final long scheduleId, final boolean enabled, final ScheduleEntryType type, final Zenith zenith, final int hour, final int minute, final int second, final int fuzzySeconds, final Property property, final double value, final BulkDto bulk) {
|
||||||
newScheduleEntry(scheduleId, enabled, ScheduleEntryType.SUNRISE, zenith, 0, 0, 0, fuzzySeconds, value, bulk);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void newScheduleEntry(final long scheduleId, final boolean enabled, final ScheduleEntryType type, final Zenith zenith, final int hour, final int minute, final int second, final int fuzzySeconds, final double value, final BulkDto bulk) {
|
|
||||||
final long id = scheduleEntryController.create(scheduleId).getId();
|
final long id = scheduleEntryController.create(scheduleId).getId();
|
||||||
scheduleEntryController.setEnabled(id, enabled);
|
scheduleEntryController.setEnabled(id, enabled);
|
||||||
scheduleEntryController.setType(id, type.name());
|
scheduleEntryController.setType(id, type.name());
|
||||||
if (zenith != null) {
|
if (zenith != null) {
|
||||||
scheduleEntryController.setZenith(id, zenith.degrees() + "");
|
scheduleEntryController.setZenith(id, zenith.degrees() + "");
|
||||||
}
|
}
|
||||||
scheduleEntryController.daySecond(id, (hour * 60 + minute) * 60 + second);
|
scheduleEntryController.setHour(id, hour);
|
||||||
|
scheduleEntryController.setMinute(id, minute);
|
||||||
|
scheduleEntryController.setSecond(id, second);
|
||||||
scheduleEntryController.setFuzzySeconds(id, fuzzySeconds);
|
scheduleEntryController.setFuzzySeconds(id, fuzzySeconds);
|
||||||
|
scheduleEntryController.setProperty(id, property == null ? null : property.getId());
|
||||||
scheduleEntryController.setValue(id, value);
|
scheduleEntryController.setValue(id, value);
|
||||||
scheduleEntryController.setBulk(id, bulk == null ? null : bulk.getId());
|
scheduleEntryController.setBulk(id, bulk == null ? null : bulk.getId());
|
||||||
scheduleEntryController.setSkip(id, 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,36 +0,0 @@
|
|||||||
package de.ph87.homeautomation;
|
|
||||||
|
|
||||||
import jakarta.persistence.criteria.Expression;
|
|
||||||
import jakarta.persistence.criteria.Predicate;
|
|
||||||
import org.springframework.data.domain.PageRequest;
|
|
||||||
import org.springframework.data.domain.Sort;
|
|
||||||
import org.springframework.data.jpa.domain.Specification;
|
|
||||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
public class RepositorySearchHelper {
|
|
||||||
|
|
||||||
public static <T, D> List<D> search(final String term, final JpaSpecificationExecutor<T> repository, final String fieldName, final Function<T, D> map) {
|
|
||||||
final Specification<T> specification = (root, query, criteriaBuilder) -> {
|
|
||||||
final Expression<String> field = criteriaBuilder.lower(root.get(fieldName));
|
|
||||||
final String term2 = term
|
|
||||||
.replaceAll("([a-zA-Z])([0-9])", "$1 $2")
|
|
||||||
.replaceAll("([0-9])([a-zA-Z])", "$1 $2")
|
|
||||||
.replaceAll("([A-Z])([A-Z])", "$1 $2")
|
|
||||||
.replaceAll("([a-z])([A-Z])", "$1 $2")
|
|
||||||
.toLowerCase(Locale.ROOT);
|
|
||||||
final List<Predicate> predicates = Arrays.stream(term2.split("\\s"))
|
|
||||||
.filter(word -> !word.isEmpty())
|
|
||||||
.map(word -> criteriaBuilder.like(field, "%" + word + "%"))
|
|
||||||
.toList();
|
|
||||||
return criteriaBuilder.and(predicates.toArray(Predicate[]::new));
|
|
||||||
};
|
|
||||||
final PageRequest pageRequest = PageRequest.of(0, 20, Sort.by(Sort.Direction.ASC, fieldName));
|
|
||||||
return repository.findAll(specification, pageRequest).stream().map(map).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
package de.ph87.homeautomation;
|
package de.ph87.homeautomation;
|
||||||
|
|
||||||
import de.ph87.homeautomation.knx.group.KnxGroupImportService;
|
import de.ph87.homeautomation.knx.group.KnxGroupImportService;
|
||||||
import de.ph87.homeautomation.property.PropertyWriter;
|
import de.ph87.homeautomation.property.PropertyWriteService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.boot.context.event.ApplicationStartedEvent;
|
import org.springframework.boot.context.event.ApplicationStartedEvent;
|
||||||
@ -17,13 +17,13 @@ public class StartupService {
|
|||||||
|
|
||||||
private final DemoDataService demoDataService;
|
private final DemoDataService demoDataService;
|
||||||
|
|
||||||
private final PropertyWriter propertyWriter;
|
private final PropertyWriteService propertyWriteService;
|
||||||
|
|
||||||
@EventListener(ApplicationStartedEvent.class)
|
@EventListener(ApplicationStartedEvent.class)
|
||||||
public void startup() {
|
public void startup() {
|
||||||
knxGroupImportService.importGroups();
|
knxGroupImportService.importGroups();
|
||||||
demoDataService.insertDemoData();
|
demoDataService.insertDemoData();
|
||||||
propertyWriter.updateAllProperties();
|
propertyWriteService.updateAllProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
package de.ph87.homeautomation.bulk;
|
package de.ph87.homeautomation.bulk;
|
||||||
|
|
||||||
import de.ph87.homeautomation.bulk.entry.BulkEntry;
|
import de.ph87.homeautomation.bulk.entry.BulkEntry;
|
||||||
import jakarta.persistence.*;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -16,39 +16,33 @@ import java.util.List;
|
|||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@SuppressWarnings("FieldMayBeFinal")
|
@SuppressWarnings("FieldMayBeFinal")
|
||||||
public class Bulk {
|
public class Bulk {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue
|
@GeneratedValue
|
||||||
private long id;
|
private long id;
|
||||||
|
|
||||||
@Version
|
@Version
|
||||||
private long version;
|
private long version;
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
@Column(nullable = false, unique = true)
|
@Column(nullable = false, unique = true)
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
@ToString.Exclude
|
@ToString.Exclude
|
||||||
@OneToMany(cascade = CascadeType.ALL)
|
@OneToMany(cascade = CascadeType.ALL)
|
||||||
private List<BulkEntry> entries = new ArrayList<>();
|
private List<BulkEntry> entries = new ArrayList<>();
|
||||||
|
|
||||||
@ToString.Include
|
@ToString.Include
|
||||||
public int entryCount() {
|
public int entryCount() {
|
||||||
return entries.size();
|
return entries.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Bulk(final Bulk original, final String name) {
|
|
||||||
this.enabled = original.isEnabled();
|
|
||||||
this.name = name;
|
|
||||||
this.entries = original.getEntries().stream().map(BulkEntry::new).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Bulk(final BulkCreateDto dto) {
|
public Bulk(final BulkCreateDto dto) {
|
||||||
this.enabled = dto.isEnabled();
|
this.enabled = dto.isEnabled();
|
||||||
this.name = dto.getName();
|
this.name = dto.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
package de.ph87.homeautomation.bulk;
|
package de.ph87.homeautomation.bulk;
|
||||||
|
|
||||||
import de.ph87.homeautomation.shared.*;
|
import de.ph87.homeautomation.property.PropertyReader;
|
||||||
import lombok.*;
|
import de.ph87.homeautomation.shared.ISearchController;
|
||||||
import org.springframework.data.domain.*;
|
import de.ph87.homeautomation.shared.SearchResult;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.List;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("bulk")
|
@RequestMapping("bulk")
|
||||||
@ -16,6 +18,8 @@ public class BulkController implements ISearchController {
|
|||||||
|
|
||||||
private final BulkReader bulkReader;
|
private final BulkReader bulkReader;
|
||||||
|
|
||||||
|
private final PropertyReader propertyReader;
|
||||||
|
|
||||||
@PostMapping("create")
|
@PostMapping("create")
|
||||||
public BulkDto create(@RequestBody final BulkCreateDto dto) {
|
public BulkDto create(@RequestBody final BulkCreateDto dto) {
|
||||||
return bulkWriter.createDto(dto);
|
return bulkWriter.createDto(dto);
|
||||||
@ -25,27 +29,17 @@ public class BulkController implements ISearchController {
|
|||||||
public Page<BulkDto> filter(@RequestBody final BulkFilter filter) {
|
public Page<BulkDto> filter(@RequestBody final BulkFilter filter) {
|
||||||
return bulkReader.filter(filter);
|
return bulkReader.filter(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("delete/{id}")
|
@GetMapping("delete/{id}")
|
||||||
public void delete(@PathVariable final long id) {
|
public void delete(@PathVariable final long id) {
|
||||||
bulkWriter.delete(id);
|
bulkWriter.delete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("run/{id}")
|
|
||||||
public void run(@PathVariable final long id) {
|
|
||||||
bulkWriter.run(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("duplicate/{id}")
|
|
||||||
public BulkDto duplicate(@PathVariable final long id) {
|
|
||||||
return bulkWriter.duplicate(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("set/{id}/name")
|
@PostMapping("set/{id}/name")
|
||||||
public BulkDto name(@PathVariable final long id, @RequestBody final String name) {
|
public BulkDto name(@PathVariable final long id, @RequestBody final String name) {
|
||||||
return bulkWriter.set(id, bulk -> bulk.setName(name));
|
return bulkWriter.set(id, bulk -> bulk.setName(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("set/{id}/enabled")
|
@PostMapping("set/{id}/enabled")
|
||||||
public BulkDto enabled(@PathVariable final long id, @RequestBody final boolean enabled) {
|
public BulkDto enabled(@PathVariable final long id, @RequestBody final boolean enabled) {
|
||||||
return bulkWriter.set(id, bulk -> bulk.setEnabled(enabled));
|
return bulkWriter.set(id, bulk -> bulk.setEnabled(enabled));
|
||||||
@ -67,7 +61,7 @@ public class BulkController implements ISearchController {
|
|||||||
@PostMapping("searchLike")
|
@PostMapping("searchLike")
|
||||||
@Deprecated(since = "Use 'filter' instead", forRemoval = true)
|
@Deprecated(since = "Use 'filter' instead", forRemoval = true)
|
||||||
public List<SearchResult> searchLike(@RequestBody final String term) {
|
public List<SearchResult> searchLike(@RequestBody final String term) {
|
||||||
return bulkReader.search(term).stream().map(this::toSearchResult).toList();
|
return bulkReader.findAllDtoLike("%" + term + "%").stream().map(this::toSearchResult).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("findAll")
|
@GetMapping("findAll")
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
package de.ph87.homeautomation.bulk;
|
package de.ph87.homeautomation.bulk;
|
||||||
|
|
||||||
import de.ph87.homeautomation.property.PropertyWriter;
|
import de.ph87.homeautomation.property.PropertyWriteService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@ -12,15 +12,11 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class BulkExecutor {
|
public class BulkExecutor {
|
||||||
|
|
||||||
private final PropertyWriter propertyWriter;
|
private final PropertyWriteService propertyWriteService;
|
||||||
|
|
||||||
public void execute(final Bulk bulk) {
|
public void execute(final Bulk bulk) {
|
||||||
if (!bulk.isEnabled()) {
|
|
||||||
log.info("Bulk disabled, not executing it: {}", bulk);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
log.info("Executing Bulk: {}", bulk);
|
log.info("Executing Bulk: {}", bulk);
|
||||||
bulk.getEntries().forEach(entry -> propertyWriter.writeToChannel(entry.getProperty(), entry.getValue()));
|
bulk.getEntries().forEach(entry -> propertyWriteService.writeToChannel(entry.getProperty(), entry.getValue()));
|
||||||
log.debug("Finished executing Bulk: {}", bulk);
|
log.debug("Finished executing Bulk: {}", bulk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
package de.ph87.homeautomation.bulk;
|
package de.ph87.homeautomation.bulk;
|
||||||
|
|
||||||
import de.ph87.homeautomation.common.Filter;
|
import de.ph87.homeautomation.common.Filter;
|
||||||
import jakarta.persistence.criteria.Predicate;
|
|
||||||
import org.springframework.data.jpa.domain.Specification;
|
import org.springframework.data.jpa.domain.Specification;
|
||||||
|
|
||||||
|
import javax.persistence.criteria.Predicate;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|||||||
@ -1,15 +1,14 @@
|
|||||||
package de.ph87.homeautomation.bulk;
|
package de.ph87.homeautomation.bulk;
|
||||||
|
|
||||||
import de.ph87.homeautomation.*;
|
import de.ph87.homeautomation.property.Property;
|
||||||
import de.ph87.homeautomation.property.*;
|
import de.ph87.homeautomation.web.NotFoundException;
|
||||||
import de.ph87.homeautomation.web.*;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.*;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import lombok.extern.slf4j.*;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.*;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.stereotype.*;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.transaction.annotation.*;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.List;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@ -51,8 +50,4 @@ public class BulkReader {
|
|||||||
return bulkRepository.findDistinctByEntries_Property(property);
|
return bulkRepository.findDistinctByEntries_Property(property);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<BulkDto> search(final String term) {
|
|
||||||
return RepositorySearchHelper.search(term, bulkRepository, "name", bulkMapper::toDto);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,19 +1,19 @@
|
|||||||
package de.ph87.homeautomation.bulk;
|
package de.ph87.homeautomation.bulk;
|
||||||
|
|
||||||
import de.ph87.homeautomation.property.*;
|
import de.ph87.homeautomation.property.Property;
|
||||||
import org.springframework.data.jpa.repository.*;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
public interface BulkRepository extends JpaRepository<Bulk, Long>, JpaSpecificationExecutor<Bulk> {
|
public interface BulkRepository extends JpaRepository<Bulk, Long>, JpaSpecificationExecutor<Bulk> {
|
||||||
|
|
||||||
@Deprecated(since = "Use 'filter' instead", forRemoval = true)
|
@Deprecated(since = "Use 'filter' instead", forRemoval = true)
|
||||||
List<Bulk> findAllByNameLikeIgnoreCase(String term);
|
List<Bulk> findAllByNameLikeIgnoreCase(String term);
|
||||||
|
|
||||||
Optional<Bulk> findByEntries_Id(long id);
|
Optional<Bulk> findByEntries_Id(long id);
|
||||||
|
|
||||||
List<Bulk> findDistinctByEntries_Property(Property property);
|
List<Bulk> findDistinctByEntries_Property(Property property);
|
||||||
|
|
||||||
boolean existsByName(String name);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,65 +1,45 @@
|
|||||||
package de.ph87.homeautomation.bulk;
|
package de.ph87.homeautomation.bulk;
|
||||||
|
|
||||||
import lombok.*;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.*;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.*;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.*;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.function.*;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@Transactional
|
@Transactional
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class BulkWriter {
|
public class BulkWriter {
|
||||||
|
|
||||||
private final BulkRepository bulkRepository;
|
private final BulkRepository bulkRepository;
|
||||||
|
|
||||||
private final BulkReader bulkReader;
|
private final BulkReader bulkReader;
|
||||||
|
|
||||||
private final BulkMapper bulkMapper;
|
private final BulkMapper bulkMapper;
|
||||||
|
|
||||||
private final BulkExecutor bulkExecutor;
|
|
||||||
|
|
||||||
public Bulk create(final BulkCreateDto dto) {
|
public Bulk create(final BulkCreateDto dto) {
|
||||||
final Bulk bulk = bulkRepository.save(new Bulk(dto));
|
final Bulk bulk = bulkRepository.save(new Bulk(dto));
|
||||||
log.info("Bulk created: {}", bulk);
|
log.info("Bulk created: {}", bulk);
|
||||||
return bulk;
|
return bulk;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void delete(final long id) {
|
public void delete(final long id) {
|
||||||
final Bulk bulk = bulkReader.getById(id);
|
final Bulk bulk = bulkReader.getById(id);
|
||||||
bulkRepository.delete(bulk);
|
bulkRepository.delete(bulk);
|
||||||
log.info("Bulk deleted: {}", bulk);
|
log.info("Bulk deleted: {}", bulk);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void run(final long id) {
|
|
||||||
final Bulk bulk = bulkReader.getById(id);
|
|
||||||
bulkExecutor.execute(bulk);
|
|
||||||
}
|
|
||||||
|
|
||||||
public BulkDto duplicate(final long id) {
|
|
||||||
final Bulk original = bulkReader.getById(id);
|
|
||||||
int number = 1;
|
|
||||||
while (true) {
|
|
||||||
final String copyName = original.getName() + " Kopie " + number;
|
|
||||||
if (!bulkRepository.existsByName(copyName)) {
|
|
||||||
final Bulk copy = bulkRepository.save(new Bulk(original, copyName));
|
|
||||||
log.info("Bulk duplicated: {}", copy);
|
|
||||||
return bulkMapper.toDto(copy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public BulkDto set(final long id, final Consumer<Bulk> consumer) {
|
public BulkDto set(final long id, final Consumer<Bulk> consumer) {
|
||||||
final Bulk bulk = bulkReader.getById(id);
|
final Bulk bulk = bulkReader.getById(id);
|
||||||
consumer.accept(bulk);
|
consumer.accept(bulk);
|
||||||
log.info("Changed Bulk: {}", bulk);
|
log.info("Changed Bulk: {}", bulk);
|
||||||
return bulkMapper.toDto(bulk);
|
return bulkMapper.toDto(bulk);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BulkDto createDto(final BulkCreateDto dto) {
|
public BulkDto createDto(final BulkCreateDto dto) {
|
||||||
return bulkMapper.toDto(create(dto));
|
return bulkMapper.toDto(create(dto));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,50 +1,44 @@
|
|||||||
package de.ph87.homeautomation.bulk.entry;
|
package de.ph87.homeautomation.bulk.entry;
|
||||||
|
|
||||||
import de.ph87.homeautomation.property.Property;
|
import de.ph87.homeautomation.property.Property;
|
||||||
import jakarta.persistence.*;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Getter
|
@Getter
|
||||||
@ToString
|
@ToString
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
public class BulkEntry {
|
public class BulkEntry {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue
|
@GeneratedValue
|
||||||
private long id;
|
private long id;
|
||||||
|
|
||||||
@Version
|
@Version
|
||||||
private long version;
|
private long version;
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
private int position;
|
private int position;
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
private Property property;
|
private Property property;
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
@Column(name = "value_")
|
@Column(name = "value_")
|
||||||
private double value;
|
private double value;
|
||||||
|
|
||||||
public BulkEntry(final BulkEntryCreateDto dto, final Property property) {
|
public BulkEntry(final BulkEntryCreateDto dto, final Property property) {
|
||||||
this.position = dto.getPosition();
|
this.position = dto.getPosition();
|
||||||
this.property = property;
|
this.property = property;
|
||||||
this.value = dto.getValue();
|
this.value = dto.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("CopyConstructorMissesField")
|
|
||||||
public BulkEntry(final BulkEntry original) {
|
|
||||||
this.position = original.getPosition();
|
|
||||||
this.property = original.getProperty();
|
|
||||||
this.value = original.getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
package de.ph87.homeautomation.channel;
|
package de.ph87.homeautomation.channel;
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
|
|||||||
@ -13,23 +13,23 @@ import java.util.List;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class ChannelController implements ISearchController {
|
public class ChannelController implements ISearchController {
|
||||||
|
|
||||||
private final ChannelReader channelReader;
|
private final ChannelService channelService;
|
||||||
|
|
||||||
@GetMapping("findAll")
|
@GetMapping("findAll")
|
||||||
public List<? extends ChannelDto> findAll() {
|
public List<? extends ChannelDto> findAll() {
|
||||||
return channelReader.findAllDto();
|
return channelService.findAllDto();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@GetMapping("searchById/{id}")
|
@GetMapping("searchById/{id}")
|
||||||
public SearchResult searchById(@PathVariable final long id) {
|
public SearchResult searchById(@PathVariable final long id) {
|
||||||
return channelReader.findDtoById(id).map(this::toSearchResult).orElseThrow(() -> new NotFoundException("Channel.id=" + id));
|
return channelService.findDtoById(id).map(this::toSearchResult).orElseThrow(() -> new NotFoundException("Channel.id=" + id));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@PostMapping("searchLike")
|
@PostMapping("searchLike")
|
||||||
public List<SearchResult> searchLike(@RequestBody final String term) {
|
public List<SearchResult> searchLike(@RequestBody final String term) {
|
||||||
return channelReader.findAllDtoLike(term).stream().map(this::toSearchResult).toList();
|
return channelService.findAllDtoLike(term).stream().map(this::toSearchResult).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private SearchResult toSearchResult(final ChannelDto dto) {
|
private SearchResult toSearchResult(final ChannelDto dto) {
|
||||||
|
|||||||
@ -2,7 +2,6 @@ package de.ph87.homeautomation.channel;
|
|||||||
|
|
||||||
import de.ph87.homeautomation.property.Property;
|
import de.ph87.homeautomation.property.Property;
|
||||||
import de.ph87.homeautomation.shared.Helpers;
|
import de.ph87.homeautomation.shared.Helpers;
|
||||||
import lombok.NonNull;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@ -16,7 +15,7 @@ import java.util.Optional;
|
|||||||
@Service
|
@Service
|
||||||
@Transactional
|
@Transactional
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class ChannelReader {
|
public class ChannelService {
|
||||||
|
|
||||||
private final List<IChannelOwner> channelOwners;
|
private final List<IChannelOwner> channelOwners;
|
||||||
|
|
||||||
@ -30,11 +29,12 @@ public class ChannelReader {
|
|||||||
return findByChannel(channel).orElseThrow(RuntimeException::new);
|
return findByChannel(channel).orElseThrow(RuntimeException::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
public void write(final Property property, final double value) {
|
||||||
public Optional<Double> read(@NonNull final Property property) {
|
final Channel channel = property.getWriteChannel();
|
||||||
return Optional.ofNullable(property.getReadChannel())
|
if (channel == null) {
|
||||||
.map(this::getByChannel)
|
return;
|
||||||
.map(owner -> owner.read(property.getReadChannel().getId()));
|
}
|
||||||
|
getByChannel(channel).write(property.getWriteChannel().getId(), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChannelDto toDtoAllowNull(final Channel channel) {
|
public ChannelDto toDtoAllowNull(final Channel channel) {
|
||||||
@ -1,36 +0,0 @@
|
|||||||
package de.ph87.homeautomation.channel;
|
|
||||||
|
|
||||||
import de.ph87.homeautomation.property.Property;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@Service
|
|
||||||
@Transactional
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class ChannelWriter {
|
|
||||||
|
|
||||||
private final List<IChannelOwner> channelOwners;
|
|
||||||
|
|
||||||
public Optional<IChannelOwner> findByChannel(final Channel channel) {
|
|
||||||
return channelOwners.stream().filter(owner -> channel.getChannelOwnerClass().isInstance(owner)).findFirst();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IChannelOwner getByChannel(final Channel channel) {
|
|
||||||
return findByChannel(channel).orElseThrow(RuntimeException::new);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void write(final Property property, final double value) {
|
|
||||||
final Channel channel = property.getWriteChannel();
|
|
||||||
if (channel == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
getByChannel(channel).write(property.getWriteChannel().getId(), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -6,8 +6,6 @@ public interface IChannelOwner {
|
|||||||
|
|
||||||
void requestUpdate(final Channel channel);
|
void requestUpdate(final Channel channel);
|
||||||
|
|
||||||
Double read(final long id);
|
|
||||||
|
|
||||||
void write(final long id, final double value);
|
void write(final long id, final double value);
|
||||||
|
|
||||||
ChannelDto toDto(final long id);
|
ChannelDto toDto(final long id);
|
||||||
|
|||||||
@ -15,60 +15,60 @@ import static de.ph87.homeautomation.shared.Helpers.mapOrNull;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class DeviceController {
|
public class DeviceController {
|
||||||
|
|
||||||
private final DeviceReader deviceReader;
|
private final DeviceReadService deviceReadService;
|
||||||
|
|
||||||
private final DeviceWriter deviceWriter;
|
private final DeviceWriteService deviceWriteService;
|
||||||
|
|
||||||
private final PropertyReader propertyReader;
|
private final PropertyReader propertyReader;
|
||||||
|
|
||||||
@GetMapping("findAll")
|
@GetMapping("findAll")
|
||||||
public List<DeviceDto> findAll() {
|
public List<DeviceDto> findAll() {
|
||||||
return deviceReader.findAll();
|
return deviceReadService.findAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("searchById/{id}")
|
@GetMapping("searchById/{id}")
|
||||||
public DeviceDto getById(@PathVariable final long id) {
|
public DeviceDto getById(@PathVariable final long id) {
|
||||||
return deviceReader.getDtoById(id);
|
return deviceReadService.getDtoById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("create")
|
@PostMapping("create")
|
||||||
public DeviceDto create(@RequestBody final String typeString) {
|
public DeviceDto create(@RequestBody final String typeString) {
|
||||||
return deviceWriter.create(typeString);
|
return deviceWriteService.create(typeString);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("delete/{id}")
|
@GetMapping("delete/{id}")
|
||||||
public void delete(@PathVariable final long id) {
|
public void delete(@PathVariable final long id) {
|
||||||
deviceWriter.delete(id);
|
deviceWriteService.delete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("set/{id}/enabled")
|
@PostMapping("set/{id}/enabled")
|
||||||
public DeviceDto setEnabled(@PathVariable final long id, @RequestBody final boolean enabled) {
|
public DeviceDto setEnabled(@PathVariable final long id, @RequestBody final boolean enabled) {
|
||||||
return deviceWriter.set(id, Device::setEnabled, enabled);
|
return deviceWriteService.set(id, Device::setEnabled, enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("set/{id}/title")
|
@PostMapping("set/{id}/title")
|
||||||
public DeviceDto setName(@PathVariable final long id, @RequestBody final String title) {
|
public DeviceDto setName(@PathVariable final long id, @RequestBody final String title) {
|
||||||
return deviceWriter.set(id, Device::setTitle, title);
|
return deviceWriteService.set(id, Device::setTitle, title);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("set/{id}/DeviceSwitch/stateProperty")
|
@PostMapping("set/{id}/DeviceSwitch/stateProperty")
|
||||||
public DeviceDto setDeviceSwitchStateProperty(@PathVariable final long id, @RequestBody(required = false) final Long propertyId) {
|
public DeviceDto setDeviceSwitchStateProperty(@PathVariable final long id, @RequestBody(required = false) final Long propertyId) {
|
||||||
return deviceWriter.setDeviceSwitch(id, (device, v) -> device.setStateProperty(mapOrNull(v, propertyReader::getById)), propertyId);
|
return deviceWriteService.setDeviceSwitch(id, (device, v) -> device.setStateProperty(mapOrNull(v, propertyReader::getById)), propertyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("set/{id}/DeviceStateScene/stateProperty")
|
@PostMapping("set/{id}/DeviceStateScene/stateProperty")
|
||||||
public DeviceDto setDeviceStateSceneStateProperty(@PathVariable final long id, @RequestBody(required = false) final Long propertyId) {
|
public DeviceDto setDeviceStateSceneStateProperty(@PathVariable final long id, @RequestBody(required = false) final Long propertyId) {
|
||||||
return deviceWriter.setDeviceStateScene(id, (device, v) -> device.setStateProperty(mapOrNull(v, propertyReader::getById)), propertyId);
|
return deviceWriteService.setDeviceStateScene(id, (device, v) -> device.setStateProperty(mapOrNull(v, propertyReader::getById)), propertyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("set/{id}/DeviceStateScene/sceneProperty")
|
@PostMapping("set/{id}/DeviceStateScene/sceneProperty")
|
||||||
public DeviceDto setDeviceStateSceneSceneProperty(@PathVariable final long id, @RequestBody(required = false) final Long propertyId) {
|
public DeviceDto setDeviceStateSceneSceneProperty(@PathVariable final long id, @RequestBody(required = false) final Long propertyId) {
|
||||||
return deviceWriter.setDeviceStateScene(id, (device, v) -> device.setSceneProperty(mapOrNull(v, propertyReader::getById)), propertyId);
|
return deviceWriteService.setDeviceStateScene(id, (device, v) -> device.setSceneProperty(mapOrNull(v, propertyReader::getById)), propertyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("set/{id}/DeviceShutter/positionProperty")
|
@PostMapping("set/{id}/DeviceShutter/positionProperty")
|
||||||
public DeviceDto setDeviceShutterPositionProperty(@PathVariable final long id, @RequestBody(required = false) final Long propertyId) {
|
public DeviceDto setDeviceShutterPositionProperty(@PathVariable final long id, @RequestBody(required = false) final Long propertyId) {
|
||||||
return deviceWriter.setDeviceShutter(id, (device, v) -> device.setPositionProperty(mapOrNull(v, propertyReader::getById)), propertyId);
|
return deviceWriteService.setDeviceShutter(id, (device, v) -> device.setPositionProperty(mapOrNull(v, propertyReader::getById)), propertyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import static de.ph87.homeautomation.shared.Helpers.mapOrNull;
|
|||||||
@Service
|
@Service
|
||||||
@Transactional
|
@Transactional
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class DeviceReader {
|
public class DeviceReadService {
|
||||||
|
|
||||||
private final DeviceRepository deviceRepository;
|
private final DeviceRepository deviceRepository;
|
||||||
|
|
||||||
@ -27,11 +27,14 @@ public class DeviceReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public DeviceDto toDto(final Device device) {
|
public DeviceDto toDto(final Device device) {
|
||||||
if (device instanceof final DeviceSwitch deviceSwitch) {
|
if (device instanceof DeviceSwitch) {
|
||||||
|
final DeviceSwitch deviceSwitch = (DeviceSwitch) device;
|
||||||
return new DeviceSwitchDto(deviceSwitch, mapOrNull(deviceSwitch.getStateProperty(), propertyMapper::toDto));
|
return new DeviceSwitchDto(deviceSwitch, mapOrNull(deviceSwitch.getStateProperty(), propertyMapper::toDto));
|
||||||
} else if (device instanceof final DeviceStateScene deviceStateScene) {
|
} else if (device instanceof DeviceStateScene) {
|
||||||
|
final DeviceStateScene deviceStateScene = (DeviceStateScene) device;
|
||||||
return new DeviceStateSceneDto(deviceStateScene, mapOrNull(deviceStateScene.getStateProperty(), propertyMapper::toDto), mapOrNull(deviceStateScene.getSceneProperty(), propertyMapper::toDto));
|
return new DeviceStateSceneDto(deviceStateScene, mapOrNull(deviceStateScene.getStateProperty(), propertyMapper::toDto), mapOrNull(deviceStateScene.getSceneProperty(), propertyMapper::toDto));
|
||||||
} else if (device instanceof final DeviceShutter deviceShutter) {
|
} else if (device instanceof DeviceShutter) {
|
||||||
|
final DeviceShutter deviceShutter = (DeviceShutter) device;
|
||||||
return new DeviceShutterDto(deviceShutter, mapOrNull(deviceShutter.getPositionProperty(), propertyMapper::toDto));
|
return new DeviceShutterDto(deviceShutter, mapOrNull(deviceShutter.getPositionProperty(), propertyMapper::toDto));
|
||||||
}
|
}
|
||||||
throw new RuntimeException();
|
throw new RuntimeException();
|
||||||
@ -1,13 +0,0 @@
|
|||||||
package de.ph87.homeautomation.device;
|
|
||||||
|
|
||||||
import de.ph87.homeautomation.device.devices.DeviceShutter;
|
|
||||||
import de.ph87.homeautomation.property.Property;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface DeviceShutterRepository extends JpaRepository<DeviceShutter, Long> {
|
|
||||||
|
|
||||||
List<DeviceShutter> findDistinctByPositionProperty(Property property);
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
package de.ph87.homeautomation.device;
|
|
||||||
|
|
||||||
import de.ph87.homeautomation.device.devices.DeviceStateScene;
|
|
||||||
import de.ph87.homeautomation.property.Property;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface DeviceStateSceneRepository extends JpaRepository<DeviceStateScene, Long> {
|
|
||||||
|
|
||||||
List<DeviceStateScene> findDistinctByStatePropertyOrSceneProperty(Property state, Property scene);
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
package de.ph87.homeautomation.device;
|
|
||||||
|
|
||||||
import de.ph87.homeautomation.device.devices.DeviceSwitch;
|
|
||||||
import de.ph87.homeautomation.property.Property;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface DeviceSwitchRepository extends JpaRepository<DeviceSwitch, Long> {
|
|
||||||
|
|
||||||
List<DeviceSwitch> findDistinctByStateProperty(Property property);
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -2,7 +2,6 @@ package de.ph87.homeautomation.device;
|
|||||||
|
|
||||||
import de.ph87.homeautomation.device.devices.*;
|
import de.ph87.homeautomation.device.devices.*;
|
||||||
import de.ph87.homeautomation.property.Property;
|
import de.ph87.homeautomation.property.Property;
|
||||||
import de.ph87.homeautomation.property.PropertyDto;
|
|
||||||
import de.ph87.homeautomation.scene.SceneDto;
|
import de.ph87.homeautomation.scene.SceneDto;
|
||||||
import de.ph87.homeautomation.schedule.ScheduleWriter;
|
import de.ph87.homeautomation.schedule.ScheduleWriter;
|
||||||
import de.ph87.homeautomation.web.BadRequestException;
|
import de.ph87.homeautomation.web.BadRequestException;
|
||||||
@ -11,32 +10,22 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.transaction.event.TransactionPhase;
|
|
||||||
import org.springframework.transaction.event.TransactionalEventListener;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
import static de.ph87.homeautomation.shared.Helpers.getCurrentTransactionName;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@Transactional
|
@Transactional
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class DeviceWriter {
|
public class DeviceWriteService {
|
||||||
|
|
||||||
private final DeviceRepository deviceRepository;
|
private final DeviceRepository deviceRepository;
|
||||||
|
|
||||||
private final DeviceReader deviceReader;
|
private final DeviceReadService deviceReadService;
|
||||||
|
|
||||||
private final WebSocketService webSocketService;
|
private final WebSocketService webSocketService;
|
||||||
|
|
||||||
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
|
|
||||||
public void onPropertyChanged(final PropertyDto dto) {
|
|
||||||
log.debug("Listen [{}]: {}", getCurrentTransactionName(), dto.getTitle());
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
public DeviceDto createDeviceSwitch(final Property stateProperty) {
|
public DeviceDto createDeviceSwitch(final Property stateProperty) {
|
||||||
return createDeviceSwitch(null, stateProperty);
|
return createDeviceSwitch(null, stateProperty);
|
||||||
}
|
}
|
||||||
@ -76,13 +65,13 @@ public class DeviceWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public <T> DeviceDto set(final long id, final BiConsumer<Device, T> setter, final T value) {
|
public <T> DeviceDto set(final long id, final BiConsumer<Device, T> setter, final T value) {
|
||||||
final Device device = deviceReader.getById(id);
|
final Device device = deviceReadService.getById(id);
|
||||||
setter.accept(device, value);
|
setter.accept(device, value);
|
||||||
return publish(device, true);
|
return publish(device, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> DeviceDto setDeviceSwitch(final long id, final BiConsumer<DeviceSwitch, T> setter, final T value) {
|
public <T> DeviceDto setDeviceSwitch(final long id, final BiConsumer<DeviceSwitch, T> setter, final T value) {
|
||||||
final Device device = deviceReader.getById(id);
|
final Device device = deviceReadService.getById(id);
|
||||||
if (!(device instanceof DeviceSwitch)) {
|
if (!(device instanceof DeviceSwitch)) {
|
||||||
throw new BadRequestException("Not a DeviceSwitch: %s", device);
|
throw new BadRequestException("Not a DeviceSwitch: %s", device);
|
||||||
}
|
}
|
||||||
@ -91,7 +80,7 @@ public class DeviceWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public <T> DeviceDto setDeviceStateScene(final long id, final BiConsumer<DeviceStateScene, T> setter, final T value) {
|
public <T> DeviceDto setDeviceStateScene(final long id, final BiConsumer<DeviceStateScene, T> setter, final T value) {
|
||||||
final Device device = deviceReader.getById(id);
|
final Device device = deviceReadService.getById(id);
|
||||||
if (!(device instanceof DeviceStateScene)) {
|
if (!(device instanceof DeviceStateScene)) {
|
||||||
throw new BadRequestException("Not a DeviceStateScene: %s", device);
|
throw new BadRequestException("Not a DeviceStateScene: %s", device);
|
||||||
}
|
}
|
||||||
@ -100,7 +89,7 @@ public class DeviceWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public <T> DeviceDto setDeviceShutter(final long id, final BiConsumer<DeviceShutter, T> setter, final T value) {
|
public <T> DeviceDto setDeviceShutter(final long id, final BiConsumer<DeviceShutter, T> setter, final T value) {
|
||||||
final Device device = deviceReader.getById(id);
|
final Device device = deviceReadService.getById(id);
|
||||||
if (!(device instanceof DeviceShutter)) {
|
if (!(device instanceof DeviceShutter)) {
|
||||||
throw new BadRequestException("Not a DeviceShutter: %s", device);
|
throw new BadRequestException("Not a DeviceShutter: %s", device);
|
||||||
}
|
}
|
||||||
@ -109,7 +98,7 @@ public class DeviceWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void delete(final long id) {
|
public void delete(final long id) {
|
||||||
final Device device = deviceReader.getById(id);
|
final Device device = deviceReadService.getById(id);
|
||||||
deviceRepository.delete(device);
|
deviceRepository.delete(device);
|
||||||
publish(device, false);
|
publish(device, false);
|
||||||
}
|
}
|
||||||
@ -129,7 +118,7 @@ public class DeviceWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private DeviceDto publish(final Device device, final boolean existing) {
|
private DeviceDto publish(final Device device, final boolean existing) {
|
||||||
return publish(deviceReader.toDto(device), existing);
|
return publish(deviceReadService.toDto(device), existing);
|
||||||
}
|
}
|
||||||
|
|
||||||
private DeviceDto publish(final DeviceDto dto, final boolean existing) {
|
private DeviceDto publish(final DeviceDto dto, final boolean existing) {
|
||||||
@ -1,11 +1,12 @@
|
|||||||
package de.ph87.homeautomation.device.devices;
|
package de.ph87.homeautomation.device.devices;
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@ToString
|
@ToString
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
package de.ph87.homeautomation.device.devices;
|
package de.ph87.homeautomation.device.devices;
|
||||||
|
|
||||||
import de.ph87.homeautomation.property.Property;
|
import de.ph87.homeautomation.property.Property;
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.ManyToOne;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.ManyToOne;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@ToString
|
@ToString
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
package de.ph87.homeautomation.device.devices;
|
package de.ph87.homeautomation.device.devices;
|
||||||
|
|
||||||
import de.ph87.homeautomation.property.Property;
|
import de.ph87.homeautomation.property.Property;
|
||||||
import jakarta.persistence.ElementCollection;
|
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.FetchType;
|
|
||||||
import jakarta.persistence.ManyToOne;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import javax.persistence.ElementCollection;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.FetchType;
|
||||||
|
import javax.persistence.ManyToOne;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
package de.ph87.homeautomation.device.devices;
|
package de.ph87.homeautomation.device.devices;
|
||||||
|
|
||||||
import de.ph87.homeautomation.property.Property;
|
import de.ph87.homeautomation.property.Property;
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.ManyToOne;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.ManyToOne;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@ToString
|
@ToString
|
||||||
|
|||||||