knxdisplay/web-interface/src/components/widgets/settings/ButtonSettings.vue
2026-02-09 15:28:12 +01:00

217 lines
11 KiB
Vue

<template>
<div>
<!-- Content -->
<h4 :class="headingClass">Inhalt</h4>
<div :class="rowClass"><label :class="labelClass">Quelle</label>
<select :class="inputClass" v-model.number="widget.textSrc">
<optgroup v-for="group in groupedSources(sourceOptions.button)" :key="group.label" :label="group.label">
<option v-for="opt in group.values" :key="opt" :value="opt">{{ textSources[opt] }}</option>
</optgroup>
</select>
</div>
<div v-if="widget.textSrc === 0" :class="rowClass">
<label :class="labelClass">Text</label><input :class="inputClass" type="text" v-model="widget.text">
</div>
<!-- Typography -->
<h4 :class="headingClass">Typo</h4>
<div :class="rowClass"><label :class="labelClass">Schriftgr.</label>
<select :class="inputClass" v-model.number="widget.fontSize">
<option v-for="(size, idx) in fontSizes" :key="idx" :value="idx">{{ size }}</option>
</select>
</div>
<div :class="rowClass"><label :class="labelClass">Ausrichtung</label>
<select :class="inputClass" v-model.number="widget.textAlign">
<option :value="0">Links</option>
<option :value="1">Zentriert</option>
<option :value="2">Rechts</option>
</select>
</div>
<!-- Icon -->
<h4 :class="headingClass">Icon</h4>
<div :class="rowClass">
<label :class="labelClass">Icon</label>
<button :class="iconSelectClass" @click="$emit('open-icon-picker')">
<span v-if="widget.iconCodepoint" class="material-symbols-outlined text-[20px]">{{ String.fromCodePoint(widget.iconCodepoint) }}</span>
<span v-else>Kein Icon</span>
</button>
<button v-if="widget.iconCodepoint" :class="iconRemoveClass" @click="widget.iconCodepoint = 0">x</button>
</div>
<template v-if="widget.iconCodepoint">
<div :class="rowClass">
<label :class="labelClass">Position</label>
<select :class="inputClass" v-model.number="widget.iconPosition">
<option :value="0">Links</option>
<option :value="1">Rechts</option>
<option :value="2">Oben</option>
<option :value="3">Unten</option>
</select>
</div>
<div :class="rowClass">
<label :class="labelClass">Icon-Gr.</label>
<select :class="inputClass" v-model.number="widget.iconSize">
<option v-for="(size, idx) in iconFontSizes" :key="idx" :value="idx">{{ size }}</option>
</select>
</div>
<div :class="rowClass">
<label :class="labelClass">Abstand</label>
<input :class="inputClass" type="number" v-model.number="widget.iconGap" min="0" max="50">
</div>
</template>
<!-- Style -->
<h4 :class="headingClass">Stil</h4>
<div :class="rowClass"><label :class="labelClass">Textfarbe</label><input :class="colorInputClass" type="color" v-model="widget.textColor"></div>
<div :class="rowClass"><label :class="labelClass">Hintergrund</label><input :class="colorInputClass" type="color" v-model="widget.bgColor"></div>
<div :class="rowClass"><label :class="labelClass">Deckkraft</label><input :class="inputClass" type="number" min="0" max="255" v-model.number="widget.bgOpacity"></div>
<div :class="rowClass"><label :class="labelClass">Radius</label><input :class="inputClass" type="number" v-model.number="widget.radius"></div>
<h4 :class="headingClass">Rand</h4>
<div :class="rowClass"><label :class="labelClass">Breite</label><input :class="inputClass" type="number" min="0" max="32" v-model.number="widget.borderWidth"></div>
<div :class="rowClass"><label :class="labelClass">Farbe</label><input :class="colorInputClass" type="color" v-model="widget.borderColor"></div>
<div :class="rowClass"><label :class="labelClass">Deckkraft</label><input :class="inputClass" type="number" min="0" max="255" v-model.number="widget.borderOpacity"></div>
<!-- Shadow -->
<h4 :class="headingClass">Schatten</h4>
<div :class="rowClass"><label :class="labelClass">Aktiv</label><input class="accent-[var(--accent)]" type="checkbox" v-model="widget.shadow.enabled"></div>
<div :class="rowClass"><label :class="labelClass">X</label><input :class="inputClass" type="number" v-model.number="widget.shadow.x"></div>
<div :class="rowClass"><label :class="labelClass">Y</label><input :class="inputClass" type="number" v-model.number="widget.shadow.y"></div>
<div :class="rowClass"><label :class="labelClass">Blur</label><input :class="inputClass" type="number" v-model.number="widget.shadow.blur"></div>
<div :class="rowClass"><label :class="labelClass">Farbe</label><input :class="colorInputClass" type="color" v-model="widget.shadow.color"></div>
<!-- Action -->
<h4 :class="headingClass">Aktion</h4>
<div :class="rowClass"><label :class="labelClass">Typ</label>
<select :class="inputClass" v-model.number="widget.action">
<option :value="BUTTON_ACTIONS.KNX">KNX</option>
<option :value="BUTTON_ACTIONS.JUMP">Sprung</option>
<option :value="BUTTON_ACTIONS.BACK">Zurueck</option>
</select>
</div>
<div v-if="widget.action === BUTTON_ACTIONS.JUMP" :class="rowClass">
<label :class="labelClass">Ziel</label>
<select :class="inputClass" v-model.number="widget.targetScreen">
<option v-for="s in store.config.screens" :key="s.id" :value="s.id">{{ s.name }}</option>
</select>
</div>
<template v-if="widget.action === BUTTON_ACTIONS.KNX">
<div :class="rowClass"><label :class="labelClass">Toggle</label><input class="accent-[var(--accent)]" type="checkbox" v-model="widget.isToggle"></div>
<div :class="rowClass"><label :class="labelClass">KNX Schreib</label>
<select :class="inputClass" v-model.number="widget.knxAddrWrite">
<option :value="0">-- Keine --</option>
<option v-for="addr in writeableAddresses" :key="`${addr.addr}-${addr.index}`" :value="addr.addr">
GA {{ addr.addrStr }} (GO{{ addr.index }})
</option>
</select>
</div>
<div :class="rowClass"><label :class="labelClass">KNX Status</label>
<select :class="inputClass" v-model.number="widget.knxAddr">
<option :value="0">-- Waehlen --</option>
<option v-for="addr in readableAddresses" :key="`${addr.addr}-${addr.index}`" :value="addr.addr">
GA {{ addr.addrStr }} (GO{{ addr.index }})
</option>
</select>
</div>
<div :class="noteClass">Tipp: Status-GA sollte DPT 1.x sein. Bedingungen z. B. <code>eq 1</code> = EIN, <code>eq 0</code> = AUS.</div>
</template>
<!-- Conditions -->
<h4 :class="headingClass">Bedingungen</h4>
<div :class="rowClass">
<label :class="labelClass">Anzahl</label>
<select :class="inputClass" v-model.number="conditionCount">
<option :value="0">Keine</option>
<option :value="1">1</option>
<option :value="2">2</option>
<option :value="3">3</option>
</select>
</div>
<div v-for="(cond, idx) in conditions" :key="idx" class="border border-border rounded-lg px-2.5 py-2 mb-2 bg-panel-2">
<div class="flex items-center gap-2 mb-2 text-[11px] text-muted">
<label class="w-[50px]">Wenn</label>
<select class="w-[60px] bg-white border border-border rounded-md px-2 py-1 text-[11px]" v-model="cond.op">
<option value="lt">&lt;</option>
<option value="lte">&lt;=</option>
<option value="eq">=</option>
<option value="gte">&gt;=</option>
<option value="gt">&gt;</option>
<option value="neq">!=</option>
</select>
<input class="flex-1 bg-white border border-border rounded-md px-2 py-1 text-[11px]" type="number" step="0.1" v-model.number="cond.threshold" placeholder="Schwelle">
</div>
<div class="flex items-center gap-2 mb-2 text-[11px] text-muted">
<label class="w-[50px]">Icon</label>
<button class="flex-1 bg-white border border-border rounded-md px-2.5 py-1 text-[11px] flex items-center justify-center gap-1 cursor-pointer hover:bg-[#e4ebf2]" @click="$emit('open-condition-icon-picker', idx)">
<span v-if="cond.icon" class="material-symbols-outlined text-[16px]">{{ String.fromCodePoint(cond.icon) }}</span>
<span v-else>Kein Icon</span>
</button>
<button v-if="cond.icon" class="w-6 h-6 rounded-md border border-red-200 bg-[#f7dede] text-[#b3261e] grid place-items-center text-[11px] cursor-pointer hover:bg-[#f2cfcf]" @click="cond.icon = 0">x</button>
</div>
<div class="flex items-center gap-2 mb-2 text-[11px] text-muted">
<label class="w-[50px]">Textfarbe</label>
<input class="h-[22px] w-[32px] cursor-pointer border-0 bg-transparent p-0" type="color" v-model="cond.textColor">
</div>
<div class="flex items-center gap-2 text-[11px] text-muted">
<label class="w-[50px]">Hintergr.</label>
<input class="h-[22px] w-[32px] cursor-pointer border-0 bg-transparent p-0" type="color" v-model="cond.bgColor">
</div>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
import { useEditorStore } from '../../../stores/editor';
import { sourceOptions, textSources, textSourceGroups, BUTTON_ACTIONS, fontSizes, iconFontSizes } from '../../../constants';
import { rowClass, labelClass, inputClass, headingClass, colorInputClass, iconSelectClass, iconRemoveClass, noteClass } from '../shared/styles';
const props = defineProps({
widget: { type: Object, required: true }
});
defineEmits(['open-icon-picker', 'open-condition-icon-picker']);
const store = useEditorStore();
const writeableAddresses = computed(() => store.knxAddresses.filter(a => a.write));
const readableAddresses = computed(() => store.knxAddresses);
const conditions = computed(() => props.widget?.conditions ?? []);
const conditionCount = computed({
get() {
return conditions.value.length || 0;
},
set(value) {
if (!props.widget) return;
const target = Math.max(0, Math.min(value, 3));
if (!Array.isArray(props.widget.conditions)) {
props.widget.conditions = [];
}
while (props.widget.conditions.length < target) {
props.widget.conditions.push({
source: 'primary',
threshold: 1,
op: 'eq',
priority: props.widget.conditions.length,
icon: 0,
textColor: '#FFFFFF',
bgColor: ''
});
}
if (props.widget.conditions.length > target) {
props.widget.conditions = props.widget.conditions.slice(0, target);
}
}
});
function groupedSources(options) {
const allowed = new Set(options || []);
return textSourceGroups
.map((group) => ({
label: group.label,
values: group.values.filter((value) => allowed.has(value))
}))
.filter((group) => group.values.length > 0);
}
</script>