2021-05-25 13:26:35 +02:00
|
|
|
import { ThemeVars } from "../../data/ws-themes";
|
2020-09-15 00:08:16 +02:00
|
|
|
import { darkStyles, derivedStyles } from "../../resources/styles";
|
2020-11-25 12:31:51 +01:00
|
|
|
import type { HomeAssistant } from "../../types";
|
2020-08-03 02:07:12 +02:00
|
|
|
import {
|
|
|
|
hex2rgb,
|
2020-09-15 00:08:16 +02:00
|
|
|
lab2hex,
|
|
|
|
lab2rgb,
|
2020-08-03 02:07:12 +02:00
|
|
|
rgb2hex,
|
|
|
|
rgb2lab,
|
|
|
|
} from "../color/convert-color";
|
2020-11-01 22:04:27 +01:00
|
|
|
import { hexBlend } from "../color/hex";
|
2020-09-15 00:08:16 +02:00
|
|
|
import { labBrighten, labDarken } from "../color/lab";
|
2020-08-03 02:07:12 +02:00
|
|
|
import { rgbContrast } from "../color/rgb";
|
2020-03-20 21:30:20 +01:00
|
|
|
|
|
|
|
interface ProcessedTheme {
|
|
|
|
keys: { [key: string]: "" };
|
2020-11-25 10:40:32 +01:00
|
|
|
styles: Record<string, string>;
|
2020-03-20 21:30:20 +01:00
|
|
|
}
|
2020-02-05 00:40:35 +01:00
|
|
|
|
2020-11-25 10:40:32 +01:00
|
|
|
let PROCESSED_THEMES: Record<string, ProcessedTheme> = {};
|
2020-03-20 21:30:20 +01:00
|
|
|
|
2018-05-10 03:33:31 +02:00
|
|
|
/**
|
|
|
|
* Apply a theme to an element by setting the CSS variables on it.
|
|
|
|
*
|
|
|
|
* element: Element to apply theme on.
|
2021-05-25 13:26:35 +02:00
|
|
|
* themes: HASS theme information.
|
|
|
|
* selectedTheme: Selected theme.
|
|
|
|
* themeSettings: Settings such as selected dark mode and colors.
|
2018-10-11 12:22:11 +02:00
|
|
|
*/
|
2019-10-21 19:38:06 +02:00
|
|
|
export const applyThemesOnElement = (
|
2018-10-11 12:22:11 +02:00
|
|
|
element,
|
2020-03-20 21:30:20 +01:00
|
|
|
themes: HomeAssistant["themes"],
|
2020-08-03 02:07:12 +02:00
|
|
|
selectedTheme?: string,
|
2021-05-27 10:21:58 +02:00
|
|
|
themeSettings?: Partial<HomeAssistant["selectedTheme"]>
|
2019-10-21 19:38:06 +02:00
|
|
|
) => {
|
2020-08-03 02:07:12 +02:00
|
|
|
let cacheKey = selectedTheme;
|
2021-05-25 13:26:35 +02:00
|
|
|
let themeRules: Partial<ThemeVars> = {};
|
2020-03-20 21:30:20 +01:00
|
|
|
|
2021-05-25 13:26:35 +02:00
|
|
|
if (themeSettings) {
|
|
|
|
if (themeSettings.dark) {
|
2020-08-03 02:07:12 +02:00
|
|
|
cacheKey = `${cacheKey}__dark`;
|
2021-05-26 00:11:17 +02:00
|
|
|
themeRules = { ...darkStyles };
|
2021-05-25 13:26:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (selectedTheme === "default") {
|
|
|
|
// Determine the primary and accent colors from the current settings.
|
|
|
|
// Fallbacks are implicitly the HA default blue and orange or the
|
|
|
|
// derived "darkStyles" values, depending on the light vs dark mode.
|
|
|
|
const primaryColor = themeSettings.primaryColor;
|
|
|
|
const accentColor = themeSettings.accentColor;
|
|
|
|
|
|
|
|
if (themeSettings.dark && primaryColor) {
|
2020-11-01 22:04:27 +01:00
|
|
|
themeRules["app-header-background-color"] = hexBlend(
|
2021-05-25 13:26:35 +02:00
|
|
|
primaryColor,
|
2020-11-01 22:04:27 +01:00
|
|
|
"#121212",
|
|
|
|
8
|
|
|
|
);
|
|
|
|
}
|
2021-04-26 23:54:47 +02:00
|
|
|
|
2021-05-25 13:26:35 +02:00
|
|
|
if (primaryColor) {
|
|
|
|
cacheKey = `${cacheKey}__primary_${primaryColor}`;
|
|
|
|
const rgbPrimaryColor = hex2rgb(primaryColor);
|
|
|
|
const labPrimaryColor = rgb2lab(rgbPrimaryColor);
|
|
|
|
themeRules["primary-color"] = primaryColor;
|
|
|
|
const rgbLightPrimaryColor = lab2rgb(labBrighten(labPrimaryColor));
|
|
|
|
themeRules["light-primary-color"] = rgb2hex(rgbLightPrimaryColor);
|
|
|
|
themeRules["dark-primary-color"] = lab2hex(labDarken(labPrimaryColor));
|
|
|
|
themeRules["text-primary-color"] =
|
|
|
|
rgbContrast(rgbPrimaryColor, [33, 33, 33]) < 6 ? "#fff" : "#212121";
|
|
|
|
themeRules["text-light-primary-color"] =
|
|
|
|
rgbContrast(rgbLightPrimaryColor, [33, 33, 33]) < 6
|
|
|
|
? "#fff"
|
|
|
|
: "#212121";
|
|
|
|
themeRules["state-icon-color"] = themeRules["dark-primary-color"];
|
|
|
|
}
|
|
|
|
if (accentColor) {
|
|
|
|
cacheKey = `${cacheKey}__accent_${accentColor}`;
|
|
|
|
themeRules["accent-color"] = accentColor;
|
|
|
|
const rgbAccentColor = hex2rgb(accentColor);
|
|
|
|
themeRules["text-accent-color"] =
|
|
|
|
rgbContrast(rgbAccentColor, [33, 33, 33]) < 6 ? "#fff" : "#212121";
|
|
|
|
}
|
|
|
|
|
|
|
|
// Nothing was changed
|
|
|
|
if (element._themes?.cacheKey === cacheKey) {
|
|
|
|
return;
|
|
|
|
}
|
2021-04-26 23:54:47 +02:00
|
|
|
}
|
2020-08-03 02:07:12 +02:00
|
|
|
}
|
|
|
|
|
2021-05-25 13:26:35 +02:00
|
|
|
// Custom theme logic (not relevant for default theme, since it would override
|
|
|
|
// the derived calculations from above)
|
|
|
|
if (
|
|
|
|
selectedTheme &&
|
|
|
|
selectedTheme !== "default" &&
|
|
|
|
themes.themes[selectedTheme]
|
|
|
|
) {
|
|
|
|
// Apply theme vars that are relevant for all modes (but extract the "modes" section first)
|
|
|
|
const { modes, ...baseThemeRules } = themes.themes[selectedTheme];
|
|
|
|
themeRules = { ...themeRules, ...baseThemeRules };
|
|
|
|
|
|
|
|
// Apply theme vars for the specific mode if available
|
|
|
|
if (modes) {
|
|
|
|
if (themeSettings?.dark) {
|
|
|
|
themeRules = { ...themeRules, ...modes.dark };
|
|
|
|
} else {
|
|
|
|
themeRules = { ...themeRules, ...modes.light };
|
|
|
|
}
|
|
|
|
}
|
2020-08-03 02:07:12 +02:00
|
|
|
}
|
|
|
|
|
2021-04-26 23:54:47 +02:00
|
|
|
if (!element._themes?.keys && !Object.keys(themeRules).length) {
|
2020-03-20 21:30:20 +01:00
|
|
|
// No styles to reset, and no styles to set
|
|
|
|
return;
|
2018-05-10 03:33:31 +02:00
|
|
|
}
|
2020-03-20 21:30:20 +01:00
|
|
|
|
2020-08-03 02:07:12 +02:00
|
|
|
const newTheme =
|
|
|
|
themeRules && cacheKey
|
|
|
|
? PROCESSED_THEMES[cacheKey] || processTheme(cacheKey, themeRules)
|
|
|
|
: undefined;
|
|
|
|
|
2020-03-20 21:30:20 +01:00
|
|
|
// Add previous set keys to reset them, and new theme
|
2021-04-26 23:54:47 +02:00
|
|
|
const styles = { ...element._themes?.keys, ...newTheme?.styles };
|
|
|
|
element._themes = { cacheKey, keys: newTheme?.keys };
|
2020-03-20 21:30:20 +01:00
|
|
|
|
|
|
|
// Set and/or reset styles
|
2018-05-27 17:56:01 +02:00
|
|
|
if (element.updateStyles) {
|
|
|
|
element.updateStyles(styles);
|
|
|
|
} else if (window.ShadyCSS) {
|
2020-03-20 21:30:20 +01:00
|
|
|
// Implement updateStyles() method of Polymer elements
|
2019-11-27 22:51:03 +01:00
|
|
|
window.ShadyCSS.styleSubtree(/** @type {!HTMLElement} */ element, styles);
|
2018-05-10 03:33:31 +02:00
|
|
|
}
|
2020-03-20 21:30:20 +01:00
|
|
|
};
|
2018-05-10 03:33:31 +02:00
|
|
|
|
2020-03-20 21:30:20 +01:00
|
|
|
const processTheme = (
|
2020-08-03 02:07:12 +02:00
|
|
|
cacheKey: string,
|
2021-05-25 13:26:35 +02:00
|
|
|
theme: Partial<ThemeVars>
|
2020-03-20 21:30:20 +01:00
|
|
|
): ProcessedTheme | undefined => {
|
2020-08-03 02:07:12 +02:00
|
|
|
if (!theme || !Object.keys(theme).length) {
|
2020-04-14 18:05:45 +02:00
|
|
|
return undefined;
|
2019-01-27 19:40:46 +01:00
|
|
|
}
|
2021-05-25 13:26:35 +02:00
|
|
|
const combinedTheme: Partial<ThemeVars> = {
|
2020-03-20 21:30:20 +01:00
|
|
|
...derivedStyles,
|
2020-08-03 02:07:12 +02:00
|
|
|
...theme,
|
2020-03-20 21:30:20 +01:00
|
|
|
};
|
|
|
|
const styles = {};
|
|
|
|
const keys = {};
|
2020-08-03 02:07:12 +02:00
|
|
|
for (const key of Object.keys(combinedTheme)) {
|
2020-03-20 21:30:20 +01:00
|
|
|
const prefixedKey = `--${key}`;
|
2020-09-15 00:08:16 +02:00
|
|
|
const value = String(combinedTheme[key]);
|
2020-03-20 21:30:20 +01:00
|
|
|
styles[prefixedKey] = value;
|
|
|
|
keys[prefixedKey] = "";
|
2018-05-10 03:33:31 +02:00
|
|
|
|
2020-08-03 02:07:12 +02:00
|
|
|
// Try to create a rgb value for this key if it is not a var
|
2020-09-15 00:08:16 +02:00
|
|
|
if (!value.startsWith("#")) {
|
2020-08-03 02:07:12 +02:00
|
|
|
// Can't convert non hex value
|
2020-03-20 21:30:20 +01:00
|
|
|
continue;
|
|
|
|
}
|
2020-08-03 02:07:12 +02:00
|
|
|
|
2020-03-20 21:30:20 +01:00
|
|
|
const rgbKey = `rgb-${key}`;
|
2020-08-03 02:07:12 +02:00
|
|
|
if (combinedTheme[rgbKey] !== undefined) {
|
2020-03-20 21:30:20 +01:00
|
|
|
// Theme has it's own rgb value
|
|
|
|
continue;
|
|
|
|
}
|
2020-08-03 02:07:12 +02:00
|
|
|
try {
|
|
|
|
const rgbValue = hex2rgb(value).join(",");
|
2020-03-20 21:30:20 +01:00
|
|
|
const prefixedRgbKey = `--${rgbKey}`;
|
|
|
|
styles[prefixedRgbKey] = rgbValue;
|
|
|
|
keys[prefixedRgbKey] = "";
|
2021-09-30 12:39:03 +02:00
|
|
|
} catch (err: any) {
|
2020-08-03 02:07:12 +02:00
|
|
|
continue;
|
2018-05-10 03:33:31 +02:00
|
|
|
}
|
|
|
|
}
|
2020-08-03 02:07:12 +02:00
|
|
|
PROCESSED_THEMES[cacheKey] = { styles, keys };
|
2020-03-20 21:30:20 +01:00
|
|
|
return { styles, keys };
|
|
|
|
};
|
|
|
|
|
|
|
|
export const invalidateThemeCache = () => {
|
|
|
|
PROCESSED_THEMES = {};
|
2019-10-21 19:38:06 +02:00
|
|
|
};
|