This commit is contained in:
Thomas Peterson 2026-01-27 10:34:08 +01:00
parent e7f3bb6b12
commit cc6b30ebfe
11 changed files with 381 additions and 40 deletions

View File

@ -306,6 +306,8 @@ void WidgetManager::applyScreen(uint8_t screenId) {
return;
}
ensureButtonLabels(*screen);
if (modalContainer_) {
ESP_LOGI(TAG, "Closing modal first");
closeModal();

66
prodxml/Display.h Normal file
View File

@ -0,0 +1,66 @@
#pragma once
#define paramDelay(time) (uint32_t)( \
(time & 0xC000) == 0xC000 ? (time & 0x3FFF) * 100 : \
(time & 0xC000) == 0x0000 ? (time & 0x3FFF) * 1000 : \
(time & 0xC000) == 0x4000 ? (time & 0x3FFF) * 60000 : \
(time & 0xC000) == 0x8000 ? ((time & 0x3FFF) > 1000 ? 3600000 : \
(time & 0x3FFF) * 3600000 ) : 0 )
// Parameter with single occurrence
#define StartupDelay 0 // int32_t
#define StartupDelaySelection 4 // 16 Bits, Bit 15-0
#define Heartbeat 6 // int32_t
#define TemperatureSensor 10 // 1 Bit, Bit 7
#define TemperatureSensorMask 0x80
#define TemperatureSensorShift 7
#define HumiditySensor 10 // 1 Bit, Bit 6
#define HumiditySensorMask 0x40
#define HumiditySensorShift 6
#define TemperatureAlign 11 // float
#define HumidityAlign 13 // float
#define HumiditySend 15 // 8 Bits, Bit 7-0
// Zeit (Sekundengenau)
#define ParamStartupDelay ((int32_t)knx.paramInt(StartupDelay))
// Zeit bis das Gerät nach einem Neustart aktiv wird
#define ParamStartupDelaySelection (knx.paramWord(StartupDelaySelection))
// 'In Betrieb'-Telegramm senden alle
#define ParamHeartbeat ((int32_t)knx.paramInt(Heartbeat))
// Temperatursensor
#define ParamTemperatureSensor ((bool)(knx.paramByte(TemperatureSensor) & TemperatureSensorMask))
// Luftfeuchtesensor
#define ParamHumiditySensor ((bool)(knx.paramByte(HumiditySensor) & HumiditySensorMask))
// Temperaturwert anpassen
#define ParamTemperatureAlign (knx.paramFloat(TemperatureAlign, Float_Enc_IEEE754Single))
// Luftfeuchte anpassen
#define ParamHumidityAlign (knx.paramFloat(HumidityAlign, Float_Enc_IEEE754Single))
// Wert senden als
#define ParamHumiditySend (knx.paramByte(HumiditySend))
// Communication objects with single occurrence
#define KoHeartbeat 1
#define KoTemperature 2
#define KoHumidity 3
#define KoRequest 4
// In Betieb
#define KoHeartbeat (knx.getGroupObject(KoHeartbeat))
// Temperaturmesswert
#define KoTemperature (knx.getGroupObject(KoTemperature))
// Luftfeuchtemesswert
#define KoHumidity (knx.getGroupObject(KoHumidity))
// Alle Messwerte anfordern
#define KoRequest (knx.getGroupObject(KoRequest))
#ifdef MAIN_FirmwareRevision
#ifndef FIRMWARE_REVISION
#define FIRMWARE_REVISION MAIN_FirmwareRevision
#endif
#endif
#ifdef MAIN_FirmwareName
#ifndef FIRMWARE_NAME
#define FIRMWARE_NAME MAIN_FirmwareName
#endif
#endif

268
prodxml/Display.xml Normal file
View File

