Skip to content

Commit

Permalink
[BasicUI] Use inline SVG for "colorless" SVG icons (#1799)
Browse files Browse the repository at this point in the history
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 <lg.hc@free.fr>
  • Loading branch information
lolodomo committed Jul 16, 2023
1 parent f51c238 commit 9be4293
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> cssClassList = new ArrayList<>();
Expand Down Expand Up @@ -90,6 +91,7 @@ public void applyConfig(Map<String, Object> 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);
Expand All @@ -115,6 +117,10 @@ public boolean isIconifyEnabled() {
return iconify;
}

public boolean isInlineSvgEnabled() {
return inlineSvg;
}

public boolean isWebAudio() {
return webAudio;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,20 @@
</options>
<default>true</default>
</parameter>
<parameter name="inlineSvg" type="text" required="true">
<label>Inline SVG</label>
<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.</description>
<options>
<option value="true">Enable</option>
<option value="false">Disable</option>
</options>
<default>false</default>
</parameter>
<parameter name="enableIconify" type="text">
<label>Enable Iconify Icons</label>
<description>If enabled, the UI will render iconify icons directly by downloading them from the Internet and caching
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
</div>
</script>
</head>
<body class="mdl-color-text--grey-700" data-sitemap="%sitemap%" data-page-id="%id%" data-theme="%theme%" data-icon-type="%icon_type%" data-primary-color="%primarycolor%" data-secondary-color="%secondarycolor%">
<body class="mdl-color-text--grey-700" data-sitemap="%sitemap%" data-page-id="%id%" data-theme="%theme%" data-icon-type="%icon_type%" data-inline-svg="%inline%" data-primary-color="%primarycolor%" data-secondary-color="%secondarycolor%">
<div class="mdl-layout mdl-js-layout mdl-layout--fixed-header">
<header class="mdl-layout__header mdl-layout__header--scroll navigation navigation-home">
<div class="mdl-layout__header-row">
Expand Down
9 changes: 9 additions & 0 deletions bundles/org.openhab.ui.basic/web-src/_layout.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
95 changes: 74 additions & 21 deletions bundles/org.openhab.ui.basic/web-src/smarthome.js
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,15 @@
_t.visible = !_t.formRow.classList.contains(o.formRowHidden);
_t.label = _t.parentNode.parentNode.querySelector(o.formLabel);

function convertToInlineSVG() {
this.removeEventListener("load", convertToInlineSVG);
if (smarthome.UI.inlineSVG) {
_t.getSVGIconAndReplaceWithInline(this.src, true, null);
}
}

function replaceImageWithNone() {
this.removeEventListener("load", convertToInlineSVG);
this.removeEventListener("error", replaceImageWithNone);
this.src = noneImageSrc;
}
Expand All @@ -371,36 +379,77 @@
_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).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 {
_t.icon.setAttribute("src",
"/icon/" +
encodeURIComponent(_t.iconName) +
"?iconset=" +
encodeURIComponent(_t.iconSet) +
"&format=" +
smarthome.UI.iconType +
"&anyFormat=true"
);
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 if (smarthome.UI.inlineSVG) {
_t.getSVGIconAndReplaceWithInline(src, false, "<svg/>");
}
}
};
Expand Down Expand Up @@ -450,6 +499,7 @@

_t.destroy = function() {
if (_t.icon !== null) {
_t.icon.removeEventListener("load", convertToInlineSVG);
_t.icon.removeEventListener("error", replaceImageWithNone);
}

Expand Down Expand Up @@ -1924,6 +1974,7 @@
_t.loading = _t.root.querySelector(o.uiLoadingBar);
_t.layoutTitle = document.querySelector(o.layoutTitle);
_t.iconType = document.body.getAttribute(o.iconTypeAttribute);
_t.inlineSVG = document.body.getAttribute(o.inlineSvgAttribute) === "true";
_t.primaryColor = document.body.getAttribute(o.primaryColorAttribute);
_t.secondaryColor = document.body.getAttribute(o.secondaryColorAttribute);
_t.notification = document.querySelector(o.notify);
Expand Down Expand Up @@ -2684,6 +2735,7 @@
idAttribute: "data-widget-id",
iconAttribute: "data-icon",
iconTypeAttribute: "data-icon-type",
inlineSvgAttribute: "data-inline-svg",
primaryColorAttribute: "data-primary-color",
secondaryColorAttribute: "data-secondary-color",
controlButton: "button",
Expand All @@ -2701,6 +2753,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",
Expand Down

0 comments on commit 9be4293

Please sign in to comment.