From 2ea18624fc756e8717bdb8bcfc305bd49d0ff32a Mon Sep 17 00:00:00 2001 From: Thomas Peterson Date: Wed, 4 Feb 2026 21:11:04 +0100 Subject: [PATCH] Fixes --- main/CMakeLists.txt | 1 + main/WidgetConfig.cpp | 12 +++++ main/WidgetConfig.hpp | 8 ++- main/WidgetManager.cpp | 19 ++++++- main/widgets/ChartWidget.cpp | 1 - main/widgets/ClockWidget.cpp | 16 ++++-- main/widgets/IconWidget.cpp | 11 ++++ main/widgets/LabelWidget.cpp | 9 ++++ main/widgets/LedWidget.cpp | 9 ++++ main/widgets/PowerFlowWidget.cpp | 15 ++++-- main/widgets/PowerNodeWidget.cpp | 15 ++++-- main/widgets/RectangleWidget.cpp | 26 +++++++++ main/widgets/RectangleWidget.hpp | 11 ++++ main/widgets/RoomCardBubbleWidget.cpp | 8 +++ main/widgets/RoomCardTileWidget.cpp | 10 +++- main/widgets/TabPageWidget.cpp | 17 ++++++ main/widgets/Widget.cpp | 10 ++++ main/widgets/WidgetFactory.cpp | 3 ++ web-interface/src/components/SidebarLeft.vue | 4 ++ web-interface/src/components/SidebarRight.vue | 4 +- .../src/components/WidgetElement.vue | 4 +- .../widgets/elements/ButtonElement.vue | 6 ++- .../widgets/elements/ChartElement.vue | 4 +- .../widgets/elements/ClockElement.vue | 10 +++- .../widgets/elements/IconElement.vue | 4 +- .../widgets/elements/LabelElement.vue | 4 +- .../widgets/elements/LedElement.vue | 4 +- .../widgets/elements/PowerFlowElement.vue | 8 ++- .../widgets/elements/PowerNodeElement.vue | 8 ++- .../widgets/elements/RectangleElement.vue | 54 +++++++++++++++++++ .../widgets/elements/RoomCardElement.vue | 15 +++--- .../widgets/elements/TabPageElement.vue | 13 ++++- .../widgets/elements/TabViewElement.vue | 5 +- .../widgets/settings/ButtonSettings.vue | 4 ++ .../widgets/settings/ChartSettings.vue | 4 ++ .../widgets/settings/ClockSettings.vue | 4 ++ .../widgets/settings/IconSettings.vue | 4 ++ .../widgets/settings/LabelSettings.vue | 4 ++ .../widgets/settings/LedSettings.vue | 4 ++ .../widgets/settings/PowerFlowSettings.vue | 4 ++ .../widgets/settings/PowerNodeSettings.vue | 4 ++ .../widgets/settings/RectangleSettings.vue | 29 ++++++++++ .../widgets/settings/RoomCardSettings.vue | 4 ++ .../widgets/settings/TabPageSettings.vue | 4 ++ .../widgets/settings/TabViewSettings.vue | 4 ++ .../src/components/widgets/shared/utils.js | 12 +++++ web-interface/src/constants.js | 39 ++++++++++++-- web-interface/src/stores/editor.js | 10 ++++ web-interface/src/utils.js | 12 +++++ 49 files changed, 448 insertions(+), 46 deletions(-) create mode 100644 main/widgets/RectangleWidget.cpp create mode 100644 main/widgets/RectangleWidget.hpp create mode 100644 web-interface/src/components/widgets/elements/RectangleElement.vue create mode 100644 web-interface/src/components/widgets/settings/RectangleSettings.vue diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 307717e..c257d5e 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -12,6 +12,7 @@ idf_component_register(SRCS "HistoryStore.cpp" "KnxWorker.cpp" "Nvs.cpp" "main.c "widgets/PowerLinkWidget.cpp" "widgets/ChartWidget.cpp" "widgets/ClockWidget.cpp" + "widgets/RectangleWidget.cpp" "widgets/RoomCardWidgetBase.cpp" "widgets/RoomCardBubbleWidget.cpp" "widgets/RoomCardTileWidget.cpp" diff --git a/main/WidgetConfig.cpp b/main/WidgetConfig.cpp index 36e2a92..59f7014 100644 --- a/main/WidgetConfig.cpp +++ b/main/WidgetConfig.cpp @@ -26,6 +26,9 @@ void WidgetConfig::serialize(uint8_t* buf) const { buf[pos++] = bgColor.r; buf[pos++] = bgColor.g; buf[pos++] = bgColor.b; buf[pos++] = bgOpacity; buf[pos++] = borderRadius; + buf[pos++] = borderWidth; + buf[pos++] = borderColor.r; buf[pos++] = borderColor.g; buf[pos++] = borderColor.b; + buf[pos++] = borderOpacity; buf[pos++] = shadow.offsetX; buf[pos++] = shadow.offsetY; @@ -162,6 +165,9 @@ void WidgetConfig::deserialize(const uint8_t* buf) { bgColor.r = buf[pos++]; bgColor.g = buf[pos++]; bgColor.b = buf[pos++]; bgOpacity = buf[pos++]; borderRadius = buf[pos++]; + borderWidth = buf[pos++]; + borderColor.r = buf[pos++]; borderColor.g = buf[pos++]; borderColor.b = buf[pos++]; + borderOpacity = buf[pos++]; shadow.offsetX = static_cast(buf[pos++]); shadow.offsetY = static_cast(buf[pos++]); @@ -325,6 +331,9 @@ WidgetConfig WidgetConfig::createLabel(uint8_t id, int16_t x, int16_t y, const c cfg.bgColor = {0, 0, 0}; cfg.bgOpacity = 0; cfg.borderRadius = 0; + cfg.borderWidth = 0; + cfg.borderColor = {255, 255, 255}; + cfg.borderOpacity = 0; cfg.shadow.enabled = false; // Icon defaults cfg.iconCodepoint = 0; @@ -375,6 +384,9 @@ WidgetConfig WidgetConfig::createButton(uint8_t id, int16_t x, int16_t y, cfg.bgColor = {33, 150, 243}; // Blue cfg.bgOpacity = 255; cfg.borderRadius = 8; + cfg.borderWidth = 0; + cfg.borderColor = {255, 255, 255}; + cfg.borderOpacity = 0; cfg.shadow.enabled = true; cfg.shadow.offsetX = 2; cfg.shadow.offsetY = 2; diff --git a/main/WidgetConfig.hpp b/main/WidgetConfig.hpp index 44fbea9..05794a9 100644 --- a/main/WidgetConfig.hpp +++ b/main/WidgetConfig.hpp @@ -29,6 +29,7 @@ enum class WidgetType : uint8_t { CHART = 9, CLOCK = 10, ROOMCARD = 11, + RECTANGLE = 12, }; enum class IconPosition : uint8_t { @@ -225,6 +226,9 @@ struct WidgetConfig { Color bgColor; uint8_t bgOpacity; // 0-255 uint8_t borderRadius; + uint8_t borderWidth; // 0-32 + Color borderColor; + uint8_t borderOpacity; // 0-255 // Shadow ShadowConfig shadow; @@ -280,8 +284,8 @@ struct WidgetConfig { TextLineConfig textLines[MAX_TEXTLINES]; // Serialization size (fixed for NVS storage) - // 197 + 4 (iconPositionX/Y) + 1 (subButtonCount) + 1 (subButtonSize) + 1 (subButtonDistance) + 1 (subButtonOpacity) + 1 (cardStyle) + 120 (6 subButtons * 20) = 326 - static constexpr size_t SERIALIZED_SIZE = 326; + // 326 + 5 (borderWidth + borderColor + borderOpacity) = 331 + static constexpr size_t SERIALIZED_SIZE = 331; void serialize(uint8_t* buf) const; void deserialize(const uint8_t* buf); diff --git a/main/WidgetManager.cpp b/main/WidgetManager.cpp index 58135ee..3a8d0f6 100644 --- a/main/WidgetManager.cpp +++ b/main/WidgetManager.cpp @@ -1522,6 +1522,12 @@ void WidgetManager::getConfigJson(char* buf, size_t bufSize) const { cJSON_AddNumberToObject(widget, "bgOpacity", w.bgOpacity); cJSON_AddNumberToObject(widget, "radius", w.borderRadius); + cJSON_AddNumberToObject(widget, "borderWidth", w.borderWidth); + cJSON_AddNumberToObject(widget, "borderOpacity", w.borderOpacity); + char borderColorStr[8]; + snprintf(borderColorStr, sizeof(borderColorStr), "#%02X%02X%02X", + w.borderColor.r, w.borderColor.g, w.borderColor.b); + cJSON_AddStringToObject(widget, "borderColor", borderColorStr); cJSON* shadow = cJSON_AddObjectToObject(widget, "shadow"); cJSON_AddBoolToObject(shadow, "enabled", w.shadow.enabled); @@ -1801,6 +1807,17 @@ bool WidgetManager::updateConfigFromJson(const char* json) { cJSON* radius = cJSON_GetObjectItem(widget, "radius"); if (cJSON_IsNumber(radius)) w.borderRadius = radius->valueint; + cJSON* borderWidth = cJSON_GetObjectItem(widget, "borderWidth"); + if (cJSON_IsNumber(borderWidth)) w.borderWidth = borderWidth->valueint; + + cJSON* borderOpacity = cJSON_GetObjectItem(widget, "borderOpacity"); + if (cJSON_IsNumber(borderOpacity)) w.borderOpacity = borderOpacity->valueint; + + cJSON* borderColor = cJSON_GetObjectItem(widget, "borderColor"); + if (cJSON_IsString(borderColor)) { + w.borderColor = Color::fromHex(parseHexColor(borderColor->valuestring)); + } + cJSON* shadow = cJSON_GetObjectItem(widget, "shadow"); if (cJSON_IsObject(shadow)) { cJSON* enabled = cJSON_GetObjectItem(shadow, "enabled"); @@ -2300,4 +2317,4 @@ bool WidgetManager::updateConfigFromJson(const char* json) { cJSON_Delete(root); ESP_LOGI(TAG, "Parsed %d screens from JSON", config_->screenCount); return true; -} \ No newline at end of file +} diff --git a/main/widgets/ChartWidget.cpp b/main/widgets/ChartWidget.cpp index f42bebb..14daabf 100644 --- a/main/widgets/ChartWidget.cpp +++ b/main/widgets/ChartWidget.cpp @@ -80,7 +80,6 @@ void ChartWidget::applyStyle() { if (!obj_) return; Widget::applyStyle(); - lv_obj_set_style_border_width(obj_, 0, 0); lv_obj_set_style_pad_all(obj_, 0, 0); if (chart_) { diff --git a/main/widgets/ClockWidget.cpp b/main/widgets/ClockWidget.cpp index 74541d3..af06318 100644 --- a/main/widgets/ClockWidget.cpp +++ b/main/widgets/ClockWidget.cpp @@ -67,9 +67,17 @@ void ClockWidget::applyStyle() { lv_obj_set_style_radius(obj_, LV_RADIUS_CIRCLE, 0); } - lv_obj_set_style_border_width(obj_, 2, 0); - lv_obj_set_style_border_color(obj_, lv_color_make( - config_.textColor.r, config_.textColor.g, config_.textColor.b), 0); + if (config_.borderWidth > 0 && config_.borderOpacity > 0) { + lv_obj_set_style_border_width(obj_, config_.borderWidth, 0); + lv_obj_set_style_border_color(obj_, lv_color_make( + config_.borderColor.r, config_.borderColor.g, config_.borderColor.b), 0); + lv_obj_set_style_border_opa(obj_, config_.borderOpacity, 0); + } else { + lv_obj_set_style_border_width(obj_, 2, 0); + lv_obj_set_style_border_color(obj_, lv_color_make( + config_.textColor.r, config_.textColor.g, config_.textColor.b), 0); + lv_obj_set_style_border_opa(obj_, LV_OPA_COVER, 0); + } lv_color_t handColor = lv_color_make( config_.textColor.r, config_.textColor.g, config_.textColor.b); @@ -159,4 +167,4 @@ void ClockWidget::updateHands(const struct tm& t) { void ClockWidget::onKnxTime(const struct tm& value, TextSource source) { updateHands(value); -} \ No newline at end of file +} diff --git a/main/widgets/IconWidget.cpp b/main/widgets/IconWidget.cpp index 71b9d9e..73e5243 100644 --- a/main/widgets/IconWidget.cpp +++ b/main/widgets/IconWidget.cpp @@ -83,6 +83,17 @@ void IconWidget::applyStyle() { lv_obj_set_style_radius(obj_, config_.borderRadius, 0); } + if (config_.borderWidth > 0 && config_.borderOpacity > 0) { + lv_obj_set_style_border_width(obj_, config_.borderWidth, 0); + lv_obj_set_style_border_color(obj_, lv_color_make( + config_.borderColor.r, config_.borderColor.g, config_.borderColor.b), 0); + lv_obj_set_style_border_opa(obj_, config_.borderOpacity, 0); + } else { + lv_obj_set_style_border_width(obj_, 0, 0); + } + + applyShadowStyle(); + // Apply icon style if (iconLabel_ != nullptr) { lv_obj_set_style_text_color(iconLabel_, lv_color_make( diff --git a/main/widgets/LabelWidget.cpp b/main/widgets/LabelWidget.cpp index d3d82bf..ef99d89 100644 --- a/main/widgets/LabelWidget.cpp +++ b/main/widgets/LabelWidget.cpp @@ -202,6 +202,15 @@ void LabelWidget::applyStyle() { if (config_.borderRadius > 0) { lv_obj_set_style_radius(obj_, config_.borderRadius, 0); } + if (config_.borderWidth > 0 && config_.borderOpacity > 0) { + lv_obj_set_style_border_width(obj_, config_.borderWidth, 0); + lv_obj_set_style_border_color(obj_, lv_color_make( + config_.borderColor.r, config_.borderColor.g, config_.borderColor.b), 0); + lv_obj_set_style_border_opa(obj_, config_.borderOpacity, 0); + } else { + lv_obj_set_style_border_width(obj_, 0, 0); + } + applyShadowStyle(); } // Apply text style diff --git a/main/widgets/LedWidget.cpp b/main/widgets/LedWidget.cpp index e0729cf..100f53c 100644 --- a/main/widgets/LedWidget.cpp +++ b/main/widgets/LedWidget.cpp @@ -25,6 +25,15 @@ void LedWidget::applyStyle() { config_.bgColor.r, config_.bgColor.g, config_.bgColor.b)); lv_led_set_brightness(obj_, config_.bgOpacity); + if (config_.borderWidth > 0 && config_.borderOpacity > 0) { + lv_obj_set_style_border_width(obj_, config_.borderWidth, 0); + lv_obj_set_style_border_color(obj_, lv_color_make( + config_.borderColor.r, config_.borderColor.g, config_.borderColor.b), 0); + lv_obj_set_style_border_opa(obj_, config_.borderOpacity, 0); + } else { + lv_obj_set_style_border_width(obj_, 0, 0); + } + // Shadow applyShadowStyle(); } diff --git a/main/widgets/PowerFlowWidget.cpp b/main/widgets/PowerFlowWidget.cpp index 36533ae..615024a 100644 --- a/main/widgets/PowerFlowWidget.cpp +++ b/main/widgets/PowerFlowWidget.cpp @@ -41,10 +41,17 @@ void PowerFlowWidget::applyStyle() { lv_obj_set_style_bg_opa(obj_, LV_OPA_TRANSP, 0); } - lv_obj_set_style_border_width(obj_, 1, 0); - lv_obj_set_style_border_color(obj_, lv_color_make( - config_.textColor.r, config_.textColor.g, config_.textColor.b), 0); - lv_obj_set_style_border_opa(obj_, LV_OPA_20, 0); + if (config_.borderWidth > 0 && config_.borderOpacity > 0) { + lv_obj_set_style_border_width(obj_, config_.borderWidth, 0); + lv_obj_set_style_border_color(obj_, lv_color_make( + config_.borderColor.r, config_.borderColor.g, config_.borderColor.b), 0); + lv_obj_set_style_border_opa(obj_, config_.borderOpacity, 0); + } else { + lv_obj_set_style_border_width(obj_, 1, 0); + lv_obj_set_style_border_color(obj_, lv_color_make( + config_.textColor.r, config_.textColor.g, config_.textColor.b), 0); + lv_obj_set_style_border_opa(obj_, LV_OPA_20, 0); + } if (config_.borderRadius > 0) { lv_obj_set_style_radius(obj_, config_.borderRadius, 0); diff --git a/main/widgets/PowerNodeWidget.cpp b/main/widgets/PowerNodeWidget.cpp index 0ab5212..db3616d 100644 --- a/main/widgets/PowerNodeWidget.cpp +++ b/main/widgets/PowerNodeWidget.cpp @@ -206,10 +206,17 @@ void PowerNodeWidget::applyStyle() { if (ring < 2) ring = 2; if (ring > 12) ring = 12; - lv_obj_set_style_border_width(obj_, ring, 0); - lv_obj_set_style_border_color(obj_, lv_color_make( - config_.bgColor.r, config_.bgColor.g, config_.bgColor.b), 0); - lv_obj_set_style_border_opa(obj_, config_.bgOpacity, 0); + if (config_.borderWidth > 0 && config_.borderOpacity > 0) { + lv_obj_set_style_border_width(obj_, config_.borderWidth, 0); + lv_obj_set_style_border_color(obj_, lv_color_make( + config_.borderColor.r, config_.borderColor.g, config_.borderColor.b), 0); + lv_obj_set_style_border_opa(obj_, config_.borderOpacity, 0); + } else { + lv_obj_set_style_border_width(obj_, ring, 0); + lv_obj_set_style_border_color(obj_, lv_color_make( + config_.bgColor.r, config_.bgColor.g, config_.bgColor.b), 0); + lv_obj_set_style_border_opa(obj_, config_.bgOpacity, 0); + } applyShadowStyle(); diff --git a/main/widgets/RectangleWidget.cpp b/main/widgets/RectangleWidget.cpp new file mode 100644 index 0000000..5bc36de --- /dev/null +++ b/main/widgets/RectangleWidget.cpp @@ -0,0 +1,26 @@ +#include "RectangleWidget.hpp" + +RectangleWidget::RectangleWidget(const WidgetConfig& config) + : Widget(config) +{ +} + +lv_obj_t* RectangleWidget::create(lv_obj_t* parent) { + obj_ = lv_obj_create(parent); + if (obj_ == nullptr) return nullptr; + + lv_obj_remove_style_all(obj_); + lv_obj_set_pos(obj_, config_.x, config_.y); + lv_obj_set_size(obj_, + config_.width > 0 ? config_.width : 180, + config_.height > 0 ? config_.height : 120); + lv_obj_clear_flag(obj_, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_clear_flag(obj_, LV_OBJ_FLAG_CLICKABLE); + + return obj_; +} + +void RectangleWidget::applyStyle() { + if (obj_ == nullptr) return; + applyCommonStyle(); +} diff --git a/main/widgets/RectangleWidget.hpp b/main/widgets/RectangleWidget.hpp new file mode 100644 index 0000000..8a7c6a0 --- /dev/null +++ b/main/widgets/RectangleWidget.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "Widget.hpp" + +class RectangleWidget : public Widget { +public: + explicit RectangleWidget(const WidgetConfig& config); + + lv_obj_t* create(lv_obj_t* parent) override; + void applyStyle() override; +}; diff --git a/main/widgets/RoomCardBubbleWidget.cpp b/main/widgets/RoomCardBubbleWidget.cpp index 61a7c14..ffbc1d2 100644 --- a/main/widgets/RoomCardBubbleWidget.cpp +++ b/main/widgets/RoomCardBubbleWidget.cpp @@ -115,6 +115,14 @@ void RoomCardBubbleWidget::applyStyle() { lv_obj_set_style_shadow_opa(bubble_, 255, 0); } + if (bubble_ && config_.borderWidth > 0 && config_.borderOpacity > 0) { + lv_obj_set_style_border_width(bubble_, config_.borderWidth, 0); + lv_obj_set_style_border_color(bubble_, lv_color_hex(config_.borderColor.toLvColor()), 0); + lv_obj_set_style_border_opa(bubble_, config_.borderOpacity, 0); + } else if (bubble_) { + lv_obj_set_style_border_width(bubble_, 0, 0); + } + // Font sizes const lv_font_t* iconFont = Fonts::iconFont(config_.iconSize); const lv_font_t* textFont = Fonts::bySizeIndex(config_.fontSize); diff --git a/main/widgets/RoomCardTileWidget.cpp b/main/widgets/RoomCardTileWidget.cpp index 2923a90..ea3dd93 100644 --- a/main/widgets/RoomCardTileWidget.cpp +++ b/main/widgets/RoomCardTileWidget.cpp @@ -191,11 +191,17 @@ void RoomCardTileWidget::applyStyle() { lv_color_t textColor = lv_color_hex(config_.textColor.toLvColor()); - // Apply border if shadow is enabled (used as accent border) - if (config_.shadow.enabled) { + if (config_.borderWidth > 0 && config_.borderOpacity > 0) { + lv_obj_set_style_border_width(obj_, config_.borderWidth, 0); + lv_obj_set_style_border_color(obj_, lv_color_hex(config_.borderColor.toLvColor()), 0); + lv_obj_set_style_border_opa(obj_, config_.borderOpacity, 0); + } else if (config_.shadow.enabled) { + // Backward-compatible accent border behavior lv_obj_set_style_border_width(obj_, 3, 0); lv_obj_set_style_border_color(obj_, lv_color_hex(config_.shadow.color.toLvColor()), 0); lv_obj_set_style_border_opa(obj_, 255, 0); + } else { + lv_obj_set_style_border_width(obj_, 0, 0); } // Room name - large font diff --git a/main/widgets/TabPageWidget.cpp b/main/widgets/TabPageWidget.cpp index c19bf08..908d5c6 100644 --- a/main/widgets/TabPageWidget.cpp +++ b/main/widgets/TabPageWidget.cpp @@ -39,5 +39,22 @@ void TabPageWidget::applyStyle() { lv_obj_set_style_bg_color(obj_, lv_color_make( config_.bgColor.r, config_.bgColor.g, config_.bgColor.b), 0); lv_obj_set_style_bg_opa(obj_, config_.bgOpacity, 0); + } else { + lv_obj_set_style_bg_opa(obj_, LV_OPA_TRANSP, 0); } + + if (config_.borderRadius > 0) { + lv_obj_set_style_radius(obj_, config_.borderRadius, 0); + } + + if (config_.borderWidth > 0 && config_.borderOpacity > 0) { + lv_obj_set_style_border_width(obj_, config_.borderWidth, 0); + lv_obj_set_style_border_color(obj_, lv_color_make( + config_.borderColor.r, config_.borderColor.g, config_.borderColor.b), 0); + lv_obj_set_style_border_opa(obj_, config_.borderOpacity, 0); + } else { + lv_obj_set_style_border_width(obj_, 0, 0); + } + + applyShadowStyle(); } diff --git a/main/widgets/Widget.cpp b/main/widgets/Widget.cpp index 0d02131..061dcee 100644 --- a/main/widgets/Widget.cpp +++ b/main/widgets/Widget.cpp @@ -183,6 +183,16 @@ void Widget::applyCommonStyle() { lv_obj_set_style_radius(obj_, config_.borderRadius, 0); } + // Border + if (config_.borderWidth > 0 && config_.borderOpacity > 0) { + lv_obj_set_style_border_width(obj_, config_.borderWidth, 0); + lv_obj_set_style_border_color(obj_, lv_color_make( + config_.borderColor.r, config_.borderColor.g, config_.borderColor.b), 0); + lv_obj_set_style_border_opa(obj_, config_.borderOpacity, 0); + } else { + lv_obj_set_style_border_width(obj_, 0, 0); + } + // Shadow applyShadowStyle(); } diff --git a/main/widgets/WidgetFactory.cpp b/main/widgets/WidgetFactory.cpp index c3a06ad..e000247 100644 --- a/main/widgets/WidgetFactory.cpp +++ b/main/widgets/WidgetFactory.cpp @@ -12,6 +12,7 @@ #include "ClockWidget.hpp" #include "RoomCardBubbleWidget.hpp" #include "RoomCardTileWidget.hpp" +#include "RectangleWidget.hpp" std::unique_ptr WidgetFactory::create(const WidgetConfig& config) { if (!config.visible) return nullptr; @@ -45,6 +46,8 @@ std::unique_ptr WidgetFactory::create(const WidgetConfig& config) { return std::make_unique(config); } return std::make_unique(config); + case WidgetType::RECTANGLE: + return std::make_unique(config); default: return nullptr; } diff --git a/web-interface/src/components/SidebarLeft.vue b/web-interface/src/components/SidebarLeft.vue index 2ee0e98..d2e0050 100644 --- a/web-interface/src/components/SidebarLeft.vue +++ b/web-interface/src/components/SidebarLeft.vue @@ -42,6 +42,10 @@ Chart Verlauf +