@ -0,0 +1,268 @@
<?xml version="1.0" encoding="utf-8"?>
<KNX xmlns="http://knx.org/xml/project/20" CreatedBy="KNX MT" ToolVersion="5.1.255.16695">
<ManufacturerData>
<Manufacturer RefId="M-00FA">
<Catalog>
<CatalogSection Id="M-00FA_CS-1" Name="Geräte" Number="1" DefaultLanguage="de">
<CatalogItem Id="%CatalogItemId%" Name="SmartHomeDisplay" Number="1" ProductRefId="%ProductId%" Hardware2ProgramRefId="%Hardware2ProgramId%" DefaultLanguage="de" />
</CatalogSection>
</Catalog>
<ApplicationPrograms>
<ApplicationProgram Id="%AID%" ApplicationNumber="712" ApplicationVersion="1" ReplacesVersions="0" ProgramType="ApplicationProgram" MaskVersion="MV-07B0" Name="SmartHomeDisplay" LoadProcedureStyle="MergedProcedure" PeiType="0" DefaultLanguage="de" DynamicTableManagement="false" Linkable="true" MinEtsVersion="5.0">
<Static>
<Code>
<RelativeSegment Id="%AID%_RS-04-00000" Name="Parameters" Offset="0" Size="%MemorySize%" LoadStateMachine="4" />
</Code>
<ParameterTypes>
<!-- the following ParameterTypes are from a productive example -->
<!-- simple integer type -->
<ParameterType Id="%AID%_PT-DelaySeconds" Name="DelaySeconds">
<TypeNumber SizeInBit="32" Type="signedInt" minInclusive="0" maxInclusive="86400" />
</ParameterType>
<!-- enumeration with 16-bit (word) values -->
<ParameterType Id="%AID%_PT-DelaySelection" Name="DelaySelection">
<TypeRestriction Base="Value" SizeInBit="16">
<Enumeration Text="1 Sekunde" Value="1" Id="%AID%_PT-DelaySelection_EN-0" />
<Enumeration Text="2 Sekunden" Value="2" Id="%AID%_PT-DelaySelection_EN-1" />
<Enumeration Text="5 Sekunden" Value="5" Id="%AID%_PT-DelaySelection_EN-2" />
<Enumeration Text="10 Sekunden" Value="10" Id="%AID%_PT-DelaySelection_EN-3" />
<Enumeration Text="30 Sekunden" Value="30" Id="%AID%_PT-DelaySelection_EN-4" />
<Enumeration Text="1 Minute" Value="60" Id="%AID%_PT-DelaySelection_EN-5" />
<Enumeration Text="2 Minuten" Value="120" Id="%AID%_PT-DelaySelection_EN-6" />
<Enumeration Text="5 Minuten" Value="300" Id="%AID%_PT-DelaySelection_EN-7" />
<Enumeration Text="10 Minuten" Value="600" Id="%AID%_PT-DelaySelection_EN-8" />
<Enumeration Text="30 Minuten" Value="1800" Id="%AID%_PT-DelaySelection_EN-9" />
<Enumeration Text="manuelle Eingabe (Sekundengenau)" Value="0" Id="%AID%_PT-DelaySelection_EN-10" />
</TypeRestriction>
</ParameterType>
<!-- Demo ParameterTypes to show available possibilities -->
<!-- Parameter type for text parameter, just for descriptions in ETS, is not transferred to device -->
<ParameterType Id="%AID%_PT-Text40Byte" Name="Text40Byte">
<TypeText SizeInBit="320" />
</ParameterType>
<!-- Parameter type for 1 bit parameter, presented as checkbox -->
<ParameterType Id="%AID%_PT-CheckBox" Name="CheckBox">
<TypeNumber SizeInBit="1" Type="unsignedInt" minInclusive="0" maxInclusive="1" UIHint="CheckBox" />
</ParameterType>
<!-- Parameter type for 1 bit parameter, presented as radio button -->
<ParameterType Id="%AID%_PT-YesNo" Name="YesNo">
<TypeRestriction Base="Value" SizeInBit="1">
<Enumeration Text="Nein" Value="0" Id="%AID%_PT-YesNo_EN-0" />
<Enumeration Text="Ja" Value="1" Id="%AID%_PT-YesNo_EN-1" />
</TypeRestriction>
</ParameterType>
<!-- Parameter type for an 8 bit percent parameter -->
<ParameterType Id="%AID%_PT-Percentage" Name="Percentage">
<TypeNumber SizeInBit="8" Type="signedInt" minInclusive="0" maxInclusive="100" />
</ParameterType>
<!-- Parameter type for an 16 bit float value like temperature -->
<ParameterType Id="%AID%_PT-ValueDpt9" Name="ValueDpt9">
<TypeFloat Encoding="IEEE-754 Single" minInclusive="-671088" maxInclusive="670760" />
</ParameterType>
<!-- Parameter type for an 8 bit enumeration -->
<ParameterType Id="%AID%_PT-DptSelect" Name="DptSelect">
<TypeRestriction Base="Value" SizeInBit="8">
<Enumeration Text="DPT 5.001 (1 Byte, Prozentwert)" Value="5" Id="%AID%_PT-DptSelect_EN-0" />
<Enumeration Text="DPT 9.007 (2 Byte, Fließkommawert)" Value="9" Id="%AID%_PT-DptSelect_EN-1" />
</TypeRestriction>
</ParameterType>
</ParameterTypes>
<Parameters>
<!-- the follwoing 3 parameters represent a productive example -->
<!-- A parameter to enter manually a number of seconds -->
<Parameter Id="%AID%_P-1" Name="StartupDelay" ParameterType="%AID%_PT-DelaySeconds" Text=" Zeit (Sekundengenau)" SuffixText="Sekunden" Value="10">
<!-- offset has always to start with 0 -->
<Memory CodeSegment="%AID%_RS-04-00000" Offset="0" BitOffset="0" />
</Parameter>
<!-- A parameter to choose predefined seconds from a dropdown -->
<Parameter Id="%AID%_P-2" Name="StartupDelaySelection" ParameterType="%AID%_PT-DelaySelection" Text="Zeit bis das Gerät nach einem Neustart aktiv wird" Value="10">
<!-- you have to calculate the right offset according to size of the used type -->
<Memory CodeSegment="%AID%_RS-04-00000" Offset="4" BitOffset="0" />
</Parameter>
<!-- A parameter to enter manually a number of seconds -->
<Parameter Id="%AID%_P-3" Name="Heartbeat" ParameterType="%AID%_PT-DelaySeconds" Text="'In Betrieb'-Telegramm senden alle" SuffixText="Sekunden" Value="300">
<Memory CodeSegment="%AID%_RS-04-00000" Offset="6" BitOffset="0" />
</Parameter>
<!-- additional demo parametes to show available possibilities -->
<!-- 1-Bit parameter usage, to choose, which sensors are attached -->
<Parameter Id="%AID%_P-4" Name="TemperatureSensor" ParameterType="%AID%_PT-CheckBox" Text="Temperatursensor" Value="0">
<Memory CodeSegment="%AID%_RS-04-00000" Offset="10" BitOffset="0" />
</Parameter>
<!-- Both parameters write into the same parameter byte, but at different BitOffsets!!! -->
<Parameter Id="%AID%_P-5" Name="HumiditySensor" ParameterType="%AID%_PT-CheckBox" Text="Luftfeuchtesensor" Value="0">
<Memory CodeSegment="%AID%_RS-04-00000" Offset="10" BitOffset="1" />
</Parameter>
<!-- A parameter to enter (informational) text, this parameter is not transferred to the device (no memory block reference) -->
<Parameter Id="%AID%_P-6" Name="TemperatureText" ParameterType="%AID%_PT-Text40Byte" Text="Bezeichnung des Temperatursensors" Value="Temperatur" />
<!-- A parameter for a float value -->
<Parameter Id="%AID%_P-7" Name="TemperatureAlign" ParameterType="%AID%_PT-ValueDpt9" Text="Temperaturwert anpassen" Value="0">
<Memory CodeSegment="%AID%_RS-04-00000" Offset="11" BitOffset="0" />
</Parameter>
<Parameter Id="%AID%_P-8" Name="HumidityText" ParameterType="%AID%_PT-Text40Byte" Text="Bezeichnung des Luftfeuchtesensors" Value="Luftfeuchte" />
<Parameter Id="%AID%_P-9" Name="HumidityAlign" ParameterType="%AID%_PT-ValueDpt9" Text="Luftfeuchte anpassen" Value="0">
<Memory CodeSegment="%AID%_RS-04-00000" Offset="13" BitOffset="0" />
</Parameter>
<!-- A parameter with a 8 bit dropdown, allows selection of a dpt for output -->
<Parameter Id="%AID%_P-10" Name="HumiditySend" ParameterType="%AID%_PT-DptSelect" Text="Wert senden als" Value="9">
<Memory CodeSegment="%AID%_RS-04-00000" Offset="15" BitOffset="0" />
</Parameter>
</Parameters>
<ParameterRefs>
<!-- ParameterRef have to be defined for each parameter, pay attention, that the ID-part (number) after R- is unique! -->
<!-- ParameterRef are used in the ETS UI -->
<ParameterRef Id="%AID%_P-1_R-1" RefId="%AID%_P-1" />
<ParameterRef Id="%AID%_P-2_R-2" RefId="%AID%_P-2" />
<ParameterRef Id="%AID%_P-3_R-3" RefId="%AID%_P-3" />
<ParameterRef Id="%AID%_P-4_R-4" RefId="%AID%_P-4" />
<ParameterRef Id="%AID%_P-5_R-5" RefId="%AID%_P-5" />
<ParameterRef Id="%AID%_P-6_R-6" RefId="%AID%_P-6" />
<ParameterRef Id="%AID%_P-7_R-7" RefId="%AID%_P-7" />
<ParameterRef Id="%AID%_P-8_R-8" RefId="%AID%_P-8" />
<ParameterRef Id="%AID%_P-9_R-9" RefId="%AID%_P-9" />
<ParameterRef Id="%AID%_P-10_R-10" RefId="%AID%_P-10" />
</ParameterRefs>
<ComObjectTable>
<!-- ComObject sending a 'still alive' signal, DPT 1 (1 bit) -->
<ComObject Id="%AID%_O-1" Name="Heartbeat" Text="In Betieb" Number="1" FunctionText="Ausgang (zyklisch)" ObjectSize="1 Bit" ReadFlag="Enabled" WriteFlag="Disabled" CommunicationFlag="Enabled" TransmitFlag="Enabled" UpdateFlag="Disabled" ReadOnInitFlag="Disabled" DatapointType="DPST-1-2" />
<!-- ComOject sending a temperature (float) value, DPT 9.001 (2 Bytes) -->
<ComObject Id="%AID%_O-2" Name="Temperature" Text="Temperaturmesswert" Number="2" FunctionText="Ausgang" ObjectSize="2 Bytes" ReadFlag="Enabled" WriteFlag="Disabled" CommunicationFlag="Enabled" TransmitFlag="Enabled" UpdateFlag="Disabled" ReadOnInitFlag="Disabled" DatapointType="DPST-9-1" />
<!-- ComObject sending a humidity value, DPT and Size are taken from according ComObjectRef, see comment there -->
<ComObject Id="%AID%_O-3" Name="Humidity" Text="Luftfeuchtemesswert" Number="3" FunctionText="Ausgang" ReadFlag="Enabled" WriteFlag="Disabled" CommunicationFlag="Enabled" TransmitFlag="Enabled" UpdateFlag="Disabled" ReadOnInitFlag="Disabled" />
<!-- ComObject as an input example, alows the requert of all measured values at once -->
<ComObject Id="%AID%_O-4" Name="Request" Text="Alle Messwerte anfordern" Number="4" FunctionText="Eingang" ObjectSize="1 Bit" ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled" TransmitFlag="Disabled" UpdateFlag="Enabled" ReadOnInitFlag="Disabled" DatapointType="DPST-1-16" />
</ComObjectTable>
<ComObjectRefs>
<!-- A ComObjecdtRef is necessary for each ComObject, ComObjectRef are used in the ETS UI -->
<ComObjectRef Id="%AID%_O-1_R-1" RefId="%AID%_O-1" />
<ComObjectRef Id="%AID%_O-2_R-2" RefId="%AID%_O-2" />
<!-- There mitght be more that one ComObjectRef for a ComObject, like here, the two different ComObjectRef define different Size and DPT for ComObject 3 -->
<!-- If there is more than one ComObjectRef for a specific ComObject, you have to ensure, that exactly one of them is visible in the UI -->
<ComObjectRef Id="%AID%_O-3_R-31" RefId="%AID%_O-3" ObjectSize="1 Byte" DatapointType="DPST-5-1" />
<ComObjectRef Id="%AID%_O-3_R-32" RefId="%AID%_O-3" ObjectSize="2 Bytes" DatapointType="DPST-9-7" />
<ComObjectRef Id="%AID%_O-4_R-4" RefId="%AID%_O-4" />
</ComObjectRefs>
<AddressTable MaxEntries="65535" />
<AssociationTable MaxEntries="65535" />
<LoadProcedures>
<LoadProcedure MergeId="2">
<LdCtrlRelSegment LsmIdx="4" Size="%MemorySize%" Mode="1" Fill="0" AppliesTo="full" />
<LdCtrlRelSegment LsmIdx="4" Size="%MemorySize%" Mode="0" Fill="0" AppliesTo="par" />
</LoadProcedure>
<LoadProcedure MergeId="4">
<LdCtrlWriteRelMem ObjIdx="4" Offset="0" Size="%MemorySize%" Verify="true" AppliesTo="full,par" />
</LoadProcedure>
<LoadProcedure MergeId="7">
<LdCtrlLoadImageProp ObjIdx="1" PropId="27" />
<LdCtrlLoadImageProp ObjIdx="2" PropId="27" />
<LdCtrlLoadImageProp ObjIdx="3" PropId="27" />
<LdCtrlLoadImageProp ObjIdx="4" PropId="27" />
</LoadProcedure>
</LoadProcedures>
<Options />
</Static>
<!-- Here statrs the UI definition -->
<Dynamic>
<!-- The following block is always there -->
<ChannelIndependentBlock>
<!-- A parameter block represents a tab on the left side, showing a page full of parameters -->
<ParameterBlock Id="%AID%_PB-1" Name="Docu" Text="Documentation">
<!-- In this example the first Tab shows some documentation -->
<ParameterSeparator Id="%AID%_PS-1" Text="Kurze Einführung:" />
<!-- A parameter seperator shows just some text -->
<ParameterSeparator Id="%AID%_PS-2" Text="Das Gerät kann ein 'In Betrieb'-Telegramm in einstellbarer Zeit senden." />
<ParameterSeparator Id="%AID%_PS-3" Text="Die Zeit, bis das Gerät nach einem Neustart erstmals meldet, ist einstellbar." />
<ParameterSeparator Id="%AID%_PS-4" Text="Es erlaubt die Auswahl der installierten Sensoren." />
<ParameterSeparator Id="%AID%_PS-5" Text="Pro Sensor können weitere Einstellugnen auf den dann erscheinenden Tabs gemacht werden." />
</ParameterBlock>
<!-- A tab page with general settings -->
<ParameterBlock Id="%AID%_PB-2" Name="General" Text="Allgemeine Parameter">
<!-- The first Parameter allows the entry of heartbeat cycle -->
<ParameterRefRef RefId="%AID%_P-3_R-3" />
<!-- conditional element referencing the content of above parameter -->
<choose ParamRefId="%AID%_P-3_R-3">
<!-- if the content of the parameter is not equal zero, the following block is shown -->
<when test="!=0">
<!-- this means: ComObject 'Heartbeat' is just shown, if the user entered a valid cycle time -->
<ComObjectRefRef RefId="%AID%_O-1_R-1" />
</when>
</choose>
<!-- Parameter to choose startup delay -->
<ParameterRefRef RefId="%AID%_P-2_R-2" />
<choose ParamRefId="%AID%_P-2_R-2">
<when test="0">
<!-- This means: If the user cooses manual entry in the dropdown, we display the 'manual entry' parameter -->
<ParameterRefRef RefId="%AID%_P-1_R-1" />
</when>
</choose>
<!-- A ParameterSeparator with an empty text is presented as a line -->
<ParameterSeparator Id="%AID%_PS-1" Text=" " />
<ParameterSeparator Id="%AID%_PS-2" Text="Angeschlossene Sensoren:" />
<!-- Checkboxes for sensor selection -->
<ParameterRefRef RefId="%AID%_P-4_R-4" />
<ParameterRefRef RefId="%AID%_P-5_R-5" />
</ParameterBlock>
</ChannelIndependentBlock>
<choose ParamRefId="%AID%_P-4_R-4">
<when test="1">
<!-- Channels are used to present group of tabs, here just as an example with one tab -->
<!-- this channel is within a choose block, it is displayed just in case the temperaturesensor is installed -->
<Channel Id="%AID%_CH-1" Name="Temperature" Number="1" Text="Temperatursensor">
<!-- This is a tab within a channel -->
<ParameterBlock Id="%AID%_PB-3" Name="Temp" Text="Einstellungen">
<!-- Display temperature settings -->
<ParameterRefRef RefId="%AID%_P-6_R-6" />
<ParameterRefRef RefId="%AID%_P-7_R-7" />
<!-- ...and according ComObjects -->
<ComObjectRefRef RefId="%AID%_O-2_R-2" />
<ComObjectRefRef RefId="%AID%_O-4_R-4" />
</ParameterBlock>
</Channel>
</when>
</choose>
<!-- same as above for humidity -->
<choose ParamRefId="%AID%_P-5_R-5">
<when test="1">
<Channel Id="%AID%_CH-2" Name="Humidity" Number="2" Text="Luftfeuchtesensor">
<ParameterBlock Id="%AID%_PB-4" Name="Hum" Text="Einstellungen">
<!-- display humidity settings -->
<ParameterRefRef RefId="%AID%_P-8_R-8" />
<ParameterRefRef RefId="%AID%_P-9_R-9" />
<ParameterRefRef RefId="%AID%_P-10_R-10" />
<!-- The output DPT depends on the chosen value in the dropdown -->
<choose ParamRefId="%AID%_P-10_R-10">
<when test="5">
<!-- Use DPT 5 ComOjbect -->
<ComObjectRefRef RefId="%AID%_O-3_R-31" />
</when>
<when test="9">
<!-- Use DPT 9 ComObject -->
<ComObjectRefRef RefId="%AID%_O-3_R-32" />
</when>
</choose>
<!-- ComObjects might be enabled more than once, they appear of course just once -->
<ComObjectRefRef RefId="%AID%_O-4_R-4" />
</ParameterBlock>
</Channel>
</when>
</choose>
</Dynamic>
</ApplicationProgram>
</ApplicationPrograms>
<Hardware>
<Hardware Id="%HardwareId%" Name="SmartHomeDisplay" SerialNumber="712" VersionNumber="1" BusCurrent="10" HasIndividualAddress="true" HasApplicationProgram="true">
<Products>
<Product Id="%ProductId%" Text="SmartHomeDisplay" OrderNumber="712" IsRailMounted="false" DefaultLanguage="de">
<RegistrationInfo RegistrationStatus="Registered" />
</Product>
</Products>
<Hardware2Programs>
<Hardware2Program Id="%Hardware2ProgramId%" MediumTypes="MT-0">
<ApplicationProgramRef RefId="%AID%" />
<RegistrationInfo RegistrationStatus="Registered" RegistrationNumber="0001/%HardwareVersionEncoded%1" />
</Hardware2Program>
</Hardware2Programs>
</Hardware>
</Hardware>
</Manufacturer>
</ManufacturerData>
</KNX>

