From 7d6f188bfc7bb89976b38439b8a50588e70d6fff Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 20 Mar 2020 21:30:20 +0100 Subject: [PATCH] Change themes logic (#5232) * Fix themes * Update hui-view.ts * Comments and bail out * Update apply_themes_on_element.ts * refactor, move meta to theme mixin, adapt lovelace theme picker * console.bye * Comments * Optimizations * Bail out early is no hex value * Cache processed themes * Remove hex-rgb cache --- hassio/src/hassio-main.ts | 3 +- src/common/dom/apply_themes_on_element.ts | 119 ++++++++++-------- src/fake_data/provide_hass.ts | 3 +- .../components/hui-theme-select-editor.ts | 55 ++++---- .../hui-alarm-panel-card-editor.ts | 2 +- .../config-elements/hui-button-card-editor.ts | 2 +- .../hui-entities-card-editor.ts | 2 +- .../config-elements/hui-gauge-card-editor.ts | 2 +- .../config-elements/hui-glance-card-editor.ts | 2 +- .../config-elements/hui-light-card-editor.ts | 2 +- .../hui-markdown-card-editor.ts | 2 +- .../hui-picture-card-editor.ts | 2 +- .../hui-picture-entity-card-editor.ts | 2 +- .../hui-picture-glance-card-editor.ts | 2 +- .../hui-plant-status-card-editor.ts | 2 +- .../config-elements/hui-sensor-card-editor.ts | 2 +- .../hui-shopping-list-editor.ts | 2 +- .../hui-thermostat-card-editor.ts | 2 +- .../hui-weather-forecast-card-editor.ts | 2 +- .../editor/view-editor/hui-view-editor.ts | 2 +- src/state/themes-mixin.ts | 23 +++- src/translations/en.json | 1 + 22 files changed, 132 insertions(+), 104 deletions(-) diff --git a/hassio/src/hassio-main.ts b/hassio/src/hassio-main.ts index 1b6ac36288..ff5def6b41 100644 --- a/hassio/src/hassio-main.ts +++ b/hassio/src/hassio-main.ts @@ -87,8 +87,7 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) { applyThemesOnElement( this.parentElement, this.hass.themes, - this.hass.selectedTheme, - true + this.hass.selectedTheme || this.hass.themes.default_theme ); this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev)); // Paulus - March 17, 2019 diff --git a/src/common/dom/apply_themes_on_element.ts b/src/common/dom/apply_themes_on_element.ts index 12cd3757cf..b8a275b35d 100644 --- a/src/common/dom/apply_themes_on_element.ts +++ b/src/common/dom/apply_themes_on_element.ts @@ -1,4 +1,10 @@ import { derivedStyles } from "../../resources/styles"; +import { HomeAssistant, Theme } from "../../types"; + +interface ProcessedTheme { + keys: { [key: string]: "" }; + styles: { [key: string]: string }; +} const hexToRgb = (hex: string): string | null => { const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; @@ -15,67 +21,82 @@ const hexToRgb = (hex: string): string | null => { : null; }; +let PROCESSED_THEMES: { [key: string]: ProcessedTheme } = {}; + /** * Apply a theme to an element by setting the CSS variables on it. * * element: Element to apply theme on. * themes: HASS Theme information - * localTheme: selected theme. - * updateMeta: boolean if we should update the theme-color meta element. + * selectedTheme: selected theme. */ export const applyThemesOnElement = ( element, - themes, - localTheme, - updateMeta = false + themes: HomeAssistant["themes"], + selectedTheme?: string ) => { - if (!element._themes) { - element._themes = {}; - } - let themeName = themes.default_theme; - if (localTheme === "default" || (localTheme && themes.themes[localTheme])) { - themeName = localTheme; - } - const styles = { ...element._themes }; - if (themeName !== "default") { - const theme = { ...derivedStyles, ...themes.themes[themeName] }; - Object.keys(theme).forEach((key) => { - const prefixedKey = `--${key}`; - element._themes[prefixedKey] = ""; - styles[prefixedKey] = theme[key]; - if (key.startsWith("rgb")) { - return; - } - const rgbKey = `rgb-${key}`; - if (theme[rgbKey] !== undefined) { - return; - } - const prefixedRgbKey = `--${rgbKey}`; - element._themes[prefixedRgbKey] = ""; - const rgbValue = hexToRgb(theme[key]); - if (rgbValue !== null) { - styles[prefixedRgbKey] = rgbValue; - } - }); - } - if (element.updateStyles) { - element.updateStyles(styles); - } else if (window.ShadyCSS) { - // implement updateStyles() method of Polymer elements - window.ShadyCSS.styleSubtree(/** @type {!HTMLElement} */ element, styles); - } + const newTheme = selectedTheme + ? PROCESSED_THEMES[selectedTheme] || processTheme(selectedTheme, themes) + : undefined; - if (!updateMeta) { + if (!element._themes && !newTheme) { + // No styles to reset, and no styles to set return; } - const meta = document.querySelector("meta[name=theme-color]"); - if (meta) { - if (!meta.hasAttribute("default-content")) { - meta.setAttribute("default-content", meta.getAttribute("content")!); - } - const themeColor = - styles["--primary-color"] || meta.getAttribute("default-content"); - meta.setAttribute("content", themeColor); + // Add previous set keys to reset them, and new theme + const styles = { ...element._themes, ...newTheme?.styles }; + element._themes = newTheme?.keys; + + // Set and/or reset styles + if (element.updateStyles) { + element.updateStyles(styles); + } else if (window.ShadyCSS) { + // Implement updateStyles() method of Polymer elements + window.ShadyCSS.styleSubtree(/** @type {!HTMLElement} */ element, styles); } }; + +const processTheme = ( + themeName: string, + themes: HomeAssistant["themes"] +): ProcessedTheme | undefined => { + if (!themes.themes[themeName]) { + return; + } + const theme: Theme = { + ...derivedStyles, + ...themes.themes[themeName], + }; + const styles = {}; + const keys = {}; + for (const key of Object.keys(theme)) { + const prefixedKey = `--${key}`; + const value = theme[key]; + styles[prefixedKey] = value; + keys[prefixedKey] = ""; + + // Try to create a rgb value for this key if it is a hex color + if (!value.startsWith("#")) { + // Not a hex color + continue; + } + const rgbKey = `rgb-${key}`; + if (theme[rgbKey] !== undefined) { + // Theme has it's own rgb value + continue; + } + const rgbValue = hexToRgb(value); + if (rgbValue !== null) { + const prefixedRgbKey = `--${rgbKey}`; + styles[prefixedRgbKey] = rgbValue; + keys[prefixedRgbKey] = ""; + } + } + PROCESSED_THEMES[themeName] = { styles, keys }; + return { styles, keys }; +}; + +export const invalidateThemeCache = () => { + PROCESSED_THEMES = {}; +}; diff --git a/src/fake_data/provide_hass.ts b/src/fake_data/provide_hass.ts index 822b1c6aae..032541f0fe 100644 --- a/src/fake_data/provide_hass.ts +++ b/src/fake_data/provide_hass.ts @@ -237,8 +237,7 @@ export const provideHass = ( applyThemesOnElement( document.documentElement, themes, - selectedTheme, - true + selectedTheme as string ); }, diff --git a/src/panels/lovelace/components/hui-theme-select-editor.ts b/src/panels/lovelace/components/hui-theme-select-editor.ts index 5902ca5310..9d206be7f5 100644 --- a/src/panels/lovelace/components/hui-theme-select-editor.ts +++ b/src/panels/lovelace/components/hui-theme-select-editor.ts @@ -10,18 +10,7 @@ import { import "@material/mwc-button"; import { HomeAssistant } from "../../../types"; -import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event"; - -declare global { - // for fire event - interface HASSDomEvents { - "theme-changed": undefined; - } - // for add event listener - interface HTMLElementEventMap { - "theme-changed": HASSDomEvent; - } -} +import { fireEvent } from "../../../common/dom/fire_event"; @customElement("hui-theme-select-editor") export class HuiThemeSelectEditor extends LitElement { @@ -30,32 +19,34 @@ export class HuiThemeSelectEditor extends LitElement { @property() public hass?: HomeAssistant; protected render(): TemplateResult { - const themes = ["Backend-selected", "default"].concat( - Object.keys(this.hass!.themes.themes).sort() - ); - return html` - ${themes.map((theme) => { - return html` - ${theme} - `; - })} + ${this.hass!.localize( + "ui.panel.lovelace.editor.card.generic.no_theme" + )} + ${Object.keys(this.hass!.themes.themes) + .sort() + .map((theme) => { + return html` + ${theme} + `; + })} `; @@ -70,11 +61,11 @@ export class HuiThemeSelectEditor extends LitElement { } private _changed(ev): void { - if (!this.hass || ev.target.value === "") { + if (!this.hass || ev.target.selected === "") { return; } - this.value = ev.target.value; - fireEvent(this, "theme-changed"); + this.value = ev.target.selected === "remove" ? "" : ev.target.selected; + fireEvent(this, "value-changed", { value: this.value }); } } diff --git a/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts index e6bbf53ab6..b1e1a1f342 100644 --- a/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts @@ -123,7 +123,7 @@ export class HuiAlarmPanelCardEditor extends LitElement .hass=${this.hass} .value="${this._theme}" .configValue="${"theme"}" - @theme-changed="${this._valueChanged}" + @value-changed="${this._valueChanged}" > `; diff --git a/src/panels/lovelace/editor/config-elements/hui-button-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-button-card-editor.ts index ac9e25c3aa..f8f292bb0e 100644 --- a/src/panels/lovelace/editor/config-elements/hui-button-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-button-card-editor.ts @@ -173,7 +173,7 @@ export class HuiButtonCardEditor extends LitElement .hass=${this.hass} .value="${this._theme}" .configValue="${"theme"}" - @theme-changed="${this._valueChanged}" + @value-changed="${this._valueChanged}" > diff --git a/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts index d1b5dbe258..0fd383e2ba 100644 --- a/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts @@ -88,7 +88,7 @@ export class HuiEntitiesCardEditor extends LitElement .hass=${this.hass} .value="${this._theme}" .configValue="${"theme"}" - @theme-changed="${this._valueChanged}" + @value-changed="${this._valueChanged}" > `; diff --git a/src/panels/lovelace/editor/config-elements/hui-markdown-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-markdown-card-editor.ts index 6bfd0c2f0a..f5b56e201c 100644 --- a/src/panels/lovelace/editor/config-elements/hui-markdown-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-markdown-card-editor.ts @@ -84,7 +84,7 @@ export class HuiMarkdownCardEditor extends LitElement .hass=${this.hass} .value="${this._theme}" .configValue="${"theme"}" - @theme-changed="${this._valueChanged}" + @value-changed="${this._valueChanged}" > `; diff --git a/src/panels/lovelace/editor/config-elements/hui-picture-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-picture-card-editor.ts index 5dddb93018..6b86440d07 100644 --- a/src/panels/lovelace/editor/config-elements/hui-picture-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-picture-card-editor.ts @@ -108,7 +108,7 @@ export class HuiPictureCardEditor extends LitElement .hass=${this.hass} .value="${this._theme}" .configValue="${"theme"}" - @theme-changed="${this._valueChanged}" + @value-changed="${this._valueChanged}" > diff --git a/src/panels/lovelace/editor/config-elements/hui-picture-entity-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-picture-entity-card-editor.ts index 2dd7b7aebd..e81e2b114d 100644 --- a/src/panels/lovelace/editor/config-elements/hui-picture-entity-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-picture-entity-card-editor.ts @@ -235,7 +235,7 @@ export class HuiPictureEntityCardEditor extends LitElement .hass=${this.hass} .value="${this._theme}" .configValue="${"theme"}" - @theme-changed="${this._valueChanged}" + @value-changed="${this._valueChanged}" > diff --git a/src/panels/lovelace/editor/config-elements/hui-picture-glance-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-picture-glance-card-editor.ts index d8d2aa690e..d9d1c2c48a 100644 --- a/src/panels/lovelace/editor/config-elements/hui-picture-glance-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-picture-glance-card-editor.ts @@ -234,7 +234,7 @@ export class HuiPictureGlanceCardEditor extends LitElement .hass=${this.hass} .value="${this._theme}" .configValue="${"theme"}" - @theme-changed="${this._valueChanged}" + @value-changed="${this._valueChanged}" > `; diff --git a/src/panels/lovelace/editor/config-elements/hui-plant-status-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-plant-status-card-editor.ts index fe2d93356c..ef84475b34 100644 --- a/src/panels/lovelace/editor/config-elements/hui-plant-status-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-plant-status-card-editor.ts @@ -85,7 +85,7 @@ export class HuiPlantStatusCardEditor extends LitElement .hass=${this.hass} .value="${this._theme}" .configValue="${"theme"}" - @theme-changed="${this._valueChanged}" + @value-changed="${this._valueChanged}" > `; diff --git a/src/panels/lovelace/editor/config-elements/hui-sensor-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-sensor-card-editor.ts index 9114bc19d0..cf861ca1ae 100644 --- a/src/panels/lovelace/editor/config-elements/hui-sensor-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-sensor-card-editor.ts @@ -170,7 +170,7 @@ export class HuiSensorCardEditor extends LitElement .hass=${this.hass} .value="${this._theme}" .configValue="${"theme"}" - @theme-changed="${this._valueChanged}" + @value-changed="${this._valueChanged}" > `; diff --git a/src/panels/lovelace/editor/config-elements/hui-thermostat-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-thermostat-card-editor.ts index 575d8d6b2e..862613d987 100644 --- a/src/panels/lovelace/editor/config-elements/hui-thermostat-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-thermostat-card-editor.ts @@ -84,7 +84,7 @@ export class HuiThermostatCardEditor extends LitElement .hass=${this.hass} .value="${this._theme}" .configValue="${"theme"}" - @theme-changed="${this._valueChanged}" + @value-changed="${this._valueChanged}" > `; diff --git a/src/panels/lovelace/editor/config-elements/hui-weather-forecast-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-weather-forecast-card-editor.ts index 4a25c8ae2d..d4ae31d7ea 100644 --- a/src/panels/lovelace/editor/config-elements/hui-weather-forecast-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-weather-forecast-card-editor.ts @@ -83,7 +83,7 @@ export class HuiWeatherForecastCardEditor extends LitElement .hass=${this.hass} .value="${this._theme}" .configValue="${"theme"}" - @theme-changed="${this._valueChanged}" + @value-changed="${this._valueChanged}" > `; diff --git a/src/panels/lovelace/editor/view-editor/hui-view-editor.ts b/src/panels/lovelace/editor/view-editor/hui-view-editor.ts index 90baf57453..baeb0aa393 100644 --- a/src/panels/lovelace/editor/view-editor/hui-view-editor.ts +++ b/src/panels/lovelace/editor/view-editor/hui-view-editor.ts @@ -116,7 +116,7 @@ export class HuiViewEditor extends LitElement { .hass=${this.hass} .value=${this._theme} .configValue=${"theme"} - @theme-changed=${this._valueChanged} + @value-changed=${this._valueChanged} > >(superClass: T) => subscribeThemes(this.hass!.connection, (themes) => { this._updateHass({ themes }); + invalidateThemeCache(); this._applyTheme(); }); } @@ -36,8 +40,21 @@ export default >(superClass: T) => applyThemesOnElement( document.documentElement, this.hass!.themes, - this.hass!.selectedTheme, - true + this.hass!.selectedTheme || this.hass!.themes.default_theme ); + + const meta = document.querySelector("meta[name=theme-color]"); + const headerColor = getComputedStyle( + document.documentElement + ).getPropertyValue("--app-header-background-color"); + if (meta) { + if (!meta.hasAttribute("default-content")) { + meta.setAttribute("default-content", meta.getAttribute("content")!); + } + const themeColor = + headerColor.trim() || + (meta.getAttribute("default-content") as string); + meta.setAttribute("content", themeColor); + } } }; diff --git a/src/translations/en.json b/src/translations/en.json index 5d0c6b5443..0bfbd33757 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2068,6 +2068,7 @@ "tap_action": "Tap Action", "title": "Title", "theme": "Theme", + "no_theme": "No theme", "unit": "Unit", "url": "Url", "state": "State"