From 7f75ca81f15f7bdafdd288a69fa9c5deeed6f1ad Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Tue, 25 May 2021 13:26:35 +0200 Subject: [PATCH] Add support for custom themes to use dark mode (#8347) --- hassio/src/hassio-main.ts | 18 +-- src/common/dom/apply_themes_on_element.ts | 108 +++++++++++------- src/components/map/ha-location-editor.ts | 6 +- src/data/ws-themes.ts | 12 +- src/data/zone.ts | 8 +- src/fake_data/provide_hass.ts | 6 +- src/layouts/supervisor-error-screen.ts | 18 +-- src/panels/lovelace/views/hui-view.ts | 2 +- src/panels/profile/ha-pick-theme-row.ts | 133 +++++++++++++--------- src/resources/ha-style.ts | 3 + src/state/connection-mixin.ts | 1 + src/state/themes-mixin.ts | 57 ++++++---- src/types.ts | 6 +- src/util/ha-pref-storage.ts | 14 ++- 14 files changed, 252 insertions(+), 140 deletions(-) diff --git a/hassio/src/hassio-main.ts b/hassio/src/hassio-main.ts index 6ee732b846..1006932d0b 100644 --- a/hassio/src/hassio-main.ts +++ b/hassio/src/hassio-main.ts @@ -103,25 +103,27 @@ export class HassioMain extends SupervisorBaseElement { private _applyTheme() { let themeName: string; - let options: Partial | undefined; + let themeSettings: + | Partial + | 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 ); } } diff --git a/src/common/dom/apply_themes_on_element.ts b/src/common/dom/apply_themes_on_element.ts index 176068c92b..c8f1365102 100644 --- a/src/common/dom/apply_themes_on_element.ts +++ b/src/common/dom/apply_themes_on_element.ts @@ -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 = {}; * 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 + themeSettings?: Partial ) => { let cacheKey = selectedTheme; - let themeRules: Partial = {}; + let themeRules: Partial = {}; - 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: Partial ): ProcessedTheme | undefined => { if (!theme || !Object.keys(theme).length) { return undefined; } - const combinedTheme: Partial = { + const combinedTheme: Partial = { ...derivedStyles, ...theme, }; diff --git a/src/components/map/ha-location-editor.ts b/src/components/map/ha-location-editor.ts index b731aaead2..4ee130d52d 100644 --- a/src/components/map/ha-location-editor.ts +++ b/src/components/map/ha-location-editor.ts @@ -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 { [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( diff --git a/src/data/ws-themes.ts b/src/data/ws-themes.ts index d758c27b1a..99a44064f9 100644 --- a/src/data/ws-themes.ts +++ b/src/data/ws-themes.ts @@ -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; + // 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; } diff --git a/src/data/zone.ts b/src/data/zone.ts index 92151c4a58..568e9ac465 100644 --- a/src/data/zone.ts +++ b/src/data/zone.ts @@ -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 { diff --git a/src/fake_data/provide_hass.ts b/src/fake_data/provide_hass.ts index d537ffa80e..d074f4c9ab 100644 --- a/src/fake_data/provide_hass.ts +++ b/src/fake_data/provide_hass.ts @@ -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 ); }, diff --git a/src/layouts/supervisor-error-screen.ts b/src/layouts/supervisor-error-screen.ts index 34ab82fdf9..29bafefbc8 100644 --- a/src/layouts/supervisor-error-screen.ts +++ b/src/layouts/supervisor-error-screen.ts @@ -81,25 +81,27 @@ class SupervisorErrorScreen extends LitElement { private _applyTheme() { let themeName: string; - let options: Partial | undefined; + let themeSettings: + | Partial + | 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 ); } diff --git a/src/panels/lovelace/views/hui-view.ts b/src/panels/lovelace/views/hui-view.ts index 53b3394483..e7f0943f3a 100644 --- a/src/panels/lovelace/views/hui-view.ts +++ b/src/panels/lovelace/views/hui-view.ts @@ -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); } diff --git a/src/panels/profile/ha-pick-theme-row.ts b/src/panels/profile/ha-pick-theme-row.ts index 57dde1cf9f..32c89b373d 100644 --- a/src/panels/profile/ha-pick-theme-row.ts +++ b/src/panels/profile/ha-pick-theme-row.ts @@ -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` - ${this._themes.map( + ${this._themeNames.map( (theme) => html`${theme}` )} - ${curTheme === "default" + ${curTheme === "default" || + (this._selectedTheme && this._supportsModeSelection(this._selectedTheme)) ? html`
@@ -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} > @@ -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} > @@ -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} > -
- - - ${this.hass!.selectedTheme?.primaryColor || - this.hass!.selectedTheme?.accentColor - ? html` - ${this.hass!.localize("ui.panel.profile.themes.reset")} - ` - : ""} -
+ ${curTheme === "default" + ? html`
+ + + ${themeSettings?.primaryColor || themeSettings?.accentColor + ? html` + ${this.hass.localize("ui.panel.profile.themes.reset")} + ` + : ""} +
` + : ""}
` : ""} `; @@ -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 { diff --git a/src/resources/ha-style.ts b/src/resources/ha-style.ts index 1e308b9aad..f3e9dcb757 100644 --- a/src/resources/ha-style.ts +++ b/src/resources/ha-style.ts @@ -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;"); diff --git a/src/state/connection-mixin.ts b/src/state/connection-mixin.ts index 7b54f8ffbb..bfe4617020 100644 --- a/src/state/connection-mixin.ts +++ b/src/state/connection-mixin.ts @@ -39,6 +39,7 @@ export const connectionMixin = >( 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, diff --git a/src/state/themes-mixin.ts b/src/state/themes-mixin.ts index 8555391b88..b2aae4e43b 100644 --- a/src/state/themes-mixin.ts +++ b/src/state/themes-mixin.ts @@ -11,10 +11,10 @@ import { HassBaseEl } from "./hass-base-mixin"; declare global { // for add event listener interface HTMLElementEventMap { - settheme: HASSDomEvent>; + settheme: HASSDomEvent>; } interface HASSDomEvents { - settheme: Partial; + settheme: Partial; } } @@ -28,7 +28,10 @@ export default >(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 >(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 = this.hass! - .selectedTheme; + let themeSettings: Partial = 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]"); diff --git a/src/types.ts b/src/types.ts index 7d9f88bf6c..2cb1af3337 100644 --- a/src/types.ts +++ b/src/types.ts @@ -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 diff --git a/src/util/ha-pref-storage.ts b/src/util/ha-pref-storage.ts index 8c21de00b7..086e8f7c46 100644 --- a/src/util/ha-pref-storage.ts +++ b/src/util/ha-pref-storage.ts @@ -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";