View File

@ -1,16 +1,16 @@
<template>
<main class="p-5 overflow-auto flex flex-col items-center gap-4 min-h-0">
<div class="w-full">
<div class="bg-panel border border-border rounded-[14px] shadow-[0_8px_18px_var(--shadow)] w-full pt-3 px-3.5" :class="screensOpen ? 'pb-3' : 'pb-2'">
<div class="bg-gradient-to-b from-white to-[#f6f9fc] border border-border rounded-[14px] shadow-[0_10px_24px_rgba(15,23,42,0.12)] w-full pt-3 px-3.5" :class="screensOpen ? 'pb-3' : 'pb-2'">
<div class="flex flex-wrap items-center justify-between gap-2.5" :class="screensOpen ? 'mb-3' : 'mb-0'">
<div class="flex items-center gap-2">
<h3 class="text-[12px] uppercase tracking-[0.08em] text-muted">Bildschirme</h3>
<h3 class="text-[12px] uppercase tracking-[0.08em] text-[#3a5f88]">Bildschirme</h3>
<span class="text-[11px] text-muted">{{ store.config.screens.length }}</span>
</div>
<div class="flex items-center gap-2">
<button class="border border-border bg-transparent text-text px-2.5 py-1.5 rounded-[10px] text-[12px] font-semibold transition hover:-translate-y-0.5 active:translate-y-0.5" @click="emit('open-screen-settings')">Einstellungen</button>
<button class="w-7 h-7 rounded-lg border border-border bg-panel-2 text-text grid place-items-center cursor-pointer hover:border-accent" @click="store.addScreen">+</button>
<button class="w-7 h-7 rounded-lg border border-border bg-panel-2 text-text grid place-items-center cursor-pointer hover:border-accent" @click="screensOpen = !screensOpen">
<button class="border border-border bg-panel-2 text-text px-2.5 py-1.5 rounded-[10px] text-[12px] font-semibold transition hover:-translate-y-0.5 hover:bg-[#e4ebf2] active:translate-y-0.5" @click="emit('open-screen-settings')">Einstellungen</button>
<button class="w-7 h-7 rounded-lg border border-border bg-panel-2 text-text grid place-items-center cursor-pointer hover:bg-[#e4ebf2] hover:border-[#b8c4d2]" @click="store.addScreen">+</button>
<button class="w-7 h-7 rounded-lg border border-border bg-panel-2 text-text grid place-items-center cursor-pointer hover:bg-[#e4ebf2] hover:border-[#b8c4d2]" @click="screensOpen = !screensOpen">
<span class="material-symbols-outlined text-[18px]">{{ screensOpen ? 'expand_less' : 'expand_more' }}</span>
</button>
</div>
@ -19,12 +19,17 @@
<button
v-for="screen in store.config.screens"
:key="screen.id"
class="bg-panel-2 border border-border rounded-full px-2.5 py-1.5 text-left text-text text-[12px] inline-flex items-center gap-2 cursor-pointer transition hover:border-accent hover:-translate-y-0.5"
:class="screen.id === store.activeScreenId ? 'border-accent-2 shadow-[0_0_0_2px_rgba(90,147,218,0.2)]' : ''"
class="border rounded-full px-3 py-1.5 text-left text-[12px] inline-flex items-center gap-2 cursor-pointer transition hover:-translate-y-0.5"
:class="screen.id === store.activeScreenId ? 'bg-[#2f6db8] text-white border-[#2b62a5] shadow-[0_0_0_2px_rgba(47,109,184,0.2)]' : 'bg-[#eef3f7] text-[#2d3c4a] border-[#c8d2dc] hover:bg-white'"
@click="selectScreen(screen.id)"
>
<span class="max-w-[160px] overflow-hidden text-ellipsis whitespace-nowrap">{{ screen.name }}</span>
<span class="text-[10px] px-1.5 py-0.5 rounded-full bg-accent-2/15 text-accent-2 uppercase tracking-[0.06em]">{{ screen.mode === 1 ? 'Modal' : 'Fullscreen' }}</span>
<span
class="text-[10px] px-1.5 py-0.5 rounded-full uppercase tracking-[0.06em]"
:class="screen.id === store.activeScreenId ? 'bg-white/20 text-white' : 'bg-[#dfe9f3] text-[#3a5f88]'"
>
{{ screen.mode === 1 ? 'Modal' : 'Fullscreen' }}
</span>
</button>
</div>
</div>

