From 5d3468bd170ec3ff68084e9c397f55cd5b4bee9d Mon Sep 17 00:00:00 2001 From: Laurent Garnier Date: Sat, 18 Mar 2023 09:51:38 +0100 Subject: [PATCH 1/2] [BasicUI] Use inline SVG for "colorless" SVG icons Related to #1743 Use a HTML svg element (inline SVG) instead of a img element when the icon servlet returns a SVG icon containing "currentColor". Then it is possible to adjust the color of these icons by using the iconcolor sitemapo attribute. Signed-off-by: Laurent Garnier --- .../org.openhab.ui.basic/web-src/_layout.scss | 9 ++ .../org.openhab.ui.basic/web-src/smarthome.js | 92 ++++++++++++++----- 2 files changed, 80 insertions(+), 21 deletions(-) diff --git a/bundles/org.openhab.ui.basic/web-src/_layout.scss b/bundles/org.openhab.ui.basic/web-src/_layout.scss index e07466ec94..07df736735 100644 --- a/bundles/org.openhab.ui.basic/web-src/_layout.scss +++ b/bundles/org.openhab.ui.basic/web-src/_layout.scss @@ -151,6 +151,15 @@ height: 28px; } } + svg { + width: 32px; + height: 32px; + object-fit: contain; + html.ui-layout-condensed & { + width: 28px; + height: 28px; + } + } iconify-icon { font-size: 32px; vertical-align: middle; diff --git a/bundles/org.openhab.ui.basic/web-src/smarthome.js b/bundles/org.openhab.ui.basic/web-src/smarthome.js index bfd9512030..52257901c0 100644 --- a/bundles/org.openhab.ui.basic/web-src/smarthome.js +++ b/bundles/org.openhab.ui.basic/web-src/smarthome.js @@ -339,7 +339,13 @@ _t.visible = !_t.formRow.classList.contains(o.formRowHidden); _t.label = _t.parentNode.parentNode.querySelector(o.formLabel); + function convertToInlineSVG() { + this.removeEventListener("load", convertToInlineSVG); + _t.getSVGIconAndReplaceWithInline(this.src, true, null); + } + function replaceImageWithNone() { + this.removeEventListener("load", convertToInlineSVG); this.removeEventListener("error", replaceImageWithNone); this.src = noneImageSrc; } @@ -349,36 +355,78 @@ _t.iconSet = splittedIconAttr[0]; _t.iconName = splittedIconAttr[1]; if (_t.icon.src !== noneImageSrc) { + _t.icon.addEventListener("load", convertToInlineSVG); _t.icon.addEventListener("error", replaceImageWithNone); } } + _t.replaceIconWithInlineSVG = function(svgText) { + var + parser, + docSvg, + newIconElement, + dataIcon; + + // Parse the SVG text and turn it into DOM nodes + parser = new DOMParser(); + docSvg = parser.parseFromString(svgText, "image/svg+xml"); + newIconElement = docSvg.querySelector("svg"); + + // Keep the attribute data-icon + dataIcon = _t.icon.getAttribute("data-icon"); + if (dataIcon !== null) { + newIconElement.setAttribute("data-icon", dataIcon); + } + + // Replace the current icon element with the built inline SVG + _t.iconContainer.replaceChild(newIconElement, _t.icon); + _t.icon = _t.parentNode.parentNode.querySelector(o.formIconSvg); + }; + + _t.getSVGIconAndReplaceWithInline = function(srcUrl, checkCurrentColor, defaultSVG) { + // fetch(srcUrl, { cache: "force-cache" }).then(function(response) { + fetch(srcUrl).then(function(response) { + if (response.ok && response.headers.get("content-type") === "image/svg+xml") { + response.text().then(function(data) { + if (!checkCurrentColor || data.indexOf("currentColor") !== -1) { + _t.replaceIconWithInlineSVG(data); + } else if (defaultSVG !== null) { + _t.replaceIconWithInlineSVG(defaultSVG); + } + }); + } else if (defaultSVG !== null) { + _t.replaceIconWithInlineSVG(defaultSVG); + } + }).catch(function() { + if (defaultSVG !== null) { + _t.replaceIconWithInlineSVG(defaultSVG); + } + }); + }; + _t.reloadIcon = function(state) { + var + src; + // Some widgets don't have icons if (_t.icon !== null) { - _t.icon.addEventListener("error", replaceImageWithNone); if (state.length < 200) { - _t.icon.setAttribute("src", - "/icon/" + - encodeURIComponent(_t.iconName) + - "?state=" + - encodeURIComponent(state) + - "&iconset=" + - encodeURIComponent(_t.iconSet) + - "&format=" + - smarthome.UI.iconType + - "&anyFormat=true" - ); + src = "/icon/" + encodeURIComponent(_t.iconName) + + "?state=" + encodeURIComponent(state) + + "&iconset=" + encodeURIComponent(_t.iconSet) + + "&format=" + smarthome.UI.iconType + + "&anyFormat=true"; + } else { + src = "/icon/" + encodeURIComponent(_t.iconName) + + "?iconset=" + encodeURIComponent(_t.iconSet) + + "&format=" + smarthome.UI.iconType + + "&anyFormat=true"; + } + if (_t.icon.tagName.toLowerCase() === "img") { + _t.icon.addEventListener("error", replaceImageWithNone); + _t.icon.setAttribute("src", src); } else { - _t.icon.setAttribute("src", - "/icon/" + - encodeURIComponent(_t.iconName) + - "?iconset=" + - encodeURIComponent(_t.iconSet) + - "&format=" + - smarthome.UI.iconType + - "&anyFormat=true" - ); + _t.getSVGIconAndReplaceWithInline(src, false, ""); } } }; @@ -428,6 +476,7 @@ _t.destroy = function() { if (_t.icon !== null) { + _t.icon.removeEventListener("load", convertToInlineSVG); _t.icon.removeEventListener("error", replaceImageWithNone); } @@ -2444,6 +2493,7 @@ formRadioControl: ".mdl-radio__button", formIcon: ".mdl-form__icon", formIconImg: ".mdl-form__icon img", + formIconSvg: ".mdl-form__icon svg", formLabel: ".mdl-form__label", uiLoadingBar: ".ui__loading", layoutTitle: ".mdl-layout-title", From fb6a94005c4c63d0259e8620e25bde668d5da6ce Mon Sep 17 00:00:00 2001 From: Laurent Garnier Date: Sun, 16 Jul 2023 11:54:58 +0200 Subject: [PATCH 2/2] Make the inline SVG optional Signed-off-by: Laurent Garnier --- .../openhab/ui/basic/internal/WebAppConfig.java | 8 +++++++- .../ui/basic/internal/render/PageRenderer.java | 1 + .../src/main/resources/OH-INF/config/config.xml | 14 ++++++++++++++ .../main/resources/OH-INF/i18n/basic.properties | 4 ++++ .../src/main/resources/snippets/main.html | 2 +- bundles/org.openhab.ui.basic/web-src/smarthome.js | 9 ++++++--- 6 files changed, 33 insertions(+), 5 deletions(-) diff --git a/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/WebAppConfig.java b/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/WebAppConfig.java index 80a65d40a4..44d27b905c 100644 --- a/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/WebAppConfig.java +++ b/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/WebAppConfig.java @@ -37,12 +37,13 @@ public class WebAppConfig { private static final String DEFAULT_THEME = THEME_NAME_BRIGHT; private static final String DEFAULT_ICONIFY = "false"; - + private static final String DEFAULT_INLINE_SVG = "false"; private static final String DEFAULT_WEB_AUDIO = "false"; private String defaultSitemap = DEFAULT_SITEMAP; private String theme = DEFAULT_THEME; private boolean iconify = Boolean.parseBoolean(DEFAULT_ICONIFY); + private boolean inlineSvg = Boolean.parseBoolean(DEFAULT_INLINE_SVG); private boolean webAudio = Boolean.parseBoolean(DEFAULT_WEB_AUDIO); private List cssClassList = new ArrayList<>(); @@ -90,6 +91,7 @@ public void applyConfig(Map configProps) { theme = DEFAULT_THEME; } iconify = "true".equalsIgnoreCase((String) configProps.getOrDefault("enableIconify", DEFAULT_ICONIFY)); + inlineSvg = "true".equalsIgnoreCase((String) configProps.getOrDefault("inlineSvg", DEFAULT_INLINE_SVG)); webAudio = "true".equalsIgnoreCase((String) configProps.getOrDefault("webAudio", DEFAULT_WEB_AUDIO)); applyCssClasses(configProps); @@ -115,6 +117,10 @@ public boolean isIconifyEnabled() { return iconify; } + public boolean isInlineSvgEnabled() { + return inlineSvg; + } + public boolean isWebAudio() { return webAudio; } diff --git a/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/render/PageRenderer.java b/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/render/PageRenderer.java index 5c0de02802..5a5ef0be2c 100644 --- a/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/render/PageRenderer.java +++ b/bundles/org.openhab.ui.basic/src/main/java/org/openhab/ui/basic/internal/render/PageRenderer.java @@ -116,6 +116,7 @@ public StringBuilder processPage(String id, String sitemap, String label, EList< snippet = snippet.replace("%sitemap%", sitemap); snippet = snippet.replace("%htmlclass%", config.getCssClassList()); snippet = snippet.replace("%icon_type%", ICON_TYPE); + snippet = snippet.replace("%inline%", config.isInlineSvgEnabled() ? "true" : "false"); snippet = snippet.replace("%theme%", config.getTheme()); snippet = snippet.replace("%sitemapquery%", String.format("?sitemap=%s", sitemap)); snippet = snippet.replace("%primarycolor%", PRIMARY_COLOR); diff --git a/bundles/org.openhab.ui.basic/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.ui.basic/src/main/resources/OH-INF/config/config.xml index 1c55eeed5a..f045147678 100644 --- a/bundles/org.openhab.ui.basic/src/main/resources/OH-INF/config/config.xml +++ b/bundles/org.openhab.ui.basic/src/main/resources/OH-INF/config/config.xml @@ -25,6 +25,20 @@ true + + + If enabled, any SVG icon provided by the openHAB icon server will automatically be converted to an + inline SVG in the WEB page, allowing control of its color with the sitemap widget property "iconcolor" in + the case + where the SVG icon sets "currentColor" as the fill color. Note that this will work with custom SVG icons but not + with all the packaged icons from the classic iconset since they are defined with a hard-coded color palette. This + feature is disabled by default. + + + + + false + If enabled, the UI will render iconify icons directly by downloading them from the Internet and caching diff --git a/bundles/org.openhab.ui.basic/src/main/resources/OH-INF/i18n/basic.properties b/bundles/org.openhab.ui.basic/src/main/resources/OH-INF/i18n/basic.properties index ecfc5b82eb..9973707d63 100644 --- a/bundles/org.openhab.ui.basic/src/main/resources/OH-INF/i18n/basic.properties +++ b/bundles/org.openhab.ui.basic/src/main/resources/OH-INF/i18n/basic.properties @@ -16,6 +16,10 @@ ui.config.basic.enableIcons.label = Enable Icons ui.config.basic.enableIcons.description = Defines whether UI renders icons for the widgets or not. ui.config.basic.enableIcons.option.true = Enable ui.config.basic.enableIcons.option.false = Disable +ui.config.basic.inlineSvg.label = Inline SVG +ui.config.basic.inlineSvg.description = If enabled, any SVG icon provided by the openHAB icon server will automatically be converted to an inline SVG in the WEB page, allowing control of its color with the sitemap widget property "iconcolor" in the case where the SVG icon sets "currentColor" as the fill color. Note that this will work with custom SVG icons but not with all the packaged icons from the classic iconset since they are defined with a hard-coded color palette. This feature is disabled by default. +ui.config.basic.inlineSvg.option.true = Enable +ui.config.basic.inlineSvg.option.false = Disable ui.config.basic.theme.label = Theme ui.config.basic.theme.description = Defines the UI theme. ui.config.basic.theme.option.bright = Bright diff --git a/bundles/org.openhab.ui.basic/src/main/resources/snippets/main.html b/bundles/org.openhab.ui.basic/src/main/resources/snippets/main.html index 43d6e2b98f..bbe93bf1d4 100644 --- a/bundles/org.openhab.ui.basic/src/main/resources/snippets/main.html +++ b/bundles/org.openhab.ui.basic/src/main/resources/snippets/main.html @@ -69,7 +69,7 @@ - +