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
This commit is contained in:
Bram Kragten 2020-03-20 21:30:20 +01:00 committed by GitHub
parent 15a144f17a
commit 7d6f188bfc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 132 additions and 104 deletions

View File

@ -87,8 +87,7 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
applyThemesOnElement( applyThemesOnElement(
this.parentElement, this.parentElement,
this.hass.themes, this.hass.themes,
this.hass.selectedTheme, this.hass.selectedTheme || this.hass.themes.default_theme
true
); );
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev)); this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
// Paulus - March 17, 2019 // Paulus - March 17, 2019

View File

@ -1,4 +1,10 @@
import { derivedStyles } from "../../resources/styles"; 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 hexToRgb = (hex: string): string | null => {
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
@ -15,67 +21,82 @@ const hexToRgb = (hex: string): string | null => {
: null; : null;
}; };
let PROCESSED_THEMES: { [key: string]: ProcessedTheme } = {};
/** /**
* Apply a theme to an element by setting the CSS variables on it. * Apply a theme to an element by setting the CSS variables on it.
* *
* element: Element to apply theme on. * element: Element to apply theme on.
* themes: HASS Theme information * themes: HASS Theme information
* localTheme: selected theme. * selectedTheme: selected theme.
* updateMeta: boolean if we should update the theme-color meta element.
*/ */
export const applyThemesOnElement = ( export const applyThemesOnElement = (
element, element,
themes, themes: HomeAssistant["themes"],
localTheme, selectedTheme?: string
updateMeta = false
) => { ) => {
if (!element._themes) { const newTheme = selectedTheme
element._themes = {}; ? PROCESSED_THEMES[selectedTheme] || processTheme(selectedTheme, themes)
} : undefined;
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);
}
if (!updateMeta) { if (!element._themes && !newTheme) {
// No styles to reset, and no styles to set
return; return;
} }
const meta = document.querySelector("meta[name=theme-color]"); // Add previous set keys to reset them, and new theme
if (meta) { const styles = { ...element._themes, ...newTheme?.styles };
if (!meta.hasAttribute("default-content")) { element._themes = newTheme?.keys;
meta.setAttribute("default-content", meta.getAttribute("content")!);
} // Set and/or reset styles
const themeColor = if (element.updateStyles) {
styles["--primary-color"] || meta.getAttribute("default-content"); element.updateStyles(styles);
meta.setAttribute("content", themeColor); } 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 = {};
};

View File

@ -237,8 +237,7 @@ export const provideHass = (
applyThemesOnElement( applyThemesOnElement(
document.documentElement, document.documentElement,
themes, themes,
selectedTheme, selectedTheme as string
true
); );
}, },

View File

