171 lines
7.4 KiB
Vue
171 lines
7.4 KiB
Vue
<template>
|
|
<aside class="h-full overflow-y-auto p-[18px] flex flex-col gap-4 border-l border-border max-[1100px]:border-l-0 max-[1100px]:border-t">
|
|
<section class="bg-gradient-to-b from-white to-[#f6f9fc] border border-border rounded-[14px] p-3.5 shadow-[0_10px_24px_rgba(15,23,42,0.12)]" id="properties">
|
|
<div v-if="!w" class="text-muted text-center py-5 text-[13px]">
|
|
Kein Widget ausgewaehlt.<br><br>
|
|
Waehle ein Widget im Canvas oder im Baum.
|
|
</div>
|
|
<div v-else>
|
|
<!-- Layout (common for all widgets) -->
|
|
<h4 :class="headingClass">Layout</h4>
|
|
<div :class="rowClass"><label :class="labelClass">X</label><input :class="inputClass" type="number" v-model.number="w.x"></div>
|
|
<div :class="rowClass"><label :class="labelClass">Y</label><input :class="inputClass" type="number" v-model.number="w.y"></div>
|
|
<div :class="rowClass"><label :class="labelClass">Breite</label><input :class="inputClass" type="number" v-model.number="w.w"></div>
|
|
<div :class="rowClass"><label :class="labelClass">Hoehe</label><input :class="inputClass" type="number" v-model.number="w.h"></div>
|
|
<div :class="rowClass"><label :class="labelClass">Sichtbar</label><input class="accent-[var(--accent)]" type="checkbox" v-model="w.visible"></div>
|
|
<div :class="rowClass"><label :class="labelClass">Farbe fixieren</label><input class="accent-[var(--accent)]" type="checkbox" v-model="w.themeFixed"></div>
|
|
|
|
<!-- Widget-specific settings -->
|
|
<component
|
|
:is="settingsComponent"
|
|
:widget="w"
|
|
@open-icon-picker="openWidgetIconPicker"
|
|
@open-condition-icon-picker="openConditionIconPicker"
|
|
@open-subbutton-icon-picker="openSubButtonIconPicker"
|
|
@open-textline-icon-picker="openTextLineIconPicker"
|
|
/>
|
|
|
|
<!-- Delete button (common for all widgets) -->
|
|
<div class="mt-4">
|
|
<button class="border border-red-200 bg-[#f7dede] text-[#b3261e] px-3.5 py-2 rounded-[10px] text-[13px] font-semibold transition hover:-translate-y-0.5 hover:bg-[#f2cfcf] active:translate-y-0.5" @click="store.deleteWidget">Widget loeschen</button>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Icon Picker Modal -->
|
|
<IconPicker
|
|
v-if="showIconPicker"
|
|
v-model="activeIconCodepoint"
|
|
@close="handleIconPickerClose"
|
|
/>
|
|
</aside>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed, ref, markRaw } from 'vue';
|
|
import { useEditorStore } from '../stores/editor';
|
|
import { typeKeyFor } from '../utils';
|
|
import { WIDGET_TYPES } from '../constants';
|
|
import { rowClass, labelClass, inputClass, headingClass } from './widgets/shared/styles';
|
|
import IconPicker from './IconPicker.vue';
|
|
|
|
// Import all widget settings components
|
|
import LabelSettings from './widgets/settings/LabelSettings.vue';
|
|
import ButtonSettings from './widgets/settings/ButtonSettings.vue';
|
|
import LedSettings from './widgets/settings/LedSettings.vue';
|
|
import IconSettings from './widgets/settings/IconSettings.vue';
|
|
import TabViewSettings from './widgets/settings/TabViewSettings.vue';
|
|
import TabPageSettings from './widgets/settings/TabPageSettings.vue';
|
|
import PowerFlowSettings from './widgets/settings/PowerFlowSettings.vue';
|
|
import PowerNodeSettings from './widgets/settings/PowerNodeSettings.vue';
|
|
import ChartSettings from './widgets/settings/ChartSettings.vue';
|
|
import ClockSettings from './widgets/settings/ClockSettings.vue';
|
|
import RoomCardSettings from './widgets/settings/RoomCardSettings.vue';
|
|
import RectangleSettings from './widgets/settings/RectangleSettings.vue';
|
|
import ArcSettings from './widgets/settings/ArcSettings.vue';
|
|
import ButtonMatrixSettings from './widgets/settings/ButtonMatrixSettings.vue';
|
|
|
|
const store = useEditorStore();
|
|
const w = computed(() => store.selectedWidget);
|
|
const key = computed(() => w.value ? typeKeyFor(w.value.type) : 'label');
|
|
|
|
const showIconPicker = ref(false);
|
|
const conditionIconPickerIdx = ref(-1);
|
|
const subButtonIconPickerIdx = ref(-1);
|
|
const subButtonIconType = ref('off'); // 'off' or 'on'
|
|
const textLineIconPickerIdx = ref(-1);
|
|
|
|
// Map widget types to settings components
|
|
const componentMap = {
|
|
[WIDGET_TYPES.LABEL]: markRaw(LabelSettings),
|
|
[WIDGET_TYPES.BUTTON]: markRaw(ButtonSettings),
|
|
[WIDGET_TYPES.LED]: markRaw(LedSettings),
|
|
[WIDGET_TYPES.ICON]: markRaw(IconSettings),
|
|
[WIDGET_TYPES.TABVIEW]: markRaw(TabViewSettings),
|
|
[WIDGET_TYPES.TABPAGE]: markRaw(TabPageSettings),
|
|
[WIDGET_TYPES.POWERFLOW]: markRaw(PowerFlowSettings),
|
|
[WIDGET_TYPES.POWERNODE]: markRaw(PowerNodeSettings),
|
|
[WIDGET_TYPES.CHART]: markRaw(ChartSettings),
|
|
[WIDGET_TYPES.CLOCK]: markRaw(ClockSettings),
|
|
[WIDGET_TYPES.ROOMCARD]: markRaw(RoomCardSettings),
|
|
[WIDGET_TYPES.RECTANGLE]: markRaw(RectangleSettings),
|
|
[WIDGET_TYPES.ARC]: markRaw(ArcSettings),
|
|
[WIDGET_TYPES.BUTTONMATRIX]: markRaw(ButtonMatrixSettings)
|
|
};
|
|
|
|
const settingsComponent = computed(() => {
|
|
if (!w.value) return null;
|
|
return componentMap[w.value.type] || LabelSettings;
|
|
});
|
|
|
|
function openWidgetIconPicker() {
|
|
conditionIconPickerIdx.value = -1;
|
|
subButtonIconPickerIdx.value = -1;
|
|
textLineIconPickerIdx.value = -1;
|
|
showIconPicker.value = true;
|
|
}
|
|
|
|
function openConditionIconPicker(idx) {
|
|
conditionIconPickerIdx.value = idx;
|
|
subButtonIconPickerIdx.value = -1;
|
|
textLineIconPickerIdx.value = -1;
|
|
showIconPicker.value = true;
|
|
}
|
|
|
|
function openSubButtonIconPicker(idx, type = 'off') {
|
|
conditionIconPickerIdx.value = -1;
|
|
subButtonIconPickerIdx.value = idx;
|
|
subButtonIconType.value = type;
|
|
textLineIconPickerIdx.value = -1;
|
|
showIconPicker.value = true;
|
|
}
|
|
|
|
function openTextLineIconPicker(idx) {
|
|
conditionIconPickerIdx.value = -1;
|
|
subButtonIconPickerIdx.value = -1;
|
|
textLineIconPickerIdx.value = idx;
|
|
showIconPicker.value = true;
|
|
}
|
|
|
|
// Dynamic icon binding for IconPicker (widget icon, condition icon, subbutton icon, or textline icon)
|
|
const activeIconCodepoint = computed({
|
|
get() {
|
|
if (conditionIconPickerIdx.value >= 0 && w.value?.conditions?.[conditionIconPickerIdx.value]) {
|
|
return w.value.conditions[conditionIconPickerIdx.value].icon || 0;
|
|
}
|
|
if (subButtonIconPickerIdx.value >= 0 && w.value?.subButtons?.[subButtonIconPickerIdx.value]) {
|
|
const sb = w.value.subButtons[subButtonIconPickerIdx.value];
|
|
return subButtonIconType.value === 'on' ? (sb.iconOn || 0) : (sb.iconOff || 0);
|
|
}
|
|
if (textLineIconPickerIdx.value >= 0 && w.value?.textLines?.[textLineIconPickerIdx.value]) {
|
|
return w.value.textLines[textLineIconPickerIdx.value].icon || 0;
|
|
}
|
|
return w.value?.iconCodepoint || 0;
|
|
},
|
|
set(value) {
|
|
if (conditionIconPickerIdx.value >= 0 && w.value?.conditions?.[conditionIconPickerIdx.value]) {
|
|
w.value.conditions[conditionIconPickerIdx.value].icon = value;
|
|
} else if (subButtonIconPickerIdx.value >= 0 && w.value?.subButtons?.[subButtonIconPickerIdx.value]) {
|
|
const sb = w.value.subButtons[subButtonIconPickerIdx.value];
|
|
if (subButtonIconType.value === 'on') {
|
|
sb.iconOn = value;
|
|
} else {
|
|
sb.iconOff = value;
|
|
}
|
|
} else if (textLineIconPickerIdx.value >= 0 && w.value?.textLines?.[textLineIconPickerIdx.value]) {
|
|
w.value.textLines[textLineIconPickerIdx.value].icon = value;
|
|
} else if (w.value) {
|
|
w.value.iconCodepoint = value;
|
|
}
|
|
}
|
|
});
|
|
|
|
function handleIconPickerClose() {
|
|
showIconPicker.value = false;
|
|
conditionIconPickerIdx.value = -1;
|
|
subButtonIconPickerIdx.value = -1;
|
|
subButtonIconType.value = 'off';
|
|
textLineIconPickerIdx.value = -1;
|
|
}
|
|
</script>
|