Merge branch 'main' of https://git.letsstein.de/tom/mqtt_creator
This commit is contained in:
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
|
||||
32
README.md
32
README.md
@@ -1,6 +1,34 @@
|
||||
# MqttCreator
|
||||
|
||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.3.4.
|
||||
This project is a simple and incomplete generator to create a Homeassinstant compatible MQTT Autodiscovery Entity
|
||||
|
||||
## Limited Coverage
|
||||
By now only the these entities (binary_sensor, light, sensor, switch) are supported by the generator, because they are the most commmonly used entities for my own needs
|
||||
|
||||
## Usage
|
||||
The UI is seperated into two parts, the global generator with settings and upper Topic and the entity itselt
|
||||
|
||||
### Generator:
|
||||
Automated Topic Settings:
|
||||
- all: the topics will be generated automatically from the other supplied inputs, all changes will be wiped
|
||||
- only changes: user inputs will be synced accross all topics (only works if the general structure stays the same and changes are not to large at once)
|
||||
- none: everything is done manually
|
||||
|
||||
### Entity:
|
||||
After choosing an entity type, change and input the corresponding values as you like and if automated topics is actived the topics will be populated automatically
|
||||
|
||||
Once done, you can press the DiscoveryString button.
|
||||
|
||||
### Discovery String:
|
||||
First you see the discovery string formatted, there you can check for any errors in the configuration
|
||||
|
||||
Below there will be a discovery string with escaped perentecies to input into print statements or similar
|
||||
|
||||
The last field Discovery Topic is the automatically generated Discovery Topic for default Homeassistant MQTT Autodiscovery
|
||||
|
||||
|
||||
|
||||
<!-- This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.3.4.
|
||||
|
||||
## Development server
|
||||
|
||||
@@ -24,4 +52,4 @@ Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To u
|
||||
|
||||
## Further help
|
||||
|
||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
|
||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. -->
|
||||
|
||||
7
docker-compose.yaml
Normal file
7
docker-compose.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
services:
|
||||
mqtt_creator:
|
||||
image: git.letsstein.de/tom/mqtt_creator:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${PORT}:80"
|
||||
|
||||
@@ -1,7 +1,54 @@
|
||||
import { DEVICE_CLASS, MQTTEntity } from "./mqtt_base";
|
||||
import { DeviceClass, iMQTTEntityBase, MQTTEntity } from './mqtt_base';
|
||||
|
||||
export class MqttBinary extends MQTTEntity {
|
||||
dev_cla: DEVICE_CLASS = 0
|
||||
pl_on: string = "1";
|
||||
pl_off: string = "0";
|
||||
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',
|
||||
'cold',
|
||||
'connectivity',
|
||||
'door',
|
||||
'garage_door',
|
||||
'gas',
|
||||
'heat',
|
||||
'light',
|
||||
'lock',
|
||||
'moisture',
|
||||
'motion',
|
||||
'moving',
|
||||
'occupancy',
|
||||
'opening',
|
||||
'plug',
|
||||
'power',
|
||||
'presence',
|
||||
'problem',
|
||||
'running',
|
||||
'safety',
|
||||
'smoke',
|
||||
'sound',
|
||||
'tamper',
|
||||
'update',
|
||||
'vibration',
|
||||
'window',
|
||||
]);
|
||||
pl_on: string = '1';
|
||||
pl_off: string = '0';
|
||||
}
|
||||
|
||||
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";
|
||||
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";
|
||||
val_tpl: string = "";
|
||||
}
|
||||
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 { DEVICE_CLASS, MQTTEntity } from "./mqtt_base";
|
||||
import { DeviceClass, iMQTTEntityBase, MQTTEntity } from './mqtt_base';
|
||||
|
||||
export class MqttSwitch extends MQTTEntity {
|
||||
dev_cla: DEVICE_CLASS = 0
|
||||
cmd_t: string = "command/topic";
|
||||
pl_on: string = "1";
|
||||
pl_off: string = "0";
|
||||
}
|
||||
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 = '';
|
||||
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,43 +1,147 @@
|
||||
export class MQTTEntity {
|
||||
name: string = "";
|
||||
stat_t: string = "state/topic";
|
||||
uniq_id: string = "unique_id";
|
||||
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;
|
||||
|
||||
createString() : string{
|
||||
let string: string = "";
|
||||
for(let property of Object.getOwnPropertyNames(this)){
|
||||
if(this[property as keyof this] == null) continue;
|
||||
string += `"${property}": "${this[property as keyof this]}",`
|
||||
}
|
||||
console.log(string);
|
||||
return string;
|
||||
attrs = new Set(['name', 'stat_t', 'uniq_id']);
|
||||
topic_updates = new EventEmitter<string>();
|
||||
readonly ent_type: string = 'base';
|
||||
|
||||
get name() {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
set name(name: string) {
|
||||
this._name = name;
|
||||
if (name == '') return;
|
||||
this._uniq_id = name + '_' + hash(name);
|
||||
this.topic_updates.next('stat_t');
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
this._size = JSON.stringify(this.toJSON()).length;
|
||||
return this._size;
|
||||
}
|
||||
|
||||
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 MQTTDevice {
|
||||
name: string = "";
|
||||
identifiers: string[] = ["MQTT"];
|
||||
serial_number: string = "";
|
||||
configuration_url: string = "";
|
||||
export class DeviceClass {
|
||||
value: number = 0;
|
||||
choices: string[] = ['None'];
|
||||
|
||||
constructor(choices: string[]) {
|
||||
this.choices = choices;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return this.choices[this.value];
|
||||
}
|
||||
}
|
||||
|
||||
// export enum ENTITY_TYPE {
|
||||
// light = 0,
|
||||
// switch = 1,
|
||||
// sensor = 2,
|
||||
// binary_sensor = 3,
|
||||
// button = 4,
|
||||
// }
|
||||
export interface iMQTTEntityBase {
|
||||
name: string;
|
||||
stat_t: string;
|
||||
uniq_id: string;
|
||||
display_name: string;
|
||||
|
||||
export enum DEVICE_CLASS {
|
||||
motion = 0,
|
||||
movement = 1,
|
||||
outlet = 2
|
||||
}
|
||||
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,50 +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 = "";
|
||||
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;
|
||||
}
|
||||
@@ -1,3 +1,11 @@
|
||||
<div>
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
<!DOCTYPE html>
|
||||
<main class="p-4">
|
||||
<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.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>
|
||||
<router-outlet></router-outlet>
|
||||
</main>
|
||||
@@ -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,27 +0,0 @@
|
||||
.property {
|
||||
background-color: #9D9D9D;
|
||||
border-radius: .5rem;
|
||||
padding: .5rem;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
column-gap: 1rem;
|
||||
}
|
||||
|
||||
.property p{
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.property > div {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.property span {
|
||||
background: #B3B3B3;
|
||||
padding: .25rem .75rem;
|
||||
border-radius: 1rem;
|
||||
/* width: 100%; */
|
||||
}
|
||||
|
||||
.buttonOff path{
|
||||
fill:#535353;
|
||||
}
|
||||
@@ -1,81 +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 placeholder="Name here" type="text"[ngModel]="entity_name" (ngModelChange)="entity_name = $event; updateStateTopic()"/>
|
||||
</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; updateStateTopic()"/>
|
||||
<button (click)="entity_uniq_id = useRandomString(10); updateStateTopic()" 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" (ngModelChange)="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)="entity_bri_cmd_t = $event"/>
|
||||
</div>
|
||||
<div class="property !flex-row" >
|
||||
<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>
|
||||
<input type="text" [ngModel]="entity_dev_cla" (ngModelChange)="entity_dev_cla = $event"/>
|
||||
</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]="state_topic" (change)="lockStateTopic($event)"/>
|
||||
<label class="randomButton" [ngClass]="auto_stat_t ? 'bg-mySecondary buttonOff' : ''">
|
||||
<input type="checkbox" class="hidden" [ngModel]="auto_stat_t" (ngModelChange)="auto_stat_t = $event; updateStateTopic()"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" class="fill-myButton" viewBox="0 0 16 16">
|
||||
<path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325"/>
|
||||
</svg>
|
||||
</label>
|
||||
</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>
|
||||
<span contenteditable>{{discoveryString}}</span>
|
||||
</div>
|
||||
<div class="col-span-2 property" >
|
||||
<p>Discovery Topic</p>
|
||||
<input type="text" readonly value="{{discoveryTopic}}">
|
||||
</div>
|
||||
</ng-container>
|
||||
</main>
|
||||
@@ -1,98 +0,0 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { GeneratorService, entity_types, randomString } from '../_services/generator.service';
|
||||
import { MqttBinary } from '../_models/mqtt-binary';
|
||||
import { 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.updateStateTopic();
|
||||
})
|
||||
}
|
||||
|
||||
readonly useObject = Object;
|
||||
readonly entities = entity_types;
|
||||
readonly useRandomString = randomString;
|
||||
|
||||
auto_stat_t: boolean = true;
|
||||
showDiscovery: boolean = false;
|
||||
|
||||
@Input() entity_type: number = 0;
|
||||
@Input() state_topic: 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 = "";
|
||||
@Input() entity_pl_on: string = "";
|
||||
@Input() entity_unit_of_meas: string = "";
|
||||
@Input() entity_val_tpl: string = "";
|
||||
@Input() entity_dev_cla: string = "";
|
||||
|
||||
updateStateTopic() {
|
||||
if (!this.auto_stat_t) return
|
||||
this.state_topic = ""
|
||||
if (this.generatorService.upperTopic != "") this.state_topic += this.generatorService.upperTopic + "/";
|
||||
if (this.entity_type != 0) this.state_topic += entity_types[this.entity_type][0] + "/"
|
||||
if (this.generatorService.device_name != "") this.state_topic += this.generatorService.device_name + "/"
|
||||
if (this.entity_name != "" && this.entity_uniq_id != "") this.state_topic += this.entity_name + "_" + this.entity_uniq_id + "/stat"
|
||||
else if (this.entity_name != "") this.state_topic += this.entity_name + "/stat"
|
||||
this.state_topic = this.state_topic.toLocaleLowerCase();
|
||||
}
|
||||
|
||||
basemodelProperty(property: string) {
|
||||
if (this.basemodel?.hasOwnProperty(property)) console.log(property)
|
||||
return this.basemodel?.stat_t
|
||||
}
|
||||
|
||||
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
|
||||
} else this.generatorService.selected_entity = null
|
||||
this.entity_type = event as number;
|
||||
this.updateStateTopic();
|
||||
}
|
||||
|
||||
lockStateTopic(event: any) {
|
||||
this.auto_stat_t = false;
|
||||
}
|
||||
|
||||
create_entity() {
|
||||
this.basemodel?.setProperty('stat_t', this.state_topic);
|
||||
|
||||
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.showDiscovery = true;
|
||||
return
|
||||
}
|
||||
|
||||
get discoveryString() {
|
||||
let discString = this.basemodel?.createString()
|
||||
if (discString == "" || discString == undefined) return "";
|
||||
discString = discString.replaceAll('"', '\\"')
|
||||
return "{" + discString + "}"
|
||||
}
|
||||
|
||||
get discoveryTopic() {
|
||||
if (this.entity_type == 0) return "";
|
||||
if (this.entity_name == "") return "";
|
||||
let discTopic = "homeassistant/" + entity_types[this.entity_type][0] + "/" + this.entity_name + "_" + this.entity_uniq_id + "/config"
|
||||
return discTopic;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +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 ;
|
||||
}
|
||||
grid-column: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.outContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
.customCheckboxContainer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.customCheckboxContainer input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.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,31 +1,74 @@
|
||||
<main class="flex flex-col gap-2 p-4">
|
||||
<div class="flex flex-row justify-left gap-4 items-center h-12">
|
||||
<img src="mqtt_creator.ico" alt="app icon" class="h-full">
|
||||
<h2 class="text-3xl font-bold text-myButton" >MQTT Discovery Creator</h2>
|
||||
</div>
|
||||
|
||||
<!-- <div class="border border-slate-800 my-2">
|
||||
<h2>Device: - not working</h2>
|
||||
<p>Name</p><input type="text" placeholder="name here" [ngModel]="generatorService.device_name"
|
||||
(ngModelChange)="generatorService.device_name = $event; generatorService.update()">
|
||||
<p>Identifier</p>
|
||||
<input type="text" placeholder="Identifier here" [ngModel]="generatorService.device_id"
|
||||
(ngModelChange)="generatorService.device_id = $event">
|
||||
<button (click)="generatorService.device_id = useRandomString(16)">Random</button>
|
||||
<p>Standalone -> no device</p>
|
||||
<input type="checkbox" [ngModel]="generatorService.device_standalone"
|
||||
(ngModelChange)="generatorService.device_standalone = $event; generatorService.update()" />
|
||||
</div> -->
|
||||
<div class="genContainer">
|
||||
<!DOCTYPE html>
|
||||
<div id="mainContainer" class="" [ngClass]="{'outEnable':outputService.output}">
|
||||
<div class="genContainer grid grid-cols-2 gap-4">
|
||||
<!-- Statetopic -->
|
||||
<h3>Data Channel</h3>
|
||||
<div>
|
||||
<p>Bereich</p>
|
||||
<input type="text" [ngModel]="generatorService.upperTopic"
|
||||
(ngModelChange)="generatorService.upperTopic = $event; generatorService.update()" />
|
||||
<!-- <h3>Data Channel</h3> -->
|
||||
|
||||
<div class="property col-span-2">
|
||||
<p>Bereich</p>
|
||||
<input type="text" [ngModel]="generatorService.upper_topic"
|
||||
(ngModelChange)="generatorService.upper_topic = $event" />
|
||||
</div>
|
||||
<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>
|
||||
|
||||
|
||||
</main>
|
||||
</div>
|
||||
<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,33 +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;
|
||||
|
||||
@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 codeSpan(): string {
|
||||
let start = this.generatorService.selected_entity == null ? 3 : 2;
|
||||
return `${start}/${3 + this.generatorService.created_entity_num}`;
|
||||
}
|
||||
|
||||
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) {}
|
||||
}
|
||||
118
src/styles.css
118
src/styles.css
@@ -3,45 +3,105 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
input, select {
|
||||
background: #B3B3B3;
|
||||
padding: .25rem .75rem;
|
||||
border-radius: 1rem;
|
||||
width: 100%;
|
||||
@apply focus:outline focus:outline-myAccent focus:outline-2;
|
||||
:root {
|
||||
--accent: #4cb926;
|
||||
--text: #3d3d3d;
|
||||
--primary: #b3b3b3;
|
||||
--secondary: #f8f8f8;
|
||||
--placeholder: #777;
|
||||
--input_bg: #b3b3b3;
|
||||
}
|
||||
|
||||
input::placeholder{
|
||||
color: #535353;
|
||||
opacity: 1;
|
||||
input,
|
||||
select,
|
||||
label,
|
||||
pre,
|
||||
button {
|
||||
background: var(--primary);
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
||||
input::-ms-input-placeholder { /* Edge 12 -18 */
|
||||
color: #535353;
|
||||
}
|
||||
|
||||
svg{
|
||||
height: 90%;
|
||||
margin: auto 0;
|
||||
width: auto;
|
||||
pre {
|
||||
text-wrap: wrap;
|
||||
}
|
||||
|
||||
*{
|
||||
color: #535353;
|
||||
input,
|
||||
select {
|
||||
width: 100%;
|
||||
@apply focus:outline focus:outline-myAccent focus:outline-2;
|
||||
}
|
||||
|
||||
button, label{
|
||||
background-color: #4CB926;
|
||||
border-radius: 1rem;
|
||||
color: #F8F8F8;
|
||||
font-weight: bold;
|
||||
min-width: 4rem;
|
||||
span {
|
||||
@apply focus-visible:outline focus-visible:outline-myAccent focus-visible:outline-2;
|
||||
}
|
||||
|
||||
.randomButton{
|
||||
@apply flex justify-center content-center p-1;
|
||||
input::placeholder {
|
||||
color: var(--placeholder);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
input::-ms-input-placeholder {
|
||||
/* Edge 12 -18 */
|
||||
color: var(--placeholder);
|
||||
}
|
||||
|
||||
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: #F8F8F8;
|
||||
}
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
.property {
|
||||
/* 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: 0.5rem;
|
||||
}
|
||||
|
||||
div.property {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.property span {
|
||||
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