@ -10,18 +10,7 @@ import {
import "@material/mwc-button"; import "@material/mwc-button";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
declare global {
// for fire event
interface HASSDomEvents {
"theme-changed": undefined;
}
// for add event listener
interface HTMLElementEventMap {
"theme-changed": HASSDomEvent<undefined>;
}
}
@customElement("hui-theme-select-editor") @customElement("hui-theme-select-editor")
export class HuiThemeSelectEditor extends LitElement { export class HuiThemeSelectEditor extends LitElement {
@ -30,32 +19,34 @@ export class HuiThemeSelectEditor extends LitElement {
@property() public hass?: HomeAssistant; @property() public hass?: HomeAssistant;
protected render(): TemplateResult { protected render(): TemplateResult {
const themes = ["Backend-selected", "default"].concat(
Object.keys(this.hass!.themes.themes).sort()
);
return html` return html`
<paper-dropdown-menu <paper-dropdown-menu
.label=${this.label || .label=${this.label ||
this.hass!.localize("ui.panel.lovelace.editor.card.generic.theme") + `${this.hass!.localize(
" (" + "ui.panel.lovelace.editor.card.generic.theme"
this.hass!.localize( )} (${this.hass!.localize(
"ui.panel.lovelace.editor.card.config.optional" "ui.panel.lovelace.editor.card.config.optional"
) + )})`}
")"}
dynamic-align dynamic-align
@value-changed="${this._changed}"
> >
<paper-listbox <paper-listbox
slot="dropdown-content" slot="dropdown-content"
.selected="${this.value}" .selected=${this.value}
attr-for-selected="theme" attr-for-selected="theme"
@iron-select=${this._changed}
> >
${themes.map((theme) => { <paper-item theme="remove"
return html` >${this.hass!.localize(
<paper-item theme="${theme}">${theme}</paper-item> "ui.panel.lovelace.editor.card.generic.no_theme"
`; )}</paper-item
})} >
${Object.keys(this.hass!.themes.themes)
.sort()
.map((theme) => {
return html`
<paper-item theme=${theme}>${theme}</paper-item>
`;
})}
</paper-listbox> </paper-listbox>
</paper-dropdown-menu> </paper-dropdown-menu>
`; `;
@ -70,11 +61,11 @@ export class HuiThemeSelectEditor extends LitElement {
} }
private _changed(ev): void { private _changed(ev): void {
if (!this.hass || ev.target.value === "") { if (!this.hass || ev.target.selected === "") {
return; return;
} }
this.value = ev.target.value; this.value = ev.target.selected === "remove" ? "" : ev.target.selected;
fireEvent(this, "theme-changed"); fireEvent(this, "value-changed", { value: this.value });
} }
} }

View File

@ -123,7 +123,7 @@ export class HuiAlarmPanelCardEditor extends LitElement
.hass=${this.hass} .hass=${this.hass}
.value="${this._theme}" .value="${this._theme}"
.configValue="${"theme"}" .configValue="${"theme"}"
@theme-changed="${this._valueChanged}" @value-changed="${this._valueChanged}"
></hui-theme-select-editor> ></hui-theme-select-editor>
</div> </div>
`; `;

View File

@ -173,7 +173,7 @@ export class HuiButtonCardEditor extends LitElement
.hass=${this.hass} .hass=${this.hass}
.value="${this._theme}" .value="${this._theme}"
.configValue="${"theme"}" .configValue="${"theme"}"
@theme-changed="${this._valueChanged}" @value-changed="${this._valueChanged}"
></hui-theme-select-editor> ></hui-theme-select-editor>
</paper-input> </paper-input>

View File

@ -88,7 +88,7 @@ export class HuiEntitiesCardEditor extends LitElement
.hass=${this.hass} .hass=${this.hass}
.value="${this._theme}" .value="${this._theme}"
.configValue="${"theme"}" .configValue="${"theme"}"
@theme-changed="${this._valueChanged}" @value-changed="${this._valueChanged}"
></hui-theme-select-editor> ></hui-theme-select-editor>
<ha-switch <ha-switch
.checked="${this._config!.show_header_toggle !== false}" .checked="${this._config!.show_header_toggle !== false}"

View File

@ -117,7 +117,7 @@ export class HuiGaugeCardEditor extends LitElement
.hass=${this.hass} .hass=${this.hass}
.value="${this._theme}" .value="${this._theme}"
.configValue="${"theme"}" .configValue="${"theme"}"
@theme-changed="${this._valueChanged}" @value-changed="${this._valueChanged}"
></hui-theme-select-editor> ></hui-theme-select-editor>
<paper-input <paper-input
type="number" type="number"

View File

@ -102,7 +102,7 @@ export class HuiGlanceCardEditor extends LitElement
.hass=${this.hass} .hass=${this.hass}
.value="${this._theme}" .value="${this._theme}"
.configValue="${"theme"}" .configValue="${"theme"}"
@theme-changed="${this._valueChanged}" @value-changed="${this._valueChanged}"
></hui-theme-select-editor> ></hui-theme-select-editor>
<paper-input <paper-input
.label="${this.hass.localize( .label="${this.hass.localize(

View File

@ -106,7 +106,7 @@ export class HuiLightCardEditor extends LitElement
.hass=${this.hass} .hass=${this.hass}
.value="${this._theme}" .value="${this._theme}"
.configValue="${"theme"}" .configValue="${"theme"}"
@theme-changed="${this._valueChanged}" @value-changed="${this._valueChanged}"
></hui-theme-select-editor> ></hui-theme-select-editor>
</div> </div>
`; `;

View File

@ -84,7 +84,7 @@ export class HuiMarkdownCardEditor extends LitElement
.hass=${this.hass} .hass=${this.hass}
.value="${this._theme}" .value="${this._theme}"
.configValue="${"theme"}" .configValue="${"theme"}"
@theme-changed="${this._valueChanged}" @value-changed="${this._valueChanged}"
></hui-theme-select-editor> ></hui-theme-select-editor>
</div> </div>
`; `;

View File

@ -108,7 +108,7 @@ export class HuiPictureCardEditor extends LitElement
.hass=${this.hass} .hass=${this.hass}
.value="${this._theme}" .value="${this._theme}"
.configValue="${"theme"}" .configValue="${"theme"}"
@theme-changed="${this._valueChanged}" @value-changed="${this._valueChanged}"
></hui-theme-select-editor> ></hui-theme-select-editor>
</div> </div>
</div> </div>

View File

@ -235,7 +235,7 @@ export class HuiPictureEntityCardEditor extends LitElement
.hass=${this.hass} .hass=${this.hass}
.value="${this._theme}" .value="${this._theme}"
.configValue="${"theme"}" .configValue="${"theme"}"
@theme-changed="${this._valueChanged}" @value-changed="${this._valueChanged}"
></hui-theme-select-editor> ></hui-theme-select-editor>
</div> </div>
</div> </div>

View File

@ -234,7 +234,7 @@ export class HuiPictureGlanceCardEditor extends LitElement
.hass=${this.hass} .hass=${this.hass}
.value="${this._theme}" .value="${this._theme}"
.configValue="${"theme"}" .configValue="${"theme"}"
@theme-changed="${this._valueChanged}" @value-changed="${this._valueChanged}"
></hui-theme-select-editor> ></hui-theme-select-editor>
</div> </div>
`; `;

View File

@ -85,7 +85,7 @@ export class HuiPlantStatusCardEditor extends LitElement
.hass=${this.hass} .hass=${this.hass}
.value="${this._theme}" .value="${this._theme}"
.configValue="${"theme"}" .configValue="${"theme"}"
@theme-changed="${this._valueChanged}" @value-changed="${this._valueChanged}"
></hui-theme-select-editor> ></hui-theme-select-editor>
</div> </div>
`; `;

View File

@ -170,7 +170,7 @@ export class HuiSensorCardEditor extends LitElement
.hass=${this.hass} .hass=${this.hass}
.value="${this._theme}" .value="${this._theme}"
.configValue="${"theme"}" .configValue="${"theme"}"
@theme-changed="${this._valueChanged}" @value-changed="${this._valueChanged}"
></hui-theme-select-editor> ></hui-theme-select-editor>
<paper-input <paper-input
.label="${this.hass.localize( .label="${this.hass.localize(

View File

@ -75,7 +75,7 @@ export class HuiShoppingListEditor extends LitElement
.hass=${this.hass} .hass=${this.hass}
.value="${this._theme}" .value="${this._theme}"
.configValue="${"theme"}" .configValue="${"theme"}"
@theme-changed="${this._valueChanged}" @value-changed="${this._valueChanged}"
></hui-theme-select-editor> ></hui-theme-select-editor>
</div> </div>
`; `;

View File

@ -84,7 +84,7 @@ export class HuiThermostatCardEditor extends LitElement
.hass=${this.hass} .hass=${this.hass}
.value="${this._theme}" .value="${this._theme}"
.configValue="${"theme"}" .configValue="${"theme"}"
@theme-changed="${this._valueChanged}" @value-changed="${this._valueChanged}"
></hui-theme-select-editor> ></hui-theme-select-editor>
</div> </div>
`; `;

View File

@ -83,7 +83,7 @@ export class HuiWeatherForecastCardEditor extends LitElement
.hass=${this.hass} .hass=${this.hass}
.value="${this._theme}" .value="${this._theme}"
.configValue="${"theme"}" .configValue="${"theme"}"
@theme-changed="${this._valueChanged}" @value-changed="${this._valueChanged}"
></hui-theme-select-editor> ></hui-theme-select-editor>
</div> </div>
`; `;

View File

@ -116,7 +116,7 @@ export class HuiViewEditor extends LitElement {
.hass=${this.hass} .hass=${this.hass}
.value=${this._theme} .value=${this._theme}
.configValue=${"theme"} .configValue=${"theme"}
@theme-changed=${this._valueChanged} @value-changed=${this._valueChanged}
></hui-theme-select-editor> ></hui-theme-select-editor>
<ha-switch <ha-switch
.checked=${this._panel !== false} .checked=${this._panel !== false}

View File

@ -1,4 +1,7 @@
import { applyThemesOnElement } from "../common/dom/apply_themes_on_element"; import {
applyThemesOnElement,
invalidateThemeCache,
} from "../common/dom/apply_themes_on_element";
import { storeState } from "../util/ha-pref-storage"; import { storeState } from "../util/ha-pref-storage";
import { subscribeThemes } from "../data/ws-themes"; import { subscribeThemes } from "../data/ws-themes";
import { HassBaseEl } from "./hass-base-mixin"; import { HassBaseEl } from "./hass-base-mixin";
@ -28,6 +31,7 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
subscribeThemes(this.hass!.connection, (themes) => { subscribeThemes(this.hass!.connection, (themes) => {
this._updateHass({ themes }); this._updateHass({ themes });
invalidateThemeCache();
this._applyTheme(); this._applyTheme();
}); });
} }
@ -36,8 +40,21 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
applyThemesOnElement( applyThemesOnElement(
document.documentElement, document.documentElement,
this.hass!.themes, this.hass!.themes,
this.hass!.selectedTheme, this.hass!.selectedTheme || this.hass!.themes.default_theme
true
); );
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);
}
} }
}; };

View File

@ -2068,6 +2068,7 @@
"tap_action": "Tap Action", "tap_action": "Tap Action",
"title": "Title", "title": "Title",
"theme": "Theme", "theme": "Theme",
"no_theme": "No theme",
"unit": "Unit", "unit": "Unit",
"url": "Url", "url": "Url",
"state": "State" "state": "State"