Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a1f42acc45 | |||
| 1484209704 | |||
| ae1fb19f37 | |||
| 184bf373ad | |||
| c2c82d73c1 | |||
| ddadb1f14b | |||
| 0403eb90d3 | |||
| f4eea9bebe | |||
| 942142f517 | |||
| e6a49d55e2 | |||
| f6fb4f6b61 | |||
| 15365cfbfe | |||
| f98a3035e1 | |||
| 897120028c | |||
| 06e2d77f08 | |||
| fe60416e8c | |||
| bbc4dd6e53 | |||
| 56365214b8 |
@@ -5,4 +5,5 @@
|
||||
/e2e
|
||||
/docs
|
||||
.gitignore
|
||||
*.zip
|
||||
*.zip
|
||||
Jenkinsfile
|
||||
|
||||
10
Changelog.md
Normal file
10
Changelog.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Changelog
|
||||
|
||||
## [1.1.0] - Devices
|
||||
- code integration for faster development
|
||||
- device and multi entity support
|
||||
- dual window view (config + discovery)
|
||||
- fixed config bug
|
||||
|
||||
## [1.0.0] - Initial
|
||||
- Single Entity creation and discovery creation
|
||||
@@ -1,7 +1,25 @@
|
||||
import { DeviceClass, MQTTEntity } from './mqtt_base';
|
||||
import { DeviceClass, iMQTTEntityBase, MQTTEntity } from './mqtt_base';
|
||||
|
||||
export class MqttBinary extends MQTTEntity {
|
||||
dev_cla: DeviceClass = new DeviceClass([
|
||||
override ent_type: string = 'binary';
|
||||
override attrs: Set<string> = new Set([
|
||||
'name',
|
||||
'uniq_id',
|
||||
'stat_t',
|
||||
'pl_on',
|
||||
'pl_off',
|
||||
'dev_cla',
|
||||
]);
|
||||
|
||||
get dev_cla(): DeviceClass {
|
||||
return this._dev_cla;
|
||||
}
|
||||
|
||||
set dev_cla(data: number) {
|
||||
this._dev_cla.value = data;
|
||||
}
|
||||
|
||||
_dev_cla: DeviceClass = new DeviceClass([
|
||||
'battery',
|
||||
'battery_charging',
|
||||
'carbon_monoxide',
|
||||
|
||||
7
src/app/_models/mqtt-device.spec.ts
Normal file
7
src/app/_models/mqtt-device.spec.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { MqttDevice } from './mqtt-device';
|
||||
|
||||
describe('MqttDevice', () => {
|
||||
it('should create an instance', () => {
|
||||
expect(new MqttDevice()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
52
src/app/_models/mqtt-device.ts
Normal file
52
src/app/_models/mqtt-device.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { EventEmitter } from "@angular/core";
|
||||
import { hash } from "./mqtt_base";
|
||||
|
||||
export class MQTTDevice {
|
||||
private _name: string = '';
|
||||
private _identifier: string = '';
|
||||
private _serial_number: string = '';
|
||||
// private configuration_url: string = '';
|
||||
|
||||
topic_updates = new EventEmitter<string>();
|
||||
|
||||
get name() {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
set name(name: string) {
|
||||
this._name = name;
|
||||
if (name == '') this._serial_number = '';
|
||||
else this._serial_number = hash(name);
|
||||
this.topic_updates.next('stat_t');
|
||||
this.topic_updates.next('cmd_t');
|
||||
this.topic_updates.next('bri_cmd_t');
|
||||
}
|
||||
|
||||
get serial_number() {
|
||||
return this._serial_number;
|
||||
}
|
||||
|
||||
set serial_number(serial_number: string) {
|
||||
this._serial_number = serial_number;
|
||||
}
|
||||
|
||||
get indetifier() {
|
||||
this._identifier =
|
||||
this._name == '' && this._serial_number == ''
|
||||
? ''
|
||||
: [this._name, this._serial_number].join('_');
|
||||
return this._identifier;
|
||||
}
|
||||
|
||||
toJSON(full: boolean = false): { [key: string]: string } {
|
||||
if (full)
|
||||
return {
|
||||
...this.toJSON(),
|
||||
name: this._name,
|
||||
serial_number: this._serial_number,
|
||||
};
|
||||
return {
|
||||
ids: this._identifier,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,56 @@
|
||||
import { MQTTEntity } from './mqtt_base';
|
||||
|
||||
export class MqttLight extends MQTTEntity {
|
||||
cmd_t: string = 'command/topic';
|
||||
bri_cmd_t: string = 'brightness/command/topic';
|
||||
pl_on: string = '1';
|
||||
pl_off: string = '0';
|
||||
pl_on: string = '';
|
||||
pl_off: string = '';
|
||||
val_tpl: string = '';
|
||||
bri_cmd_t: string = 'brightness/command/topic';
|
||||
_cmd_t: string = 'command/topic';
|
||||
|
||||
override readonly ent_type: string = 'light';
|
||||
override attrs: Set<string> = new Set([
|
||||
'name',
|
||||
'uniq_id',
|
||||
'stat_t',
|
||||
'pl_on',
|
||||
'pl_off',
|
||||
'val_tpl',
|
||||
'bri_cmd_t',
|
||||
'cmd_t',
|
||||
]);
|
||||
|
||||
get cmd_t() {
|
||||
return this._cmd_t;
|
||||
}
|
||||
set cmd_t(data: string) {
|
||||
this._cmd_t = data;
|
||||
}
|
||||
|
||||
override set name(data: string) {
|
||||
super.name = data;
|
||||
this.topic_updates.next('cmd_t');
|
||||
this.topic_updates.next('bri_cmd_t');
|
||||
}
|
||||
|
||||
override get name() {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
override set uniq_id(data: string) {
|
||||
super.uniq_id = data;
|
||||
this.topic_updates.next('cmd_t');
|
||||
this.topic_updates.next('bri_cmd_t');
|
||||
}
|
||||
|
||||
override get uniq_id() {
|
||||
return this._uniq_id;
|
||||
}
|
||||
|
||||
override publish_topics(index: number = 1): string[] {
|
||||
return [
|
||||
`String stat_topic_${index} = "${this.stat_t}";`,
|
||||
`String cmd_topic_${index} = "${this.cmd_t}";`,
|
||||
`String bri_cmd_topic_${index} = "${this.bri_cmd_t}";`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
import { MQTTEntity } from './mqtt_base';
|
||||
import { iMQTTEntityBase, MQTTEntity } from './mqtt_base';
|
||||
|
||||
export class MqttSensor extends MQTTEntity {
|
||||
unit_of_meas: string = 'meassure';
|
||||
export class MqttSensor extends MQTTEntity implements iMqttSensor {
|
||||
unit_of_meas: string = '';
|
||||
override ent_type: string = 'sensor';
|
||||
override attrs: Set<string> = new Set([
|
||||
'name',
|
||||
'uniq_id',
|
||||
'stat_t',
|
||||
'unit_of_meas',
|
||||
]);
|
||||
}
|
||||
|
||||
export interface iMqttSensor extends iMQTTEntityBase {
|
||||
unit_of_meas: string;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,57 @@
|
||||
import { DeviceClass, MQTTEntity } from './mqtt_base';
|
||||
import { DeviceClass, iMQTTEntityBase, MQTTEntity } from './mqtt_base';
|
||||
|
||||
export class MqttSwitch extends MQTTEntity {
|
||||
dev_cla: DeviceClass = new DeviceClass(['switch', 'outlet']);
|
||||
override ent_type: string = 'switch';
|
||||
override attrs: Set<string> = new Set([
|
||||
'name',
|
||||
'uniq_id',
|
||||
'stat_t',
|
||||
'dev_cla',
|
||||
'cmd_t',
|
||||
'pl_on',
|
||||
'pl_off',
|
||||
]);
|
||||
get dev_cla(): DeviceClass {
|
||||
return this._dev_cla;
|
||||
}
|
||||
|
||||
set dev_cla(data: number) {
|
||||
this._dev_cla.value = data;
|
||||
}
|
||||
|
||||
_dev_cla: DeviceClass = new DeviceClass(['switch', 'outlet']);
|
||||
cmd_t: string = 'command/topic';
|
||||
pl_on: string = '1';
|
||||
pl_off: string = '0';
|
||||
pl_on: string = '';
|
||||
pl_off: string = '';
|
||||
|
||||
override set name(name: string) {
|
||||
super.name = name;
|
||||
this.topic_updates.next('cmd_t');
|
||||
}
|
||||
|
||||
override set uniq_id(uniq_id: string) {
|
||||
super.uniq_id = uniq_id;
|
||||
this.topic_updates.next('cmd_t');
|
||||
}
|
||||
|
||||
override get name() {
|
||||
return this._name;
|
||||
}
|
||||
override get uniq_id() {
|
||||
return this._uniq_id;
|
||||
}
|
||||
|
||||
override publish_topics(index: number = 1): string[] {
|
||||
return [
|
||||
`String stat_topic_${index} = "${this.stat_t}";`,
|
||||
`String cmd_topic_${index} = "${this.cmd_t}";`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export interface iMqttSwitch extends iMQTTEntityBase {
|
||||
cmd_t: string;
|
||||
pl_on: string;
|
||||
pl_off: string;
|
||||
dev_cla: DeviceClass;
|
||||
}
|
||||
|
||||
@@ -1,23 +1,58 @@
|
||||
export class MQTTEntity {
|
||||
name: string = '';
|
||||
stat_t: string = '';
|
||||
uniq_id: string = '';
|
||||
// dev: MQTTDevice | null = null;
|
||||
// entity_type: ENTITY_TYPE = 0;
|
||||
import { EventEmitter, Injectable } from '@angular/core';
|
||||
|
||||
setProperty(name: unknown, value: any): void {
|
||||
if (!this.hasOwnProperty(String(name))) return;
|
||||
this[name as keyof this] = value;
|
||||
@Injectable()
|
||||
export class MQTTEntity implements iMQTTEntityBase {
|
||||
protected _name: string = '';
|
||||
protected _stat_t: string = 'state/topic';
|
||||
protected _uniq_id: string = '';
|
||||
private _size: number = 0;
|
||||
|
||||
attrs = new Set(['name', 'stat_t', 'uniq_id']);
|
||||
topic_updates = new EventEmitter<string>();
|
||||
readonly ent_type: string = 'base';
|
||||
|
||||
get name() {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
getProperty(name: string): any {
|
||||
if (!this.hasOwnProperty(name)) return;
|
||||
return this[name as keyof this];
|
||||
set name(name: string) {
|
||||
this._name = name;
|
||||
if (name == '') return;
|
||||
this._uniq_id = name + '_' + hash(name);
|
||||
this.topic_updates.next('stat_t');
|
||||
}
|
||||
|
||||
toJSON(): any {
|
||||
let jsonObject: { [key: string]: string } = {};
|
||||
for (let prop_name of Object.getOwnPropertyNames(this)) {
|
||||
get stat_t() {
|
||||
return this._stat_t;
|
||||
}
|
||||
|
||||
set stat_t(stat: string) {
|
||||
this._stat_t = stat;
|
||||
}
|
||||
|
||||
get uniq_id() {
|
||||
return this._uniq_id;
|
||||
}
|
||||
|
||||
set uniq_id(uniq_id: string) {
|
||||
this._uniq_id = uniq_id;
|
||||
this.topic_updates.next('stat_t');
|
||||
}
|
||||
|
||||
get display_name() {
|
||||
if (this._uniq_id != '') {
|
||||
return this._uniq_id;
|
||||
}
|
||||
return this._name;
|
||||
}
|
||||
|
||||
publish_topics(index: number = 1): string[] {
|
||||
return [`String stat_topic_${index} = "${this.stat_t}";`];
|
||||
}
|
||||
|
||||
toJSON(): { [key: string]: string | {} } {
|
||||
let jsonObject: { [key: string]: string | {} } = {};
|
||||
for (let prop_name of this.attrs.values()) {
|
||||
let property = this[prop_name as keyof this];
|
||||
if (property == '') continue;
|
||||
jsonObject[prop_name] = String(property);
|
||||
@@ -25,20 +60,20 @@ export class MQTTEntity {
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
// toJSON(): any {
|
||||
// return {
|
||||
// name: this.name,
|
||||
// stat_t: this.stat_t,
|
||||
// uniq_id: this.uniq_id,
|
||||
// };
|
||||
// }
|
||||
}
|
||||
get size(): number {
|
||||
this._size = JSON.stringify(this.toJSON()).length;
|
||||
return this._size;
|
||||
}
|
||||
|
||||
export class MQTTDevice {
|
||||
name: string = '';
|
||||
identifiers: string[] = ['MQTT'];
|
||||
serial_number: string = '';
|
||||
configuration_url: string = '';
|
||||
getProperty(name: string): any {
|
||||
if (!this.attrs.has(name)) return '';
|
||||
return this[name as keyof this];
|
||||
}
|
||||
|
||||
setProperty(name: string, value: any) {
|
||||
if (!this.attrs.has(name)) return;
|
||||
this[name as keyof this] = value;
|
||||
}
|
||||
}
|
||||
|
||||
export class DeviceClass {
|
||||
@@ -53,3 +88,60 @@ export class DeviceClass {
|
||||
return this.choices[this.value];
|
||||
}
|
||||
}
|
||||
|
||||
export interface iMQTTEntityBase {
|
||||
name: string;
|
||||
stat_t: string;
|
||||
uniq_id: string;
|
||||
display_name: string;
|
||||
|
||||
attrs: Set<string>;
|
||||
readonly ent_type: string;
|
||||
topic_updates: EventEmitter<string>;
|
||||
|
||||
publish_topics: (index: number) => string[];
|
||||
toJSON: () => { [key: string]: string | {} };
|
||||
}
|
||||
|
||||
export function hash(str: string, digits: number = 5): string {
|
||||
let seed = cyrb128(str);
|
||||
let rand = sfc32(seed[0], seed[1], seed[2], seed[3]);
|
||||
let rand_num = Math.floor(rand() * 10 ** digits).toString();
|
||||
return rand_num;
|
||||
}
|
||||
|
||||
function cyrb128(str: string) {
|
||||
let h1 = 1779033703,
|
||||
h2 = 3144134277,
|
||||
h3 = 1013904242,
|
||||
h4 = 2773480762;
|
||||
for (let i = 0, k; i < str.length; i++) {
|
||||
k = str.charCodeAt(i);
|
||||
h1 = h2 ^ Math.imul(h1 ^ k, 597399067);
|
||||
h2 = h3 ^ Math.imul(h2 ^ k, 2869860233);
|
||||
h3 = h4 ^ Math.imul(h3 ^ k, 951274213);
|
||||
h4 = h1 ^ Math.imul(h4 ^ k, 2716044179);
|
||||
}
|
||||
h1 = Math.imul(h3 ^ (h1 >>> 18), 597399067);
|
||||
h2 = Math.imul(h4 ^ (h2 >>> 22), 2869860233);
|
||||
h3 = Math.imul(h1 ^ (h3 >>> 17), 951274213);
|
||||
h4 = Math.imul(h2 ^ (h4 >>> 19), 2716044179);
|
||||
(h1 ^= h2 ^ h3 ^ h4), (h2 ^= h1), (h3 ^= h1), (h4 ^= h1);
|
||||
return [h1 >>> 0, h2 >>> 0, h3 >>> 0, h4 >>> 0];
|
||||
}
|
||||
|
||||
function sfc32(a: number, b: number, c: number, d: number) {
|
||||
return function () {
|
||||
a |= 0;
|
||||
b |= 0;
|
||||
c |= 0;
|
||||
d |= 0;
|
||||
let t = (((a + b) | 0) + d) | 0;
|
||||
d = (d + 1) | 0;
|
||||
a = b ^ (b >>> 9);
|
||||
b = (c + (c << 3)) | 0;
|
||||
c = (c << 21) | (c >>> 11);
|
||||
c = (c + t) | 0;
|
||||
return (t >>> 0) / 4294967296;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,51 +1,107 @@
|
||||
import { EventEmitter, Injectable, Input } from '@angular/core';
|
||||
import { MQTTEntity } from '../_models/mqtt_base';
|
||||
import { MqttLight } from '../_models/mqtt-light';
|
||||
import { MqttSwitch } from '../_models/mqtt-switch';
|
||||
import { MqttSensor } from '../_models/mqtt-sensor';
|
||||
import { MqttBinary } from '../_models/mqtt-binary';
|
||||
import { MqttLight } from '../_models/mqtt-light';
|
||||
import { MqttSensor } from '../_models/mqtt-sensor';
|
||||
import { MqttSwitch } from '../_models/mqtt-switch';
|
||||
import { MQTTEntity } from '../_models/mqtt_base';
|
||||
import { MQTTDevice } from '../_models/mqtt-device';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class GeneratorService {
|
||||
public selected_entity: MQTTEntity | null = null;
|
||||
public created_enteties: Array<MQTTEntity> = [];
|
||||
public _selected_entity: MQTTEntity | null = null;
|
||||
public created_enteties: Set<MQTTEntity> = new Set();
|
||||
|
||||
@Input() device_name: string = "";
|
||||
@Input() device_id: string = "";
|
||||
@Input() device_standalone: boolean = false;
|
||||
@Input() upperTopic: string = "";
|
||||
@Input() auto_topic: number = 2;
|
||||
updateObserver: EventEmitter<boolean> = new EventEmitter<boolean>();
|
||||
@Input() auto_topic: boolean = true;
|
||||
@Input() use_device: boolean = false;
|
||||
|
||||
device: MQTTDevice = new MQTTDevice();
|
||||
_upper_topic: string = '';
|
||||
|
||||
constructor() { }
|
||||
|
||||
get_properties() {
|
||||
return Object.getOwnPropertyNames(this.selected_entity);
|
||||
constructor() {
|
||||
this.device.topic_updates.subscribe((topic) => {
|
||||
for (let entity of [...this.created_enteties, this.selected_entity]) {
|
||||
entity?.setProperty(topic, this.updateTopic(entity, topic));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
update(){
|
||||
this.updateObserver.emit();
|
||||
get upper_topic(): string {
|
||||
return this._upper_topic;
|
||||
}
|
||||
|
||||
set upper_topic(value: string) {
|
||||
this._upper_topic = value;
|
||||
for (let entity of [...this.created_enteties, this.selected_entity]) {
|
||||
entity?.setProperty('stat_t', this.updateTopic(entity, 'stat_t'));
|
||||
entity?.setProperty('cmd_t', this.updateTopic(entity, 'cmd_t'));
|
||||
entity?.setProperty('bri_cmd_t', this.updateTopic(entity, 'bri_cmd_t'));
|
||||
}
|
||||
}
|
||||
|
||||
updateObserver: EventEmitter<boolean> = new EventEmitter<boolean>();
|
||||
|
||||
set selected_entity(entity: MQTTEntity | null) {
|
||||
this._selected_entity = entity;
|
||||
entity?.topic_updates.subscribe((topic: string) => {
|
||||
if (this.auto_topic)
|
||||
entity.setProperty(topic, this.updateTopic(entity, topic));
|
||||
});
|
||||
}
|
||||
|
||||
get selected_entity(): MQTTEntity | null {
|
||||
return this._selected_entity;
|
||||
}
|
||||
|
||||
get created_entity_num(): number {
|
||||
return Array.from(this.created_enteties).length;
|
||||
}
|
||||
|
||||
updateTopic(entity: MQTTEntity, topic: string) {
|
||||
let topicStr: string = entity.getProperty(topic).split('/').pop();
|
||||
let customTopic =
|
||||
topicStr == 'topic' || topicStr == topic ? topic : topicStr;
|
||||
return join(
|
||||
'/',
|
||||
this.upper_topic,
|
||||
this.use_device ? this.device.indetifier : entity.ent_type,
|
||||
entity.display_name,
|
||||
customTopic
|
||||
).toLowerCase();
|
||||
}
|
||||
|
||||
createEntity() {
|
||||
console.log('Creating entity...');
|
||||
if (this._selected_entity) {
|
||||
this.created_enteties.add(this._selected_entity);
|
||||
this._selected_entity = null;
|
||||
}
|
||||
}
|
||||
|
||||
deleteEntity(entity: MQTTEntity) {
|
||||
this.created_enteties.delete(entity);
|
||||
}
|
||||
|
||||
has_property(property: string): boolean {
|
||||
if (this.selected_entity == null) return false;
|
||||
if (this.selected_entity.hasOwnProperty(property)) return true;
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
entity_types: { [id: string]: [string, typeof MQTTEntity] } = {
|
||||
'0': ['select type', MQTTEntity],
|
||||
'1': ['light', MqttLight],
|
||||
'2': ['switch', MqttSwitch],
|
||||
'3': ['sensor', MqttSensor],
|
||||
'4': ['binary_sensor', MqttBinary],
|
||||
};
|
||||
}
|
||||
|
||||
export function randomString(length: number): string {
|
||||
return Math.round((Math.pow(36, length + 1) - Math.random() * Math.pow(36, length))).toString(36).slice(1);
|
||||
}
|
||||
|
||||
export const entity_types: { [id: string]: [string, typeof MQTTEntity] } = {
|
||||
'0': ["select type", MQTTEntity],
|
||||
'1': ["light", MqttLight],
|
||||
'2': ["switch", MqttSwitch],
|
||||
'3': ["sensor", MqttSensor],
|
||||
'4': ["binary_sensor", MqttBinary]
|
||||
function join(seperator = '/', first: string, ...args: string[]): string {
|
||||
let result = '';
|
||||
for (let str of args) {
|
||||
if (str == '') continue;
|
||||
result += seperator + str;
|
||||
}
|
||||
if (first == '') return result.slice(1);
|
||||
return first + result;
|
||||
}
|
||||
|
||||
16
src/app/_services/output.service.spec.ts
Normal file
16
src/app/_services/output.service.spec.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { OutputService } from './output.service';
|
||||
|
||||
describe('OutputService', () => {
|
||||
let service: OutputService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(OutputService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
119
src/app/_services/output.service.ts
Normal file
119
src/app/_services/output.service.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { MQTTEntity } from '../_models/mqtt_base';
|
||||
import { GeneratorService } from './generator.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class OutputService {
|
||||
constructor(private generatorService: GeneratorService) {}
|
||||
basecode: {} = { 'mqtt-init': '{testestsdsdf}' };
|
||||
output: boolean = false;
|
||||
_integrated_output: boolean = false;
|
||||
_device_entity_cached: { entity: MQTTEntity; listlen: number } = {
|
||||
entity: new MQTTEntity(),
|
||||
listlen: 0,
|
||||
};
|
||||
|
||||
get device_entity(): MQTTEntity {
|
||||
if (
|
||||
this._device_entity_cached.listlen ===
|
||||
this.generatorService.created_entity_num
|
||||
)
|
||||
return this._device_entity_cached.entity;
|
||||
let entites = Array.from(this.generatorService.created_enteties).sort(
|
||||
(a, b) => {
|
||||
return a.size - b.size;
|
||||
}
|
||||
);
|
||||
this._device_entity_cached = {
|
||||
entity: entites[0],
|
||||
listlen: this.generatorService.created_entity_num,
|
||||
};
|
||||
console.log('sizing');
|
||||
console.log(this._device_entity_cached);
|
||||
return entites[0];
|
||||
}
|
||||
|
||||
get seperate_outputs(): boolean {
|
||||
return this.output && !this._integrated_output;
|
||||
}
|
||||
|
||||
get integrated_output(): boolean {
|
||||
return this.output && this._integrated_output;
|
||||
}
|
||||
|
||||
set integrated_output(value: boolean) {
|
||||
this._integrated_output = value;
|
||||
}
|
||||
|
||||
getDiscoveryString(entity: MQTTEntity, escaped = false): string | {} {
|
||||
let str = entity.toJSON();
|
||||
if (this.generatorService.use_device) {
|
||||
str['dev'] = this.generatorService.device.toJSON(
|
||||
entity.uniq_id == this.device_entity.uniq_id
|
||||
);
|
||||
}
|
||||
|
||||
if (escaped) {
|
||||
return JSON.stringify(str).replaceAll('"', '\\"');
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
getDiscoveryTopic(entity: MQTTEntity): string {
|
||||
return join(
|
||||
'/',
|
||||
'homeassistant',
|
||||
entity.ent_type,
|
||||
entity.display_name,
|
||||
'config'
|
||||
).toLowerCase();
|
||||
}
|
||||
|
||||
integrateCode(): string {
|
||||
let mqtt_init =
|
||||
'#include <EspMQTTClient.h>\n\nEspMQTTClient mqtt(\n\t"wifi_name",\n\t"wifi_pw",\n\t"broker_ip",\n\t"mqtt_acc",\n\t"mqtt_pw",\n\t"device_name",\n\t"mqtt_port"\n);\n';
|
||||
let setup = 'void setup(){\nclient.setMaxPacketSize(512);\n}\n';
|
||||
let onConnectStart = 'void onConnectionEstablished() {\n\tdelay=10\n';
|
||||
let onConnectEnd = '\tmqtt.loop()\n}\n';
|
||||
let loop = 'void loop() {\n\tmqtt.loop();\n\tdelay(100);\n}';
|
||||
|
||||
let discoveryMsg: string[] = [];
|
||||
let publishTopics: string[] = [];
|
||||
for (let [index, entity] of Array.from(
|
||||
this.generatorService.created_enteties
|
||||
).entries()) {
|
||||
discoveryMsg.push('\t// Sending discovery for Entity ' + index);
|
||||
discoveryMsg.push(
|
||||
`\tmqtt.publish(${this.getDiscoveryTopic(
|
||||
entity
|
||||
)}, ${this.getDiscoveryString(entity, true)}, true);\n`
|
||||
);
|
||||
|
||||
publishTopics.push(...entity.publish_topics(index));
|
||||
publishTopics.push();
|
||||
}
|
||||
publishTopics.push('\n');
|
||||
return (
|
||||
mqtt_init +
|
||||
'\n' +
|
||||
publishTopics.join('\n') +
|
||||
setup +
|
||||
onConnectStart +
|
||||
discoveryMsg.join('\n') +
|
||||
onConnectEnd +
|
||||
loop
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function join(seperator = '/', first: string, ...args: string[]): string {
|
||||
let result = '';
|
||||
for (let str of args) {
|
||||
if (str == '') continue;
|
||||
result += seperator + str;
|
||||
}
|
||||
if (first == '') return result.slice(1);
|
||||
return first + result;
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="flex flex-row justify-left gap-4 items-center h-12 mb-2 px-2">
|
||||
<img src="mqtt_creator.ico" alt="app icon" class="h-full">
|
||||
<h2 class="text-3xl font-bold text-myButton text-nowrap" >MQTT Discovery Creator</h2>
|
||||
<p class="w-auto text-nowrap w-full">v.1.0</p>
|
||||
<p class="w-auto text-nowrap w-full">v.1.1</p>
|
||||
<a class="w-auto text-nowrap hover:text-myButton" href="http://www.tomtroeger.de">My Homepage</a>
|
||||
<a class="w-auto text-nowrap hover:text-myButton" href="https://git.letsstein.de/tom/mqtt_creator">Code</a>
|
||||
</div>
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule, provideClientHydration } from '@angular/platform-browser';
|
||||
import {
|
||||
BrowserModule,
|
||||
provideClientHydration,
|
||||
} from '@angular/platform-browser';
|
||||
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
import { GeneratorComponent } from './generator/generator.component';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { EntityComponent } from './entity/entity.component';
|
||||
import { EntityDataComponent } from './entity-data/entity-data.component';
|
||||
import { EntityOutputComponent } from './entity-output/entity-output.component';
|
||||
import { OutputComponent } from './output/output.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
GeneratorComponent,
|
||||
EntityComponent
|
||||
EntityDataComponent,
|
||||
EntityOutputComponent,
|
||||
OutputComponent,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
AppRoutingModule,
|
||||
FormsModule,
|
||||
],
|
||||
providers: [
|
||||
provideClientHydration()
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
imports: [BrowserModule, AppRoutingModule, FormsModule],
|
||||
providers: [provideClientHydration()],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
export class AppModule { }
|
||||
export class AppModule {}
|
||||
|
||||
18
src/app/entity-data/entity-data.component.css
Normal file
18
src/app/entity-data/entity-data.component.css
Normal file
@@ -0,0 +1,18 @@
|
||||
.heading {
|
||||
display: flex;
|
||||
position: inherit;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.heading button {
|
||||
padding: 0.25rem;
|
||||
min-width: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.heading h2 {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
62
src/app/entity-data/entity-data.component.html
Normal file
62
src/app/entity-data/entity-data.component.html
Normal file
@@ -0,0 +1,62 @@
|
||||
<!DOCTYPE html5>
|
||||
<div class="heading" *ngIf="created" >
|
||||
<h2>entity {{ent_index}} - {{basemodel.ent_type}}</h2>
|
||||
<button (click)="generatorService.deleteEntity(basemodel)" ><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-x" viewBox="0 0 16 16">
|
||||
<path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<form class="grid grid-cols-2 gap-4" (submit)="$event.preventDefault();generatorService.createEntity()" >
|
||||
<div class="property" *ngIf="hasProperty('name')">
|
||||
<p>Name</p>
|
||||
<input tabindex="0" #nameinput autocomplete="name" placeholder="entity name" type="text" name="entity_name" [ngModel]="entity_name" (ngModelChange)="entity_name = $event"/>
|
||||
</div>
|
||||
<div class="property" *ngIf="hasProperty('uniq_id')">
|
||||
<p>Uniqe ID</p>
|
||||
<div class="flex-row flex gap-2">
|
||||
<input type="text" name="entity_uniq_id" [ngModel]="entity_uniq_id" (ngModelChange)="entity_uniq_id = $event"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="property" *ngIf="hasProperty('cmd_t')">
|
||||
<p>Command Topic</p>
|
||||
<input placeholder="command/topic" type="text" name="entity_cmd_t" [ngModel]="entity_cmd_t" (ngModelChange)="entity_cmd_t = $event" /> <!-- (input)="lock_auto_topic(); update('entity_cmd_t', $event)"-->
|
||||
</div>
|
||||
<div class="property" *ngIf="hasProperty('bri_cmd_t')">
|
||||
<p>Brightness Command Topic</p>
|
||||
<input placeholder="brightness/command/topic" type="text" name="entity_bri_cmd_t" [ngModel]="entity_bri_cmd_t" (ngModelChange)="entity_bri_cmd_t = $event" /> <!--(ngModelChange)="lock_auto_topic(); update('entity_bri_cmd_t', $event)"-->
|
||||
</div>
|
||||
<div class="property !flex-row" *ngIf="hasProperty('pl_off') || hasProperty('pl_on')">
|
||||
<div *ngIf="hasProperty('pl_on')">
|
||||
<p>Payload on</p>
|
||||
<input placeholder="1" type="text" name="entity_pl_on" [ngModel]="entity_pl_on" (ngModelChange)="entity_pl_on = $event"/>
|
||||
</div>
|
||||
<div *ngIf="hasProperty('pl_off')">
|
||||
<p>Payload off</p>
|
||||
<input placeholder="0" type="text" name="entity_pl_off" [ngModel]="entity_pl_off" (ngModelChange)="entity_pl_off = $event"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="property" *ngIf="hasProperty('unit_of_meas')">
|
||||
<p>Unit of meassurement</p>
|
||||
<input placeholder="°C" type="text" name="entity_unit_of_meas" [ngModel]="entity_unit_of_meas" (ngModelChange)="entity_unit_of_meas = $event"/>
|
||||
</div>
|
||||
<div class="property" *ngIf="hasProperty('val_tpl')">
|
||||
<p>Value Template</p>
|
||||
<input type="text" name="entity_val_tpl" [ngModel]="entity_val_tpl" (ngModelChange)="entity_val_tpl = $event"/>
|
||||
</div>
|
||||
<div class="property " *ngIf="hasProperty('dev_cla')">
|
||||
<p>Device Class</p>
|
||||
<select name="entity_dev_cla" [ngModel]="entity_dev_cla" (ngModelChange)="entity_dev_cla =$event">
|
||||
<option *ngFor="let choice of basemodel.getProperty('dev_cla').choices" value="{{basemodel.getProperty('dev_cla').choices.indexOf(choice)}}">{{choice}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="property" *ngIf="hasProperty('stat_t')">
|
||||
<p>State Topic</p>
|
||||
<div class="flex-row flex gap-2">
|
||||
<input placeholder="state/topic" type="text" name="stat_t" [ngModel]="basemodel.getProperty('stat_t')" (ngModelChange)="basemodel.stat_t= $event" /> <!--(ngModelChange)="lock_auto_topic(); update('stat_t', $event)"-->
|
||||
</div>
|
||||
</div>
|
||||
<div class="property col-span-2" *ngIf="!created">
|
||||
<button class="w-full" (click)="generatorService.createEntity()">Add Entity</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
23
src/app/entity-data/entity-data.component.spec.ts
Normal file
23
src/app/entity-data/entity-data.component.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { EntityDataComponent } from './entity-data.component';
|
||||
|
||||
describe('EntityDataComponent', () => {
|
||||
let component: EntityDataComponent;
|
||||
let fixture: ComponentFixture<EntityDataComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [EntityDataComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(EntityDataComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
157
src/app/entity-data/entity-data.component.ts
Normal file
157
src/app/entity-data/entity-data.component.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
import { Component, Input, ViewChild } from '@angular/core';
|
||||
import { DeviceClass, MQTTEntity } from '../_models/mqtt_base';
|
||||
import { GeneratorService } from '../_services/generator.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-entity-data',
|
||||
templateUrl: './entity-data.component.html',
|
||||
styleUrl: './entity-data.component.css',
|
||||
})
|
||||
export class EntityDataComponent {
|
||||
@Input() entity_type: number = 1;
|
||||
@Input() basemodel!: MQTTEntity;
|
||||
@Input() created: boolean = false;
|
||||
@Input() ent_index: number = 0;
|
||||
|
||||
@ViewChild('nameinput') nameinput!: any;
|
||||
constructor(public generatorService: GeneratorService) {
|
||||
if (generatorService.selected_entity) {
|
||||
this.basemodel = generatorService.selected_entity;
|
||||
}
|
||||
}
|
||||
ngAfterViewInit() {
|
||||
if (!this.created) this.nameinput.nativeElement.focus();
|
||||
}
|
||||
|
||||
_entity_name: string = '';
|
||||
_entity_uniq_id: string = '';
|
||||
_entity_stat_t: string = '';
|
||||
_entity_cmd_t: string = '';
|
||||
_entity_bri_cmd_t: string = '';
|
||||
_entity_pl_off: string = '';
|
||||
_entity_pl_on: string = '';
|
||||
_entity_unit_of_meas: string = '';
|
||||
_entity_val_tpl: string = '';
|
||||
_entity_dev_cla: DeviceClass = new DeviceClass([]);
|
||||
|
||||
get entity_name() {
|
||||
let base = this.basemodel.getProperty('name');
|
||||
if (base == '') {
|
||||
this.basemodel.setProperty('name', this._entity_name);
|
||||
return this._entity_name;
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
set entity_name(data: string) {
|
||||
this._entity_name = data;
|
||||
this.basemodel.setProperty('name', data);
|
||||
}
|
||||
|
||||
get entity_uniq_id() {
|
||||
let base = this.basemodel.getProperty('uniq_id');
|
||||
if (base == '') {
|
||||
this.basemodel.setProperty('uniq_id', this._entity_uniq_id);
|
||||
return this._entity_uniq_id;
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
set entity_uniq_id(data: string) {
|
||||
this._entity_uniq_id = data;
|
||||
this.basemodel.setProperty('uniq_id', data);
|
||||
}
|
||||
|
||||
get entity_stat_t() {
|
||||
let base = this.basemodel.getProperty('stat_t');
|
||||
if (base == 'state/topic') {
|
||||
this.basemodel.setProperty('stat_t', this._entity_stat_t);
|
||||
return this._entity_stat_t;
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
set entity_stat_t(data: string) {
|
||||
this._entity_stat_t = data;
|
||||
this.basemodel.setProperty('stat_t', data);
|
||||
}
|
||||
|
||||
get entity_cmd_t() {
|
||||
let base = this.basemodel.getProperty('cmd_t');
|
||||
if (base == 'command/topic') {
|
||||
this.basemodel.setProperty('cmd_t', this._entity_cmd_t);
|
||||
return this._entity_cmd_t;
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
set entity_cmd_t(data: string) {
|
||||
this._entity_cmd_t = data;
|
||||
this.basemodel.setProperty('cmd_t', data);
|
||||
}
|
||||
|
||||
get entity_bri_cmd_t() {
|
||||
let base = this.basemodel.getProperty('bri_cmd_t');
|
||||
if (base == 'brightness/command/topic') {
|
||||
this.basemodel.setProperty('bri_cmd_t', this._entity_bri_cmd_t);
|
||||
return this._entity_bri_cmd_t;
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
set entity_bri_cmd_t(data: string) {
|
||||
this._entity_bri_cmd_t = data;
|
||||
this.basemodel.setProperty('bri_cmd_t', data);
|
||||
}
|
||||
|
||||
get entity_pl_off() {
|
||||
return this._entity_pl_off;
|
||||
}
|
||||
|
||||
set entity_pl_off(data: string) {
|
||||
this._entity_pl_off = data;
|
||||
this.basemodel.setProperty('pl_off', data);
|
||||
}
|
||||
|
||||
get entity_pl_on() {
|
||||
return this._entity_pl_on;
|
||||
}
|
||||
|
||||
set entity_pl_on(data: string) {
|
||||
this._entity_pl_on = data;
|
||||
this.basemodel.setProperty('pl_on', data);
|
||||
}
|
||||
|
||||
get entity_unit_of_meas() {
|
||||
return this._entity_unit_of_meas;
|
||||
}
|
||||
|
||||
set entity_unit_of_meas(data: string) {
|
||||
this._entity_unit_of_meas = data;
|
||||
this.basemodel.setProperty('unit_of_meas', data);
|
||||
}
|
||||
|
||||
get entity_val_tpl() {
|
||||
return this._entity_val_tpl;
|
||||
}
|
||||
|
||||
set entity_val_tpl(data: string) {
|
||||
this._entity_val_tpl = data;
|
||||
this.basemodel.setProperty('val_tpl', data);
|
||||
}
|
||||
|
||||
get entity_dev_cla(): number {
|
||||
this._entity_dev_cla = this.basemodel.getProperty('dev_cla');
|
||||
return this._entity_dev_cla.value;
|
||||
}
|
||||
|
||||
set entity_dev_cla(data: number) {
|
||||
data = Number(data);
|
||||
this._entity_dev_cla.value = data;
|
||||
this.basemodel.setProperty('dev_cla', data);
|
||||
}
|
||||
|
||||
hasProperty(property: string) {
|
||||
return this.basemodel.attrs.has(property);
|
||||
}
|
||||
}
|
||||
5
src/app/entity-output/entity-output.component.css
Normal file
5
src/app/entity-output/entity-output.component.css
Normal file
@@ -0,0 +1,5 @@
|
||||
.jsonPrev {
|
||||
max-width: 50%;
|
||||
text-wrap: wrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
12
src/app/entity-output/entity-output.component.html
Normal file
12
src/app/entity-output/entity-output.component.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<div class="property" >
|
||||
<p>Discovery String</p>
|
||||
<div class="flex flex-row gap-2">
|
||||
<pre class="jsonPrev" >{{outputService.getDiscoveryString(basemodel) | json}}</pre>
|
||||
<span contenteditable>{{outputService.getDiscoveryString(basemodel, true)}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="property" >
|
||||
<p>Discovery Topic</p>
|
||||
<input type="text" readonly [ngModel]="outputService.getDiscoveryTopic(basemodel)">
|
||||
</div>
|
||||
23
src/app/entity-output/entity-output.component.spec.ts
Normal file
23
src/app/entity-output/entity-output.component.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { EntityOutputComponent } from './entity-output.component';
|
||||
|
||||
describe('EntityOutputComponent', () => {
|
||||
let component: EntityOutputComponent;
|
||||
let fixture: ComponentFixture<EntityOutputComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [EntityOutputComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(EntityOutputComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
13
src/app/entity-output/entity-output.component.ts
Normal file
13
src/app/entity-output/entity-output.component.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { MQTTEntity } from '../_models/mqtt_base';
|
||||
import { OutputService } from '../_services/output.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-entity-output',
|
||||
templateUrl: './entity-output.component.html',
|
||||
styleUrl: './entity-output.component.css',
|
||||
})
|
||||
export class EntityOutputComponent {
|
||||
constructor(public outputService: OutputService) {}
|
||||
@Input() basemodel!: MQTTEntity;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
pre{
|
||||
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
<main class="grid grid-cols-2 gap-4">
|
||||
<!-- Entity -->
|
||||
<div class="col-span-2 property">
|
||||
<h3>EntityTyp:</h3>
|
||||
<select [ngModel]="entity_type" (ngModelChange)="select_type($event)">
|
||||
<option *ngFor="let key of useObject.keys(entities)" value="{{key}}">{{entities[key][0]}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="property" *ngIf="generatorService.has_property('name')">
|
||||
<p>Name</p>
|
||||
<input required placeholder="Name here" type="text"[ngModel]="entity_name" (ngModelChange)="entity_name = $event; update()"/>
|
||||
</div>
|
||||
<div class="property" *ngIf="generatorService.has_property('uniq_id')">
|
||||
<p>Uniqe ID</p>
|
||||
<div class="flex-row flex gap-2">
|
||||
<input type="text"[ngModel]="entity_uniq_id" (ngModelChange)="entity_uniq_id = $event; update()"/>
|
||||
<button (click)="entity_uniq_id = useRandomString(10); update()" class="randomButton" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-dice-3" viewBox="0 0 16 16">
|
||||
<path d="M13 1a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2zM3 0a3 3 0 0 0-3 3v10a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V3a3 3 0 0 0-3-3z"/>
|
||||
<path d="M5.5 4a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0m8 8a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0m-4-4a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="property" *ngIf="generatorService.has_property('cmd_t')">
|
||||
<p>Command Topic</p>
|
||||
<input type="text" [ngModel]="entity_cmd_t" (input)="lock_auto_topic(); update('entity_cmd_t', $event)"/>
|
||||
</div>
|
||||
<div class="property" *ngIf="generatorService.has_property('bri_cmd_t')">
|
||||
<p>Brightness Command Topic</p>
|
||||
<input type="text" [ngModel]="entity_bri_cmd_t" (ngModelChange)="lock_auto_topic(); update('entity_bri_cmd_t', $event)"/>
|
||||
</div>
|
||||
<div class="property !flex-row" *ngIf="generatorService.has_property('pl_off') || generatorService.has_property('pl_on')">
|
||||
<div *ngIf="generatorService.has_property('pl_off')">
|
||||
<p>Payload off</p>
|
||||
<input type="text" [ngModel]="entity_pl_off" (ngModelChange)="entity_pl_off = $event"/>
|
||||
</div>
|
||||
<div *ngIf="generatorService.has_property('pl_on')">
|
||||
<p>Payload on</p>
|
||||
<input type="text" [ngModel]="entity_pl_on" (ngModelChange)="entity_pl_on = $event"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="property" *ngIf="generatorService.has_property('unit_of_meas')">
|
||||
<p>Unit of meassurement</p>
|
||||
<input type="text" [ngModel]="entity_unit_of_meas" (ngModelChange)="entity_unit_of_meas = $event"/>
|
||||
</div>
|
||||
<div class="property" *ngIf="generatorService.has_property('val_tpl')">
|
||||
<p>Value Template</p>
|
||||
<input type="text" [ngModel]="entity_val_tpl" (ngModelChange)="entity_val_tpl = $event"/>
|
||||
</div>
|
||||
<div class="property bg-mySecondary" *ngIf="generatorService.has_property('dev_cla')">
|
||||
<p>Device Class</p>
|
||||
<select [ngModel]="entity_dev_cla.value" (ngModelChange)="entity_dev_cla.value =$event">
|
||||
<option *ngFor="let choice of entity_dev_cla.choices" value="{{entity_dev_cla.choices.indexOf(choice)}}">{{choice}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="property" *ngIf="generatorService.has_property('stat_t')">
|
||||
<p>State Topic</p>
|
||||
<div class="flex-row flex gap-2">
|
||||
<input type="text" [ngModel]="stat_t" (ngModelChange)="lock_auto_topic(); update('stat_t', $event)"/>
|
||||
</div>
|
||||
</div>
|
||||
<button class="col-span-2 py-1" (click)="create_entity()" >Create Entity</button>
|
||||
<ng-container *ngIf="showDiscovery">
|
||||
<div class="col-span-2 property" >
|
||||
<p>Discovery String</p>
|
||||
<div class="flex flex-row gap-2">
|
||||
<pre>{{discoveryString | json}}</pre>
|
||||
<span contenteditable>{{JSONdiscoveryString}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-2 property" >
|
||||
<p>Discovery Topic</p>
|
||||
<input type="text" readonly [ngModel]="_discoveryTopic">
|
||||
</div>
|
||||
</ng-container>
|
||||
</main>
|
||||
@@ -1,177 +0,0 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import {
|
||||
GeneratorService,
|
||||
entity_types,
|
||||
randomString,
|
||||
} from '../_services/generator.service';
|
||||
import { MqttBinary } from '../_models/mqtt-binary';
|
||||
import { DeviceClass, MQTTEntity } from '../_models/mqtt_base';
|
||||
|
||||
@Component({
|
||||
selector: 'app-entity',
|
||||
templateUrl: './entity.component.html',
|
||||
styleUrl: './entity.component.css',
|
||||
})
|
||||
export class EntityComponent {
|
||||
constructor(public generatorService: GeneratorService) {
|
||||
generatorService.updateObserver.subscribe((date) => {
|
||||
this.update();
|
||||
});
|
||||
}
|
||||
|
||||
readonly useObject = Object;
|
||||
readonly entities = entity_types;
|
||||
readonly useRandomString = randomString;
|
||||
|
||||
showDiscovery: boolean = false;
|
||||
_discoveryTopic: string = '';
|
||||
|
||||
@Input() entity_type: number = 0;
|
||||
@Input() stat_t: string = '';
|
||||
@Input() basemodel: MQTTEntity | null = null;
|
||||
|
||||
@Input() entity_name: string = '';
|
||||
@Input() entity_uniq_id: string = '';
|
||||
@Input() entity_cmd_t: string = '';
|
||||
@Input() entity_bri_cmd_t: string = '';
|
||||
@Input() entity_pl_off: string = '0';
|
||||
@Input() entity_pl_on: string = '1';
|
||||
@Input() entity_unit_of_meas: string = '';
|
||||
@Input() entity_val_tpl: string = '';
|
||||
@Input() entity_dev_cla: DeviceClass = new DeviceClass([]);
|
||||
|
||||
get display_name() {
|
||||
if (this.entity_name != '' && this.entity_uniq_id != '') {
|
||||
return this.entity_name + '_' + this.entity_uniq_id;
|
||||
} else {
|
||||
return this.entity_name;
|
||||
}
|
||||
}
|
||||
|
||||
update(model: keyof this = 'stat_t', event: Event | boolean = false): void {
|
||||
if (this.generatorService.auto_topic == 2) {
|
||||
this.stat_t = this.updateTopic('stat');
|
||||
this.entity_cmd_t = this.updateTopic('cmd');
|
||||
this.entity_bri_cmd_t = this.updateTopic('bri_cmd');
|
||||
return;
|
||||
}
|
||||
if (event instanceof Event)
|
||||
var value = (event.target as HTMLInputElement).value;
|
||||
else return;
|
||||
if (this.generatorService.auto_topic == 1) {
|
||||
this.sync_topcis(model, value);
|
||||
} else {
|
||||
if (!this.hasOwnProperty(model)) return;
|
||||
if (typeof this[model] === 'string') {
|
||||
this[model] = value as (typeof this)[keyof this];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sync_topcis(model: keyof this, value: string) {
|
||||
let new_list = value.split('/');
|
||||
let old_list = String(this[model]).split('/');
|
||||
let missing = new_list.filter((item) => old_list.indexOf(item) < 0);
|
||||
|
||||
if (old_list.filter((item) => new_list.indexOf(item) < 0).length > 1)
|
||||
return (this.generatorService.auto_topic = 0);
|
||||
if (missing.length == 0) return; //(this.generatorService.auto_topic = 2);
|
||||
if (new_list.indexOf(missing[0]) == new_list.length - 1) {
|
||||
this[model] = value as (typeof this)[keyof this];
|
||||
return;
|
||||
}
|
||||
let missing_index = new_list.indexOf(missing[0]);
|
||||
this.stat_t = this.replaceComponent(this.stat_t, missing[0], missing_index);
|
||||
this.entity_cmd_t = this.replaceComponent(
|
||||
this.entity_cmd_t,
|
||||
missing[0],
|
||||
missing_index
|
||||
);
|
||||
this.entity_bri_cmd_t = this.replaceComponent(
|
||||
this.entity_bri_cmd_t,
|
||||
missing[0],
|
||||
missing_index
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
replaceComponent(object: string, value: string, index: number) {
|
||||
let param_list = object.split('/');
|
||||
param_list[index] = value;
|
||||
return param_list.join('/');
|
||||
}
|
||||
|
||||
updateTopic(topic: string) {
|
||||
let topic_str = join(
|
||||
'/',
|
||||
this.generatorService.upperTopic,
|
||||
entity_types[this.entity_type][0],
|
||||
this.generatorService.device_name,
|
||||
this.display_name,
|
||||
topic
|
||||
).toLowerCase();
|
||||
return topic_str;
|
||||
}
|
||||
|
||||
select_type(event: unknown) {
|
||||
let ent_type = entity_types[event as keyof typeof entity_types];
|
||||
let ent_class = ent_type[1];
|
||||
if (typeof ent_class === 'function' && event != 0) {
|
||||
this.basemodel = new ent_class();
|
||||
this.generatorService.selected_entity = this.basemodel;
|
||||
if (this.generatorService.has_property('dev_cla')) {
|
||||
this.entity_dev_cla = this.basemodel.getProperty('dev_cla');
|
||||
}
|
||||
} else this.generatorService.selected_entity = null;
|
||||
this.entity_type = event as number;
|
||||
this.update();
|
||||
}
|
||||
|
||||
lock_auto_topic() {
|
||||
this.generatorService.auto_topic = 1;
|
||||
}
|
||||
|
||||
create_entity() {
|
||||
this.basemodel?.setProperty('stat_t', this.stat_t);
|
||||
this.basemodel?.setProperty('name', this.entity_name);
|
||||
this.basemodel?.setProperty('cmd_t', this.entity_cmd_t);
|
||||
this.basemodel?.setProperty('bri_cmd_t', this.entity_bri_cmd_t);
|
||||
this.basemodel?.setProperty('pl_off', this.entity_pl_off);
|
||||
this.basemodel?.setProperty('pl_on', this.entity_pl_on);
|
||||
this.basemodel?.setProperty('unit_of_meas', this.entity_unit_of_meas);
|
||||
this.basemodel?.setProperty('val_tpl', this.entity_val_tpl);
|
||||
this.basemodel?.setProperty('dev_cla', this.entity_dev_cla);
|
||||
this._discoveryTopic = this.discoveryTopic;
|
||||
this.showDiscovery = true;
|
||||
return;
|
||||
}
|
||||
|
||||
get discoveryString() {
|
||||
return this.basemodel?.toJSON();
|
||||
}
|
||||
|
||||
get JSONdiscoveryString() {
|
||||
return JSON.stringify(this.basemodel?.toJSON()).replaceAll('"', '\\"');
|
||||
}
|
||||
|
||||
get discoveryTopic() {
|
||||
if (this.entity_type == 0) return '';
|
||||
return join(
|
||||
'/',
|
||||
'homeassistant',
|
||||
entity_types[this.entity_type][0],
|
||||
this.display_name
|
||||
).toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
function join(seperator = '/', first: string, ...args: string[]): string {
|
||||
let result = '';
|
||||
for (let str of args) {
|
||||
if (str == '') continue;
|
||||
result += seperator + str;
|
||||
}
|
||||
if (first == '') return result.slice(1);
|
||||
return first + result;
|
||||
}
|
||||
@@ -1,32 +1,53 @@
|
||||
#mainContainer {
|
||||
display: grid;
|
||||
grid-template-columns: 3fr 1fr;
|
||||
gap: 0.5rem;
|
||||
transition: all 0.25s;
|
||||
}
|
||||
|
||||
.genContainer,
|
||||
.outContainer {
|
||||
background: #9d9d9d;
|
||||
border-radius: 1rem;
|
||||
padding: 0.75rem;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.genContainer {
|
||||
background: #9D9D9D;
|
||||
border-radius: 1rem;
|
||||
padding: 1rem .8rem ;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
grid-column: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.customCheckboxContainer{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: .5rem;
|
||||
.outContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
.customCheckboxContainer input[type="radio"]{
|
||||
display: none;
|
||||
.customCheckboxContainer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.customCheckbox{
|
||||
width: 100%;
|
||||
padding: .25rem 0;
|
||||
text-align: center;
|
||||
background: #B3B3B3;
|
||||
transition: background .25s ease-in-out, color .25s ease-in-out;
|
||||
user-select: none;
|
||||
.customCheckboxContainer input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.customCheckboxContainer input:checked + label{
|
||||
background: var(--accent);
|
||||
color: var(--secondary);
|
||||
}
|
||||
.customCheckbox {
|
||||
width: 100%;
|
||||
padding: 0.25rem 0;
|
||||
text-align: center;
|
||||
background: #b3b3b3;
|
||||
transition: background 0.25s ease-in-out, color 0.25s ease-in-out;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.customCheckboxContainer input:checked + label {
|
||||
background: var(--accent);
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
.outEnable {
|
||||
grid-template-columns: 1fr 1fr !important;
|
||||
}
|
||||
|
||||
@@ -1,28 +1,74 @@
|
||||
<!DOCTYPE html>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="genContainer">
|
||||
<div id="mainContainer" class="" [ngClass]="{'outEnable':outputService.output}">
|
||||
<div class="genContainer grid grid-cols-2 gap-4">
|
||||
<!-- Statetopic -->
|
||||
<!-- <h3>Data Channel</h3> -->
|
||||
|
||||
<div class="property">
|
||||
<div class="property col-span-2">
|
||||
<p>Bereich</p>
|
||||
<input type="text" [ngModel]="generatorService.upperTopic"
|
||||
(ngModelChange)="generatorService.upperTopic = $event; generatorService.update()" />
|
||||
<input type="text" [ngModel]="generatorService.upper_topic"
|
||||
(ngModelChange)="generatorService.upper_topic = $event" />
|
||||
</div>
|
||||
<div class="property">
|
||||
<p>Automatische Topics</p>
|
||||
<div class="customCheckboxContainer">
|
||||
<input [ngModel]="auto_topic" (ngModelChange)="auto_topic=$event" id="auto_topic_0" type="radio" name="auto_topic" value="2">
|
||||
<label class="customCheckbox" for="auto_topic_0">Alles</label>
|
||||
<input [ngModel]="auto_topic" (ngModelChange)="auto_topic=$event" id="auto_topic_1" type="radio" name="auto_topic" value="1">
|
||||
<label class="customCheckbox" for="auto_topic_1">Änderungen übernehmen</label>
|
||||
<input [ngModel]="auto_topic" (ngModelChange)="auto_topic=$event" id="auto_topic_2" type="radio" name="auto_topic" value="0">
|
||||
<label class="customCheckbox" for="auto_topic_2">Aus</label>
|
||||
<div class="property" >
|
||||
<p>Device</p>
|
||||
<div class="customCheckboxContainer" >
|
||||
<input [ngModel]="generatorService.use_device" (ngModelChange)="generatorService.use_device = $event" id="device" type="checkbox">
|
||||
<label class="customCheckbox" for="device" >combine in device</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="property">
|
||||
<p>Automatic Topics</p>
|
||||
<div class="customCheckboxContainer">
|
||||
<input [ngModel]="generatorService.auto_topic" (ngModelChange)="generatorService.auto_topic=$event" id="auto_topic" type="checkbox" name="auto_topic">
|
||||
<label class="customCheckbox" for="auto_topic">automatically generate mqtt topics</label>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="generatorService.use_device">
|
||||
<div class="property">
|
||||
<p>Device Name</p>
|
||||
<input type="text" [ngModel]="device.name" (ngModelChange)="device.name = $event" >
|
||||
</div>
|
||||
<div class="property">
|
||||
<p>Device Identifier</p>
|
||||
<input type="text" [ngModel]="device.indetifier">
|
||||
</div>
|
||||
<!-- <div class="property">
|
||||
<p>Device Serial Number</p>
|
||||
</div>
|
||||
<div class="property">
|
||||
<p>Device Config Url</p>
|
||||
</div> -->
|
||||
</ng-container>
|
||||
<div class="col-span-2 property">
|
||||
<h3>EntityTyp:</h3>
|
||||
<select #typeinput autofocus [ngModel]="entity_type" (ngModelChange)="select_type($event)">
|
||||
<option *ngFor="let key of useObject.keys(generatorService.entity_types)" value="{{key}}">{{generatorService.entity_types[key][0]}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<app-entity class="genContainer" ></app-entity>
|
||||
<div class="outContainer">
|
||||
<h2>Output</h2>
|
||||
<div class="customCheckboxContainer">
|
||||
<input id="output" type="checkbox" [ngModel]="outputService.output" (ngModelChange)="outputService.output = $event">
|
||||
<label class="customCheckbox" for="output" >show output</label>
|
||||
</div>
|
||||
<p>Seperate Output</p>
|
||||
<div class="customCheckboxContainer">
|
||||
<input id="seperate_output" type="checkbox" [ngModel]="outputService.integrated_output" (ngModelChange)="outputService.integrated_output = $event">
|
||||
<label class="customCheckbox" for="seperate_output">Integrate into Code</label>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="generatorService.selected_entity == null" >
|
||||
<button (click)="select_type(entity_type)" class="genContainer !col-span-2" id="entityPlaceholder">Create new entity</button>
|
||||
</ng-container>
|
||||
<app-entity-data class="genContainer" [basemodel]="generatorService.selected_entity" *ngIf="generatorService.selected_entity != null" ></app-entity-data>
|
||||
<app-output class="outContainer" [ngStyle]="{'grid-row': codeSpan}" *ngIf="outputService.integrated_output" ></app-output>
|
||||
<ng-container *ngFor="let entity of created_entities.reverse()">
|
||||
<app-entity-data class="genContainer" [basemodel]="entity" [created]="true" [ent_index]="created_entities.indexOf(entity)" ></app-entity-data>
|
||||
<app-entity-output class="outContainer" *ngIf="outputService.seperate_outputs" [basemodel]="entity" ></app-entity-output>
|
||||
</ng-container>
|
||||
</div>
|
||||
@@ -1,44 +1,49 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { MqttLight } from '../_models/mqtt-light';
|
||||
import { MqttSwitch } from '../_models/mqtt-switch';
|
||||
import { MqttSensor } from '../_models/mqtt-sensor';
|
||||
import { MqttBinary } from '../_models/mqtt-binary';
|
||||
import { GeneratorService, entity_types, randomString } from '../_services/generator.service';
|
||||
import { Component, Input, ViewChild } from '@angular/core';
|
||||
import { GeneratorService } from '../_services/generator.service';
|
||||
import { MQTTEntity } from '../_models/mqtt_base';
|
||||
import { OutputService } from '../_services/output.service';
|
||||
import { MQTTDevice } from '../_models/mqtt-device';
|
||||
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
templateUrl: './generator.component.html',
|
||||
styleUrl: './generator.component.css'
|
||||
styleUrl: './generator.component.css',
|
||||
})
|
||||
|
||||
export class GeneratorComponent {
|
||||
constructor(public generatorService: GeneratorService) {
|
||||
}
|
||||
|
||||
constructor(
|
||||
public generatorService: GeneratorService,
|
||||
public outputService: OutputService
|
||||
) {}
|
||||
|
||||
readonly useObject = Object;
|
||||
readonly useRandomString = randomString;
|
||||
|
||||
set auto_topic(value: number) {
|
||||
this.generatorService.auto_topic = value;
|
||||
console.log(this.generatorService.auto_topic);
|
||||
@ViewChild('typeinput') typeinput: any;
|
||||
@Input() entity_type: number = 0;
|
||||
|
||||
device: MQTTDevice = this.generatorService.device;
|
||||
|
||||
get created_entities(): MQTTEntity[] {
|
||||
return Array.from(this.generatorService.created_enteties);
|
||||
}
|
||||
|
||||
get auto_topic(): string {
|
||||
return String(this.generatorService.auto_topic);
|
||||
get codeSpan(): string {
|
||||
let start = this.generatorService.selected_entity == null ? 3 : 2;
|
||||
return `${start}/${3 + this.generatorService.created_entity_num}`;
|
||||
}
|
||||
|
||||
log(event:any){
|
||||
console.log(Number(event));
|
||||
select_type(entity_num: number) {
|
||||
this.entity_type = entity_num;
|
||||
let entity_type =
|
||||
this.generatorService.entity_types[
|
||||
entity_num as keyof typeof this.generatorService.entity_types
|
||||
];
|
||||
let entity_class = entity_type[1];
|
||||
|
||||
if (typeof entity_class === 'function' && entity_num != 0) {
|
||||
this.generatorService.selected_entity = new entity_class();
|
||||
} else {
|
||||
this.generatorService.selected_entity = null;
|
||||
this.typeinput.nativeElement.focus();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
5
src/app/output/output.component.css
Normal file
5
src/app/output/output.component.css
Normal file
@@ -0,0 +1,5 @@
|
||||
pre {
|
||||
max-width: calc(50vw - 3rem);
|
||||
overflow-x: scroll;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
5
src/app/output/output.component.html
Normal file
5
src/app/output/output.component.html
Normal file
@@ -0,0 +1,5 @@
|
||||
<div>
|
||||
<h2>Code Output</h2>
|
||||
<pre>{{outputService.integrateCode()}}</pre>
|
||||
<p></p>
|
||||
</div>
|
||||
@@ -1,18 +1,18 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { EntityComponent } from './entity.component';
|
||||
import { OutputComponent } from './output.component';
|
||||
|
||||
describe('EntityComponent', () => {
|
||||
let component: EntityComponent;
|
||||
let fixture: ComponentFixture<EntityComponent>;
|
||||
describe('OutputComponent', () => {
|
||||
let component: OutputComponent;
|
||||
let fixture: ComponentFixture<OutputComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [EntityComponent]
|
||||
declarations: [OutputComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(EntityComponent);
|
||||
fixture = TestBed.createComponent(OutputComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
11
src/app/output/output.component.ts
Normal file
11
src/app/output/output.component.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { OutputService } from '../_services/output.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-output',
|
||||
templateUrl: './output.component.html',
|
||||
styleUrl: './output.component.css',
|
||||
})
|
||||
export class OutputComponent {
|
||||
constructor(public outputService: OutputService) {}
|
||||
}
|
||||
126
src/styles.css
126
src/styles.css
@@ -3,81 +3,105 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root{
|
||||
--accent: #4cb926;
|
||||
--text: #535353;
|
||||
--primary: #b3b3b3;
|
||||
--secondary: #f8f8f8;
|
||||
|
||||
:root {
|
||||
--accent: #4cb926;
|
||||
--text: #3d3d3d;
|
||||
--primary: #b3b3b3;
|
||||
--secondary: #f8f8f8;
|
||||
--placeholder: #777;
|
||||
--input_bg: #b3b3b3;
|
||||
}
|
||||
|
||||
input, select, label, pre {
|
||||
background: var(--primary);
|
||||
padding: .25rem .75rem;
|
||||
border-radius: 1rem;
|
||||
input,
|
||||
select,
|
||||
label,
|
||||
pre,
|
||||
button {
|
||||
background: var(--primary);
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
||||
input, select{
|
||||
width: 100%;
|
||||
@apply focus:outline focus:outline-myAccent focus:outline-2;
|
||||
pre {
|
||||
text-wrap: wrap;
|
||||
}
|
||||
|
||||
input::placeholder{
|
||||
color: var(--text);
|
||||
opacity: 1;
|
||||
input,
|
||||
select {
|
||||
width: 100%;
|
||||
@apply focus:outline focus:outline-myAccent focus:outline-2;
|
||||
}
|
||||
|
||||
input::-ms-input-placeholder { /* Edge 12 -18 */
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
svg{
|
||||
height: 90%;
|
||||
margin: auto 0;
|
||||
width: auto;
|
||||
span {
|
||||
@apply focus-visible:outline focus-visible:outline-myAccent focus-visible:outline-2;
|
||||
}
|
||||
|
||||
*{
|
||||
color: var(--text);
|
||||
input::placeholder {
|
||||
color: var(--placeholder);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
button{
|
||||
background-color: var(--accent);
|
||||
border-radius: 1rem;
|
||||
color: var(--secondary);
|
||||
font-weight: bold;
|
||||
min-width: 4rem;
|
||||
input::-ms-input-placeholder {
|
||||
/* Edge 12 -18 */
|
||||
color: var(--placeholder);
|
||||
}
|
||||
|
||||
.randomButton{
|
||||
@apply flex justify-center content-center p-1;
|
||||
svg {
|
||||
height: 90%;
|
||||
margin: auto 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
* {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: var(--accent);
|
||||
border-radius: 1rem;
|
||||
color: var(--secondary);
|
||||
font-weight: bold;
|
||||
min-width: 4rem;
|
||||
}
|
||||
|
||||
button path {
|
||||
color: var(--secondary);
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
.property {
|
||||
background-color: #9D9D9D;
|
||||
border-radius: .5rem;
|
||||
/* padding: .5rem; */
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
gap: .25rem;
|
||||
/* background-color: #9d9d9d; */
|
||||
border-radius: 0.5rem;
|
||||
/* padding: .5rem; */
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.property p, .property h3{
|
||||
align-self: left;
|
||||
padding-left: .5rem;
|
||||
.property p,
|
||||
.property h3 {
|
||||
align-self: left;
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
||||
.property > div {
|
||||
width: 100%;
|
||||
div.property {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.property span {
|
||||
background: #B3B3B3;
|
||||
padding: .25rem .75rem;
|
||||
border-radius: 1rem;
|
||||
/* width: 100%; */
|
||||
}
|
||||
background: var(--input_bg);
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 1rem;
|
||||
/* width: 100%; */
|
||||
}
|
||||
|
||||
#entityPlaceholder {
|
||||
background: transparent;
|
||||
border: 2px dotted var(--input_bg);
|
||||
color: var(--input_bg);
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#entityPlaceholder:hover {
|
||||
border-color: var(--secondary);
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user