View File

@ -3,12 +3,12 @@
class="fixed inset-0 bg-black/60 flex items-center justify-center z-50"
@click.self="$emit('close')"
>
<div class="border border-border rounded-2xl w-[90%] max-w-2xl max-h-[80vh] flex flex-col shadow-2xl bg-panel">
<div class="border border-border rounded-2xl w-[90%] max-w-2xl max-h-[80vh] flex flex-col shadow-2xl bg-gradient-to-b from-white to-[#f6f9fc]">
<!-- Header -->
<div class="flex items-center justify-between px-5 py-4 border-b border-border">
<h3 class="text-base font-semibold">Icon auswaehlen</h3>
<h3 class="text-base font-semibold text-[#3a5f88]">Icon auswaehlen</h3>
<button
class="w-7 h-7 rounded-lg border border-border bg-panel-2 text-text cursor-pointer hover:border-accent"
class="w-7 h-7 rounded-lg border border-border bg-panel-2 text-text cursor-pointer hover:bg-[#e4ebf2] hover:border-[#b8c4d2]"
@click="$emit('close')"
>
x
@ -33,7 +33,7 @@
<span class="material-symbols-outlined text-2xl text-accent">{{ String.fromCodePoint(modelValue) }}</span>
<span class="flex-1 text-sm">{{ currentIconName }}</span>
<button
class="bg-transparent border border-red-300 text-red-600 px-3 py-1.5 rounded-md text-xs cursor-pointer hover:bg-red-50"
class="bg-[#f7dede] border border-red-200 text-[#b3261e] px-3 py-1.5 rounded-md text-xs cursor-pointer hover:bg-[#f2cfcf]"
@click="selectIcon(0)"
>
Entfernen

