diff --git a/hassio/src/components/hassio-upload-backup.ts b/hassio/src/components/hassio-upload-backup.ts index 57046f2818..3cc379438e 100644 --- a/hassio/src/components/hassio-upload-backup.ts +++ b/hassio/src/components/hassio-upload-backup.ts @@ -33,7 +33,6 @@ export class HassioUploadBackup extends LitElement { label="Upload backup" supports="Supports .TAR files" @file-picked=${this._uploadFile} - auto-open-file-dialog > `; } diff --git a/pyproject.toml b/pyproject.toml index a98ecab272..88a62aa207 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20230904.0" +version = "20230905.0" license = {text = "Apache-2.0"} description = "The Home Assistant frontend" readme = "README.md" diff --git a/src/common/translations/localize.ts b/src/common/translations/localize.ts index c12f468423..c53d2862c8 100644 --- a/src/common/translations/localize.ts +++ b/src/common/translations/localize.ts @@ -22,14 +22,7 @@ export type LocalizeKeys = | `ui.dialogs.unhealthy.reason.${string}` | `ui.dialogs.unsupported.reason.${string}` | `ui.panel.config.${string}.${"caption" | "description"}` - | `ui.panel.config.automation.${string}` | `ui.panel.config.dashboard.${string}` - | `ui.panel.config.devices.${string}` - | `ui.panel.config.energy.${string}` - | `ui.panel.config.info.${string}` - | `ui.panel.config.lovelace.${string}` - | `ui.panel.config.network.${string}` - | `ui.panel.config.scene.${string}` | `ui.panel.config.zha.${string}` | `ui.panel.config.zwave_js.${string}` | `ui.panel.lovelace.card.${string}` diff --git a/src/components/ha-form/compute-initial-ha-form-data.ts b/src/components/ha-form/compute-initial-ha-form-data.ts index 8844e2d9e0..d3c7e2b847 100644 --- a/src/components/ha-form/compute-initial-ha-form-data.ts +++ b/src/components/ha-form/compute-initial-ha-form-data.ts @@ -27,7 +27,8 @@ export const computeInitialHaFormData = ( data[field.name] = 0.0; } else if (field.type === "select") { if (field.options.length) { - data[field.name] = field.options[0][0]; + const val = field.options[0]; + data[field.name] = Array.isArray(val) ? val[0] : val; } } else if (field.type === "positive_time_period_dict") { data[field.name] = { @@ -61,7 +62,7 @@ export const computeInitialHaFormData = ( } else if ("select" in selector) { if (selector.select?.options.length) { const val = selector.select.options[0]; - data[field.name] = Array.isArray(val) ? val[0] : val; + data[field.name] = typeof val === "string" ? val : val.value; } } else if ("duration" in selector) { data[field.name] = { diff --git a/src/data/automation.ts b/src/data/automation.ts index eff766e10d..b207f7095b 100644 --- a/src/data/automation.ts +++ b/src/data/automation.ts @@ -238,11 +238,13 @@ export interface ZoneCondition extends BaseCondition { zone: string; } +type Weekday = "sun" | "mon" | "tue" | "wed" | "thu" | "fri" | "sat"; + export interface TimeCondition extends BaseCondition { condition: "time"; after?: string; before?: string; - weekday?: string | string[]; + weekday?: Weekday | Weekday[]; } export interface TemplateCondition extends BaseCondition { diff --git a/src/data/cloud.ts b/src/data/cloud.ts index 694d468790..897364e4cc 100644 --- a/src/data/cloud.ts +++ b/src/data/cloud.ts @@ -1,7 +1,5 @@ import { EntityFilter } from "../common/entity/entity_filter"; -import { PlaceholderContainer } from "../panels/config/automation/thingtalk/dialog-thingtalk"; import { HomeAssistant } from "../types"; -import { AutomationConfig } from "./automation"; interface CloudStatusNotLoggedIn { logged_in: false; @@ -66,11 +64,6 @@ export interface CloudWebhook { managed?: boolean; } -export interface ThingTalkConversion { - config: Partial; - placeholders: PlaceholderContainer; -} - export const cloudLogin = ( hass: HomeAssistant, email: string, @@ -136,9 +129,6 @@ export const disconnectCloudRemote = (hass: HomeAssistant) => export const fetchCloudSubscriptionInfo = (hass: HomeAssistant) => hass.callWS({ type: "cloud/subscription" }); -export const convertThingTalk = (hass: HomeAssistant, query: string) => - hass.callWS({ type: "cloud/thingtalk/convert", query }); - export const updateCloudPref = ( hass: HomeAssistant, prefs: { diff --git a/src/data/entity_sources.ts b/src/data/entity_sources.ts index 67455c1d22..851a70bfa6 100644 --- a/src/data/entity_sources.ts +++ b/src/data/entity_sources.ts @@ -1,46 +1,25 @@ import { timeCachePromiseFunc } from "../common/util/time-cache-function-promise"; import { HomeAssistant } from "../types"; -interface EntitySourceConfigEntry { - source: "config_entry"; +interface EntitySource { domain: string; - custom_component: boolean; - config_entry: string; } -interface EntitySourcePlatformConfig { - source: "platform_config"; - domain: string; - custom_component: boolean; -} +export type EntitySources = Record; -export type EntitySources = Record< - string, - EntitySourceConfigEntry | EntitySourcePlatformConfig ->; - -const fetchEntitySources = ( - hass: HomeAssistant, - entity_id?: string -): Promise => - hass.callWS({ - type: "entity/source", - entity_id, - }); +const fetchEntitySources = (hass: HomeAssistant): Promise => + hass.callWS({ type: "entity/source" }); export const fetchEntitySourcesWithCache = ( - hass: HomeAssistant, - entity_id?: string + hass: HomeAssistant ): Promise => - entity_id - ? fetchEntitySources(hass, entity_id) - : timeCachePromiseFunc( - "_entitySources", - // cache for 30 seconds - 30000, - fetchEntitySources, - // We base the cache on number of states. If number of states - // changes we force a refresh - (hass2) => Object.keys(hass2.states).length, - hass - ); + timeCachePromiseFunc( + "_entitySources", + // cache for 30 seconds + 30000, + fetchEntitySources, + // We base the cache on number of states. If number of states + // changes we force a refresh + (hass2) => Object.keys(hass2.states).length, + hass + ); diff --git a/src/data/weather.ts b/src/data/weather.ts index 9e66ada7f3..b23bfdd85d 100644 --- a/src/data/weather.ts +++ b/src/data/weather.ts @@ -36,7 +36,9 @@ export const enum WeatherEntityFeature { FORECAST_TWICE_DAILY = 4, } -export type ForecastType = "legacy" | "hourly" | "daily" | "twice_daily"; +export type ModernForecastType = "hourly" | "daily" | "twice_daily"; + +export type ForecastType = ModernForecastType | "legacy"; interface ForecastAttribute { temperature: number; @@ -636,7 +638,7 @@ export const getForecast = ( export const subscribeForecast = ( hass: HomeAssistant, entity_id: string, - forecast_type: "daily" | "hourly" | "twice_daily", + forecast_type: ModernForecastType, callback: (forecastevent: ForecastEvent) => void ) => hass.connection.subscribeMessage(callback, { @@ -645,6 +647,22 @@ export const subscribeForecast = ( entity_id, }); +export const getSupportedForecastTypes = ( + stateObj: HassEntityBase +): ModernForecastType[] => { + const supported: ModernForecastType[] = []; + if (supportsFeature(stateObj, WeatherEntityFeature.FORECAST_DAILY)) { + supported.push("daily"); + } + if (supportsFeature(stateObj, WeatherEntityFeature.FORECAST_TWICE_DAILY)) { + supported.push("twice_daily"); + } + if (supportsFeature(stateObj, WeatherEntityFeature.FORECAST_HOURLY)) { + supported.push("hourly"); + } + return supported; +}; + export const getDefaultForecastType = (stateObj: HassEntityBase) => { if (supportsFeature(stateObj, WeatherEntityFeature.FORECAST_DAILY)) { return "daily"; diff --git a/src/dialogs/more-info/components/lights/light-color-rgb-picker.ts b/src/dialogs/more-info/components/lights/light-color-rgb-picker.ts index 0a66aabcf6..2bf8a5b44b 100644 --- a/src/dialogs/more-info/components/lights/light-color-rgb-picker.ts +++ b/src/dialogs/more-info/components/lights/light-color-rgb-picker.ts @@ -1,6 +1,4 @@ import "@material/mwc-button"; -import "@material/mwc-tab-bar/mwc-tab-bar"; -import "@material/mwc-tab/mwc-tab"; import { mdiEyedropper } from "@mdi/js"; import { css, diff --git a/src/dialogs/more-info/controls/more-info-weather.ts b/src/dialogs/more-info/controls/more-info-weather.ts index 123d3ca4de..5da649ae4f 100644 --- a/src/dialogs/more-info/controls/more-info-weather.ts +++ b/src/dialogs/more-info/controls/more-info-weather.ts @@ -1,3 +1,5 @@ +import "@material/mwc-tab"; +import "@material/mwc-tab-bar"; import { mdiEye, mdiGauge, @@ -14,14 +16,17 @@ import { nothing, } from "lit"; import { customElement, property, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; import { formatDateWeekdayDay } from "../../../common/datetime/format_date"; import { formatTimeWeekday } from "../../../common/datetime/format_time"; import "../../../components/ha-svg-icon"; import { ForecastEvent, + ModernForecastType, WeatherEntity, getDefaultForecastType, getForecast, + getSupportedForecastTypes, getWind, subscribeForecast, weatherIcons, @@ -36,6 +41,8 @@ class MoreInfoWeather extends LitElement { @state() private _forecastEvent?: ForecastEvent; + @state() private _forecastType?: ModernForecastType; + @state() private _subscribed?: Promise<() => void>; private _unsubscribeForecastEvents() { @@ -43,25 +50,28 @@ class MoreInfoWeather extends LitElement { this._subscribed.then((unsub) => unsub()); this._subscribed = undefined; } + this._forecastEvent = undefined; } private async _subscribeForecastEvents() { this._unsubscribeForecastEvents(); - if (!this.isConnected || !this.hass || !this.stateObj) { + if ( + !this.isConnected || + !this.hass || + !this.stateObj || + !this._forecastType + ) { return; } - const forecastType = getDefaultForecastType(this.stateObj); - if (forecastType) { - this._subscribed = subscribeForecast( - this.hass!, - this.stateObj!.entity_id, - forecastType, - (event) => { - this._forecastEvent = event; - } - ); - } + this._subscribed = subscribeForecast( + this.hass!, + this.stateObj!.entity_id, + this._forecastType, + (event) => { + this._forecastEvent = event; + } + ); } public connectedCallback() { @@ -93,10 +103,10 @@ class MoreInfoWeather extends LitElement { return false; } - protected updated(changedProps: PropertyValues): void { - super.updated(changedProps); + protected willUpdate(changedProps: PropertyValues): void { + super.willUpdate(changedProps); - if (changedProps.has("stateObj") || !this._subscribed) { + if ((changedProps.has("stateObj") || !this._subscribed) && this.stateObj) { const oldState = changedProps.get("stateObj") as | WeatherEntity | undefined; @@ -104,16 +114,25 @@ class MoreInfoWeather extends LitElement { oldState?.entity_id !== this.stateObj?.entity_id || !this._subscribed ) { + this._forecastType = getDefaultForecastType(this.stateObj); this._subscribeForecastEvents(); } + } else if (changedProps.has("_forecastType")) { + this._subscribeForecastEvents(); } } + private _supportedForecasts = memoizeOne((stateObj: WeatherEntity) => + getSupportedForecastTypes(stateObj) + ); + protected render() { if (!this.hass || !this.stateObj) { return nothing; } + const supportedForecasts = this._supportedForecasts(this.stateObj); + const forecastData = getForecast( this.stateObj.attributes, this._forecastEvent @@ -210,6 +229,23 @@ class MoreInfoWeather extends LitElement {
${this.hass.localize("ui.card.weather.forecast")}:
+ ${supportedForecasts.length > 1 + ? html` item === this._forecastType + )} + @MDCTabBar:activated=${this._handleForecastTypeChanged} + > + ${supportedForecasts.map( + (forecastType) => + html`` + )} + ` + : nothing} ${forecast.map((item) => this._showValue(item.templow) || this._showValue(item.temperature) ? html`
@@ -283,12 +319,23 @@ class MoreInfoWeather extends LitElement { `; } + private _handleForecastTypeChanged(ev: CustomEvent): void { + this._forecastType = this._supportedForecasts(this.stateObj!)[ + ev.detail.index + ]; + } + static get styles(): CSSResultGroup { return css` ha-svg-icon { color: var(--paper-item-icon-color); margin-left: 8px; } + + mwc-tab-bar { + margin-bottom: 4px; + } + .section { margin: 16px 0 8px 0; font-size: 1.2em; diff --git a/src/dialogs/more-info/ha-more-info-settings.ts b/src/dialogs/more-info/ha-more-info-settings.ts index b3a4c2eed1..2446da7805 100644 --- a/src/dialogs/more-info/ha-more-info-settings.ts +++ b/src/dialogs/more-info/ha-more-info-settings.ts @@ -1,5 +1,3 @@ -import "@material/mwc-tab"; -import "@material/mwc-tab-bar"; import { css, CSSResultGroup, diff --git a/src/onboarding/ha-onboarding.ts b/src/onboarding/ha-onboarding.ts index 7a06c0e3cf..8cd3f0fab4 100644 --- a/src/onboarding/ha-onboarding.ts +++ b/src/onboarding/ha-onboarding.ts @@ -15,7 +15,11 @@ import { } from "../common/auth/token_storage"; import { applyThemesOnElement } from "../common/dom/apply_themes_on_element"; import { HASSDomEvent } from "../common/dom/fire_event"; -import { extractSearchParamsObject } from "../common/url/search-params"; +import { + addSearchParam, + extractSearchParam, + extractSearchParamsObject, +} from "../common/url/search-params"; import { subscribeOne } from "../common/util/subscribe-one"; import "../components/ha-card"; import "../components/ha-language-picker"; @@ -39,6 +43,8 @@ import "./onboarding-loading"; import "./onboarding-welcome"; import "./onboarding-welcome-links"; import { makeDialogManager } from "../dialogs/make-dialog-manager"; +import { navigate } from "../common/navigate"; +import { mainWindow } from "../common/dom/get_main_window"; type OnboardingEvent = | { @@ -96,6 +102,27 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) { @state() private _steps?: OnboardingStep[]; + @state() private _page = extractSearchParam("page"); + + private _mobileApp = + extractSearchParam("redirect_uri") === "homeassistant://auth-callback"; + + connectedCallback() { + super.connectedCallback(); + mainWindow.addEventListener("location-changed", this._updatePage); + mainWindow.addEventListener("popstate", this._updatePage); + } + + disconnectedCallback() { + super.connectedCallback(); + mainWindow.removeEventListener("location-changed", this._updatePage); + mainWindow.removeEventListener("popstate", this._updatePage); + } + + private _updatePage = () => { + this._page = extractSearchParam("page"); + }; + protected render() { return html`
${this._renderStep()}
- ${this._init + ${this._init && !this._restoring ? html`` : nothing}