From ec3fdc0ea7e821ffd1f15515bb0e2ee8cae3e2e4 Mon Sep 17 00:00:00 2001 From: Wendelin <12148533+wendevlin@users.noreply.github.com> Date: Mon, 12 May 2025 16:01:56 +0200 Subject: [PATCH] Subscribe to frontend user data (#25445) --- src/data/frontend.ts | 30 +++++-------------- src/data/translation.ts | 11 ++++++- src/panels/profile/ha-advanced-mode-row.ts | 28 ++++++++++++----- src/panels/profile/ha-entity-id-picker-row.ts | 28 ++++++++++++----- .../profile/ha-profile-section-general.ts | 27 +++++++++-------- src/state/connection-mixin.ts | 12 ++++---- src/state/translations-mixin.ts | 10 +++++-- src/util/common-translation.ts | 17 +++++------ 8 files changed, 93 insertions(+), 70 deletions(-) diff --git a/src/data/frontend.ts b/src/data/frontend.ts index 555919c0ed..7e46217cca 100644 --- a/src/data/frontend.ts +++ b/src/data/frontend.ts @@ -1,5 +1,4 @@ import type { Connection } from "home-assistant-js-websocket"; -import { getOptimisticCollection } from "./collection"; export interface CoreFrontendUserData { showAdvanced?: boolean; @@ -42,30 +41,15 @@ export const saveFrontendUserData = async < value, }); -export const getOptimisticFrontendUserDataCollection = < - UserDataKey extends ValidUserDataKey, ->( - conn: Connection, - userDataKey: UserDataKey -) => - getOptimisticCollection( - (_conn, data) => - saveFrontendUserData( - conn, - userDataKey, - // @ts-ignore - data - ), - conn, - `_frontendUserData-${userDataKey}`, - () => fetchFrontendUserData(conn, userDataKey) - ); - export const subscribeFrontendUserData = ( conn: Connection, userDataKey: UserDataKey, - onChange: (state: FrontendUserData[UserDataKey] | null) => void + onChange: (data: { value: FrontendUserData[UserDataKey] | null }) => void ) => - getOptimisticFrontendUserDataCollection(conn, userDataKey).subscribe( - onChange + conn.subscribeMessage<{ value: FrontendUserData[UserDataKey] | null }>( + onChange, + { + type: "frontend/subscribe_user_data", + key: userDataKey, + } ); diff --git a/src/data/translation.ts b/src/data/translation.ts index c3b10f4286..260294e506 100644 --- a/src/data/translation.ts +++ b/src/data/translation.ts @@ -1,5 +1,9 @@ import type { HomeAssistant } from "../types"; -import { fetchFrontendUserData, saveFrontendUserData } from "./frontend"; +import { + fetchFrontendUserData, + saveFrontendUserData, + subscribeFrontendUserData, +} from "./frontend"; export enum NumberFormat { language = "language", @@ -77,6 +81,11 @@ export type TranslationCategory = export const fetchTranslationPreferences = (hass: HomeAssistant) => fetchFrontendUserData(hass.connection, "language"); +export const subscribeTranslationPreferences = ( + hass: HomeAssistant, + callback: (data: { value: FrontendLocaleData | null }) => void +) => subscribeFrontendUserData(hass.connection, "language", callback); + export const saveTranslationPreferences = ( hass: HomeAssistant, data: FrontendLocaleData diff --git a/src/panels/profile/ha-advanced-mode-row.ts b/src/panels/profile/ha-advanced-mode-row.ts index b67e98d26a..f3e28fa5d1 100644 --- a/src/panels/profile/ha-advanced-mode-row.ts +++ b/src/panels/profile/ha-advanced-mode-row.ts @@ -1,11 +1,12 @@ import type { TemplateResult } from "lit"; -import { css, html, LitElement } from "lit"; -import { customElement, property } from "lit/decorators"; +import { css, html, LitElement, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import "../../components/ha-alert"; import "../../components/ha-card"; import "../../components/ha-settings-row"; import "../../components/ha-switch"; import type { CoreFrontendUserData } from "../../data/frontend"; -import { getOptimisticFrontendUserDataCollection } from "../../data/frontend"; +import { saveFrontendUserData } from "../../data/frontend"; import type { HomeAssistant } from "../../types"; @customElement("ha-advanced-mode-row") @@ -16,8 +17,13 @@ class AdvancedModeRow extends LitElement { @property({ attribute: false }) public coreUserData?: CoreFrontendUserData; + @state() private _error?: string; + protected render(): TemplateResult { return html` + ${this._error + ? html`${this._error}` + : nothing} ${this.hass.localize("ui.panel.profile.advanced_mode.title")} @@ -41,16 +47,24 @@ class AdvancedModeRow extends LitElement { } private async _advancedToggled(ev) { - getOptimisticFrontendUserDataCollection(this.hass.connection, "core").save({ - ...this.coreUserData, - showAdvanced: ev.currentTarget.checked, - }); + try { + saveFrontendUserData(this.hass.connection, "core", { + ...this.coreUserData, + showAdvanced: ev.currentTarget.checked, + }); + } catch (err: any) { + this._error = err.message || err; + } } static styles = css` a { color: var(--primary-color); } + ha-alert { + margin: 0 16px; + display: block; + } `; } diff --git a/src/panels/profile/ha-entity-id-picker-row.ts b/src/panels/profile/ha-entity-id-picker-row.ts index a6b5567f18..6a651debbf 100644 --- a/src/panels/profile/ha-entity-id-picker-row.ts +++ b/src/panels/profile/ha-entity-id-picker-row.ts @@ -1,11 +1,12 @@ import type { TemplateResult } from "lit"; -import { css, html, LitElement } from "lit"; -import { customElement, property } from "lit/decorators"; +import { css, html, LitElement, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import "../../components/ha-alert"; import "../../components/ha-card"; import "../../components/ha-settings-row"; import "../../components/ha-switch"; import type { CoreFrontendUserData } from "../../data/frontend"; -import { getOptimisticFrontendUserDataCollection } from "../../data/frontend"; +import { saveFrontendUserData } from "../../data/frontend"; import type { HomeAssistant } from "../../types"; @customElement("ha-entity-id-picker-row") @@ -16,8 +17,13 @@ class EntityIdPickerRow extends LitElement { @property({ attribute: false }) public coreUserData?: CoreFrontendUserData; + @state() private _error?: string; + protected render(): TemplateResult { return html` + ${this._error + ? html`${this._error}` + : nothing} ${this.hass.localize("ui.panel.profile.entity_id_picker.title")}; private _getCoreData() { - this._unsubCoreData = getOptimisticFrontendUserDataCollection( + this._unsubCoreData = subscribeFrontendUserData( this.hass.connection, - "core" - ).subscribe((coreUserData) => { - this._coreUserData = coreUserData; - }); + "core", + ({ value }) => { + this._coreUserData = value; + } + ); } public connectedCallback() { @@ -70,7 +71,7 @@ class HaProfileSectionGeneral extends LitElement { public disconnectedCallback() { super.disconnectedCallback(); if (this._unsubCoreData) { - this._unsubCoreData(); + this._unsubCoreData.then((unsub) => unsub()); this._unsubCoreData = undefined; } } diff --git a/src/state/connection-mixin.ts b/src/state/connection-mixin.ts index 725201f6cd..82fc5f3caf 100644 --- a/src/state/connection-mixin.ts +++ b/src/state/connection-mixin.ts @@ -8,6 +8,7 @@ import { subscribeServices, } from "home-assistant-js-websocket"; import { fireEvent } from "../common/dom/fire_event"; +import { promiseTimeout } from "../common/util/promise-timeout"; import { subscribeAreaRegistry } from "../data/area_registry"; import { broadcastConnectionStatus } from "../data/connection-status"; import { subscribeDeviceRegistry } from "../data/device_registry"; @@ -22,6 +23,8 @@ import { TimeFormat, TimeZone, } from "../data/translation"; +import { subscribeEntityRegistryDisplay } from "../data/ws-entity_registry_display"; +import { subscribeFloorRegistry } from "../data/ws-floor_registry"; import { subscribePanels } from "../data/ws-panels"; import { translationMetadata } from "../resources/translations-metadata"; import type { Constructor, HomeAssistant, ServiceCallResponse } from "../types"; @@ -30,9 +33,6 @@ import { fetchWithAuth } from "../util/fetch-with-auth"; import { getState } from "../util/ha-pref-storage"; import hassCallApi, { hassCallApiRaw } from "../util/hass-call-api"; import type { HassBaseEl } from "./hass-base-mixin"; -import { promiseTimeout } from "../common/util/promise-timeout"; -import { subscribeFloorRegistry } from "../data/ws-floor_registry"; -import { subscribeEntityRegistryDisplay } from "../data/ws-entity_registry_display"; export const connectionMixin = >( superClass: T @@ -280,9 +280,9 @@ export const connectionMixin = >( subscribeConfig(conn, (config) => this._updateHass({ config })); subscribeServices(conn, (services) => this._updateHass({ services })); subscribePanels(conn, (panels) => this._updateHass({ panels })); - subscribeFrontendUserData(conn, "core", (userData) => - this._updateHass({ userData }) - ); + subscribeFrontendUserData(conn, "core", ({ value: userData }) => { + this._updateHass({ userData }); + }); clearInterval(this.__backendPingInterval); this.__backendPingInterval = setInterval(() => { diff --git a/src/state/translations-mixin.ts b/src/state/translations-mixin.ts index f7df357768..2b587acadb 100644 --- a/src/state/translations-mixin.ts +++ b/src/state/translations-mixin.ts @@ -8,17 +8,18 @@ import { } from "../common/util/compute_rtl"; import { debounce } from "../common/util/debounce"; import type { + DateFormat, FirstWeekday, NumberFormat, TimeFormat, - DateFormat, - TranslationCategory, TimeZone, + TranslationCategory, } from "../data/translation"; import { getHassTranslations, getHassTranslationsPre109, saveTranslationPreferences, + subscribeTranslationPreferences, } from "../data/translation"; import { translationMetadata } from "../resources/translations-metadata"; import type { Constructor, HomeAssistant } from "../types"; @@ -119,7 +120,10 @@ export default >(superClass: T) => protected hassConnected() { super.hassConnected(); - getUserLocale(this.hass!).then((locale) => { + + subscribeTranslationPreferences(this.hass!, async ({ value }) => { + const locale = await getUserLocale(value); + if (locale?.language && this.hass!.language !== locale.language) { // We just got language from backend, no need to save back this._selectLanguage(locale.language, false); diff --git a/src/util/common-translation.ts b/src/util/common-translation.ts index 0b79f2e747..b043653684 100644 --- a/src/util/common-translation.ts +++ b/src/util/common-translation.ts @@ -1,7 +1,5 @@ import type { FrontendLocaleData } from "../data/translation"; -import { fetchTranslationPreferences } from "../data/translation"; import { translationMetadata } from "../resources/translations-metadata"; -import type { HomeAssistant } from "../types"; const BASE_URL = `${__STATIC_PATH__}translations`; const STORAGE = window.localStorage || {}; @@ -68,15 +66,14 @@ export function findAvailableLanguage(language: string) { * Get user selected locale data from backend */ export async function getUserLocale( - hass: HomeAssistant + data: FrontendLocaleData | null ): Promise> { - const result = await fetchTranslationPreferences(hass); - const language = result?.language; - const number_format = result?.number_format; - const time_format = result?.time_format; - const date_format = result?.date_format; - const time_zone = result?.time_zone; - const first_weekday = result?.first_weekday; + const language = data?.language; + const number_format = data?.number_format; + const time_format = data?.time_format; + const date_format = data?.date_format; + const time_zone = data?.time_zone; + const first_weekday = data?.first_weekday; if (language) { const availableLanguage = findAvailableLanguage(language); if (availableLanguage) {