Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BasicUI] Use inline SVG for "colorless" SVG icons #1799

Merged
merged 3 commits into from
Jul 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
lolodomo marked this conversation as resolved.
Show resolved Hide resolved
} 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