View File

@ -1,13 +1,13 @@
<template>
<div class="fixed inset-0 bg-black/60 flex items-center justify-center p-4 z-50" @click.self="emit('close')">
<div class="w-[min(720px,96vw)] max-h-[85vh] bg-panel border border-border rounded-2xl shadow-[0_20px_40px_var(--shadow)] flex flex-col">
<div class="w-[min(720px,96vw)] max-h-[85vh] bg-gradient-to-b from-white to-[#f6f9fc] border border-border rounded-2xl shadow-[0_20px_40px_rgba(15,23,42,0.18)] flex flex-col">
<div class="flex items-center justify-between gap-3 px-[18px] py-4 border-b border-border">
<h3 class="text-[12px] uppercase tracking-[0.08em] text-muted">Bildschirm</h3>
<button class="w-7 h-7 rounded-lg border border-border bg-panel-2 text-text grid place-items-center cursor-pointer hover:border-accent" @click="emit('close')">x</button>
<h3 class="text-[12px] uppercase tracking-[0.08em] text-[#3a5f88]">Bildschirm</h3>
<button class="w-7 h-7 rounded-lg border border-border bg-panel-2 text-text grid place-items-center cursor-pointer hover:bg-[#e4ebf2] hover:border-[#b8c4d2]" @click="emit('close')">x</button>
</div>
<div class="px-[18px] py-4 overflow-y-auto flex flex-col gap-3.5" v-if="screen">
<div class="flex flex-col gap-2.5">
<div class="text-[12px] uppercase tracking-[0.08em] text-muted">Allgemein</div>
<div class="text-[12px] uppercase tracking-[0.08em] text-[#3a5f88]">Allgemein</div>
<div class="flex items-center justify-between gap-2.5">
<label class="text-[12px] text-muted">Name</label>
<input class="flex-1 bg-panel-2 border border-border rounded-lg px-2 py-1.5 text-text text-[12px] focus:outline-none focus:border-accent" type="text" v-model="screen.name">
@ -26,7 +26,7 @@
</div>
<div class="flex flex-col gap-2.5" v-if="screen.mode === 1">
<div class="text-[12px] uppercase tracking-[0.08em] text-muted">Modal</div>
<div class="text-[12px] uppercase tracking-[0.08em] text-[#3a5f88]">Modal</div>
<div class="flex items-center justify-between gap-2.5"><label class="text-[12px] text-muted">X</label><input class="flex-1 bg-panel-2 border border-border rounded-lg px-2 py-1.5 text-text text-[12px] focus:outline-none focus:border-accent" type="number" v-model.number="screen.modal.x"></div>
<div class="flex items-center justify-between gap-2.5"><label class="text-[12px] text-muted">Y</label><input class="flex-1 bg-panel-2 border border-border rounded-lg px-2 py-1.5 text-text text-[12px] focus:outline-none focus:border-accent" type="number" v-model.number="screen.modal.y"></div>
<div class="flex items-center justify-between gap-2.5"><label class="text-[12px] text-muted">Breite</label><input class="flex-1 bg-panel-2 border border-border rounded-lg px-2 py-1.5 text-text text-[12px] focus:outline-none focus:border-accent" type="number" v-model.number="screen.modal.w"></div>
@ -42,8 +42,8 @@
<div class="text-[12px] text-muted">Kein Bildschirm ausgewaehlt.</div>
</div>
<div class="px-[18px] py-3 border-t border-border flex justify-end gap-2.5">
<button class="border border-border bg-transparent text-text px-3.5 py-2 rounded-[10px] text-[13px] font-semibold transition hover:-translate-y-0.5 active:translate-y-0.5" @click="emit('close')">Schliessen</button>
<button class="border border-red-300 bg-transparent text-red-600 px-3.5 py-2 rounded-[10px] text-[13px] font-semibold transition hover:-translate-y-0.5 hover:bg-red-50 active:translate-y-0.5 disabled:opacity-50 disabled:cursor-not-allowed" :disabled="!canDelete" @click="handleDelete">Screen loeschen</button>
<button class="border border-border bg-panel-2 text-text px-3.5 py-2 rounded-[10px] text-[13px] font-semibold transition hover:-translate-y-0.5 hover:bg-[#e4ebf2] active:translate-y-0.5" @click="emit('close')">Schliessen</button>
<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 disabled:opacity-50 disabled:cursor-not-allowed" :disabled="!canDelete" @click="handleDelete">Screen loeschen</button>
</div>
</div>
</div>

