mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 01:36:49 +00:00
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:
parent
15a144f17a
commit
7d6f188bfc
@ -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
|
||||
|
@ -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 = {};
|
||||
};
|
||||
|
@ -237,8 +237,7 @@ export const provideHass = (
|
||||
applyThemesOnElement(
|
||||
document.documentElement,
|
||||
themes,
|
||||
selectedTheme,
|
||||
true
|
||||
selectedTheme as string
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
`;
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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}"
|
||||
|
@ -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"
|
||||
|
@ -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(
|
||||
|
@ -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>
|
||||
`;
|
||||
|
@ -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>
|
||||
`;
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
`;
|
||||
|
@ -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>
|
||||
`;
|
||||
|
@ -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(
|
||||
|
@ -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>
|
||||
`;
|
||||
|
@ -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>
|
||||
`;
|
||||
|
@ -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>
|
||||
`;
|
||||
|
@ -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}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -2068,6 +2068,7 @@
|
||||
"tap_action": "Tap Action",
|
||||
"title": "Title",
|
||||
"theme": "Theme",
|
||||
"no_theme": "No theme",
|
||||
"unit": "Unit",
|
||||
"url": "Url",
|
||||
"state": "State"
|
||||
|
Loading…
x
Reference in New Issue
Block a user