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(
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

View File

@ -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 = {};
};

View File

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

View File

@ -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<undefined>;
}
}
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`
<paper-dropdown-menu
.label=${this.label ||
this.hass!.localize("ui.panel.lovelace.editor.card.generic.theme") +
" (" +
this.hass!.localize(
"ui.panel.lovelace.editor.card.config.optional"
) +
")"}
`${this.hass!.localize(
"ui.panel.lovelace.editor.card.generic.theme"
)} (${this.hass!.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})`}
dynamic-align
@value-changed="${this._changed}"
>
<paper-listbox
slot="dropdown-content"
.selected="${this.value}"
.selected=${this.value}
attr-for-selected="theme"
@iron-select=${this._changed}
>
${themes.map((theme) => {
return html`
<paper-item theme="${theme}">${theme}</paper-item>
`;
})}
<paper-item theme="remove"
>${this.hass!.localize(
"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-dropdown-menu>
`;
@ -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 });
}
}

View File

@ -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}"
></hui-theme-select-editor>
</div>
`;

View File

@ -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}"
></hui-theme-select-editor>
</paper-input>

View File

@ -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}"
></hui-theme-select-editor>
<ha-switch
.checked="${this._config!.show_header_toggle !== false}"

View File

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

View File

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

View File

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

View File

@ -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}"
></hui-theme-select-editor>
</div>
`;

View File

@ -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}"
></hui-theme-select-editor>
</div>
</div>

View File

@ -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}"
></hui-theme-select-editor>
</div>
</div>

View File

@ -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}"
></hui-theme-select-editor>
</div>
`;

View File

@ -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}"
></hui-theme-select-editor>
</div>
`;

View File

@ -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}"
></hui-theme-select-editor>
<paper-input
.label="${this.hass.localize(

View File

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

View File

@ -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}"
></hui-theme-select-editor>
</div>
`;

View File

@ -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}"
></hui-theme-select-editor>
</div>
`;

View File

@ -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}
></hui-theme-select-editor>
<ha-switch
.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 { subscribeThemes } from "../data/ws-themes";
import { HassBaseEl } from "./hass-base-mixin";
@ -28,6 +31,7 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
subscribeThemes(this.hass!.connection, (themes) => {
this._updateHass({ themes });
invalidateThemeCache();
this._applyTheme();
});
}
@ -36,8 +40,21 @@ export default <T extends Constructor<HassBaseEl>>(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);
}
}
};

View File

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