View File

@ -1,13 +1,13 @@
<template>
<div class="fixed inset-0 bg-black/60 flex items-center justify-center p-4 z-50" @click.self="emit('close')">
<div class="w-[min(720px,96vw)] max-h-[85vh] bg-panel border border-border rounded-2xl shadow-[0_20px_40px_var(--shadow)] flex flex-col">
<div class="w-[min(720px,96vw)] max-h-[85vh] bg-gradient-to-b from-white to-[#f6f9fc] border border-border rounded-2xl shadow-[0_20px_40px_rgba(15,23,42,0.18)] flex flex-col">
<div class="flex items-center justify-between gap-3 px-[18px] py-4 border-b border-border">
<h3 class="text-[12px] uppercase tracking-[0.08em] text-muted">Einstellungen</h3>
<button class="w-7 h-7 rounded-lg border border-border bg-panel-2 text-text grid place-items-center cursor-pointer hover:border-accent" @click="emit('close')">x</button>
<h3 class="text-[12px] uppercase tracking-[0.08em] text-[#3a5f88]">Einstellungen</h3>
<button class="w-7 h-7 rounded-lg border border-border bg-panel-2 text-text grid place-items-center cursor-pointer hover:bg-[#e4ebf2] hover:border-[#b8c4d2]" @click="emit('close')">x</button>
</div>
<div class="px-[18px] py-4 overflow-y-auto flex flex-col gap-3.5">
<div class="flex flex-col gap-2.5">
<div class="text-[12px] uppercase tracking-[0.08em] text-muted">Allgemein</div>
<div class="text-[12px] uppercase tracking-[0.08em] text-[#3a5f88]">Allgemein</div>
<div class="flex items-center justify-between gap-2.5">
<label class="text-[12px] text-muted">Startscreen</label>
<select class="flex-1 bg-panel-2 border border-border rounded-lg px-2 py-1.5 text-text text-[12px] focus:outline-none focus:border-accent" v-model.number="store.config.startScreen">
@ -32,7 +32,7 @@
</div>
</div>
<div class="px-[18px] py-3 border-t border-border flex justify-end gap-2.5">
<button class="border border-border bg-transparent text-text px-3.5 py-2 rounded-[10px] text-[13px] font-semibold transition hover:-translate-y-0.5 active:translate-y-0.5" @click="emit('close')">Schliessen</button>
<button class="border border-border bg-panel-2 text-text px-3.5 py-2 rounded-[10px] text-[13px] font-semibold transition hover:-translate-y-0.5 hover:bg-[#e4ebf2] active:translate-y-0.5" @click="emit('close')">Schliessen</button>
</div>
</div>
</div>

