diff --git a/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/render/ChartRenderer.java b/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/render/ChartRenderer.java index 37a03cc8d4..44b59ed30f 100644 --- a/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/render/ChartRenderer.java +++ b/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/render/ChartRenderer.java @@ -15,10 +15,12 @@ import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Date; +import java.util.List; import org.eclipse.emf.common.util.ECollections; import org.eclipse.emf.common.util.EList; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.TranslationProvider; import org.openhab.core.items.GroupItem; @@ -91,7 +93,9 @@ public EList renderWidget(Widget w, StringBuilder sb, String sitemap) th } // if legend parameter is given, add corresponding GET parameter + boolean legend = item instanceof GroupItem && !forceAsItem; if (chart.getLegend() != null) { + legend = chart.getLegend(); if (chart.getLegend()) { chartUrl += "&legend=true"; } else { @@ -123,7 +127,10 @@ public EList renderWidget(Widget w, StringBuilder sb, String sitemap) th } String snippet = getSnippet("chart"); - snippet = preprocessSnippet(snippet, w); + snippet = preprocessSnippet(snippet, w, true); + + // Process the color tags + snippet = processColor(w, snippet); if (chart.getRefresh() > 0) { snippet = snippet.replace("%update_interval%", Integer.toString(chart.getRefresh())); @@ -136,6 +143,18 @@ public EList renderWidget(Widget w, StringBuilder sb, String sitemap) th snippet = snippet.replace("%valid_url%", "true"); snippet = snippet.replace("%ignore_refresh%", ignoreRefresh ? "true" : "false"); snippet = snippet.replace("%url%", url); + snippet = snippet.replace("%legend%", Boolean.valueOf(legend).toString()); + + List> periods = List.of(List.of("Last hour", "h"), List.of("Last 4 hours", "4h"), + List.of("Last 8 hours", "8h"), List.of("Last 12 hours", "12h"), List.of("Last day", "D"), + List.of("Last 2 days", "2D"), List.of("Last 3 days", "3D"), List.of("Last week", "W"), + List.of("Last 2 weeks", "2W"), List.of("Last month", "M"), List.of("Last 2 months", "2M"), + List.of("Last 4 months", "4M"), List.of("Last year", "Y")); + StringBuilder rowSB = new StringBuilder(); + for (List period : periods) { + buildRow(chart, period.get(0), period.get(1), chart.getPeriod(), rowSB); + } + snippet = snippet.replace("%period_rows%", rowSB.toString()); sb.append(snippet); } catch (ItemNotFoundException e) { @@ -143,4 +162,24 @@ public EList renderWidget(Widget w, StringBuilder sb, String sitemap) th } return ECollections.emptyEList(); } + + private void buildRow(Chart w, @Nullable String lab, String cmd, String current, StringBuilder rowSB) + throws RenderException { + String rowSnippet = getSnippet("selection_row"); + + String command = cmd; + String label = lab == null ? cmd : lab; + + rowSnippet = rowSnippet.replace("%item%", w.getItem() != null ? w.getItem() : ""); + rowSnippet = rowSnippet.replace("%cmd%", escapeHtml(command)); + rowSnippet = rowSnippet.replace("%label%", escapeHtml(label)); + + if (command.equals(current)) { + rowSnippet = rowSnippet.replace("%checked%", "checked=\"true\""); + } else { + rowSnippet = rowSnippet.replace("%checked%", ""); + } + + rowSB.append(rowSnippet); + } } diff --git a/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/render/ImageRenderer.java b/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/render/ImageRenderer.java index e6c691e45a..51d6e9d4f9 100644 --- a/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/render/ImageRenderer.java +++ b/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/render/ImageRenderer.java @@ -69,7 +69,10 @@ public EList renderWidget(Widget w, StringBuilder sb, String sitemap) th String widgetId = itemUIRegistry.getWidgetId(w); snippet = snippet.replace("%id%", widgetId); - snippet = preprocessSnippet(snippet, w); + snippet = preprocessSnippet(snippet, w, true); + + // Process the color tags + snippet = processColor(w, snippet); boolean validUrl = isValidURL(image.getUrl()); String proxiedUrl = "../proxy?sitemap=" + sitemap + "&widgetId=" + widgetId; diff --git a/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/render/MapviewRenderer.java b/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/render/MapviewRenderer.java index d900d8168e..13c96c14ee 100644 --- a/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/render/MapviewRenderer.java +++ b/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/render/MapviewRenderer.java @@ -57,7 +57,14 @@ public boolean canRender(Widget w) { public EList renderWidget(Widget w, StringBuilder sb, String sitemap) throws RenderException { Mapview mapview = (Mapview) w; String snippet = getSnippet("mapview"); + + boolean showHeaderRow = !getLabel(w).isEmpty(); + snippet = snippet.replace("%header_visibility_class%", + showHeaderRow ? "%visibility_class%" : "mdl-form__row--hidden"); + snippet = snippet.replace("%header_row%", Boolean.valueOf(showHeaderRow).toString()); + snippet = preprocessSnippet(snippet, mapview, true); + // Process the color tags snippet = processColor(w, snippet); diff --git a/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/render/VideoRenderer.java b/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/render/VideoRenderer.java index ab95955437..58739d4319 100644 --- a/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/render/VideoRenderer.java +++ b/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/render/VideoRenderer.java @@ -66,7 +66,16 @@ public EList renderWidget(Widget w, StringBuilder sb, String sitemap) th && videoWidget.getEncoding().toLowerCase().contains("mjpeg")) ? "image" : "video"; snippet = getSnippet(snippetName); - snippet = preprocessSnippet(snippet, w); + + boolean showHeaderRow = !getLabel(w).isEmpty(); + snippet = snippet.replace("%header_visibility_class%", + showHeaderRow ? "%visibility_class%" : "mdl-form__row--hidden"); + snippet = snippet.replace("%header_row%", Boolean.valueOf(showHeaderRow).toString()); + + snippet = preprocessSnippet(snippet, w, true); + + // Process the color tags + snippet = processColor(w, snippet); State state = itemUIRegistry.getState(w); String url; diff --git a/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/render/WebviewRenderer.java b/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/render/WebviewRenderer.java index 649773b424..c24dfb3bad 100644 --- a/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/render/WebviewRenderer.java +++ b/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/render/WebviewRenderer.java @@ -52,7 +52,14 @@ public boolean canRender(Widget w) { public EList renderWidget(Widget w, StringBuilder sb, String sitemap) throws RenderException { Webview webview = (Webview) w; String snippet = getSnippet("webview"); + + boolean showHeaderRow = !getLabel(w).isEmpty(); + snippet = snippet.replace("%header_visibility_class%", + showHeaderRow ? "%visibility_class%" : "mdl-form__row--hidden"); + snippet = snippet.replace("%header_row%", Boolean.valueOf(showHeaderRow).toString()); + snippet = preprocessSnippet(snippet, webview, true); + // Process the color tags snippet = processColor(w, snippet); diff --git a/bundles/org.openhab.ui.basic/src/main/resources/snippets/chart.html b/bundles/org.openhab.ui.basic/src/main/resources/snippets/chart.html index a2b44a5ff7..063fb7e82b 100644 --- a/bundles/org.openhab.ui.basic/src/main/resources/snippets/chart.html +++ b/bundles/org.openhab.ui.basic/src/main/resources/snippets/chart.html @@ -1,3 +1,38 @@ +
+ + %icon_snippet% + + + %label% + +
+ + Show or hde legend + + Choose time frame + + + Upscale chart or not + + Refresh chart +
+
diff --git a/bundles/org.openhab.ui.basic/src/main/resources/snippets/image.html b/bundles/org.openhab.ui.basic/src/main/resources/snippets/image.html index c51025a2bd..98b742eea5 100644 --- a/bundles/org.openhab.ui.basic/src/main/resources/snippets/image.html +++ b/bundles/org.openhab.ui.basic/src/main/resources/snippets/image.html @@ -1,3 +1,18 @@ +
+ + %icon_snippet% + + + %label% + +
+ + Upscale image or not +
+
diff --git a/bundles/org.openhab.ui.basic/src/main/resources/snippets/mapview.html b/bundles/org.openhab.ui.basic/src/main/resources/snippets/mapview.html index dffb31cc72..a78eddc2d1 100644 --- a/bundles/org.openhab.ui.basic/src/main/resources/snippets/mapview.html +++ b/bundles/org.openhab.ui.basic/src/main/resources/snippets/mapview.html @@ -1,10 +1,10 @@ -
- - %icon_snippet% - - - %label% - +
+ + %icon_snippet% + + + %label% +
diff --git a/bundles/org.openhab.ui.basic/src/main/resources/snippets/video.html b/bundles/org.openhab.ui.basic/src/main/resources/snippets/video.html index 257d533fa8..370de2fe28 100644 --- a/bundles/org.openhab.ui.basic/src/main/resources/snippets/video.html +++ b/bundles/org.openhab.ui.basic/src/main/resources/snippets/video.html @@ -1,8 +1,17 @@ +
+ + %icon_snippet% + + + %label% + +
diff --git a/bundles/org.openhab.ui.basic/src/main/resources/snippets/webview.html b/bundles/org.openhab.ui.basic/src/main/resources/snippets/webview.html index aab1f5e96d..7a033b4f9b 100644 --- a/bundles/org.openhab.ui.basic/src/main/resources/snippets/webview.html +++ b/bundles/org.openhab.ui.basic/src/main/resources/snippets/webview.html @@ -1,16 +1,17 @@ -
- - %icon_snippet% - - - %label% - +
+ + %icon_snippet% + + + %label% +
diff --git a/bundles/org.openhab.ui.basic/web-src/_layout.scss b/bundles/org.openhab.ui.basic/web-src/_layout.scss index e2a08660e7..399833ef8f 100644 --- a/bundles/org.openhab.ui.basic/web-src/_layout.scss +++ b/bundles/org.openhab.ui.basic/web-src/_layout.scss @@ -70,6 +70,9 @@ @include align-items-2011(center); white-space: nowrap; height: $form-row-desktop-height; + html.ui-layout-condensed & { + height: $form-row-desktop-height-condensed; + } box-sizing: content-box; padding: 4px 0; margin: 0 $mdl-grid-spacing; @@ -79,7 +82,7 @@ padding: 4px $mdl-grid-spacing; margin: 0; html.ui-layout-condensed & { - height: $form-row-desktop-height-condensed; + height: $form-row-mobile-height-condensed; } } @media screen and (min-width: $layout-tablet-size-threshold) { @@ -98,12 +101,19 @@ height: auto; } } + &--header { + border-bottom: none; + padding-bottom: 0; + padding-left: 48px; + padding-right: 48px; + @media screen and (max-width: $layout-tablet-size-threshold) { + padding-left: $mdl-grid-spacing + 16px; + padding-right: $mdl-grid-spacing + 16px; + } + } &--hidden { display: none; } - html.ui-layout-condensed & { - height: $form-row-desktop-height-condensed; - } } &__image { &.mdl-form__control { @@ -114,11 +124,7 @@ max-width: 100%; } } - @media screen and (max-width: $layout-tablet-size-threshold) { - &.mdl-form__control { - padding-left: 0; - padding-right: 0; - } + &-upscale { width: 100%; img { height: auto; @@ -264,6 +270,22 @@ padding-top: 6px; } } + &__header { + padding-left: 8px; + padding-right: $form-row-desktop-padding; + @media screen and (max-width: $layout-tablet-size-threshold) { + padding-right: $form-row-mobile-padding; + } + .mdl-button { + box-shadow: none; + -webkit-box-shadow: none; + min-width: 0; + padding-top: 6px; + } + &-rows { + display: none; + } + } &__group { padding-top: 4px; } diff --git a/bundles/org.openhab.ui.basic/web-src/_theming.scss b/bundles/org.openhab.ui.basic/web-src/_theming.scss index b4d1d63d01..99f6e4609f 100644 --- a/bundles/org.openhab.ui.basic/web-src/_theming.scss +++ b/bundles/org.openhab.ui.basic/web-src/_theming.scss @@ -84,6 +84,10 @@ body { color: var(--container-text-color, #616161) !important; } +.mdl-form__row--header { + border-bottom: none; +} + .mdl-switch .mdl-switch__track { background: rgba(0,0,0,.26); background: var(--switch-off-track-bg, rgba(0,0,0,.26)); diff --git a/bundles/org.openhab.ui.basic/web-src/smarthome.js b/bundles/org.openhab.ui.basic/web-src/smarthome.js index 58c74243f7..83e800aa9d 100644 --- a/bundles/org.openhab.ui.basic/web-src/smarthome.js +++ b/bundles/org.openhab.ui.basic/web-src/smarthome.js @@ -358,10 +358,19 @@ } _t.item = _t.parentNode.getAttribute(o.itemAttribute); _t.id = _t.parentNode.getAttribute(o.idAttribute); - _t.iconContainer = _t.formRow.querySelector(o.formIcon); - _t.icon = _t.formRow.querySelector(o.formIconImg); _t.visible = !_t.formRow.classList.contains(o.formRowHidden); - _t.label = _t.formRow.querySelector(o.formLabel); + _t.headerRow = _t.parentNode.getAttribute("data-header-row"); + if (_t.headerRow !== null) { + _t.formHeaderRow = _t.formRow.previousElementSibling; + _t.iconContainer = _t.formHeaderRow.querySelector(o.formIcon); + _t.icon = _t.formHeaderRow.querySelector(o.formIconImg); + _t.label = _t.formHeaderRow.querySelector(o.formLabel); + } else { + _t.formHeaderRow = null; + _t.iconContainer = _t.formRow.querySelector(o.formIcon); + _t.icon = _t.formRow.querySelector(o.formIconImg); + _t.label = _t.formRow.querySelector(o.formLabel); + } function convertToInlineSVG() { this.removeEventListener("load", convertToInlineSVG); @@ -406,7 +415,11 @@ // Replace the current icon element with the built inline SVG _t.iconContainer.replaceChild(newIconElement, _t.icon); - _t.icon = _t.parentNode.parentNode.querySelector(o.formIconSvg); + if (_t.headerRow !== null) { + _t.icon = _t.formHeaderRow.querySelector(o.formIconSvg); + } else { + _t.icon = _t.formRow.querySelector(o.formIconSvg); + } }; _t.getSVGIconAndReplaceWithInline = function(srcUrl, checkCurrentColor, defaultSVG) { @@ -458,8 +471,14 @@ _t.setVisible = function(state) { if (state) { + if (_t.headerRow === "true") { + _t.formHeaderRow.classList.remove(o.formRowHidden); + } _t.formRow.classList.remove(o.formRowHidden); } else { + if (_t.headerRow === "true") { + _t.formHeaderRow.classList.add(o.formRowHidden); + } _t.formRow.classList.add(o.formRowHidden); } @@ -555,6 +574,12 @@ } else { this.parentNode = parentNode; this.id = this.parentNode.getAttribute(o.idAttribute); + this.headerRow = this.parentNode.getAttribute("data-header-row"); + if (this.headerRow !== null) { + this.formHeaderRow = this.parentNode.parentNode.previousElementSibling; + } else { + this.formHeaderRow = null; + } } var @@ -568,6 +593,27 @@ _t.url = parentNode.getAttribute("data-proxied-url"); _t.validUrl = parentNode.getAttribute("data-valid-url") === "true"; _t.ignoreRefresh = parentNode.getAttribute("data-ignore-refresh") === "true"; + _t.legendButton = null; + _t.periodButton = null; + _t.upscaleButton = null; + _t.refreshButton = null; + _t.displayLegend = _t.parentNode.getAttribute("data-legend") === "true"; + _t.period = null; + _t.upscale = false; + + if (_t.headerRow === "true") { + _t.legendButton = _t.formHeaderRow.querySelector(o.image.legendButton); + _t.periodButton = _t.formHeaderRow.querySelector(o.image.periodButton); + _t.upscaleButton = _t.formHeaderRow.querySelector(o.image.upscaleButton); + _t.refreshButton = _t.formHeaderRow.querySelector(o.image.refreshButton); + if (_t.legendButton !== null) { + if (_t.displayLegend) { + _t.legendButton.classList.add(o.buttonActiveClass); + } else { + _t.legendButton.classList.remove(o.buttonActiveClass); + } + } + } _t.setValuePrivate = function(value, itemState, visible) { if (!visible) { @@ -591,9 +637,15 @@ _t.setVisible = function(state) { if (state) { + if (_t.headerRow === "true") { + _t.formHeaderRow.classList.remove(o.formRowHidden); + } _t.formRow.classList.remove(o.formRowHidden); _t.activateRefresh(); } else { + if (_t.headerRow === "true") { + _t.formHeaderRow.classList.add(o.formRowHidden); + } _t.formRow.classList.add(o.formRowHidden); _t.deactivateRefresh(); } @@ -628,14 +680,134 @@ }, _t.updateInterval); }; + function onLegendClick() { + _t.displayLegend = !_t.displayLegend; + if (_t.displayLegend) { + _t.legendButton.classList.add(o.buttonActiveClass); + } else { + _t.legendButton.classList.remove(o.buttonActiveClass); + } + _t.url = _t.url.replace(/&legend=(true|false)/, ""); + if (_t.displayLegend) { + _t.url = _t.url + "&legend=true"; + } else { + _t.url = _t.url + "&legend=false"; + } + _t.image.setAttribute("src", _t.url + "&t=" + Date.now()); + } + + function onPeriodChange(event) { + event.stopPropagation(); + + if (event.target.tagName.toLowerCase() !== "input") { + return; + } + + _t.period = event.target.getAttribute("value"); + _t.url = _t.url.replace(/&period=(h|4h|8h|12h|D|2D|3D|W|2W|M|2M|4M|Y)/, "&period=" + _t.period); + _t.image.setAttribute("src", _t.url + "&t=" + Date.now()); + + setTimeout(function() { + _t.modalPeriods.hide(); + }, 300); + } + + _t.showModalPeriods = function() { + var + content = _t.formHeaderRow.querySelector(o.image.periodRows).innerHTML; + + _t.modalPeriods = new Modal(content); + _t.modalPeriods.show(); + _t.modalPeriods.onHide = function() { + var + items = [].slice.call(_t.modalPeriods.container.querySelectorAll(o.formRadio)); + + componentHandler.downgradeElements(items); + items.forEach(function(control) { + control.removeEventListener("click", onPeriodChange); + }); + + _t.modalPeriods = null; + }; + + var + controls = [].slice.call(_t.modalPeriods.container.querySelectorAll(o.formRadio)); + + if (_t.period !== null) { + var + items = [].slice.call(_t.modalPeriods.container.querySelectorAll("input[type=radio]")); + + items.forEach(function(radioItem) { + if (radioItem.value === _t.period) { + radioItem.checked = true; + } else { + radioItem.checked = false; + } + }); + } + + controls.forEach(function(control) { + componentHandler.upgradeElement(control, "MaterialRadio"); + control.addEventListener("click", onPeriodChange); + }); + }; + + function onUpscaleClick() { + _t.upscale = !_t.upscale; + if (_t.upscale) { + _t.upscaleButton.classList.add(o.buttonActiveClass); + } else { + _t.upscaleButton.classList.remove(o.buttonActiveClass); + } + if (_t.upscale) { + _t.parentNode.classList.add(o.image.upscaleClass); + } else { + _t.parentNode.classList.remove(o.image.upscaleClass); + } + } + + function onRefreshClick() { + _t.image.setAttribute("src", _t.url + "&t=" + Date.now()); + } + _t.destroy = function() { var imageParent = _t.image.parentNode; _t.image.setAttribute("src", urlNoneIcon); imageParent.removeChild(_t.image); + + if (_t.legendButton !== null) { + componentHandler.downgradeElements([ _t.legendButton ]); + _t.legendButton.removeEventListener("click", onLegendClick); + } + if (_t.periodButton !== null) { + componentHandler.downgradeElements([ _t.periodButton ]); + _t.periodButton.removeEventListener("click", _t.showModalPeriods); + } + if (_t.upscaleButton !== null) { + componentHandler.downgradeElements([ _t.upscaleButton ]); + _t.upscaleButton.removeEventListener("click", onUpscaleClick); + } + if (_t.refreshButton !== null) { + componentHandler.downgradeElements([ _t.refreshButton ]); + _t.refreshButton.removeEventListener("click", onRefreshClick); + } }; + if (_t.legendButton !== null) { + _t.legendButton.addEventListener("click", onLegendClick); + } + if (_t.periodButton !== null) { + _t.periodButton.addEventListener("click", _t.showModalPeriods); + } + if (_t.upscaleButton !== null) { + _t.upscaleButton.addEventListener("click", onUpscaleClick); + } + if (_t.refreshButton !== null) { + _t.refreshButton.addEventListener("click", onRefreshClick); + } + if (_t.visible) { _t.activateRefresh(); } @@ -2783,6 +2955,14 @@ colorpicker: ".colorpicker", button: ".colorpicker__buttons > button" }, + image: { + legendButton: ".chart-legend-button", + periodButton: ".chart-period-button", + periodRows: ".mdl-form__header-rows", + upscaleButton: ".image-upscale-button", + upscaleClass: "mdl-form__image-upscale", + refreshButton: ".chart-refresh-button" + }, notify: ".mdl-notify__container", notifyHidden: "mdl-notify--hidden", notifyTemplateOffline: "template-offline-notify",