Add support for custom themes to use dark mode (#8347)

This commit is contained in:
Philip Allgaier 2021-05-25 13:26:35 +02:00 committed by GitHub
parent 8af05e2726
commit 7f75ca81f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 252 additions and 140 deletions

View File

@ -103,25 +103,27 @@ export class HassioMain extends SupervisorBaseElement {
private _applyTheme() {
let themeName: string;
let options: Partial<HomeAssistant["selectedTheme"]> | undefined;
let themeSettings:
| Partial<HomeAssistant["selectedThemeSettings"]>
| undefined;
if (atLeastVersion(this.hass.config.version, 0, 114)) {
themeName =
this.hass.selectedTheme?.theme ||
this.hass.selectedThemeSettings?.theme ||
(this.hass.themes.darkMode && this.hass.themes.default_dark_theme
? this.hass.themes.default_dark_theme!
: this.hass.themes.default_theme);
options = this.hass.selectedTheme;
if (themeName === "default" && options?.dark === undefined) {
options = {
...this.hass.selectedTheme,
themeSettings = this.hass.selectedThemeSettings;
if (themeSettings?.dark === undefined) {
themeSettings = {
...this.hass.selectedThemeSettings,
dark: this.hass.themes.darkMode,
};
}
} else {
themeName =
((this.hass.selectedTheme as unknown) as string) ||
((this.hass.selectedThemeSettings as unknown) as string) ||
this.hass.themes.default_theme;
}
@ -129,7 +131,7 @@ export class HassioMain extends SupervisorBaseElement {
this.parentElement,
this.hass.themes,
themeName,
options
themeSettings
);
}
}

View File

@ -1,4 +1,4 @@
import { Theme } from "../../data/ws-themes";
import { ThemeVars } from "../../data/ws-themes";
import { darkStyles, derivedStyles } from "../../resources/styles";
import type { HomeAssistant } from "../../types";
import {
@ -23,62 +23,90 @@ let PROCESSED_THEMES: Record<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
* selectedTheme: selected theme.
* themes: HASS theme information.
* selectedTheme: Selected theme.
* themeSettings: Settings such as selected dark mode and colors.
*/
export const applyThemesOnElement = (
element,
themes: HomeAssistant["themes"],
selectedTheme?: string,
themeOptions?: Partial<HomeAssistant["selectedTheme"]>
themeSettings?: Partial<HomeAssistant["selectedThemeSettings"]>
) => {
let cacheKey = selectedTheme;
let themeRules: Partial<Theme> = {};
let themeRules: Partial<ThemeVars> = {};
if (selectedTheme === "default" && themeOptions) {
if (themeOptions.dark) {
if (themeSettings) {
if (themeSettings.dark) {
cacheKey = `${cacheKey}__dark`;
themeRules = darkStyles;
if (themeOptions.primaryColor) {
}
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) {
themeRules["app-header-background-color"] = hexBlend(
themeOptions.primaryColor,
primaryColor,
"#121212",
8
);
}
}
if (themeOptions.primaryColor) {
cacheKey = `${cacheKey}__primary_${themeOptions.primaryColor}`;
const rgbPrimaryColor = hex2rgb(themeOptions.primaryColor);
const labPrimaryColor = rgb2lab(rgbPrimaryColor);
themeRules["primary-color"] = themeOptions.primaryColor;
const rgbLigthPrimaryColor = lab2rgb(labBrighten(labPrimaryColor));
themeRules["light-primary-color"] = rgb2hex(rgbLigthPrimaryColor);
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(rgbLigthPrimaryColor, [33, 33, 33]) < 6
? "#fff"
: "#212121";
themeRules["state-icon-color"] = themeRules["dark-primary-color"];
}
if (themeOptions.accentColor) {
cacheKey = `${cacheKey}__accent_${themeOptions.accentColor}`;
themeRules["accent-color"] = themeOptions.accentColor;
const rgbAccentColor = hex2rgb(themeOptions.accentColor);
themeRules["text-accent-color"] =
rgbContrast(rgbAccentColor, [33, 33, 33]) < 6 ? "#fff" : "#212121";
}
// Nothing was changed
if (element._themes?.cacheKey === cacheKey) {
return;
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;
}
}
}
if (selectedTheme && themes.themes[selectedTheme]) {
themeRules = themes.themes[selectedTheme];
// 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 };
}
}
}
if (!element._themes?.keys && !Object.keys(themeRules).length) {
@ -106,12 +134,12 @@ export const applyThemesOnElement = (
const processTheme = (
cacheKey: string,
theme: Partial<Theme>
theme: Partial<ThemeVars>
): ProcessedTheme | undefined => {
if (!theme || !Object.keys(theme).length) {
return undefined;
}
const combinedTheme: Partial<Theme> = {
const combinedTheme: Partial<ThemeVars> = {
...derivedStyles,
...theme,
};

View File

@ -108,7 +108,7 @@ class LocationEditor extends LitElement {
if (changedProps.has("hass")) {
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (!oldHass || oldHass.themes?.darkMode === this.hass.themes?.darkMode) {
if (!oldHass || oldHass.themes.darkMode === this.hass.themes.darkMode) {
return;
}
if (!this._leafletMap || !this._tileLayer) {
@ -118,7 +118,7 @@ class LocationEditor extends LitElement {
this.Leaflet,
this._leafletMap,
this._tileLayer,
this.hass.themes?.darkMode
this.hass.themes.darkMode
);
}
}
@ -130,7 +130,7 @@ class LocationEditor extends LitElement {
private async _initMap(): Promise<void> {
[this._leafletMap, this.Leaflet, this._tileLayer] = await setupLeafletMap(
this._mapEl,
this.darkMode ?? this.hass.themes?.darkMode,
this.darkMode ?? this.hass.themes.darkMode,
Boolean(this.radius)
);
this._leafletMap.addEventListener(

View File

@ -1,6 +1,6 @@
import { Connection, createCollection } from "home-assistant-js-websocket";
export interface Theme {
export interface ThemeVars {
// Incomplete
"primary-color": string;
"text-primary-color": string;
@ -8,10 +8,20 @@ export interface Theme {
[key: string]: string;
}
export type Theme = ThemeVars & {
modes?: {
light?: ThemeVars;
dark?: ThemeVars;
};
};
export interface Themes {
default_theme: string;
default_dark_theme: string | null;
themes: Record<string, Theme>;
// Currently effective dark mode. Will never be undefined. If user selected "auto"
// in theme picker, this property will still contain either true or false based on
// what has been determined via system preferences and support from the selected theme.
darkMode: boolean;
}

View File

@ -1,8 +1,12 @@
import { navigate } from "../common/navigate";
import {
DEFAULT_ACCENT_COLOR,
DEFAULT_PRIMARY_COLOR,
} from "../resources/ha-style";
import { HomeAssistant } from "../types";
export const defaultRadiusColor = "#FF9800";
export const homeRadiusColor = "#03a9f4";
export const defaultRadiusColor = DEFAULT_ACCENT_COLOR;
export const homeRadiusColor = DEFAULT_PRIMARY_COLOR;
export const passiveRadiusColor = "#9b9b9b";
export interface Zone {

View File

@ -277,7 +277,7 @@ export const provideHass = (
mockTheme(theme) {
invalidateThemeCache();
hass().updateHass({
selectedTheme: { theme: theme ? "mock" : "default" },
selectedThemeSettings: { theme: theme ? "mock" : "default" },
themes: {
...hass().themes,
themes: {
@ -285,11 +285,11 @@ export const provideHass = (
},
},
});
const { themes, selectedTheme } = hass();
const { themes, selectedThemeSettings } = hass();
applyThemesOnElement(
document.documentElement,
themes,
selectedTheme!.theme
selectedThemeSettings!.theme
);
},

View File

@ -81,25 +81,27 @@ class SupervisorErrorScreen extends LitElement {
private _applyTheme() {
let themeName: string;
let options: Partial<HomeAssistant["selectedTheme"]> | undefined;
let themeSettings:
| Partial<HomeAssistant["selectedThemeSettings"]>
| undefined;
if (atLeastVersion(this.hass.config.version, 0, 114)) {
themeName =
this.hass.selectedTheme?.theme ||
this.hass.selectedThemeSettings?.theme ||
(this.hass.themes.darkMode && this.hass.themes.default_dark_theme
? this.hass.themes.default_dark_theme!
: this.hass.themes.default_theme);
options = this.hass.selectedTheme;
if (themeName === "default" && options?.dark === undefined) {
options = {
...this.hass.selectedTheme,
themeSettings = this.hass.selectedThemeSettings;
if (themeName === "default" && themeSettings?.dark === undefined) {
themeSettings = {
...this.hass.selectedThemeSettings,
dark: this.hass.themes.darkMode,
};
}
} else {
themeName =
((this.hass.selectedTheme as unknown) as string) ||
((this.hass.selectedThemeSettings as unknown) as string) ||
this.hass.themes.default_theme;
}
@ -107,7 +109,7 @@ class SupervisorErrorScreen extends LitElement {
this.parentElement,
this.hass.themes,
themeName,
options
themeSettings
);
}

View File

@ -152,7 +152,7 @@ export class HUIView extends ReactiveElement {
changedProperties.has("hass") &&
(!oldHass ||
this.hass.themes !== oldHass.themes ||
this.hass.selectedTheme !== oldHass.selectedTheme)
this.hass.selectedThemeSettings !== oldHass.selectedThemeSettings)
) {
applyThemesOnElement(this, this.hass.themes, this._viewConfigTheme);
}

View File

@ -17,6 +17,11 @@ import "../../components/ha-paper-dropdown-menu";
import "../../components/ha-radio";
import type { HaRadio } from "../../components/ha-radio";
import "../../components/ha-settings-row";
import { Theme } from "../../data/ws-themes";
import {
DEFAULT_PRIMARY_COLOR,
DEFAULT_ACCENT_COLOR,
} from "../../resources/ha-style";
import { HomeAssistant } from "../../types";
import { documentationUrl } from "../../util/documentation-url";
@ -26,15 +31,20 @@ export class HaPickThemeRow extends LitElement {
@property({ type: Boolean }) public narrow!: boolean;
@state() _themes: string[] = [];
@state() _themeNames: string[] = [];
@state() _selectedTheme = 0;
@state() _selectedThemeIndex = 0;
@state() _selectedTheme?: Theme;
protected render(): TemplateResult {
const hasThemes =
this.hass.themes?.themes && Object.keys(this.hass.themes.themes).length;
this.hass.themes.themes && Object.keys(this.hass.themes.themes).length;
const curTheme =
this.hass!.selectedTheme?.theme || this.hass!.themes.default_theme;
this.hass.selectedThemeSettings?.theme || this.hass.themes.default_theme;
const themeSettings = this.hass.selectedThemeSettings;
return html`
<ha-settings-row .narrow=${this.narrow}>
<span slot="heading"
@ -46,7 +56,7 @@ export class HaPickThemeRow extends LitElement {
: ""}
<a
href="${documentationUrl(
this.hass!,
this.hass,
"/integrations/frontend/#defining-themes"
)}"
target="_blank"
@ -62,19 +72,20 @@ export class HaPickThemeRow extends LitElement {
>
<paper-listbox
slot="dropdown-content"
.selected=${this._selectedTheme}
.selected=${this._selectedThemeIndex}
@iron-select=${this._handleThemeSelection}
>
${this._themes.map(
${this._themeNames.map(
(theme) => html`<paper-item .theme=${theme}>${theme}</paper-item>`
)}
</paper-listbox>
</ha-paper-dropdown-menu>
</ha-settings-row>
${curTheme === "default"
${curTheme === "default" ||
(this._selectedTheme && this._supportsModeSelection(this._selectedTheme))
? html` <div class="inputs">
<ha-formfield
.label=${this.hass!.localize(
.label=${this.hass.localize(
"ui.panel.profile.themes.dark_mode.auto"
)}
>
@ -82,11 +93,11 @@ export class HaPickThemeRow extends LitElement {
@change=${this._handleDarkMode}
name="dark_mode"
value="auto"
?checked=${this.hass.selectedTheme?.dark === undefined}
?checked=${themeSettings?.dark === undefined}
></ha-radio>
</ha-formfield>
<ha-formfield
.label=${this.hass!.localize(
.label=${this.hass.localize(
"ui.panel.profile.themes.dark_mode.light"
)}
>
@ -94,12 +105,12 @@ export class HaPickThemeRow extends LitElement {
@change=${this._handleDarkMode}
name="dark_mode"
value="light"
?checked=${this.hass.selectedTheme?.dark === false}
?checked=${themeSettings?.dark === false}
>
</ha-radio>
</ha-formfield>
<ha-formfield
.label=${this.hass!.localize(
.label=${this.hass.localize(
"ui.panel.profile.themes.dark_mode.dark"
)}
>
@ -107,36 +118,38 @@ export class HaPickThemeRow extends LitElement {
@change=${this._handleDarkMode}
name="dark_mode"
value="dark"
?checked=${this.hass.selectedTheme?.dark === true}
?checked=${themeSettings?.dark === true}
>
</ha-radio>
</ha-formfield>
<div class="color-pickers">
<paper-input
.value=${this.hass!.selectedTheme?.primaryColor || "#03a9f4"}
type="color"
.label=${this.hass!.localize(
"ui.panel.profile.themes.primary_color"
)}
.name=${"primaryColor"}
@change=${this._handleColorChange}
></paper-input>
<paper-input
.value=${this.hass!.selectedTheme?.accentColor || "#ff9800"}
type="color"
.label=${this.hass!.localize(
"ui.panel.profile.themes.accent_color"
)}
.name=${"accentColor"}
@change=${this._handleColorChange}
></paper-input>
${this.hass!.selectedTheme?.primaryColor ||
this.hass!.selectedTheme?.accentColor
? html` <mwc-button @click=${this._resetColors}>
${this.hass!.localize("ui.panel.profile.themes.reset")}
</mwc-button>`
: ""}
</div>
${curTheme === "default"
? html` <div class="color-pickers">
<paper-input
.value=${themeSettings?.primaryColor ||
DEFAULT_PRIMARY_COLOR}
type="color"
.label=${this.hass.localize(
"ui.panel.profile.themes.primary_color"
)}
.name=${"primaryColor"}
@change=${this._handleColorChange}
></paper-input>
<paper-input
.value=${themeSettings?.accentColor || DEFAULT_ACCENT_COLOR}
type="color"
.label=${this.hass.localize(
"ui.panel.profile.themes.accent_color"
)}
.name=${"accentColor"}
@change=${this._handleColorChange}
></paper-input>
${themeSettings?.primaryColor || themeSettings?.accentColor
? html` <mwc-button @click=${this._resetColors}>
${this.hass.localize("ui.panel.profile.themes.reset")}
</mwc-button>`
: ""}
</div>`
: ""}
</div>`
: ""}
`;
@ -146,27 +159,31 @@ export class HaPickThemeRow extends LitElement {
const oldHass = changedProperties.get("hass") as undefined | HomeAssistant;
const themesChanged =
changedProperties.has("hass") &&
(!oldHass || oldHass.themes?.themes !== this.hass.themes?.themes);
(!oldHass || oldHass.themes.themes !== this.hass.themes.themes);
const selectedThemeChanged =
changedProperties.has("hass") &&
(!oldHass || oldHass.selectedTheme !== this.hass.selectedTheme);
(!oldHass ||
oldHass.selectedThemeSettings !== this.hass.selectedThemeSettings);
if (themesChanged) {
this._themes = ["Backend-selected", "default"].concat(
this._themeNames = ["Backend-selected", "default"].concat(
Object.keys(this.hass.themes.themes).sort()
);
}
if (selectedThemeChanged) {
if (
this.hass.selectedTheme &&
this._themes.indexOf(this.hass.selectedTheme.theme) > 0
this.hass.selectedThemeSettings &&
this._themeNames.indexOf(this.hass.selectedThemeSettings.theme) > 0
) {
this._selectedTheme = this._themes.indexOf(
this.hass.selectedTheme.theme
this._selectedThemeIndex = this._themeNames.indexOf(
this.hass.selectedThemeSettings.theme
);
} else if (!this.hass.selectedTheme) {
this._selectedTheme = 0;
this._selectedTheme = this.hass.themes.themes[
this.hass.selectedThemeSettings.theme
];
} else if (!this.hass.selectedThemeSettings) {
this._selectedThemeIndex = 0;
}
}
}
@ -183,6 +200,10 @@ export class HaPickThemeRow extends LitElement {
});
}
private _supportsModeSelection(theme: Theme): boolean {
return theme.modes?.light !== undefined && theme.modes?.dark !== undefined;
}
private _handleDarkMode(ev: CustomEvent) {
let dark: boolean | undefined;
switch ((ev.target as HaRadio).value) {
@ -199,12 +220,20 @@ export class HaPickThemeRow extends LitElement {
private _handleThemeSelection(ev: CustomEvent) {
const theme = ev.detail.item.theme;
if (theme === "Backend-selected") {
if (this.hass.selectedTheme?.theme) {
fireEvent(this, "settheme", { theme: "" });
if (this.hass.selectedThemeSettings?.theme) {
fireEvent(this, "settheme", {
theme: "",
primaryColor: undefined,
accentColor: undefined,
});
}
return;
}
fireEvent(this, "settheme", { theme });
fireEvent(this, "settheme", {
theme,
primaryColor: undefined,
accentColor: undefined,
});
}
static get styles(): CSSResultGroup {

View File

@ -2,6 +2,9 @@ import "@polymer/paper-styles/paper-styles";
import "@polymer/polymer/lib/elements/custom-style";
import { derivedStyles } from "./styles";
export const DEFAULT_PRIMARY_COLOR = "#03a9f4";
export const DEFAULT_ACCENT_COLOR = "#ff9800";
const documentContainer = document.createElement("template");
documentContainer.setAttribute("style", "display: none;");

View File

@ -39,6 +39,7 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
states: null as any,
config: null as any,
themes: null as any,
selectedThemeSettings: null,
panels: null as any,
services: null as any,
user: null as any,

View File

@ -11,10 +11,10 @@ import { HassBaseEl } from "./hass-base-mixin";
declare global {
// for add event listener
interface HTMLElementEventMap {
settheme: HASSDomEvent<Partial<HomeAssistant["selectedTheme"]>>;
settheme: HASSDomEvent<Partial<HomeAssistant["selectedThemeSettings"]>>;
}
interface HASSDomEvents {
settheme: Partial<HomeAssistant["selectedTheme"]>;
settheme: Partial<HomeAssistant["selectedThemeSettings"]>;
}
}
@ -28,7 +28,10 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
super.firstUpdated(changedProps);
this.addEventListener("settheme", (ev) => {
this._updateHass({
selectedTheme: { ...this.hass!.selectedTheme!, ...ev.detail },
selectedThemeSettings: {
...this.hass!.selectedThemeSettings!,
...ev.detail,
},
});
this._applyTheme(mql.matches);
storeState(this.hass!);
@ -60,41 +63,57 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
});
}
private _applyTheme(dark: boolean) {
private _applyTheme(darkPreferred: boolean) {
if (!this.hass) {
return;
}
const themeName =
this.hass.selectedTheme?.theme ||
(dark && this.hass.themes.default_dark_theme
this.hass.selectedThemeSettings?.theme ||
(darkPreferred && this.hass.themes.default_dark_theme
? this.hass.themes.default_dark_theme!
: this.hass.themes.default_theme);
let options: Partial<HomeAssistant["selectedTheme"]> = this.hass!
.selectedTheme;
let themeSettings: Partial<HomeAssistant["selectedThemeSettings"]> = this
.hass!.selectedThemeSettings;
if (themeName === "default" && options?.dark === undefined) {
options = {
...this.hass.selectedTheme!,
dark,
};
let darkMode =
themeSettings?.dark === undefined ? darkPreferred : themeSettings?.dark;
const selectedTheme =
themeSettings?.theme !== undefined
? this.hass.themes.themes[themeSettings.theme]
: undefined;
if (selectedTheme) {
// Override dark mode selection depending on what the theme actually provides.
// Leave the selection as-is if the theme supports the requested mode.
if (darkMode && !selectedTheme.modes?.dark) {
darkMode = false;
} else if (
!darkMode &&
!selectedTheme.modes?.light &&
selectedTheme.modes?.dark
) {
darkMode = true;
}
}
themeSettings = { ...this.hass.selectedThemeSettings, dark: darkMode };
applyThemesOnElement(
document.documentElement,
this.hass.themes,
themeName,
options
themeSettings
);
const darkMode =
themeName === "default"
? !!options?.dark
: !!(dark && this.hass.themes.default_dark_theme);
// Now determine value that should be stored in the local storage settings
darkMode =
darkMode || !!(darkPreferred && this.hass.themes.default_dark_theme);
if (darkMode !== this.hass.themes.darkMode) {
this._updateHass({
themes: { ...this.hass.themes, darkMode },
themes: { ...this.hass.themes!, darkMode },
});
const schemeMeta = document.querySelector("meta[name=color-scheme]");

View File

@ -82,8 +82,12 @@ export interface CurrentUser {
mfa_modules: MFAModule[];
}
// Currently selected theme and its settings. These are the values stored in local storage.
export interface ThemeSettings {
theme: string;
// Radio box selection for theme picker. Do not use in cards as
// it can be undefined == auto.
// Property hass.themes.darkMode carries effective current mode.
dark?: boolean;
primaryColor?: string;
accentColor?: string;
@ -190,7 +194,7 @@ export interface HomeAssistant {
services: HassServices;
config: HassConfig;
themes: Themes;
selectedTheme?: ThemeSettings | null;
selectedThemeSettings: ThemeSettings | null;
panels: Panels;
panelUrl: string;
// i18n

View File

@ -2,13 +2,16 @@ import { HomeAssistant } from "../types";
const STORED_STATE = [
"dockedSidebar",
"selectedTheme",
"selectedThemeSettings",
"selectedLanguage",
"vibrate",
"suspendWhenHidden",
"enableShortcuts",
"defaultPanel",
];
// Deprecated states will be loaded once so that the values can be migrated to other states if required,
// but during the next state storing, the deprecated keys will be removed.
const STORED_STATE_DEPRECATED = ["selectedTheme"];
const STORAGE = window.localStorage || {};
export function storeState(hass: HomeAssistant) {
@ -17,6 +20,9 @@ export function storeState(hass: HomeAssistant) {
const value = hass[key];
STORAGE[key] = JSON.stringify(value === undefined ? null : value);
});
STORED_STATE_DEPRECATED.forEach((key) => {
if (key in STORAGE) delete STORAGE[key];
});
} catch (err) {
// Safari throws exception in private mode
}
@ -25,13 +31,17 @@ export function storeState(hass: HomeAssistant) {
export function getState() {
const state = {};
STORED_STATE.forEach((key) => {
STORED_STATE.concat(STORED_STATE_DEPRECATED).forEach((key) => {
if (key in STORAGE) {
let value = JSON.parse(STORAGE[key]);
// selectedTheme went from string to object on 20200718
if (key === "selectedTheme" && typeof value === "string") {
value = { theme: value };
}
// selectedTheme was renamed to selectedThemeSettings on 20210207
if (key === "selectedTheme") {
key = "selectedThemeSettings";
}
// dockedSidebar went from boolean to enum on 20190720
if (key === "dockedSidebar" && typeof value === "boolean") {
value = value ? "docked" : "auto";