View File

@ -1,8 +1,8 @@
<template>
<aside class="h-full overflow-y-auto p-[18px] flex flex-col gap-4 border-r border-border max-[1100px]:border-r-0 max-[1100px]:border-b">
<section class="bg-panel border border-border rounded-[14px] p-3.5 shadow-[0_8px_18px_var(--shadow)]">
<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)]">
<div class="flex items-center justify-between gap-2.5 mb-3">
<h3 class="text-[12px] uppercase tracking-[0.08em] text-muted">Elemente</h3>
<h3 class="text-[12px] uppercase tracking-[0.08em] text-[#3a5f88]">Elemente</h3>
<span class="text-[11px] text-muted">Klick zum Hinzufuegen</span>
</div>
<div class="grid grid-cols-2 gap-2.5">
@ -29,9 +29,9 @@
</div>
</section>
<section class="bg-panel border border-border rounded-[14px] p-3.5 shadow-[0_8px_18px_var(--shadow)]">
<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)]">
<div class="flex items-center justify-between gap-2.5 mb-3">
<h3 class="text-[12px] uppercase tracking-[0.08em] text-muted">Baum</h3>
<h3 class="text-[12px] uppercase tracking-[0.08em] text-[#3a5f88]">Baum</h3>
<span class="text-[11px] text-muted">{{ store.activeScreen?.widgets.length || 0 }}</span>
</div>
<div class="flex flex-col gap-2">
@ -48,9 +48,9 @@
</div>
</section>
<section class="bg-panel border border-border rounded-[14px] p-3.5 shadow-[0_8px_18px_var(--shadow)]">
<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)]">
<div class="flex items-center justify-between gap-2.5 mb-3">
<h3 class="text-[12px] uppercase tracking-[0.08em] text-muted">Canvas</h3>
<h3 class="text-[12px] uppercase tracking-[0.08em] text-[#3a5f88]">Canvas</h3>
</div>
<div class="flex items-center justify-between gap-2.5 mb-2.5">
<label class="text-[12px] text-muted">Zoom</label>

