mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-25 18:26:35 +00:00
20230905.0 (#17828)
This commit is contained in:
commit
8acf557137
@ -33,7 +33,6 @@ export class HassioUploadBackup extends LitElement {
|
|||||||
label="Upload backup"
|
label="Upload backup"
|
||||||
supports="Supports .TAR files"
|
supports="Supports .TAR files"
|
||||||
@file-picked=${this._uploadFile}
|
@file-picked=${this._uploadFile}
|
||||||
auto-open-file-dialog
|
|
||||||
></ha-file-upload>
|
></ha-file-upload>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "home-assistant-frontend"
|
name = "home-assistant-frontend"
|
||||||
version = "20230904.0"
|
version = "20230905.0"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "The Home Assistant frontend"
|
description = "The Home Assistant frontend"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -22,14 +22,7 @@ export type LocalizeKeys =
|
|||||||
| `ui.dialogs.unhealthy.reason.${string}`
|
| `ui.dialogs.unhealthy.reason.${string}`
|
||||||
| `ui.dialogs.unsupported.reason.${string}`
|
| `ui.dialogs.unsupported.reason.${string}`
|
||||||
| `ui.panel.config.${string}.${"caption" | "description"}`
|
| `ui.panel.config.${string}.${"caption" | "description"}`
|
||||||
| `ui.panel.config.automation.${string}`
|
|
||||||
| `ui.panel.config.dashboard.${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.zha.${string}`
|
||||||
| `ui.panel.config.zwave_js.${string}`
|
| `ui.panel.config.zwave_js.${string}`
|
||||||
| `ui.panel.lovelace.card.${string}`
|
| `ui.panel.lovelace.card.${string}`
|
||||||
|
@ -27,7 +27,8 @@ export const computeInitialHaFormData = (
|
|||||||
data[field.name] = 0.0;
|
data[field.name] = 0.0;
|
||||||
} else if (field.type === "select") {
|
} else if (field.type === "select") {
|
||||||
if (field.options.length) {
|
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") {
|
} else if (field.type === "positive_time_period_dict") {
|
||||||
data[field.name] = {
|
data[field.name] = {
|
||||||
@ -61,7 +62,7 @@ export const computeInitialHaFormData = (
|
|||||||
} else if ("select" in selector) {
|
} else if ("select" in selector) {
|
||||||
if (selector.select?.options.length) {
|
if (selector.select?.options.length) {
|
||||||
const val = selector.select.options[0];
|
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) {
|
} else if ("duration" in selector) {
|
||||||
data[field.name] = {
|
data[field.name] = {
|
||||||
|
@ -238,11 +238,13 @@ export interface ZoneCondition extends BaseCondition {
|
|||||||
zone: string;
|
zone: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Weekday = "sun" | "mon" | "tue" | "wed" | "thu" | "fri" | "sat";
|
||||||
|
|
||||||
export interface TimeCondition extends BaseCondition {
|
export interface TimeCondition extends BaseCondition {
|
||||||
condition: "time";
|
condition: "time";
|
||||||
after?: string;
|
after?: string;
|
||||||
before?: string;
|
before?: string;
|
||||||
weekday?: string | string[];
|
weekday?: Weekday | Weekday[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TemplateCondition extends BaseCondition {
|
export interface TemplateCondition extends BaseCondition {
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import { EntityFilter } from "../common/entity/entity_filter";
|
import { EntityFilter } from "../common/entity/entity_filter";
|
||||||
import { PlaceholderContainer } from "../panels/config/automation/thingtalk/dialog-thingtalk";
|
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import { AutomationConfig } from "./automation";
|
|
||||||
|
|
||||||
interface CloudStatusNotLoggedIn {
|
interface CloudStatusNotLoggedIn {
|
||||||
logged_in: false;
|
logged_in: false;
|
||||||
@ -66,11 +64,6 @@ export interface CloudWebhook {
|
|||||||
managed?: boolean;
|
managed?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ThingTalkConversion {
|
|
||||||
config: Partial<AutomationConfig>;
|
|
||||||
placeholders: PlaceholderContainer;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const cloudLogin = (
|
export const cloudLogin = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
email: string,
|
email: string,
|
||||||
@ -136,9 +129,6 @@ export const disconnectCloudRemote = (hass: HomeAssistant) =>
|
|||||||
export const fetchCloudSubscriptionInfo = (hass: HomeAssistant) =>
|
export const fetchCloudSubscriptionInfo = (hass: HomeAssistant) =>
|
||||||
hass.callWS<SubscriptionInfo>({ type: "cloud/subscription" });
|
hass.callWS<SubscriptionInfo>({ type: "cloud/subscription" });
|
||||||
|
|
||||||
export const convertThingTalk = (hass: HomeAssistant, query: string) =>
|
|
||||||
hass.callWS<ThingTalkConversion>({ type: "cloud/thingtalk/convert", query });
|
|
||||||
|
|
||||||
export const updateCloudPref = (
|
export const updateCloudPref = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
prefs: {
|
prefs: {
|
||||||
|
@ -1,46 +1,25 @@
|
|||||||
import { timeCachePromiseFunc } from "../common/util/time-cache-function-promise";
|
import { timeCachePromiseFunc } from "../common/util/time-cache-function-promise";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
interface EntitySourceConfigEntry {
|
interface EntitySource {
|
||||||
source: "config_entry";
|
|
||||||
domain: string;
|
domain: string;
|
||||||
custom_component: boolean;
|
|
||||||
config_entry: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EntitySourcePlatformConfig {
|
export type EntitySources = Record<string, EntitySource>;
|
||||||
source: "platform_config";
|
|
||||||
domain: string;
|
|
||||||
custom_component: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EntitySources = Record<
|
const fetchEntitySources = (hass: HomeAssistant): Promise<EntitySources> =>
|
||||||
string,
|
hass.callWS({ type: "entity/source" });
|
||||||
EntitySourceConfigEntry | EntitySourcePlatformConfig
|
|
||||||
>;
|
|
||||||
|
|
||||||
const fetchEntitySources = (
|
|
||||||
hass: HomeAssistant,
|
|
||||||
entity_id?: string
|
|
||||||
): Promise<EntitySources> =>
|
|
||||||
hass.callWS({
|
|
||||||
type: "entity/source",
|
|
||||||
entity_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const fetchEntitySourcesWithCache = (
|
export const fetchEntitySourcesWithCache = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant
|
||||||
entity_id?: string
|
|
||||||
): Promise<EntitySources> =>
|
): Promise<EntitySources> =>
|
||||||
entity_id
|
timeCachePromiseFunc(
|
||||||
? fetchEntitySources(hass, entity_id)
|
"_entitySources",
|
||||||
: timeCachePromiseFunc(
|
// cache for 30 seconds
|
||||||
"_entitySources",
|
30000,
|
||||||
// cache for 30 seconds
|
fetchEntitySources,
|
||||||
30000,
|
// We base the cache on number of states. If number of states
|
||||||
fetchEntitySources,
|
// changes we force a refresh
|
||||||
// We base the cache on number of states. If number of states
|
(hass2) => Object.keys(hass2.states).length,
|
||||||
// changes we force a refresh
|
hass
|
||||||
(hass2) => Object.keys(hass2.states).length,
|
);
|
||||||
hass
|
|
||||||
);
|
|
||||||
|
@ -36,7 +36,9 @@ export const enum WeatherEntityFeature {
|
|||||||
FORECAST_TWICE_DAILY = 4,
|
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 {
|
interface ForecastAttribute {
|
||||||
temperature: number;
|
temperature: number;
|
||||||
@ -636,7 +638,7 @@ export const getForecast = (
|
|||||||
export const subscribeForecast = (
|
export const subscribeForecast = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entity_id: string,
|
entity_id: string,
|
||||||
forecast_type: "daily" | "hourly" | "twice_daily",
|
forecast_type: ModernForecastType,
|
||||||
callback: (forecastevent: ForecastEvent) => void
|
callback: (forecastevent: ForecastEvent) => void
|
||||||
) =>
|
) =>
|
||||||
hass.connection.subscribeMessage<ForecastEvent>(callback, {
|
hass.connection.subscribeMessage<ForecastEvent>(callback, {
|
||||||
@ -645,6 +647,22 @@ export const subscribeForecast = (
|
|||||||
entity_id,
|
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) => {
|
export const getDefaultForecastType = (stateObj: HassEntityBase) => {
|
||||||
if (supportsFeature(stateObj, WeatherEntityFeature.FORECAST_DAILY)) {
|
if (supportsFeature(stateObj, WeatherEntityFeature.FORECAST_DAILY)) {
|
||||||
return "daily";
|
return "daily";
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import "@material/mwc-tab-bar/mwc-tab-bar";
|
|
||||||
import "@material/mwc-tab/mwc-tab";
|
|
||||||
import { mdiEyedropper } from "@mdi/js";
|
import { mdiEyedropper } from "@mdi/js";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import "@material/mwc-tab";
|
||||||
|
import "@material/mwc-tab-bar";
|
||||||
import {
|
import {
|
||||||
mdiEye,
|
mdiEye,
|
||||||
mdiGauge,
|
mdiGauge,
|
||||||
@ -14,14 +16,17 @@ import {
|
|||||||
nothing,
|
nothing,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
import { formatDateWeekdayDay } from "../../../common/datetime/format_date";
|
import { formatDateWeekdayDay } from "../../../common/datetime/format_date";
|
||||||
import { formatTimeWeekday } from "../../../common/datetime/format_time";
|
import { formatTimeWeekday } from "../../../common/datetime/format_time";
|
||||||
import "../../../components/ha-svg-icon";
|
import "../../../components/ha-svg-icon";
|
||||||
import {
|
import {
|
||||||
ForecastEvent,
|
ForecastEvent,
|
||||||
|
ModernForecastType,
|
||||||
WeatherEntity,
|
WeatherEntity,
|
||||||
getDefaultForecastType,
|
getDefaultForecastType,
|
||||||
getForecast,
|
getForecast,
|
||||||
|
getSupportedForecastTypes,
|
||||||
getWind,
|
getWind,
|
||||||
subscribeForecast,
|
subscribeForecast,
|
||||||
weatherIcons,
|
weatherIcons,
|
||||||
@ -36,6 +41,8 @@ class MoreInfoWeather extends LitElement {
|
|||||||
|
|
||||||
@state() private _forecastEvent?: ForecastEvent;
|
@state() private _forecastEvent?: ForecastEvent;
|
||||||
|
|
||||||
|
@state() private _forecastType?: ModernForecastType;
|
||||||
|
|
||||||
@state() private _subscribed?: Promise<() => void>;
|
@state() private _subscribed?: Promise<() => void>;
|
||||||
|
|
||||||
private _unsubscribeForecastEvents() {
|
private _unsubscribeForecastEvents() {
|
||||||
@ -43,25 +50,28 @@ class MoreInfoWeather extends LitElement {
|
|||||||
this._subscribed.then((unsub) => unsub());
|
this._subscribed.then((unsub) => unsub());
|
||||||
this._subscribed = undefined;
|
this._subscribed = undefined;
|
||||||
}
|
}
|
||||||
|
this._forecastEvent = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _subscribeForecastEvents() {
|
private async _subscribeForecastEvents() {
|
||||||
this._unsubscribeForecastEvents();
|
this._unsubscribeForecastEvents();
|
||||||
if (!this.isConnected || !this.hass || !this.stateObj) {
|
if (
|
||||||
|
!this.isConnected ||
|
||||||
|
!this.hass ||
|
||||||
|
!this.stateObj ||
|
||||||
|
!this._forecastType
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const forecastType = getDefaultForecastType(this.stateObj);
|
this._subscribed = subscribeForecast(
|
||||||
if (forecastType) {
|
this.hass!,
|
||||||
this._subscribed = subscribeForecast(
|
this.stateObj!.entity_id,
|
||||||
this.hass!,
|
this._forecastType,
|
||||||
this.stateObj!.entity_id,
|
(event) => {
|
||||||
forecastType,
|
this._forecastEvent = event;
|
||||||
(event) => {
|
}
|
||||||
this._forecastEvent = event;
|
);
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public connectedCallback() {
|
public connectedCallback() {
|
||||||
@ -93,10 +103,10 @@ class MoreInfoWeather extends LitElement {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updated(changedProps: PropertyValues): void {
|
protected willUpdate(changedProps: PropertyValues): void {
|
||||||
super.updated(changedProps);
|
super.willUpdate(changedProps);
|
||||||
|
|
||||||
if (changedProps.has("stateObj") || !this._subscribed) {
|
if ((changedProps.has("stateObj") || !this._subscribed) && this.stateObj) {
|
||||||
const oldState = changedProps.get("stateObj") as
|
const oldState = changedProps.get("stateObj") as
|
||||||
| WeatherEntity
|
| WeatherEntity
|
||||||
| undefined;
|
| undefined;
|
||||||
@ -104,16 +114,25 @@ class MoreInfoWeather extends LitElement {
|
|||||||
oldState?.entity_id !== this.stateObj?.entity_id ||
|
oldState?.entity_id !== this.stateObj?.entity_id ||
|
||||||
!this._subscribed
|
!this._subscribed
|
||||||
) {
|
) {
|
||||||
|
this._forecastType = getDefaultForecastType(this.stateObj);
|
||||||
this._subscribeForecastEvents();
|
this._subscribeForecastEvents();
|
||||||
}
|
}
|
||||||
|
} else if (changedProps.has("_forecastType")) {
|
||||||
|
this._subscribeForecastEvents();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _supportedForecasts = memoizeOne((stateObj: WeatherEntity) =>
|
||||||
|
getSupportedForecastTypes(stateObj)
|
||||||
|
);
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
if (!this.hass || !this.stateObj) {
|
if (!this.hass || !this.stateObj) {
|
||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const supportedForecasts = this._supportedForecasts(this.stateObj);
|
||||||
|
|
||||||
const forecastData = getForecast(
|
const forecastData = getForecast(
|
||||||
this.stateObj.attributes,
|
this.stateObj.attributes,
|
||||||
this._forecastEvent
|
this._forecastEvent
|
||||||
@ -210,6 +229,23 @@ class MoreInfoWeather extends LitElement {
|
|||||||
<div class="section">
|
<div class="section">
|
||||||
${this.hass.localize("ui.card.weather.forecast")}:
|
${this.hass.localize("ui.card.weather.forecast")}:
|
||||||
</div>
|
</div>
|
||||||
|
${supportedForecasts.length > 1
|
||||||
|
? html`<mwc-tab-bar
|
||||||
|
.activeIndex=${supportedForecasts.findIndex(
|
||||||
|
(item) => item === this._forecastType
|
||||||
|
)}
|
||||||
|
@MDCTabBar:activated=${this._handleForecastTypeChanged}
|
||||||
|
>
|
||||||
|
${supportedForecasts.map(
|
||||||
|
(forecastType) =>
|
||||||
|
html`<mwc-tab
|
||||||
|
.label=${this.hass!.localize(
|
||||||
|
`ui.card.weather.${forecastType}`
|
||||||
|
)}
|
||||||
|
></mwc-tab>`
|
||||||
|
)}
|
||||||
|
</mwc-tab-bar>`
|
||||||
|
: nothing}
|
||||||
${forecast.map((item) =>
|
${forecast.map((item) =>
|
||||||
this._showValue(item.templow) || this._showValue(item.temperature)
|
this._showValue(item.templow) || this._showValue(item.temperature)
|
||||||
? html`<div class="flex">
|
? html`<div class="flex">
|
||||||
@ -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 {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
ha-svg-icon {
|
ha-svg-icon {
|
||||||
color: var(--paper-item-icon-color);
|
color: var(--paper-item-icon-color);
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mwc-tab-bar {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.section {
|
.section {
|
||||||
margin: 16px 0 8px 0;
|
margin: 16px 0 8px 0;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import "@material/mwc-tab";
|
|
||||||
import "@material/mwc-tab-bar";
|
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
|
@ -15,7 +15,11 @@ import {
|
|||||||
} from "../common/auth/token_storage";
|
} from "../common/auth/token_storage";
|
||||||
import { applyThemesOnElement } from "../common/dom/apply_themes_on_element";
|
import { applyThemesOnElement } from "../common/dom/apply_themes_on_element";
|
||||||
import { HASSDomEvent } from "../common/dom/fire_event";
|
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 { subscribeOne } from "../common/util/subscribe-one";
|
||||||
import "../components/ha-card";
|
import "../components/ha-card";
|
||||||
import "../components/ha-language-picker";
|
import "../components/ha-language-picker";
|
||||||
@ -39,6 +43,8 @@ import "./onboarding-loading";
|
|||||||
import "./onboarding-welcome";
|
import "./onboarding-welcome";
|
||||||
import "./onboarding-welcome-links";
|
import "./onboarding-welcome-links";
|
||||||
import { makeDialogManager } from "../dialogs/make-dialog-manager";
|
import { makeDialogManager } from "../dialogs/make-dialog-manager";
|
||||||
|
import { navigate } from "../common/navigate";
|
||||||
|
import { mainWindow } from "../common/dom/get_main_window";
|
||||||
|
|
||||||
type OnboardingEvent =
|
type OnboardingEvent =
|
||||||
| {
|
| {
|
||||||
@ -96,6 +102,27 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
|||||||
|
|
||||||
@state() private _steps?: OnboardingStep[];
|
@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() {
|
protected render() {
|
||||||
return html`<mwc-linear-progress
|
return html`<mwc-linear-progress
|
||||||
.progress=${this._progress}
|
.progress=${this._progress}
|
||||||
@ -103,9 +130,10 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
|||||||
<ha-card>
|
<ha-card>
|
||||||
<div class="card-content">${this._renderStep()}</div>
|
<div class="card-content">${this._renderStep()}</div>
|
||||||
</ha-card>
|
</ha-card>
|
||||||
${this._init
|
${this._init && !this._restoring
|
||||||
? html`<onboarding-welcome-links
|
? html`<onboarding-welcome-links
|
||||||
.localize=${this.localize}
|
.localize=${this.localize}
|
||||||
|
.mobileApp=${this._mobileApp}
|
||||||
></onboarding-welcome-links>`
|
></onboarding-welcome-links>`
|
||||||
: nothing}
|
: nothing}
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
@ -125,6 +153,14 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _renderStep() {
|
private _renderStep() {
|
||||||
|
if (this._restoring) {
|
||||||
|
return html`<onboarding-restore-backup
|
||||||
|
.hass=${this.hass}
|
||||||
|
.localize=${this.localize}
|
||||||
|
>
|
||||||
|
</onboarding-restore-backup>`;
|
||||||
|
}
|
||||||
|
|
||||||
if (this._init) {
|
if (this._init) {
|
||||||
return html`<onboarding-welcome
|
return html`<onboarding-welcome
|
||||||
.localize=${this.localize}
|
.localize=${this.localize}
|
||||||
@ -133,11 +169,6 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
|||||||
></onboarding-welcome>`;
|
></onboarding-welcome>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._restoring) {
|
|
||||||
return html`<onboarding-restore-backup .localize=${this.localize}>
|
|
||||||
</onboarding-restore-backup>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const step = this._curStep()!;
|
const step = this._curStep()!;
|
||||||
|
|
||||||
if (this._loading || !step) {
|
if (this._loading || !step) {
|
||||||
@ -195,6 +226,12 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
|||||||
|
|
||||||
protected updated(changedProps: PropertyValues) {
|
protected updated(changedProps: PropertyValues) {
|
||||||
super.updated(changedProps);
|
super.updated(changedProps);
|
||||||
|
if (changedProps.has("_page")) {
|
||||||
|
this._restoring = this._page === "restore_backup";
|
||||||
|
if (this._page === null && this._steps && !this._steps[0].done) {
|
||||||
|
this._init = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (changedProps.has("language")) {
|
if (changedProps.has("language")) {
|
||||||
document.querySelector("html")!.setAttribute("lang", this.language!);
|
document.querySelector("html")!.setAttribute("lang", this.language!);
|
||||||
}
|
}
|
||||||
@ -312,6 +349,10 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
|||||||
this._restoring = stepResult.result.restore;
|
this._restoring = stepResult.result.restore;
|
||||||
if (!this._restoring) {
|
if (!this._restoring) {
|
||||||
this._progress = 0.25;
|
this._progress = 0.25;
|
||||||
|
} else {
|
||||||
|
navigate(
|
||||||
|
`${location.pathname}?${addSearchParam({ page: "restore_backup" })}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else if (stepResult.type === "user") {
|
} else if (stepResult.type === "user") {
|
||||||
const result = stepResult.result as OnboardingResponses["user"];
|
const result = stepResult.result as OnboardingResponses["user"];
|
||||||
|
@ -1,42 +1,55 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { showBackupUploadDialog } from "../../hassio/src/dialogs/backup/show-dialog-backup-upload";
|
|
||||||
import { showHassioBackupDialog } from "../../hassio/src/dialogs/backup/show-dialog-hassio-backup";
|
import { showHassioBackupDialog } from "../../hassio/src/dialogs/backup/show-dialog-hassio-backup";
|
||||||
|
import "../../hassio/src/components/hassio-upload-backup";
|
||||||
import type { LocalizeFunc } from "../common/translations/localize";
|
import type { LocalizeFunc } from "../common/translations/localize";
|
||||||
import "../components/ha-ansi-to-html";
|
import "../components/ha-ansi-to-html";
|
||||||
import "../components/ha-card";
|
import "../components/ha-card";
|
||||||
import { fetchInstallationType } from "../data/onboarding";
|
import { fetchInstallationType } from "../data/onboarding";
|
||||||
|
import { HomeAssistant } from "../types";
|
||||||
import "./onboarding-loading";
|
import "./onboarding-loading";
|
||||||
import { onBoardingStyles } from "./styles";
|
import { onBoardingStyles } from "./styles";
|
||||||
|
import { removeSearchParam } from "../common/url/search-params";
|
||||||
|
import { navigate } from "../common/navigate";
|
||||||
|
|
||||||
@customElement("onboarding-restore-backup")
|
@customElement("onboarding-restore-backup")
|
||||||
class OnboardingRestoreBackup extends LitElement {
|
class OnboardingRestoreBackup extends LitElement {
|
||||||
@property() public localize!: LocalizeFunc;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public localize!: LocalizeFunc;
|
||||||
|
|
||||||
@property() public language!: string;
|
@property() public language!: string;
|
||||||
|
|
||||||
@state() public _restoring = false;
|
@state() public _restoring = false;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return this._restoring
|
return html`${this._restoring
|
||||||
? html`<h1>
|
? html`<h1>
|
||||||
${this.localize("ui.panel.page-onboarding.restore.in_progress")}
|
${this.localize("ui.panel.page-onboarding.restore.in_progress")}
|
||||||
</h1>
|
</h1>
|
||||||
<onboarding-loading></onboarding-loading>`
|
<onboarding-loading></onboarding-loading>`
|
||||||
: html`
|
: html` <h1>
|
||||||
<h1>${this.localize("ui.panel.page-onboarding.restore.header")}</h1>
|
${this.localize("ui.panel.page-onboarding.restore.header")}
|
||||||
<ha-button unelevated @click=${this._uploadBackup}>
|
</h1>
|
||||||
${this.localize("ui.panel.page-onboarding.restore.upload_backup")}
|
<hassio-upload-backup
|
||||||
</ha-button>
|
@backup-uploaded=${this._backupUploaded}
|
||||||
`;
|
.hass=${this.hass}
|
||||||
|
></hassio-upload-backup>`}
|
||||||
|
<div class="footer">
|
||||||
|
<mwc-button @click=${this._back} .disabled=${this._restoring}>
|
||||||
|
${this.localize("ui.panel.page-onboarding.back")}
|
||||||
|
</mwc-button>
|
||||||
|
</div> `;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _uploadBackup(): void {
|
private _back(): void {
|
||||||
showBackupUploadDialog(this, {
|
navigate(`${location.pathname}?${removeSearchParam("page")}`);
|
||||||
showBackup: (slug: string) => this._showBackupDialog(slug),
|
}
|
||||||
onboarding: true,
|
|
||||||
});
|
private _backupUploaded(ev) {
|
||||||
|
const backup = ev.detail.backup;
|
||||||
|
this._showBackupDialog(backup.slug);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected firstUpdated(changedProps) {
|
protected firstUpdated(changedProps) {
|
||||||
@ -76,6 +89,13 @@ class OnboardingRestoreBackup extends LitElement {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
hassio-upload-backup {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -94,6 +94,7 @@ class OnboardingWelcomeLink extends LitElement {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
padding: 32px 16px;
|
padding: 32px 16px;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
ha-svg-icon {
|
ha-svg-icon {
|
||||||
color: var(--text-primary-color);
|
color: var(--text-primary-color);
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
import { mdiAccountGroup, mdiFileDocument, mdiTabletCellphone } from "@mdi/js";
|
import { mdiAccountGroup, mdiFileDocument, mdiTabletCellphone } from "@mdi/js";
|
||||||
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
|
import {
|
||||||
|
CSSResultGroup,
|
||||||
|
LitElement,
|
||||||
|
TemplateResult,
|
||||||
|
css,
|
||||||
|
html,
|
||||||
|
nothing,
|
||||||
|
} from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import type { LocalizeFunc } from "../common/translations/localize";
|
import type { LocalizeFunc } from "../common/translations/localize";
|
||||||
import "../components/ha-card";
|
import "../components/ha-card";
|
||||||
@ -14,6 +21,8 @@ class OnboardingWelcomeLinks extends LitElement {
|
|||||||
|
|
||||||
@property() public localize!: LocalizeFunc;
|
@property() public localize!: LocalizeFunc;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public mobileApp!: boolean;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`<a
|
return html`<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@ -34,13 +43,17 @@ class OnboardingWelcomeLinks extends LitElement {
|
|||||||
.label=${this.localize("ui.panel.page-onboarding.welcome.community")}
|
.label=${this.localize("ui.panel.page-onboarding.welcome.community")}
|
||||||
>
|
>
|
||||||
</onboarding-welcome-link>
|
</onboarding-welcome-link>
|
||||||
<onboarding-welcome-link
|
${this.mobileApp
|
||||||
class="app"
|
? nothing
|
||||||
@click=${this._openApp}
|
: html`<onboarding-welcome-link
|
||||||
.iconPath=${mdiTabletCellphone}
|
class="app"
|
||||||
.label=${this.localize("ui.panel.page-onboarding.welcome.download_app")}
|
@click=${this._openApp}
|
||||||
>
|
.iconPath=${mdiTabletCellphone}
|
||||||
</onboarding-welcome-link>`;
|
.label=${this.localize(
|
||||||
|
"ui.panel.page-onboarding.welcome.download_app"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
</onboarding-welcome-link>`}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _openCommunity(): void {
|
private _openCommunity(): void {
|
||||||
|
@ -80,9 +80,7 @@ export class DialogAddApplicationCredential extends LitElement {
|
|||||||
name: domainToName(this.hass.localize, domain),
|
name: domainToName(this.hass.localize, domain),
|
||||||
}));
|
}));
|
||||||
await this.hass.loadBackendTranslation("application_credentials");
|
await this.hass.loadBackendTranslation("application_credentials");
|
||||||
if (this._domain) {
|
this._updateDescription();
|
||||||
this._updateDescription();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
@ -265,11 +263,15 @@ export class DialogAddApplicationCredential extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _updateDescription() {
|
private async _updateDescription() {
|
||||||
|
if (!this._domain) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await this.hass.loadBackendTranslation(
|
await this.hass.loadBackendTranslation(
|
||||||
"application_credentials",
|
"application_credentials",
|
||||||
this._domain
|
this._domain
|
||||||
);
|
);
|
||||||
const info = this._config!.integrations[this._domain!];
|
const info = this._config!.integrations[this._domain];
|
||||||
this._description = this.hass.localize(
|
this._description = this.hass.localize(
|
||||||
`component.${this._domain}.application_credentials.description`,
|
`component.${this._domain}.application_credentials.description`,
|
||||||
info.description_placeholders
|
info.description_placeholders
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { consume } from "@lit-labs/context";
|
||||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import {
|
import {
|
||||||
@ -25,7 +26,6 @@ import {
|
|||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { consume } from "@lit-labs/context";
|
|
||||||
import { storage } from "../../../../common/decorators/storage";
|
import { storage } from "../../../../common/decorators/storage";
|
||||||
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
|
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
@ -40,6 +40,7 @@ import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
|
|||||||
import { ACTION_TYPES, YAML_ONLY_ACTION_TYPES } from "../../../../data/action";
|
import { ACTION_TYPES, YAML_ONLY_ACTION_TYPES } from "../../../../data/action";
|
||||||
import { AutomationClipboard } from "../../../../data/automation";
|
import { AutomationClipboard } from "../../../../data/automation";
|
||||||
import { validateConfig } from "../../../../data/config";
|
import { validateConfig } from "../../../../data/config";
|
||||||
|
import { fullEntitiesContext } from "../../../../data/context";
|
||||||
import { EntityRegistryEntry } from "../../../../data/entity_registry";
|
import { EntityRegistryEntry } from "../../../../data/entity_registry";
|
||||||
import {
|
import {
|
||||||
Action,
|
Action,
|
||||||
@ -70,19 +71,20 @@ import "./types/ha-automation-action-service";
|
|||||||
import "./types/ha-automation-action-stop";
|
import "./types/ha-automation-action-stop";
|
||||||
import "./types/ha-automation-action-wait_for_trigger";
|
import "./types/ha-automation-action-wait_for_trigger";
|
||||||
import "./types/ha-automation-action-wait_template";
|
import "./types/ha-automation-action-wait_template";
|
||||||
import { fullEntitiesContext } from "../../../../data/context";
|
|
||||||
|
|
||||||
export const getType = (action: Action | undefined) => {
|
export const getType = (action: Action | undefined) => {
|
||||||
if (!action) {
|
if (!action) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
if ("service" in action || "scene" in action) {
|
if ("service" in action || "scene" in action) {
|
||||||
return getActionType(action);
|
return getActionType(action) as "activate_scene" | "service" | "play_media";
|
||||||
}
|
}
|
||||||
if (["and", "or", "not"].some((key) => key in action)) {
|
if (["and", "or", "not"].some((key) => key in action)) {
|
||||||
return "condition";
|
return "condition" as const;
|
||||||
}
|
}
|
||||||
return Object.keys(ACTION_TYPES).find((option) => option in action);
|
return Object.keys(ACTION_TYPES).find(
|
||||||
|
(option) => option in action
|
||||||
|
) as keyof typeof ACTION_TYPES;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ActionElement extends LitElement {
|
export interface ActionElement extends LitElement {
|
||||||
|
@ -3,41 +3,42 @@ import type { ActionDetail } from "@material/mwc-list";
|
|||||||
import {
|
import {
|
||||||
mdiArrowDown,
|
mdiArrowDown,
|
||||||
mdiArrowUp,
|
mdiArrowUp,
|
||||||
|
mdiContentPaste,
|
||||||
mdiDrag,
|
mdiDrag,
|
||||||
mdiPlus,
|
mdiPlus,
|
||||||
mdiContentPaste,
|
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import deepClone from "deep-clone-simple";
|
import deepClone from "deep-clone-simple";
|
||||||
import {
|
import {
|
||||||
css,
|
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
html,
|
|
||||||
LitElement,
|
LitElement,
|
||||||
nothing,
|
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
|
css,
|
||||||
|
html,
|
||||||
|
nothing,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { repeat } from "lit/directives/repeat";
|
import { repeat } from "lit/directives/repeat";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import type { SortableEvent } from "sortablejs";
|
import type { SortableEvent } from "sortablejs";
|
||||||
|
import { storage } from "../../../../common/decorators/storage";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import { stringCompare } from "../../../../common/string/compare";
|
import { stringCompare } from "../../../../common/string/compare";
|
||||||
import { LocalizeFunc } from "../../../../common/translations/localize";
|
import { LocalizeFunc } from "../../../../common/translations/localize";
|
||||||
import "../../../../components/ha-button-menu";
|
|
||||||
import "../../../../components/ha-button";
|
import "../../../../components/ha-button";
|
||||||
|
import "../../../../components/ha-button-menu";
|
||||||
import type { HaSelect } from "../../../../components/ha-select";
|
import type { HaSelect } from "../../../../components/ha-select";
|
||||||
import "../../../../components/ha-svg-icon";
|
import "../../../../components/ha-svg-icon";
|
||||||
import { ACTION_TYPES } from "../../../../data/action";
|
import { ACTION_TYPES } from "../../../../data/action";
|
||||||
import { Action } from "../../../../data/script";
|
|
||||||
import { AutomationClipboard } from "../../../../data/automation";
|
import { AutomationClipboard } from "../../../../data/automation";
|
||||||
|
import { Action } from "../../../../data/script";
|
||||||
import { sortableStyles } from "../../../../resources/ha-sortable-style";
|
import { sortableStyles } from "../../../../resources/ha-sortable-style";
|
||||||
import {
|
import {
|
||||||
loadSortable,
|
|
||||||
SortableInstance,
|
SortableInstance,
|
||||||
|
loadSortable,
|
||||||
} from "../../../../resources/sortable.ondemand";
|
} from "../../../../resources/sortable.ondemand";
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { Entries, HomeAssistant } from "../../../../types";
|
||||||
import { getType } from "./ha-automation-action-row";
|
|
||||||
import type HaAutomationActionRow from "./ha-automation-action-row";
|
import type HaAutomationActionRow from "./ha-automation-action-row";
|
||||||
|
import { getType } from "./ha-automation-action-row";
|
||||||
import "./types/ha-automation-action-activate_scene";
|
import "./types/ha-automation-action-activate_scene";
|
||||||
import "./types/ha-automation-action-choose";
|
import "./types/ha-automation-action-choose";
|
||||||
import "./types/ha-automation-action-condition";
|
import "./types/ha-automation-action-condition";
|
||||||
@ -52,7 +53,6 @@ import "./types/ha-automation-action-service";
|
|||||||
import "./types/ha-automation-action-stop";
|
import "./types/ha-automation-action-stop";
|
||||||
import "./types/ha-automation-action-wait_for_trigger";
|
import "./types/ha-automation-action-wait_for_trigger";
|
||||||
import "./types/ha-automation-action-wait_template";
|
import "./types/ha-automation-action-wait_template";
|
||||||
import { storage } from "../../../../common/decorators/storage";
|
|
||||||
|
|
||||||
const PASTE_VALUE = "__paste__";
|
const PASTE_VALUE = "__paste__";
|
||||||
|
|
||||||
@ -174,9 +174,9 @@ export default class HaAutomationAction extends LitElement {
|
|||||||
"ui.panel.config.automation.editor.actions.paste"
|
"ui.panel.config.automation.editor.actions.paste"
|
||||||
)}
|
)}
|
||||||
(${this.hass.localize(
|
(${this.hass.localize(
|
||||||
`ui.panel.config.automation.editor.actions.type.${getType(
|
`ui.panel.config.automation.editor.actions.type.${
|
||||||
this._clipboard.action
|
getType(this._clipboard.action) || "unknown"
|
||||||
)}.label`
|
}.label`
|
||||||
)})
|
)})
|
||||||
<ha-svg-icon slot="graphic" .path=${mdiContentPaste}></ha-svg-icon
|
<ha-svg-icon slot="graphic" .path=${mdiContentPaste}></ha-svg-icon
|
||||||
></mwc-list-item>`
|
></mwc-list-item>`
|
||||||
@ -333,7 +333,7 @@ export default class HaAutomationAction extends LitElement {
|
|||||||
|
|
||||||
private _processedTypes = memoizeOne(
|
private _processedTypes = memoizeOne(
|
||||||
(localize: LocalizeFunc): [string, string, string][] =>
|
(localize: LocalizeFunc): [string, string, string][] =>
|
||||||
Object.entries(ACTION_TYPES)
|
(Object.entries(ACTION_TYPES) as Entries<typeof ACTION_TYPES>)
|
||||||
.map(
|
.map(
|
||||||
([action, icon]) =>
|
([action, icon]) =>
|
||||||
[
|
[
|
||||||
|
@ -8,7 +8,7 @@ import "../../../../../components/ha-select";
|
|||||||
import type { HaSelect } from "../../../../../components/ha-select";
|
import type { HaSelect } from "../../../../../components/ha-select";
|
||||||
import type { Condition } from "../../../../../data/automation";
|
import type { Condition } from "../../../../../data/automation";
|
||||||
import { CONDITION_TYPES } from "../../../../../data/condition";
|
import { CONDITION_TYPES } from "../../../../../data/condition";
|
||||||
import { HomeAssistant } from "../../../../../types";
|
import { Entries, HomeAssistant } from "../../../../../types";
|
||||||
import "../../condition/ha-automation-condition-editor";
|
import "../../condition/ha-automation-condition-editor";
|
||||||
import type { ActionElement } from "../ha-automation-action-row";
|
import type { ActionElement } from "../ha-automation-action-row";
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ export class HaConditionAction extends LitElement implements ActionElement {
|
|||||||
|
|
||||||
private _processedTypes = memoizeOne(
|
private _processedTypes = memoizeOne(
|
||||||
(localize: LocalizeFunc): [string, string, string][] =>
|
(localize: LocalizeFunc): [string, string, string][] =>
|
||||||
Object.entries(CONDITION_TYPES)
|
(Object.entries(CONDITION_TYPES) as Entries<typeof CONDITION_TYPES>)
|
||||||
.map(
|
.map(
|
||||||
([condition, icon]) =>
|
([condition, icon]) =>
|
||||||
[
|
[
|
||||||
|
@ -28,12 +28,13 @@ import type {
|
|||||||
AutomationClipboard,
|
AutomationClipboard,
|
||||||
Condition,
|
Condition,
|
||||||
} from "../../../../data/automation";
|
} from "../../../../data/automation";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
import type { Entries, HomeAssistant } from "../../../../types";
|
||||||
import "./ha-automation-condition-row";
|
import "./ha-automation-condition-row";
|
||||||
import type HaAutomationConditionRow from "./ha-automation-condition-row";
|
import type HaAutomationConditionRow from "./ha-automation-condition-row";
|
||||||
// Uncommenting these and this element doesn't load
|
// Uncommenting these and this element doesn't load
|
||||||
// import "./types/ha-automation-condition-not";
|
// import "./types/ha-automation-condition-not";
|
||||||
// import "./types/ha-automation-condition-or";
|
// import "./types/ha-automation-condition-or";
|
||||||
|
import { storage } from "../../../../common/decorators/storage";
|
||||||
import { stringCompare } from "../../../../common/string/compare";
|
import { stringCompare } from "../../../../common/string/compare";
|
||||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||||
import type { HaSelect } from "../../../../components/ha-select";
|
import type { HaSelect } from "../../../../components/ha-select";
|
||||||
@ -52,7 +53,6 @@ import "./types/ha-automation-condition-template";
|
|||||||
import "./types/ha-automation-condition-time";
|
import "./types/ha-automation-condition-time";
|
||||||
import "./types/ha-automation-condition-trigger";
|
import "./types/ha-automation-condition-trigger";
|
||||||
import "./types/ha-automation-condition-zone";
|
import "./types/ha-automation-condition-zone";
|
||||||
import { storage } from "../../../../common/decorators/storage";
|
|
||||||
|
|
||||||
const PASTE_VALUE = "__paste__";
|
const PASTE_VALUE = "__paste__";
|
||||||
|
|
||||||
@ -364,7 +364,7 @@ export default class HaAutomationCondition extends LitElement {
|
|||||||
|
|
||||||
private _processedTypes = memoizeOne(
|
private _processedTypes = memoizeOne(
|
||||||
(localize: LocalizeFunc): [string, string, string][] =>
|
(localize: LocalizeFunc): [string, string, string][] =>
|
||||||
Object.entries(CONDITION_TYPES)
|
(Object.entries(CONDITION_TYPES) as Entries<typeof CONDITION_TYPES>)
|
||||||
.map(
|
.map(
|
||||||
([condition, icon]) =>
|
([condition, icon]) =>
|
||||||
[
|
[
|
||||||
|
@ -53,11 +53,6 @@ export class HaZoneCondition extends LitElement {
|
|||||||
allow-custom-entity
|
allow-custom-entity
|
||||||
.includeDomains=${includeDomains}
|
.includeDomains=${includeDomains}
|
||||||
></ha-entity-picker>
|
></ha-entity-picker>
|
||||||
<label id="eventlabel">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.conditions.type.zone.event"
|
|
||||||
)}
|
|
||||||
</label>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,6 +49,8 @@ import {
|
|||||||
showAutomationEditor,
|
showAutomationEditor,
|
||||||
triggerAutomationActions,
|
triggerAutomationActions,
|
||||||
} from "../../../data/automation";
|
} from "../../../data/automation";
|
||||||
|
import { validateConfig } from "../../../data/config";
|
||||||
|
import { UNAVAILABLE } from "../../../data/entity";
|
||||||
import { fetchEntityRegistry } from "../../../data/entity_registry";
|
import { fetchEntityRegistry } from "../../../data/entity_registry";
|
||||||
import {
|
import {
|
||||||
showAlertDialog,
|
showAlertDialog,
|
||||||
@ -57,15 +59,13 @@ import {
|
|||||||
import "../../../layouts/hass-subpage";
|
import "../../../layouts/hass-subpage";
|
||||||
import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin";
|
import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin";
|
||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import { HomeAssistant, Route } from "../../../types";
|
import { Entries, HomeAssistant, Route } from "../../../types";
|
||||||
import { showToast } from "../../../util/toast";
|
import { showToast } from "../../../util/toast";
|
||||||
import "../ha-config-section";
|
import "../ha-config-section";
|
||||||
import { showAutomationModeDialog } from "./automation-mode-dialog/show-dialog-automation-mode";
|
import { showAutomationModeDialog } from "./automation-mode-dialog/show-dialog-automation-mode";
|
||||||
import { showAutomationRenameDialog } from "./automation-rename-dialog/show-dialog-automation-rename";
|
import { showAutomationRenameDialog } from "./automation-rename-dialog/show-dialog-automation-rename";
|
||||||
import "./blueprint-automation-editor";
|
import "./blueprint-automation-editor";
|
||||||
import "./manual-automation-editor";
|
import "./manual-automation-editor";
|
||||||
import { UNAVAILABLE } from "../../../data/entity";
|
|
||||||
import { validateConfig } from "../../../data/config";
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
@ -489,7 +489,9 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
condition: this._config.condition,
|
condition: this._config.condition,
|
||||||
action: this._config.action,
|
action: this._config.action,
|
||||||
});
|
});
|
||||||
this._validationErrors = Object.entries(validation).map(([key, value]) =>
|
this._validationErrors = (
|
||||||
|
Object.entries(validation) as Entries<typeof validation>
|
||||||
|
).map(([key, value]) =>
|
||||||
value.valid
|
value.valid
|
||||||
? ""
|
? ""
|
||||||
: html`${this.hass.localize(
|
: html`${this.hass.localize(
|
||||||
|
@ -1,273 +0,0 @@
|
|||||||
import "@material/mwc-button";
|
|
||||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
|
||||||
import "../../../../components/ha-circular-progress";
|
|
||||||
import "../../../../components/ha-dialog";
|
|
||||||
import "../../../../components/ha-textfield";
|
|
||||||
import type { HaTextField } from "../../../../components/ha-textfield";
|
|
||||||
import type { AutomationConfig } from "../../../../data/automation";
|
|
||||||
import { convertThingTalk } from "../../../../data/cloud";
|
|
||||||
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
|
||||||
import type { HomeAssistant } from "../../../../types";
|
|
||||||
import "./ha-thingtalk-placeholders";
|
|
||||||
import type { PlaceholderValues } from "./ha-thingtalk-placeholders";
|
|
||||||
import type { ThingtalkDialogParams } from "./show-dialog-thingtalk";
|
|
||||||
|
|
||||||
export interface Placeholder {
|
|
||||||
name: string;
|
|
||||||
index: number;
|
|
||||||
fields: string[];
|
|
||||||
domains: string[];
|
|
||||||
device_classes?: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PlaceholderContainer {
|
|
||||||
[key: string]: Placeholder[];
|
|
||||||
}
|
|
||||||
|
|
||||||
@customElement("ha-dialog-thinktalk")
|
|
||||||
class DialogThingtalk extends LitElement {
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@state() private _error?: string;
|
|
||||||
|
|
||||||
@state() private _params?: ThingtalkDialogParams;
|
|
||||||
|
|
||||||
@state() private _submitting = false;
|
|
||||||
|
|
||||||
@state() private _placeholders?: PlaceholderContainer;
|
|
||||||
|
|
||||||
@query("#input") private _input?: HaTextField;
|
|
||||||
|
|
||||||
private _value?: string;
|
|
||||||
|
|
||||||
private _config!: Partial<AutomationConfig>;
|
|
||||||
|
|
||||||
public async showDialog(params: ThingtalkDialogParams): Promise<void> {
|
|
||||||
this._params = params;
|
|
||||||
this._error = undefined;
|
|
||||||
if (params.input) {
|
|
||||||
this._value = params.input;
|
|
||||||
await this.updateComplete;
|
|
||||||
this._generate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public closeDialog() {
|
|
||||||
this._placeholders = undefined;
|
|
||||||
this._params = undefined;
|
|
||||||
if (this._input) {
|
|
||||||
this._input.value = "";
|
|
||||||
}
|
|
||||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
|
||||||
}
|
|
||||||
|
|
||||||
public closeInitDialog() {
|
|
||||||
if (this._placeholders) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.closeDialog();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render() {
|
|
||||||
if (!this._params) {
|
|
||||||
return nothing;
|
|
||||||
}
|
|
||||||
if (this._placeholders) {
|
|
||||||
return html`
|
|
||||||
<ha-thingtalk-placeholders
|
|
||||||
.hass=${this.hass}
|
|
||||||
.placeholders=${this._placeholders}
|
|
||||||
.skip=${this._skip}
|
|
||||||
@closed=${this.closeDialog}
|
|
||||||
@placeholders-filled=${this._handlePlaceholders}
|
|
||||||
>
|
|
||||||
</ha-thingtalk-placeholders>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
return html`
|
|
||||||
<ha-dialog
|
|
||||||
open
|
|
||||||
@closed=${this.closeInitDialog}
|
|
||||||
.heading=${this.hass.localize(
|
|
||||||
`ui.panel.config.automation.thingtalk.task_selection.header`
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
|
|
||||||
${this.hass.localize(
|
|
||||||
`ui.panel.config.automation.thingtalk.task_selection.introduction`
|
|
||||||
)}<br /><br />
|
|
||||||
${this.hass.localize(
|
|
||||||
`ui.panel.config.automation.thingtalk.task_selection.language_note`
|
|
||||||
)}<br /><br />
|
|
||||||
${this.hass.localize(
|
|
||||||
`ui.panel.config.automation.thingtalk.task_selection.for_example`
|
|
||||||
)}
|
|
||||||
<ul @click=${this._handleExampleClick}>
|
|
||||||
<li>
|
|
||||||
<button class="link">
|
|
||||||
Turn off the lights when I leave home
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<button class="link">
|
|
||||||
Turn on the lights when the sun is set
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<button class="link">
|
|
||||||
Notify me if the door opens and I am not at home
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<button class="link">
|
|
||||||
Turn the light on when motion is detected
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<ha-textfield
|
|
||||||
id="input"
|
|
||||||
label="What should this automation do?"
|
|
||||||
.value=${this._value}
|
|
||||||
autofocus
|
|
||||||
@keyup=${this._handleKeyUp}
|
|
||||||
></ha-textfield>
|
|
||||||
<a
|
|
||||||
href="https://almond.stanford.edu/"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
class="attribution"
|
|
||||||
>Powered by Almond</a
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<mwc-button class="left" @click=${this._skip} slot="secondaryAction">
|
|
||||||
${this.hass.localize(`ui.common.skip`)}
|
|
||||||
</mwc-button>
|
|
||||||
<mwc-button
|
|
||||||
@click=${this._generate}
|
|
||||||
.disabled=${this._submitting}
|
|
||||||
slot="primaryAction"
|
|
||||||
>
|
|
||||||
${this._submitting
|
|
||||||
? html`<ha-circular-progress
|
|
||||||
active
|
|
||||||
size="small"
|
|
||||||
title="Creating your automation..."
|
|
||||||
></ha-circular-progress>`
|
|
||||||
: ""}
|
|
||||||
${this.hass.localize(`ui.panel.config.automation.thingtalk.create`)}
|
|
||||||
</mwc-button>
|
|
||||||
</ha-dialog>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _generate() {
|
|
||||||
this._value = this._input!.value as string;
|
|
||||||
if (!this._value) {
|
|
||||||
this._error = this.hass.localize(
|
|
||||||
`ui.panel.config.automation.thingtalk.task_selection.error_empty`
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._submitting = true;
|
|
||||||
let config: Partial<AutomationConfig>;
|
|
||||||
let placeholders: PlaceholderContainer;
|
|
||||||
try {
|
|
||||||
const result = await convertThingTalk(this.hass, this._value);
|
|
||||||
config = result.config;
|
|
||||||
placeholders = result.placeholders;
|
|
||||||
} catch (err: any) {
|
|
||||||
this._error = err.message;
|
|
||||||
this._submitting = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._submitting = false;
|
|
||||||
|
|
||||||
if (!Object.keys(config).length) {
|
|
||||||
this._error = this.hass.localize(
|
|
||||||
`ui.panel.config.automation.thingtalk.task_selection.error_unsupported`
|
|
||||||
);
|
|
||||||
} else if (Object.keys(placeholders).length) {
|
|
||||||
this._config = config;
|
|
||||||
this._placeholders = placeholders;
|
|
||||||
} else {
|
|
||||||
this._sendConfig(this._value, config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _handlePlaceholders(ev: CustomEvent) {
|
|
||||||
const placeholderValues = ev.detail.value as PlaceholderValues;
|
|
||||||
Object.entries(placeholderValues).forEach(([type, values]) => {
|
|
||||||
Object.entries(values).forEach(([index, placeholder]) => {
|
|
||||||
const devices = Object.values(placeholder);
|
|
||||||
if (devices.length === 1) {
|
|
||||||
Object.entries(devices[0]).forEach(([field, value]) => {
|
|
||||||
this._config[type][index][field] = value;
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const automation = { ...this._config[type][index] };
|
|
||||||
const newAutomations: any[] = [];
|
|
||||||
devices.forEach((fields) => {
|
|
||||||
const newAutomation = { ...automation };
|
|
||||||
Object.entries(fields).forEach(([field, value]) => {
|
|
||||||
newAutomation[field] = value;
|
|
||||||
});
|
|
||||||
newAutomations.push(newAutomation);
|
|
||||||
});
|
|
||||||
this._config[type].splice(index, 1, ...newAutomations);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
this._sendConfig(this._value, this._config);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _sendConfig(input, config) {
|
|
||||||
this._params!.callback({ alias: input, ...config });
|
|
||||||
this.closeDialog();
|
|
||||||
}
|
|
||||||
|
|
||||||
private _skip = () => {
|
|
||||||
this._params!.callback(undefined);
|
|
||||||
this.closeDialog();
|
|
||||||
};
|
|
||||||
|
|
||||||
private _handleKeyUp(ev: KeyboardEvent) {
|
|
||||||
if (ev.key === "Enter") {
|
|
||||||
this._generate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _handleExampleClick(ev: Event) {
|
|
||||||
this._input!.value = (ev.target as HTMLAnchorElement).innerText;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return [
|
|
||||||
haStyle,
|
|
||||||
haStyleDialog,
|
|
||||||
css`
|
|
||||||
ha-dialog {
|
|
||||||
max-width: 500px;
|
|
||||||
}
|
|
||||||
mwc-button.left {
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
.error {
|
|
||||||
color: var(--error-color);
|
|
||||||
}
|
|
||||||
.attribution {
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ha-dialog-thinktalk": DialogThingtalk;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,483 +0,0 @@
|
|||||||
/* eslint-disable lit/no-template-arrow */
|
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
|
||||||
import {
|
|
||||||
css,
|
|
||||||
CSSResultGroup,
|
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
PropertyValues,
|
|
||||||
TemplateResult,
|
|
||||||
} from "lit";
|
|
||||||
import { customElement, property, state } from "lit/decorators";
|
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
|
||||||
import { computeDomain } from "../../../../common/entity/compute_domain";
|
|
||||||
import { applyPatch, getPath } from "../../../../common/util/patch";
|
|
||||||
import "../../../../components/device/ha-area-devices-picker";
|
|
||||||
import "../../../../components/entity/ha-entity-picker";
|
|
||||||
import {
|
|
||||||
AreaRegistryEntry,
|
|
||||||
subscribeAreaRegistry,
|
|
||||||
} from "../../../../data/area_registry";
|
|
||||||
import {
|
|
||||||
DeviceRegistryEntry,
|
|
||||||
subscribeDeviceRegistry,
|
|
||||||
} from "../../../../data/device_registry";
|
|
||||||
import { subscribeEntityRegistry } from "../../../../data/entity_registry";
|
|
||||||
import { domainToName } from "../../../../data/integration";
|
|
||||||
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
|
||||||
import { haStyleDialog } from "../../../../resources/styles";
|
|
||||||
import { HomeAssistant } from "../../../../types";
|
|
||||||
import { Placeholder, PlaceholderContainer } from "./dialog-thingtalk";
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
// for fire event
|
|
||||||
interface HASSDomEvents {
|
|
||||||
"placeholders-filled": { value: PlaceholderValues };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PlaceholderValues {
|
|
||||||
[key: string]: {
|
|
||||||
[index: number]: {
|
|
||||||
[index: number]: { device_id?: string; entity_id?: string };
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ExtraInfo {
|
|
||||||
[key: string]: {
|
|
||||||
[index: number]: {
|
|
||||||
[index: number]: {
|
|
||||||
area_id?: string;
|
|
||||||
device_ids?: string[];
|
|
||||||
manualEntity: boolean;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DeviceEntitiesLookup {
|
|
||||||
[deviceId: string]: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
@customElement("ha-thingtalk-placeholders")
|
|
||||||
export class ThingTalkPlaceholders extends SubscribeMixin(LitElement) {
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@property() public opened!: boolean;
|
|
||||||
|
|
||||||
public skip!: () => void;
|
|
||||||
|
|
||||||
@property() public placeholders!: PlaceholderContainer;
|
|
||||||
|
|
||||||
@state() private _error?: string;
|
|
||||||
|
|
||||||
private _deviceEntityLookup: DeviceEntitiesLookup = {};
|
|
||||||
|
|
||||||
@state() private _extraInfo: ExtraInfo = {};
|
|
||||||
|
|
||||||
@state() private _placeholderValues: PlaceholderValues = {};
|
|
||||||
|
|
||||||
private _devices?: DeviceRegistryEntry[];
|
|
||||||
|
|
||||||
private _areas?: AreaRegistryEntry[];
|
|
||||||
|
|
||||||
private _search = false;
|
|
||||||
|
|
||||||
public hassSubscribe() {
|
|
||||||
return [
|
|
||||||
subscribeEntityRegistry(this.hass.connection, (entries) => {
|
|
||||||
for (const entity of entries) {
|
|
||||||
if (!entity.device_id) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!(entity.device_id in this._deviceEntityLookup)) {
|
|
||||||
this._deviceEntityLookup[entity.device_id] = [];
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
!this._deviceEntityLookup[entity.device_id].includes(
|
|
||||||
entity.entity_id
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
this._deviceEntityLookup[entity.device_id].push(entity.entity_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
subscribeDeviceRegistry(this.hass.connection!, (devices) => {
|
|
||||||
this._devices = devices;
|
|
||||||
this._searchNames();
|
|
||||||
}),
|
|
||||||
subscribeAreaRegistry(this.hass.connection!, (areas) => {
|
|
||||||
this._areas = areas;
|
|
||||||
this._searchNames();
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
protected updated(changedProps: PropertyValues) {
|
|
||||||
if (changedProps.has("placeholders")) {
|
|
||||||
this._search = true;
|
|
||||||
this._searchNames();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
|
||||||
return html`
|
|
||||||
<ha-dialog
|
|
||||||
open
|
|
||||||
scrimClickAction
|
|
||||||
.heading=${this.hass.localize(
|
|
||||||
`ui.panel.config.automation.thingtalk.link_devices.header`
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
|
|
||||||
${Object.entries(this.placeholders).map(
|
|
||||||
([type, placeholders]) => html`
|
|
||||||
<h3>
|
|
||||||
${this.hass.localize(
|
|
||||||
`ui.panel.config.automation.editor.${type}s.name`
|
|
||||||
)}:
|
|
||||||
</h3>
|
|
||||||
${placeholders.map((placeholder) => {
|
|
||||||
if (placeholder.fields.includes("device_id")) {
|
|
||||||
const extraInfo = getPath(this._extraInfo, [
|
|
||||||
type,
|
|
||||||
placeholder.index,
|
|
||||||
]);
|
|
||||||
return html`
|
|
||||||
<ha-area-devices-picker
|
|
||||||
.type=${type}
|
|
||||||
.placeholder=${placeholder}
|
|
||||||
@value-changed=${this._devicePicked}
|
|
||||||
.hass=${this.hass}
|
|
||||||
.area=${extraInfo ? extraInfo.area_id : undefined}
|
|
||||||
.devices=${extraInfo && extraInfo.device_ids
|
|
||||||
? extraInfo.device_ids
|
|
||||||
: undefined}
|
|
||||||
.includeDomains=${placeholder.domains}
|
|
||||||
.includeDeviceClasses=${placeholder.device_classes}
|
|
||||||
.label=${this._getLabel(
|
|
||||||
placeholder.domains,
|
|
||||||
placeholder.device_classes
|
|
||||||
)}
|
|
||||||
></ha-area-devices-picker>
|
|
||||||
${extraInfo && extraInfo.manualEntity
|
|
||||||
? html`
|
|
||||||
<h3>
|
|
||||||
${this.hass.localize(
|
|
||||||
`ui.panel.config.automation.thingtalk.link_devices.ambiguous_entities`
|
|
||||||
)}
|
|
||||||
</h3>
|
|
||||||
${Object.keys(extraInfo.manualEntity).map(
|
|
||||||
(idx) => html`
|
|
||||||
<ha-entity-picker
|
|
||||||
id="device-entity-picker"
|
|
||||||
.type=${type}
|
|
||||||
.placeholder=${placeholder}
|
|
||||||
.index=${idx}
|
|
||||||
@change=${this._entityPicked}
|
|
||||||
.includeDomains=${placeholder.domains}
|
|
||||||
.includeDeviceClasses=${placeholder.device_classes}
|
|
||||||
.hass=${this.hass}
|
|
||||||
.label=${`${this._getLabel(
|
|
||||||
placeholder.domains,
|
|
||||||
placeholder.device_classes
|
|
||||||
)} of device ${this._getDeviceName(
|
|
||||||
getPath(this._placeholderValues, [
|
|
||||||
type,
|
|
||||||
placeholder.index,
|
|
||||||
idx,
|
|
||||||
"device_id",
|
|
||||||
])
|
|
||||||
)}`}
|
|
||||||
.entityFilter=${(entityState: HassEntity) => {
|
|
||||||
const devId =
|
|
||||||
this._placeholderValues[type][
|
|
||||||
placeholder.index
|
|
||||||
][idx].device_id;
|
|
||||||
return this._deviceEntityLookup[
|
|
||||||
devId
|
|
||||||
].includes(entityState.entity_id);
|
|
||||||
}}
|
|
||||||
></ha-entity-picker>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
if (placeholder.fields.includes("entity_id")) {
|
|
||||||
return html`
|
|
||||||
<ha-entity-picker
|
|
||||||
.type=${type}
|
|
||||||
.placeholder=${placeholder}
|
|
||||||
@change=${this._entityPicked}
|
|
||||||
.includeDomains=${placeholder.domains}
|
|
||||||
.includeDeviceClasses=${placeholder.device_classes}
|
|
||||||
.hass=${this.hass}
|
|
||||||
.label=${this._getLabel(
|
|
||||||
placeholder.domains,
|
|
||||||
placeholder.device_classes
|
|
||||||
)}
|
|
||||||
></ha-entity-picker>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
return html`
|
|
||||||
<div class="error">
|
|
||||||
${this.hass.localize(
|
|
||||||
`ui.panel.config.automation.thingtalk.link_devices.unknown_placeholder`
|
|
||||||
)}<br />
|
|
||||||
${placeholder.domains}<br />
|
|
||||||
${placeholder.fields.map((field) => html` ${field}<br /> `)}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
})}
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<mwc-button @click=${this.skip} slot="secondaryAction">
|
|
||||||
${this.hass.localize(`ui.common.skip`)}
|
|
||||||
</mwc-button>
|
|
||||||
<mwc-button
|
|
||||||
@click=${this._done}
|
|
||||||
.disabled=${!this._isDone}
|
|
||||||
slot="primaryAction"
|
|
||||||
>
|
|
||||||
${this.hass.localize(`ui.panel.config.automation.thingtalk.create`)}
|
|
||||||
</mwc-button>
|
|
||||||
</ha-dialog>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _getDeviceName(deviceId: string): string {
|
|
||||||
if (!this._devices) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
const foundDevice = this._devices.find((device) => device.id === deviceId);
|
|
||||||
if (!foundDevice) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return foundDevice.name_by_user || foundDevice.name || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private _searchNames() {
|
|
||||||
if (!this._search || !this._areas || !this._devices) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._search = false;
|
|
||||||
Object.entries(this.placeholders).forEach(([type, placeholders]) =>
|
|
||||||
placeholders.forEach((placeholder) => {
|
|
||||||
if (!placeholder.name) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const name = placeholder.name;
|
|
||||||
const foundArea = this._areas!.find((area) =>
|
|
||||||
area.name.toLowerCase().includes(name)
|
|
||||||
);
|
|
||||||
if (foundArea) {
|
|
||||||
applyPatch(
|
|
||||||
this._extraInfo,
|
|
||||||
[type, placeholder.index, "area_id"],
|
|
||||||
foundArea.area_id
|
|
||||||
);
|
|
||||||
this.requestUpdate("_extraInfo");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const foundDevices = this._devices!.filter((device) => {
|
|
||||||
const deviceName = device.name_by_user || device.name;
|
|
||||||
if (!deviceName) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return deviceName.toLowerCase().includes(name);
|
|
||||||
});
|
|
||||||
if (foundDevices.length) {
|
|
||||||
applyPatch(
|
|
||||||
this._extraInfo,
|
|
||||||
[type, placeholder.index, "device_ids"],
|
|
||||||
foundDevices.map((device) => device.id)
|
|
||||||
);
|
|
||||||
this.requestUpdate("_extraInfo");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private get _isDone(): boolean {
|
|
||||||
return Object.entries(this.placeholders).every(([type, placeholders]) =>
|
|
||||||
placeholders.every((placeholder) =>
|
|
||||||
placeholder.fields.every((field) => {
|
|
||||||
const entries: {
|
|
||||||
[key: number]: { device_id?: string; entity_id?: string };
|
|
||||||
} = getPath(this._placeholderValues, [type, placeholder.index]);
|
|
||||||
if (!entries) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const values = Object.values(entries);
|
|
||||||
return values.every(
|
|
||||||
(entry) => entry[field] !== undefined && entry[field] !== ""
|
|
||||||
);
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _getLabel(domains: string[], deviceClasses?: string[]) {
|
|
||||||
return `${domains
|
|
||||||
.map((domain) => domainToName(this.hass.localize, domain))
|
|
||||||
.join(", ")}${
|
|
||||||
deviceClasses ? ` of type ${deviceClasses.join(", ")}` : ""
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _devicePicked(ev: CustomEvent): void {
|
|
||||||
const value: string[] = ev.detail.value;
|
|
||||||
if (!value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const target = ev.target as any;
|
|
||||||
const placeholder = target.placeholder as Placeholder;
|
|
||||||
const type = target.type;
|
|
||||||
|
|
||||||
let oldValues = getPath(this._placeholderValues, [type, placeholder.index]);
|
|
||||||
if (oldValues) {
|
|
||||||
oldValues = Object.values(oldValues);
|
|
||||||
}
|
|
||||||
const oldExtraInfo = getPath(this._extraInfo, [type, placeholder.index]);
|
|
||||||
|
|
||||||
if (this._placeholderValues[type]) {
|
|
||||||
delete this._placeholderValues[type][placeholder.index];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._extraInfo[type]) {
|
|
||||||
delete this._extraInfo[type][placeholder.index];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!value.length) {
|
|
||||||
this.requestUpdate("_placeholderValues");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
value.forEach((deviceId, index) => {
|
|
||||||
let oldIndex;
|
|
||||||
if (oldValues) {
|
|
||||||
const oldDevice = oldValues.find((oldVal, idx) => {
|
|
||||||
oldIndex = idx;
|
|
||||||
return oldVal.device_id === deviceId;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (oldDevice) {
|
|
||||||
applyPatch(
|
|
||||||
this._placeholderValues,
|
|
||||||
[type, placeholder.index, index],
|
|
||||||
oldDevice
|
|
||||||
);
|
|
||||||
if (oldExtraInfo) {
|
|
||||||
applyPatch(
|
|
||||||
this._extraInfo,
|
|
||||||
[type, placeholder.index, index],
|
|
||||||
oldExtraInfo[oldIndex]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
applyPatch(
|
|
||||||
this._placeholderValues,
|
|
||||||
[type, placeholder.index, index, "device_id"],
|
|
||||||
deviceId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!placeholder.fields.includes("entity_id")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const devEntities = this._deviceEntityLookup[deviceId];
|
|
||||||
|
|
||||||
const entities = devEntities.filter((eid) => {
|
|
||||||
if (placeholder.device_classes) {
|
|
||||||
const stateObj = this.hass.states[eid];
|
|
||||||
if (!stateObj) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
placeholder.domains.includes(computeDomain(eid)) &&
|
|
||||||
stateObj.attributes.device_class &&
|
|
||||||
placeholder.device_classes.includes(
|
|
||||||
stateObj.attributes.device_class
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return placeholder.domains.includes(computeDomain(eid));
|
|
||||||
});
|
|
||||||
if (entities.length === 0) {
|
|
||||||
// Should not happen because we filter the device picker on domain
|
|
||||||
this._error = `No ${placeholder.domains
|
|
||||||
.map((domain) => domainToName(this.hass.localize, domain))
|
|
||||||
.join(", ")} entities found in this device.`;
|
|
||||||
} else if (entities.length === 1) {
|
|
||||||
applyPatch(
|
|
||||||
this._placeholderValues,
|
|
||||||
[type, placeholder.index, index, "entity_id"],
|
|
||||||
entities[0]
|
|
||||||
);
|
|
||||||
this.requestUpdate("_placeholderValues");
|
|
||||||
} else {
|
|
||||||
delete this._placeholderValues[type][placeholder.index][index]
|
|
||||||
.entity_id;
|
|
||||||
applyPatch(
|
|
||||||
this._extraInfo,
|
|
||||||
[type, placeholder.index, "manualEntity", index],
|
|
||||||
true
|
|
||||||
);
|
|
||||||
this.requestUpdate("_placeholderValues");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _entityPicked(ev: Event): void {
|
|
||||||
const target = ev.target as any;
|
|
||||||
const placeholder = target.placeholder as Placeholder;
|
|
||||||
const value = target.value;
|
|
||||||
const type = target.type;
|
|
||||||
const index = target.index || 0;
|
|
||||||
applyPatch(
|
|
||||||
this._placeholderValues,
|
|
||||||
[type, placeholder.index, index, "entity_id"],
|
|
||||||
value
|
|
||||||
);
|
|
||||||
this.requestUpdate("_placeholderValues");
|
|
||||||
}
|
|
||||||
|
|
||||||
private _done(): void {
|
|
||||||
fireEvent(this, "placeholders-filled", { value: this._placeholderValues });
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return [
|
|
||||||
haStyleDialog,
|
|
||||||
css`
|
|
||||||
ha-dialog {
|
|
||||||
max-width: 500px;
|
|
||||||
}
|
|
||||||
mwc-button.left {
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
h3 {
|
|
||||||
margin: 10px 0 0 0;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
.error {
|
|
||||||
color: var(--error-color);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ha-thingtalk-placeholders": ThingTalkPlaceholders;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
|
||||||
import { AutomationConfig } from "../../../../data/automation";
|
|
||||||
|
|
||||||
export interface ThingtalkDialogParams {
|
|
||||||
callback: (config: Partial<AutomationConfig> | undefined) => void;
|
|
||||||
input?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const loadThingtalkDialog = () => import("./dialog-thingtalk");
|
|
||||||
|
|
||||||
export const showThingtalkDialog = (
|
|
||||||
element: HTMLElement,
|
|
||||||
dialogParams: ThingtalkDialogParams
|
|
||||||
): void => {
|
|
||||||
fireEvent(element, "show-dialog", {
|
|
||||||
dialogTag: "ha-dialog-thinktalk",
|
|
||||||
dialogImport: loadThingtalkDialog,
|
|
||||||
dialogParams,
|
|
||||||
});
|
|
||||||
};
|
|
@ -3,39 +3,41 @@ import type { ActionDetail } from "@material/mwc-list";
|
|||||||
import {
|
import {
|
||||||
mdiArrowDown,
|
mdiArrowDown,
|
||||||
mdiArrowUp,
|
mdiArrowUp,
|
||||||
|
mdiContentPaste,
|
||||||
mdiDrag,
|
mdiDrag,
|
||||||
mdiPlus,
|
mdiPlus,
|
||||||
mdiContentPaste,
|
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import deepClone from "deep-clone-simple";
|
import deepClone from "deep-clone-simple";
|
||||||
import {
|
import {
|
||||||
css,
|
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
html,
|
|
||||||
LitElement,
|
LitElement,
|
||||||
nothing,
|
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
|
css,
|
||||||
|
html,
|
||||||
|
nothing,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { repeat } from "lit/directives/repeat";
|
import { repeat } from "lit/directives/repeat";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import type { SortableEvent } from "sortablejs";
|
import type { SortableEvent } from "sortablejs";
|
||||||
|
import { storage } from "../../../../common/decorators/storage";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import { stringCompare } from "../../../../common/string/compare";
|
import { stringCompare } from "../../../../common/string/compare";
|
||||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||||
import "../../../../components/ha-button-menu";
|
|
||||||
import "../../../../components/ha-button";
|
import "../../../../components/ha-button";
|
||||||
|
import "../../../../components/ha-button-menu";
|
||||||
import type { HaSelect } from "../../../../components/ha-select";
|
import type { HaSelect } from "../../../../components/ha-select";
|
||||||
import "../../../../components/ha-svg-icon";
|
import "../../../../components/ha-svg-icon";
|
||||||
import { Trigger, AutomationClipboard } from "../../../../data/automation";
|
import { AutomationClipboard, Trigger } from "../../../../data/automation";
|
||||||
import { TRIGGER_TYPES } from "../../../../data/trigger";
|
import { TRIGGER_TYPES } from "../../../../data/trigger";
|
||||||
import { sortableStyles } from "../../../../resources/ha-sortable-style";
|
import { sortableStyles } from "../../../../resources/ha-sortable-style";
|
||||||
import { SortableInstance } from "../../../../resources/sortable";
|
import { SortableInstance } from "../../../../resources/sortable";
|
||||||
import { loadSortable } from "../../../../resources/sortable.ondemand";
|
import { loadSortable } from "../../../../resources/sortable.ondemand";
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { Entries, HomeAssistant } from "../../../../types";
|
||||||
import "./ha-automation-trigger-row";
|
import "./ha-automation-trigger-row";
|
||||||
import type HaAutomationTriggerRow from "./ha-automation-trigger-row";
|
import type HaAutomationTriggerRow from "./ha-automation-trigger-row";
|
||||||
import "./types/ha-automation-trigger-calendar";
|
import "./types/ha-automation-trigger-calendar";
|
||||||
|
import "./types/ha-automation-trigger-conversation";
|
||||||
import "./types/ha-automation-trigger-device";
|
import "./types/ha-automation-trigger-device";
|
||||||
import "./types/ha-automation-trigger-event";
|
import "./types/ha-automation-trigger-event";
|
||||||
import "./types/ha-automation-trigger-geo_location";
|
import "./types/ha-automation-trigger-geo_location";
|
||||||
@ -43,7 +45,6 @@ import "./types/ha-automation-trigger-homeassistant";
|
|||||||
import "./types/ha-automation-trigger-mqtt";
|
import "./types/ha-automation-trigger-mqtt";
|
||||||
import "./types/ha-automation-trigger-numeric_state";
|
import "./types/ha-automation-trigger-numeric_state";
|
||||||
import "./types/ha-automation-trigger-persistent_notification";
|
import "./types/ha-automation-trigger-persistent_notification";
|
||||||
import "./types/ha-automation-trigger-conversation";
|
|
||||||
import "./types/ha-automation-trigger-state";
|
import "./types/ha-automation-trigger-state";
|
||||||
import "./types/ha-automation-trigger-sun";
|
import "./types/ha-automation-trigger-sun";
|
||||||
import "./types/ha-automation-trigger-tag";
|
import "./types/ha-automation-trigger-tag";
|
||||||
@ -52,7 +53,6 @@ import "./types/ha-automation-trigger-time";
|
|||||||
import "./types/ha-automation-trigger-time_pattern";
|
import "./types/ha-automation-trigger-time_pattern";
|
||||||
import "./types/ha-automation-trigger-webhook";
|
import "./types/ha-automation-trigger-webhook";
|
||||||
import "./types/ha-automation-trigger-zone";
|
import "./types/ha-automation-trigger-zone";
|
||||||
import { storage } from "../../../../common/decorators/storage";
|
|
||||||
|
|
||||||
const PASTE_VALUE = "__paste__";
|
const PASTE_VALUE = "__paste__";
|
||||||
|
|
||||||
@ -339,7 +339,7 @@ export default class HaAutomationTrigger extends LitElement {
|
|||||||
|
|
||||||
private _processedTypes = memoizeOne(
|
private _processedTypes = memoizeOne(
|
||||||
(localize: LocalizeFunc): [string, string, string][] =>
|
(localize: LocalizeFunc): [string, string, string][] =>
|
||||||
Object.entries(TRIGGER_TYPES)
|
(Object.entries(TRIGGER_TYPES) as Entries<typeof TRIGGER_TYPES>)
|
||||||
.map(
|
.map(
|
||||||
([action, icon]) =>
|
([action, icon]) =>
|
||||||
[
|
[
|
||||||
|
@ -123,10 +123,17 @@ export class HaCalendarTrigger extends LitElement implements TriggerElement {
|
|||||||
|
|
||||||
private _computeLabelCallback = (
|
private _computeLabelCallback = (
|
||||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||||
): string =>
|
): string => {
|
||||||
this.hass.localize(
|
switch (schema.name) {
|
||||||
`ui.panel.config.automation.editor.triggers.type.calendar.${schema.name}`
|
case "entity_id":
|
||||||
);
|
return this.hass.localize("ui.components.entity.entity-picker.entity");
|
||||||
|
case "event":
|
||||||
|
return this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.triggers.type.calendar.event"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -27,12 +27,7 @@ import { documentationUrl } from "../../../util/documentation-url";
|
|||||||
const JS_TYPE = __BUILD__;
|
const JS_TYPE = __BUILD__;
|
||||||
const JS_VERSION = __VERSION__;
|
const JS_VERSION = __VERSION__;
|
||||||
|
|
||||||
const PAGES: Array<{
|
const PAGES = [
|
||||||
name: string;
|
|
||||||
path: string;
|
|
||||||
iconPath: string;
|
|
||||||
iconColor: string;
|
|
||||||
}> = [
|
|
||||||
{
|
{
|
||||||
name: "change_log",
|
name: "change_log",
|
||||||
path: "/latest-release-notes/",
|
path: "/latest-release-notes/",
|
||||||
@ -75,7 +70,12 @@ const PAGES: Array<{
|
|||||||
iconPath: mdiFileDocument,
|
iconPath: mdiFileDocument,
|
||||||
iconColor: "#518C43",
|
iconColor: "#518C43",
|
||||||
},
|
},
|
||||||
];
|
] as const satisfies readonly {
|
||||||
|
name: string;
|
||||||
|
path: string;
|
||||||
|
iconPath: string;
|
||||||
|
iconColor: string;
|
||||||
|
}[];
|
||||||
|
|
||||||
class HaConfigInfo extends LitElement {
|
class HaConfigInfo extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
@ -38,6 +38,15 @@ import { HomeAssistant, Route } from "../../../../types";
|
|||||||
import { lovelaceTabs } from "../ha-config-lovelace";
|
import { lovelaceTabs } from "../ha-config-lovelace";
|
||||||
import { showDashboardDetailDialog } from "./show-dialog-lovelace-dashboard-detail";
|
import { showDashboardDetailDialog } from "./show-dialog-lovelace-dashboard-detail";
|
||||||
|
|
||||||
|
type DataTableItem = Pick<
|
||||||
|
LovelaceDashboard,
|
||||||
|
"icon" | "title" | "show_in_sidebar" | "require_admin" | "mode" | "url_path"
|
||||||
|
> & {
|
||||||
|
default: boolean;
|
||||||
|
filename: string;
|
||||||
|
iconColor?: string;
|
||||||
|
};
|
||||||
|
|
||||||
@customElement("ha-config-lovelace-dashboards")
|
@customElement("ha-config-lovelace-dashboards")
|
||||||
export class HaConfigLovelaceDashboards extends LitElement {
|
export class HaConfigLovelaceDashboards extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@ -52,14 +61,14 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
|||||||
|
|
||||||
private _columns = memoize(
|
private _columns = memoize(
|
||||||
(narrow: boolean, _language, dashboards): DataTableColumnContainer => {
|
(narrow: boolean, _language, dashboards): DataTableColumnContainer => {
|
||||||
const columns: DataTableColumnContainer = {
|
const columns: DataTableColumnContainer<DataTableItem> = {
|
||||||
icon: {
|
icon: {
|
||||||
title: "",
|
title: "",
|
||||||
label: this.hass.localize(
|
label: this.hass.localize(
|
||||||
"ui.panel.config.lovelace.dashboards.picker.headers.icon"
|
"ui.panel.config.lovelace.dashboards.picker.headers.icon"
|
||||||
),
|
),
|
||||||
type: "icon",
|
type: "icon",
|
||||||
template: (icon, dashboard) =>
|
template: (icon: DataTableItem["icon"], dashboard) =>
|
||||||
icon
|
icon
|
||||||
? html`
|
? html`
|
||||||
<ha-icon
|
<ha-icon
|
||||||
@ -82,7 +91,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
grows: true,
|
grows: true,
|
||||||
template: (title, dashboard: any) => {
|
template: (title: DataTableItem["title"], dashboard) => {
|
||||||
const titleTemplate = html`
|
const titleTemplate = html`
|
||||||
${title}
|
${title}
|
||||||
${dashboard.default
|
${dashboard.default
|
||||||
@ -123,7 +132,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
width: "20%",
|
width: "20%",
|
||||||
template: (mode) => html`
|
template: (mode: DataTableItem["mode"]) => html`
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
`ui.panel.config.lovelace.dashboards.conf_mode.${mode}`
|
`ui.panel.config.lovelace.dashboards.conf_mode.${mode}`
|
||||||
) || mode}
|
) || mode}
|
||||||
@ -146,7 +155,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
type: "icon",
|
type: "icon",
|
||||||
width: "100px",
|
width: "100px",
|
||||||
template: (requireAdmin: boolean) =>
|
template: (requireAdmin: DataTableItem["require_admin"]) =>
|
||||||
requireAdmin
|
requireAdmin
|
||||||
? html`<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>`
|
? html`<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>`
|
||||||
: html`—`,
|
: html`—`,
|
||||||
@ -157,7 +166,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
|||||||
),
|
),
|
||||||
type: "icon",
|
type: "icon",
|
||||||
width: "121px",
|
width: "121px",
|
||||||
template: (sidebar) =>
|
template: (sidebar: DataTableItem["show_in_sidebar"]) =>
|
||||||
sidebar
|
sidebar
|
||||||
? html`<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>`
|
? html`<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>`
|
||||||
: html`—`,
|
: html`—`,
|
||||||
@ -202,7 +211,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
|||||||
).mode;
|
).mode;
|
||||||
const defaultUrlPath = this.hass.defaultPanel;
|
const defaultUrlPath = this.hass.defaultPanel;
|
||||||
const isDefault = defaultUrlPath === "lovelace";
|
const isDefault = defaultUrlPath === "lovelace";
|
||||||
const result: Record<string, any>[] = [
|
const result: DataTableItem[] = [
|
||||||
{
|
{
|
||||||
icon: "hass:view-dashboard",
|
icon: "hass:view-dashboard",
|
||||||
title: this.hass.localize("panel.states"),
|
title: this.hass.localize("panel.states"),
|
||||||
@ -224,6 +233,8 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
|||||||
url_path: "energy",
|
url_path: "energy",
|
||||||
filename: "",
|
filename: "",
|
||||||
iconColor: "var(--label-badge-yellow)",
|
iconColor: "var(--label-badge-yellow)",
|
||||||
|
default: false,
|
||||||
|
require_admin: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ export class HaConfigLovelaceRescources extends LitElement {
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
width: "30%",
|
width: "30%",
|
||||||
template: (type) => html`
|
template: (type: LovelaceResource["type"]) => html`
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
`ui.panel.config.lovelace.resources.types.${type}`
|
`ui.panel.config.lovelace.resources.types.${type}`
|
||||||
) || type}
|
) || type}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
import "@material/mwc-list/mwc-list";
|
import "@material/mwc-list/mwc-list";
|
||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import "@material/mwc-tab";
|
|
||||||
import "@material/mwc-tab-bar";
|
|
||||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import "../../../components/ha-alert";
|
import "../../../components/ha-alert";
|
||||||
|
@ -443,9 +443,6 @@ export class HaSceneEditor extends SubscribeMixin(
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.scene.editor.entities.device_entities"
|
|
||||||
)}
|
|
||||||
<ha-entity-picker
|
<ha-entity-picker
|
||||||
@value-changed=${this._entityPicked}
|
@value-changed=${this._entityPicked}
|
||||||
.excludeDomains=${SCENE_IGNORED_DOMAINS}
|
.excludeDomains=${SCENE_IGNORED_DOMAINS}
|
||||||
|
@ -55,7 +55,7 @@ import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box
|
|||||||
import "../../../layouts/hass-subpage";
|
import "../../../layouts/hass-subpage";
|
||||||
import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin";
|
import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin";
|
||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import type { HomeAssistant, Route } from "../../../types";
|
import type { Entries, HomeAssistant, Route } from "../../../types";
|
||||||
import { documentationUrl } from "../../../util/documentation-url";
|
import { documentationUrl } from "../../../util/documentation-url";
|
||||||
import { showToast } from "../../../util/toast";
|
import { showToast } from "../../../util/toast";
|
||||||
import "./blueprint-script-editor";
|
import "./blueprint-script-editor";
|
||||||
@ -529,7 +529,9 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
const validation = await validateConfig(this.hass, {
|
const validation = await validateConfig(this.hass, {
|
||||||
action: this._config.sequence,
|
action: this._config.sequence,
|
||||||
});
|
});
|
||||||
this._validationErrors = Object.entries(validation).map(([key, value]) =>
|
this._validationErrors = (
|
||||||
|
Object.entries(validation) as Entries<typeof validation>
|
||||||
|
).map(([key, value]) =>
|
||||||
value.valid
|
value.valid
|
||||||
? ""
|
? ""
|
||||||
: html`${this.hass.localize(
|
: html`${this.hass.localize(
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import "@material/mwc-tab-bar/mwc-tab-bar";
|
|
||||||
import "@material/mwc-tab/mwc-tab";
|
|
||||||
import Fuse from "fuse.js";
|
import Fuse from "fuse.js";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
|
@ -261,6 +261,9 @@
|
|||||||
"forecast_daily": "Forecast daily",
|
"forecast_daily": "Forecast daily",
|
||||||
"forecast_hourly": "Forecast hourly",
|
"forecast_hourly": "Forecast hourly",
|
||||||
"forecast_twice_daily": "Forecast twice daily",
|
"forecast_twice_daily": "Forecast twice daily",
|
||||||
|
"daily": "Daily",
|
||||||
|
"hourly": "Hourly",
|
||||||
|
"twice_daily": "Twice daily",
|
||||||
"high": "High",
|
"high": "High",
|
||||||
"low": "Low"
|
"low": "Low"
|
||||||
}
|
}
|
||||||
@ -2245,6 +2248,7 @@
|
|||||||
"duplicate": "[%key:ui::common::duplicate%]",
|
"duplicate": "[%key:ui::common::duplicate%]",
|
||||||
"disabled": "Disabled",
|
"disabled": "Disabled",
|
||||||
"filtered_by_blueprint": "blueprint: {name}",
|
"filtered_by_blueprint": "blueprint: {name}",
|
||||||
|
"traces_not_available": "[%key:ui::panel::config::automation::editor::traces_not_available%]",
|
||||||
"headers": {
|
"headers": {
|
||||||
"toggle": "Enable/disable",
|
"toggle": "Enable/disable",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
@ -2815,26 +2819,13 @@
|
|||||||
"description": {
|
"description": {
|
||||||
"full": "Test {condition}"
|
"full": "Test {condition}"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"unknown": {
|
||||||
|
"label": "Unknown"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"thingtalk": {
|
|
||||||
"create": "Create automation",
|
|
||||||
"task_selection": {
|
|
||||||
"header": "Create a new automation",
|
|
||||||
"introduction": "Type below what this automation should do, and we will try to convert it into a Home Assistant automation.",
|
|
||||||
"language_note": "Note: Only English is supported for now.",
|
|
||||||
"for_example": "For example:",
|
|
||||||
"error_empty": "Enter a command or tap skip.",
|
|
||||||
"error_unsupported": "We couldn't create an automation for that (yet?)."
|
|
||||||
},
|
|
||||||
"link_devices": {
|
|
||||||
"header": "Great! Now we need to link some devices",
|
|
||||||
"ambiguous_entities": "One or more devices have more than one matching entity, please pick the one you want to use.",
|
|
||||||
"unknown_placeholder": "Unknown placeholder"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"trace": {
|
"trace": {
|
||||||
"refresh": "[%key:ui::common::refresh%]",
|
"refresh": "[%key:ui::common::refresh%]",
|
||||||
"download_trace": "Download trace",
|
"download_trace": "Download trace",
|
||||||
@ -2994,6 +2985,7 @@
|
|||||||
"duplicate_scene": "Duplicate scene",
|
"duplicate_scene": "Duplicate scene",
|
||||||
"duplicate": "[%key:ui::common::duplicate%]",
|
"duplicate": "[%key:ui::common::duplicate%]",
|
||||||
"headers": {
|
"headers": {
|
||||||
|
"state": "State",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"last_activated": "Last activated"
|
"last_activated": "Last activated"
|
||||||
}
|
}
|
||||||
@ -5690,6 +5682,7 @@
|
|||||||
},
|
},
|
||||||
"page-onboarding": {
|
"page-onboarding": {
|
||||||
"intro": "Are you ready to awaken your home, reclaim your privacy and join a worldwide community of tinkerers?",
|
"intro": "Are you ready to awaken your home, reclaim your privacy and join a worldwide community of tinkerers?",
|
||||||
|
"back": "Back",
|
||||||
"next": "Next",
|
"next": "Next",
|
||||||
"finish": "Finish",
|
"finish": "Finish",
|
||||||
"help": "Help",
|
"help": "Help",
|
||||||
|
@ -291,3 +291,5 @@ export type AsyncReturnType<T extends (...args: any) => any> = T extends (
|
|||||||
: T extends (...args: any) => infer U
|
: T extends (...args: any) => infer U
|
||||||
? U
|
? U
|
||||||
: never;
|
: never;
|
||||||
|
|
||||||
|
export type Entries<T> = [keyof T, T[keyof T]][];
|
||||||
|
Loading…
x
Reference in New Issue
Block a user