View File

@ -1,6 +1,6 @@
<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-panel border border-border rounded-[14px] p-3.5 shadow-[0_8px_18px_var(--shadow)]" id="properties">
<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.
@ -225,7 +225,7 @@
</template>
<div class="mt-4">
<button class="border border-red-300 bg-transparent text-red-600 px-3.5 py-2 rounded-[10px] text-[13px] font-semibold transition hover:-translate-y-0.5 hover:bg-red-50 active:translate-y-0.5" @click="store.deleteWidget">Widget loeschen</button>
<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>
@ -255,10 +255,10 @@ const writeableAddresses = computed(() => store.knxAddresses.filter(a => a.write
const rowClass = 'flex items-center gap-2.5 mb-2';
const labelClass = 'w-[90px] text-[12px] text-muted';
const inputClass = 'flex-1 bg-panel-2 border border-border rounded-lg px-2 py-1.5 text-text text-[12px] focus:outline-none focus:border-accent';
const headingClass = 'mt-4 mb-2.5 text-[12px] uppercase tracking-[0.08em] text-muted';
const headingClass = 'mt-4 mb-2.5 text-[12px] uppercase tracking-[0.08em] text-[#3a5f88]';
const noteClass = 'text-[11px] text-muted leading-tight';
const iconSelectClass = 'flex-1 bg-panel-2 border border-border rounded-lg px-2.5 py-1.5 text-text text-[12px] flex items-center justify-center gap-1.5 cursor-pointer hover:border-accent';
const iconRemoveClass = 'w-7 h-7 rounded-md border border-red-300 bg-transparent text-red-600 grid place-items-center text-[12px] cursor-pointer hover:bg-red-50';
const iconSelectClass = 'flex-1 bg-panel-2 border border-border rounded-lg px-2.5 py-1.5 text-text text-[12px] flex items-center justify-center gap-1.5 cursor-pointer hover:bg-[#e4ebf2] hover:border-[#b8c4d2]';
const iconRemoveClass = 'w-7 h-7 rounded-md border border-red-200 bg-[#f7dede] text-[#b3261e] grid place-items-center text-[12px] cursor-pointer hover:bg-[#f2cfcf]';
function handleTextSrcChange() {
if (!w.value) return;

View File

@ -8,10 +8,10 @@
</div>
</div>
<div class="flex items-center gap-2.5 flex-wrap justify-end">
<button class="border border-border bg-transparent text-text px-3.5 py-2 rounded-[10px] text-[13px] font-semibold transition hover:-translate-y-0.5 active:translate-y-0.5" @click="emit('open-settings')">Einstellungen</button>
<button class="border border-border bg-transparent text-text px-3.5 py-2 rounded-[10px] text-[13px] font-semibold transition hover:-translate-y-0.5 active:translate-y-0.5" @click="enableUsbMode">USB-Modus</button>
<button class="border border-red-300 bg-transparent text-red-600 px-3.5 py-2 rounded-[10px] text-[13px] font-semibold transition hover:-translate-y-0.5 hover:bg-red-50 active:translate-y-0.5" @click="handleReset">Zuruecksetzen</button>
<button class="border border-transparent bg-gradient-to-br from-accent to-[#86b7e6] text-white px-3.5 py-2 rounded-[10px] text-[13px] font-semibold transition hover:-translate-y-0.5 active:translate-y-0.5 shadow-[0_8px_18px_rgba(90,147,218,0.35)]" @click="handleSave">Speichern & Anwenden</button>
<button class="border border-border bg-panel-2 text-text px-3.5 py-2 rounded-[10px] text-[13px] font-semibold transition hover:-translate-y-0.5 hover:bg-[#e4ebf2] active:translate-y-0.5" @click="emit('open-settings')">Einstellungen</button>
<button class="border border-border bg-panel-2 text-text px-3.5 py-2 rounded-[10px] text-[13px] font-semibold transition hover:-translate-y-0.5 hover:bg-[#e4ebf2] active:translate-y-0.5" @click="enableUsbMode">USB-Modus</button>
<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="handleReset">Zuruecksetzen</button>
<button class="border border-[#2b62a5] bg-[#2f6db8] text-white px-3.5 py-2 rounded-[10px] text-[13px] font-semibold transition hover:-translate-y-0.5 hover:bg-[#2b62a5] active:translate-y-0.5 shadow-[0_8px_18px_rgba(47,109,184,0.3)]" @click="handleSave">Speichern & Anwenden</button>
</div>
</header>
</template>

View File

@ -6,7 +6,7 @@
--bg-2: #e6edf3;
--panel: #ffffff;
--panel-2: #f3f6f9;
--border: #d5dce3;
--border: #c8d2dc;
--text: #1f2a33;
--muted: #6b7885;
--accent: #5a93da;