mirror of
https://github.com/home-assistant/frontend.git
synced 2026-05-13 04:36:53 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 94bc9d8567 | |||
| 18268c67dc | |||
| 75a2331c87 | |||
| 40fa954542 |
@@ -1,4 +1,3 @@
|
||||
/* global process */
|
||||
// Tasks to generate entry HTML
|
||||
|
||||
import {
|
||||
@@ -26,7 +25,6 @@ const SAFARI_TO_MACOS = {
|
||||
16: [11, 0, 0],
|
||||
17: [12, 0, 0],
|
||||
18: [13, 0, 0],
|
||||
26: [26, 0, 0],
|
||||
};
|
||||
|
||||
const getCommonTemplateVars = () => {
|
||||
|
||||
+4
-4
@@ -137,11 +137,11 @@
|
||||
"@bundle-stats/plugin-webpack-filter": "4.22.1",
|
||||
"@eslint/js": "10.0.1",
|
||||
"@html-eslint/eslint-plugin": "0.60.0",
|
||||
"@lokalise/node-api": "16.0.0",
|
||||
"@lokalise/node-api": "15.7.1",
|
||||
"@octokit/auth-oauth-device": "8.0.3",
|
||||
"@octokit/plugin-retry": "8.1.0",
|
||||
"@octokit/rest": "22.0.1",
|
||||
"@rsdoctor/rspack-plugin": "1.5.10",
|
||||
"@rsdoctor/rspack-plugin": "1.5.9",
|
||||
"@rspack/core": "2.0.2",
|
||||
"@rspack/dev-server": "2.0.1",
|
||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||
@@ -186,7 +186,7 @@
|
||||
"husky": "9.1.7",
|
||||
"jsdom": "29.1.1",
|
||||
"jszip": "3.10.1",
|
||||
"lint-staged": "17.0.3",
|
||||
"lint-staged": "17.0.2",
|
||||
"lit-analyzer": "2.0.3",
|
||||
"lodash.merge": "4.6.2",
|
||||
"lodash.template": "4.18.1",
|
||||
@@ -197,7 +197,7 @@
|
||||
"serve": "14.2.6",
|
||||
"sinon": "22.0.0",
|
||||
"tar": "7.5.15",
|
||||
"terser-webpack-plugin": "5.6.0",
|
||||
"terser-webpack-plugin": "5.5.0",
|
||||
"ts-lit-plugin": "2.0.2",
|
||||
"typescript": "6.0.3",
|
||||
"typescript-eslint": "8.59.2",
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
import {
|
||||
mdiBattery,
|
||||
mdiBattery10,
|
||||
mdiBattery20,
|
||||
mdiBattery30,
|
||||
mdiBattery40,
|
||||
mdiBattery50,
|
||||
mdiBattery60,
|
||||
mdiBattery70,
|
||||
mdiBattery80,
|
||||
mdiBattery90,
|
||||
mdiBatteryAlertVariantOutline,
|
||||
mdiBatteryUnknown,
|
||||
} from "@mdi/js";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
|
||||
const BATTERY_ICONS = {
|
||||
@@ -26,18 +12,6 @@ const BATTERY_ICONS = {
|
||||
90: "mdi:battery-90",
|
||||
100: "mdi:battery",
|
||||
};
|
||||
const BATTERY_ICON_PATHS = {
|
||||
10: mdiBattery10,
|
||||
20: mdiBattery20,
|
||||
30: mdiBattery30,
|
||||
40: mdiBattery40,
|
||||
50: mdiBattery50,
|
||||
60: mdiBattery60,
|
||||
70: mdiBattery70,
|
||||
80: mdiBattery80,
|
||||
90: mdiBattery90,
|
||||
100: mdiBattery,
|
||||
};
|
||||
const BATTERY_CHARGING_ICONS = {
|
||||
10: "mdi:battery-charging-10",
|
||||
20: "mdi:battery-charging-20",
|
||||
@@ -83,15 +57,3 @@ export const batteryLevelIcon = (
|
||||
}
|
||||
return BATTERY_ICONS[batteryRound];
|
||||
};
|
||||
|
||||
export const batteryLevelIconPath = (batteryLevel: number | string): string => {
|
||||
const batteryValue = Number(batteryLevel);
|
||||
if (isNaN(batteryValue)) {
|
||||
return mdiBatteryUnknown;
|
||||
}
|
||||
if (batteryValue <= 5) {
|
||||
return mdiBatteryAlertVariantOutline;
|
||||
}
|
||||
const batteryRound = Math.round(batteryValue / 10) * 10;
|
||||
return BATTERY_ICON_PATHS[batteryRound];
|
||||
};
|
||||
|
||||
@@ -137,10 +137,7 @@ export const computeEntityPickerDisplay = (
|
||||
hass.floors
|
||||
);
|
||||
|
||||
const isRTL = computeRTL(
|
||||
hass.language,
|
||||
hass.translationMetadata.translations
|
||||
);
|
||||
const isRTL = computeRTL(hass);
|
||||
|
||||
const primary = entityName || deviceName || stateObj.entity_id;
|
||||
const secondary =
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
import type { LitElement } from "lit";
|
||||
import type { HomeAssistant, Translation } from "../../types";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
|
||||
export function computeRTL(
|
||||
language = "en",
|
||||
translations: Record<string, Translation>
|
||||
) {
|
||||
if (translations[language]) {
|
||||
return translations[language].isRTL || false;
|
||||
export function computeRTL(hass: HomeAssistant) {
|
||||
const lang = hass.language || "en";
|
||||
if (hass.translationMetadata.translations[lang]) {
|
||||
return hass.translationMetadata.translations[lang].isRTL || false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function computeRTLDirection(hass: HomeAssistant) {
|
||||
return emitRTLDirection(
|
||||
computeRTL(hass.language, hass.translationMetadata.translations)
|
||||
);
|
||||
return emitRTLDirection(computeRTL(hass));
|
||||
}
|
||||
|
||||
export function emitRTLDirection(rtl: boolean) {
|
||||
|
||||
@@ -293,10 +293,7 @@ export class StateHistoryChartLine extends LitElement {
|
||||
(changedProps.has("hass") &&
|
||||
this._hasEntityStatesChanged(changedProps.get("hass")))
|
||||
) {
|
||||
const rtl = computeRTL(
|
||||
this.hass.language,
|
||||
this.hass.translationMetadata.translations
|
||||
);
|
||||
const rtl = computeRTL(this.hass);
|
||||
let minYAxis: number | ((values: { min: number }) => number) | undefined =
|
||||
this.minYAxis;
|
||||
let maxYAxis: number | ((values: { max: number }) => number) | undefined =
|
||||
|
||||
@@ -144,10 +144,7 @@ export class StateHistoryChartTimeline extends LitElement {
|
||||
"ui.components.history_charts.duration"
|
||||
)}: ${millisecondsToDuration(durationInMs)}`;
|
||||
|
||||
const markerLocalized = !computeRTL(
|
||||
this.hass.language,
|
||||
this.hass.translationMetadata.translations
|
||||
)
|
||||
const markerLocalized = !computeRTL(this.hass)
|
||||
? marker
|
||||
: `<span style="direction: rtl;display:inline-block;margin-right:4px;margin-inline-end:4px;border-radius:10px;width:10px;height:10px;background-color:${color};"></span>`;
|
||||
|
||||
@@ -170,12 +167,11 @@ export class StateHistoryChartTimeline extends LitElement {
|
||||
|
||||
public willUpdate(changedProps: PropertyValues) {
|
||||
if (
|
||||
this.isConnected &&
|
||||
(changedProps.has("startTime") ||
|
||||
changedProps.has("endTime") ||
|
||||
changedProps.has("data") ||
|
||||
this._chartTime <
|
||||
new Date(this.endTime.getTime() - MIN_TIME_BETWEEN_UPDATES))
|
||||
changedProps.has("startTime") ||
|
||||
changedProps.has("endTime") ||
|
||||
changedProps.has("data") ||
|
||||
this._chartTime <
|
||||
new Date(this.endTime.getTime() - MIN_TIME_BETWEEN_UPDATES)
|
||||
) {
|
||||
// If the line is more than 5 minutes old, re-gen it
|
||||
// so the X axis grows even if there is no new data
|
||||
@@ -202,10 +198,7 @@ export class StateHistoryChartTimeline extends LitElement {
|
||||
? Math.max(this.paddingYAxis, this._yWidth)
|
||||
: 0;
|
||||
const labelMargin = 5;
|
||||
const rtl = computeRTL(
|
||||
this.hass.language,
|
||||
this.hass.translationMetadata.translations
|
||||
);
|
||||
const rtl = computeRTL(this.hass);
|
||||
this._chartOptions = {
|
||||
xAxis: {
|
||||
type: "time",
|
||||
|
||||
@@ -13,9 +13,7 @@ import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
import type { HASSDomEvent } from "../../common/dom/fire_event";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
|
||||
import { formatDate } from "../../common/datetime/format_date";
|
||||
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
|
||||
import { formatTimeWithSeconds } from "../../common/datetime/format_time";
|
||||
import {
|
||||
formatNumber,
|
||||
getNumberFormatOptions,
|
||||
@@ -243,8 +241,6 @@ export class StatisticsChart extends LitElement {
|
||||
|
||||
private _renderTooltip = (params: any) => {
|
||||
const rendered: Record<string, boolean> = {};
|
||||
const chartIsBar = this.chartType.startsWith("bar");
|
||||
const period = this.period;
|
||||
const unit = this.unit
|
||||
? `${blankBeforeUnit(this.unit, this.hass.locale)}${this.unit}`
|
||||
: "";
|
||||
@@ -256,67 +252,8 @@ export class StatisticsChart extends LitElement {
|
||||
const statisticId = this._statisticIds[param.seriesIndex];
|
||||
const stateObj = this.hass.states[statisticId];
|
||||
const entry = this.hass.entities[statisticId];
|
||||
let rawValue: string;
|
||||
let rawTime: string;
|
||||
if (chartIsBar) {
|
||||
// For bar charts value is always second value.
|
||||
rawValue = String(param.value[1]);
|
||||
// Time value is third value (un-shifted date) if given, otherwise first value
|
||||
let startTime: Date;
|
||||
let endTime: Date | undefined;
|
||||
if (param.value[2]) {
|
||||
startTime = new Date(param.value[2]);
|
||||
if (param.value[3]) {
|
||||
endTime = new Date(param.value[3]);
|
||||
}
|
||||
} else {
|
||||
startTime = new Date(param.value[0]);
|
||||
}
|
||||
if (
|
||||
period === "year" ||
|
||||
period === "month" ||
|
||||
period === "week" ||
|
||||
period === "day"
|
||||
) {
|
||||
// For year/month/day periods, show only the date
|
||||
rawTime =
|
||||
formatDate(startTime, this.hass.locale, this.hass.config) +
|
||||
(endTime && period !== "day"
|
||||
? ` – ${formatDate(
|
||||
endTime,
|
||||
this.hass.locale,
|
||||
this.hass.config
|
||||
)}`
|
||||
: "") +
|
||||
"<br>";
|
||||
} else {
|
||||
// For other time periods, include time in render, and optionally show range
|
||||
// if we have an end time.
|
||||
rawTime =
|
||||
formatDateTimeWithSeconds(
|
||||
startTime,
|
||||
this.hass.locale,
|
||||
this.hass.config
|
||||
) +
|
||||
(endTime
|
||||
? ` – ${formatTimeWithSeconds(
|
||||
endTime,
|
||||
this.hass.locale,
|
||||
this.hass.config
|
||||
)}`
|
||||
: "") +
|
||||
"<br>";
|
||||
}
|
||||
} else {
|
||||
// For lines max series can have 3 values, as the second value is the max-min to form a band
|
||||
rawValue = String(param.value[2] ?? param.value[1]);
|
||||
// Time value is always first value
|
||||
rawTime = `${formatDateTimeWithSeconds(
|
||||
new Date(param.value[0]),
|
||||
this.hass.locale,
|
||||
this.hass.config
|
||||
)} <br>`;
|
||||
}
|
||||
// max series can have 3 values, as the second value is the max-min to form a band
|
||||
const rawValue = String(param.value[2] ?? param.value[1]);
|
||||
|
||||
const options = getNumberFormatOptions(stateObj, entry) ?? {
|
||||
maximumFractionDigits: 2,
|
||||
@@ -328,7 +265,14 @@ export class StatisticsChart extends LitElement {
|
||||
options
|
||||
)}${unit}`;
|
||||
|
||||
const time = index === 0 ? rawTime : "";
|
||||
const time =
|
||||
index === 0
|
||||
? formatDateTimeWithSeconds(
|
||||
new Date(param.value[0]),
|
||||
this.hass.locale,
|
||||
this.hass.config
|
||||
) + "<br>"
|
||||
: "";
|
||||
return `${time}${param.marker} ${param.seriesName}: ${value}`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
@@ -424,12 +368,7 @@ export class StatisticsChart extends LitElement {
|
||||
nameTextStyle: {
|
||||
align: "left",
|
||||
},
|
||||
position: computeRTL(
|
||||
this.hass.language,
|
||||
this.hass.translationMetadata.translations
|
||||
)
|
||||
? "right"
|
||||
: "left",
|
||||
position: computeRTL(this.hass) ? "right" : "left",
|
||||
scale:
|
||||
this.chartType.startsWith("line") ||
|
||||
this.logarithmicScale ||
|
||||
@@ -567,53 +506,33 @@ export class StatisticsChart extends LitElement {
|
||||
const statDataSets: (LineSeriesOption | BarSeriesOption)[] = [];
|
||||
const statLegendData: typeof legendData = [];
|
||||
|
||||
// Place bars at centre of their specified time range if this is a bar chart
|
||||
// and the period is 5minute or hour.
|
||||
const centerBars =
|
||||
chartType === "bar" &&
|
||||
(this.period === "5minute" || this.period === "hour");
|
||||
|
||||
const pushData = (
|
||||
start: Date, // Data point start time
|
||||
end: Date, // Data point end time
|
||||
limit: Date, // Limit for end time (e.g. now)
|
||||
start: Date,
|
||||
end: Date,
|
||||
dataValues: (number | null)[][]
|
||||
) => {
|
||||
if (!dataValues.length) return;
|
||||
// Limit for time range is lesser of overall limit and data point end
|
||||
limit = end.getTime() < limit.getTime() ? end : limit;
|
||||
if (start.getTime() > limit.getTime()) {
|
||||
if (start > end) {
|
||||
// Drop data points that are after the requested endTime. This could happen if
|
||||
// endTime is "now" and client time is not in sync with server time.
|
||||
return;
|
||||
}
|
||||
statDataSets.forEach((d, i) => {
|
||||
if (chartType === "line") {
|
||||
if (
|
||||
prevEndTime &&
|
||||
prevValues &&
|
||||
prevEndTime.getTime() !== start.getTime()
|
||||
) {
|
||||
// if the end of the previous data doesn't match the start of the current data,
|
||||
// we have to draw a gap so add a value at the end time, and then an empty value.
|
||||
d.data!.push([prevEndTime, ...prevValues[i]!]);
|
||||
d.data!.push([prevEndTime, null]);
|
||||
}
|
||||
d.data!.push([start, ...dataValues[i]!]);
|
||||
} else {
|
||||
let time = start;
|
||||
if (centerBars) {
|
||||
// If centering bars, set the time to the midpoint between start and end instead
|
||||
// of the start time.
|
||||
time = new Date((start.getTime() + end.getTime()) / 2);
|
||||
}
|
||||
// Data value should always be a scalar for bar charts. Pass in
|
||||
// real start time as extra value to allow formatting tooltip.
|
||||
d.data!.push([time, dataValues[i][0]!, start, end]);
|
||||
if (
|
||||
chartType === "line" &&
|
||||
prevEndTime &&
|
||||
prevValues &&
|
||||
prevEndTime.getTime() !== start.getTime()
|
||||
) {
|
||||
// if the end of the previous data doesn't match the start of the current data,
|
||||
// we have to draw a gap so add a value at the end time, and then an empty value.
|
||||
d.data!.push([prevEndTime, ...prevValues[i]!]);
|
||||
d.data!.push([prevEndTime, null]);
|
||||
}
|
||||
d.data!.push([start, ...dataValues[i]!]);
|
||||
});
|
||||
prevValues = dataValues;
|
||||
prevEndTime = limit;
|
||||
prevEndTime = end;
|
||||
};
|
||||
|
||||
let color = colors[statistic_id];
|
||||
@@ -773,7 +692,11 @@ export class StatisticsChart extends LitElement {
|
||||
dataValues.push(val);
|
||||
});
|
||||
if (!this._hiddenStats.has(statistic_id)) {
|
||||
pushData(startDate, endDate, endTime, dataValues);
|
||||
pushData(
|
||||
startDate,
|
||||
endDate.getTime() < endTime.getTime() ? endDate : endTime,
|
||||
dataValues
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -127,6 +127,7 @@ export class DialogDataTableSettings extends LitElement {
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${localize("ui.components.data-table.settings.header")}
|
||||
@closed=${this._dialogClosed}
|
||||
|
||||
@@ -22,14 +22,6 @@ const isOn = (stateObj?: HassEntity) =>
|
||||
!STATES_OFF.includes(stateObj.state) &&
|
||||
!isUnavailableState(stateObj.state);
|
||||
|
||||
/**
|
||||
* @element ha-entity-toggle
|
||||
*
|
||||
* @cssprop --ha-entity-toggle-switch-width - Width of the switch track. Defaults to `38px`.
|
||||
* @cssprop --ha-entity-toggle-switch-size - Height of the switch track. Defaults to `20px`.
|
||||
* @cssprop --ha-entity-toggle-switch-thumb-size - Size of the switch thumb. Defaults to `14px`.
|
||||
*/
|
||||
|
||||
@customElement("ha-entity-toggle")
|
||||
export class HaEntityToggle extends LitElement {
|
||||
// hass is not a property so that we only re-render on stateObj changes
|
||||
@@ -173,9 +165,9 @@ export class HaEntityToggle extends LitElement {
|
||||
white-space: nowrap;
|
||||
}
|
||||
ha-switch {
|
||||
--ha-switch-width: var(--ha-entity-toggle-switch-width, 38px);
|
||||
--ha-switch-size: var(--ha-entity-toggle-switch-size, 20px);
|
||||
--ha-switch-thumb-size: var(--ha-entity-toggle-switch-thumb-size, 14px);
|
||||
--ha-switch-width: 38px;
|
||||
--ha-switch-size: 20px;
|
||||
--ha-switch-thumb-size: 14px;
|
||||
}
|
||||
ha-icon-button {
|
||||
--ha-icon-button-size: 40px;
|
||||
|
||||
@@ -130,6 +130,7 @@ export class HaStateLabelBadge extends LitElement {
|
||||
? html`<ha-state-icon
|
||||
.icon=${this.icon}
|
||||
.stateObj=${entityState}
|
||||
.hass=${this.hass}
|
||||
></ha-state-icon>`
|
||||
: ""}
|
||||
${value && !image && !showIcon
|
||||
|
||||
@@ -210,10 +210,7 @@ export class HaStatisticPicker extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
const isRTL = computeRTL(
|
||||
hass.language,
|
||||
hass.translationMetadata.translations
|
||||
);
|
||||
const isRTL = computeRTL(hass);
|
||||
|
||||
const output: StatisticComboBoxItem[] = [];
|
||||
|
||||
@@ -356,10 +353,7 @@ export class HaStatisticPicker extends LitElement {
|
||||
this.hass.floors
|
||||
);
|
||||
|
||||
const isRTL = computeRTL(
|
||||
this.hass.language,
|
||||
this.hass.translationMetadata.translations
|
||||
);
|
||||
const isRTL = computeRTL(this.hass);
|
||||
|
||||
const primary = entityName || deviceName || statisticId;
|
||||
const secondary = [areaName, entityName ? deviceName : undefined]
|
||||
|
||||
@@ -98,6 +98,7 @@ export class StateBadge extends LitElement {
|
||||
const domain = stateObj ? computeStateDomain(stateObj) : undefined;
|
||||
|
||||
return html`<ha-state-icon
|
||||
.hass=${this.hass}
|
||||
style=${styleMap(this._iconStyle)}
|
||||
data-domain=${ifDefined(domain)}
|
||||
data-state=${ifDefined(stateObj?.state)}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { listenMediaQuery } from "../common/dom/media_query";
|
||||
import { internationalizationContext } from "../data/context";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-bottom-sheet";
|
||||
import "./ha-dialog-header";
|
||||
import "./ha-icon-button";
|
||||
@@ -81,6 +82,8 @@ export const ADAPTIVE_DIALOG_MEDIA_QUERY =
|
||||
*/
|
||||
@customElement("ha-adaptive-dialog")
|
||||
export class HaAdaptiveDialog extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: "aria-labelledby" })
|
||||
public ariaLabelledBy?: string;
|
||||
|
||||
@@ -199,6 +202,7 @@ export class HaAdaptiveDialog extends LitElement {
|
||||
.ariaLabelledBy=${this._defaultAriaLabelledBy}
|
||||
.ariaDescribedBy=${this.ariaDescribedBy}
|
||||
.flexContent=${this.flexContent}
|
||||
.hass=${this.hass}
|
||||
.open=${this.open}
|
||||
.preventScrimClose=${this.preventScrimClose}
|
||||
>
|
||||
@@ -217,6 +221,7 @@ export class HaAdaptiveDialog extends LitElement {
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this.open}
|
||||
.type=${this.type}
|
||||
.width=${this.width}
|
||||
|
||||
@@ -184,10 +184,7 @@ export class HaAreaControlsPicker extends LitElement {
|
||||
const allEntityIds = Object.values(controlEntities).flat();
|
||||
const uniqueEntityIds = Array.from(new Set(allEntityIds));
|
||||
|
||||
const isRTL = computeRTL(
|
||||
this.hass.language,
|
||||
this.hass.translationMetadata.translations
|
||||
);
|
||||
const isRTL = computeRTL(this.hass);
|
||||
|
||||
uniqueEntityIds.forEach((entityId) => {
|
||||
if (isSelected(entityId)) {
|
||||
@@ -264,6 +261,7 @@ export class HaAreaControlsPicker extends LitElement {
|
||||
${item.type === "entity" && item.stateObj
|
||||
? html`<ha-state-icon
|
||||
slot="start"
|
||||
.hass=${this.hass}
|
||||
.stateObj=${item.stateObj}
|
||||
></ha-state-icon>`
|
||||
: item.domain
|
||||
|
||||
@@ -7,6 +7,7 @@ import { fireEvent } from "../common/dom/fire_event";
|
||||
import { SwipeGestureRecognizer } from "../common/util/swipe-gesture-recognizer";
|
||||
import { ScrollableFadeMixin } from "../mixins/scrollable-fade-mixin";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
export const BOTTOM_SHEET_ANIMATION_DURATION_MS = 300;
|
||||
|
||||
@@ -46,6 +47,8 @@ const SWIPE_LOCKED_CLASSES = new Set(["volume-slider-container", "forecast"]);
|
||||
*/
|
||||
@customElement("ha-bottom-sheet")
|
||||
export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: "aria-labelledby" })
|
||||
public ariaLabelledBy?: string;
|
||||
|
||||
@@ -64,11 +67,6 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
|
||||
|
||||
@state() private _sliderInteractionActive = false;
|
||||
|
||||
// disabled till iOS app fix the "focus_element" implementation
|
||||
// @state()
|
||||
// @consume({ context: configContext, subscribe: true })
|
||||
// private _hassConfig?: ContextType<typeof configContext>;
|
||||
|
||||
@query("#drawer") private _drawer!: HTMLElement;
|
||||
|
||||
@query("#body") private _bodyElement!: HTMLDivElement;
|
||||
@@ -92,13 +90,13 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
// disabled till iOS app fix the "focus_element" implementation
|
||||
// if (this._hassConfig?.auth.external && isIosApp(this._hassConfig.auth.external)) {
|
||||
// if (this.hass && isIosApp(this.hass.auth.external)) {
|
||||
// const element = this.renderRoot.querySelector("[autofocus]");
|
||||
// if (element !== null) {
|
||||
// if (!element.id) {
|
||||
// element.id = "ha-bottom-sheet-autofocus";
|
||||
// }
|
||||
// this._hassConfig.auth.external.fireMessage({
|
||||
// this.hass.auth.external?.fireMessage({
|
||||
// type: "focus_element",
|
||||
// payload: {
|
||||
// element_id: element.id,
|
||||
|
||||
@@ -39,12 +39,7 @@ export class HaEntitiesDisplayEditor extends LitElement {
|
||||
const items: DisplayItem[] = entities.map((entity) => ({
|
||||
value: entity.entity_id,
|
||||
label: computeStateName(entity),
|
||||
icon: entityIcon(
|
||||
this.hass.entities,
|
||||
this.hass.config,
|
||||
this.hass.connection,
|
||||
entity
|
||||
),
|
||||
icon: entityIcon(this.hass, entity),
|
||||
}));
|
||||
|
||||
const value: DisplayValue = {
|
||||
|
||||
@@ -122,7 +122,11 @@ export class HaFilterEntities extends LitElement {
|
||||
.selected=${this.value?.includes(entity.entity_id) ?? false}
|
||||
graphic="icon"
|
||||
>
|
||||
<ha-state-icon slot="graphic" .stateObj=${entity}></ha-state-icon>
|
||||
<ha-state-icon
|
||||
slot="graphic"
|
||||
.hass=${this.hass}
|
||||
.stateObj=${entity}
|
||||
></ha-state-icon>
|
||||
${computeStateName(entity)}
|
||||
</ha-check-list-item>`;
|
||||
|
||||
|
||||
@@ -137,10 +137,7 @@ export class HaFilterFloorAreas extends LitElement {
|
||||
.selected=${this.value?.areas?.includes(area.area_id) || false}
|
||||
.type=${"areas"}
|
||||
class=${classMap({
|
||||
rtl: computeRTL(
|
||||
this.hass.language,
|
||||
this.hass.translationMetadata.translations
|
||||
),
|
||||
rtl: computeRTL(this.hass),
|
||||
floor: hasFloor,
|
||||
})}
|
||||
>
|
||||
|
||||
@@ -166,6 +166,7 @@ export class HaRelatedItems extends LitElement {
|
||||
graphic="icon"
|
||||
>
|
||||
<ha-state-icon
|
||||
.hass=${this.hass}
|
||||
.stateObj=${entity}
|
||||
slot="graphic"
|
||||
></ha-state-icon>
|
||||
@@ -321,6 +322,7 @@ export class HaRelatedItems extends LitElement {
|
||||
graphic="icon"
|
||||
>
|
||||
<ha-state-icon
|
||||
.hass=${this.hass}
|
||||
.stateObj=${group}
|
||||
slot="graphic"
|
||||
></ha-state-icon>
|
||||
@@ -345,6 +347,7 @@ export class HaRelatedItems extends LitElement {
|
||||
graphic="icon"
|
||||
>
|
||||
<ha-state-icon
|
||||
.hass=${this.hass}
|
||||
.stateObj=${scene}
|
||||
slot="graphic"
|
||||
></ha-state-icon>
|
||||
@@ -397,6 +400,7 @@ export class HaRelatedItems extends LitElement {
|
||||
graphic="icon"
|
||||
>
|
||||
<ha-state-icon
|
||||
.hass=${this.hass}
|
||||
.stateObj=${automation}
|
||||
slot="graphic"
|
||||
></ha-state-icon>
|
||||
@@ -448,6 +452,7 @@ export class HaRelatedItems extends LitElement {
|
||||
graphic="icon"
|
||||
>
|
||||
<ha-state-icon
|
||||
.hass=${this.hass}
|
||||
.stateObj=${script}
|
||||
slot="graphic"
|
||||
></ha-state-icon>
|
||||
|
||||
@@ -63,12 +63,7 @@ export class HaSelectBox extends LitElement {
|
||||
const selected = option.value === this.value;
|
||||
|
||||
const isDark = this.hass?.themes.darkMode || false;
|
||||
const isRTL = this.hass
|
||||
? computeRTL(
|
||||
this.hass.language,
|
||||
this.hass.translationMetadata.translations
|
||||
)
|
||||
: false;
|
||||
const isRTL = this.hass ? computeRTL(this.hass) : false;
|
||||
|
||||
const imageSrc =
|
||||
typeof option.image === "object"
|
||||
|
||||
@@ -36,15 +36,7 @@ export class HaIconSelector extends LitElement {
|
||||
const placeholder =
|
||||
this.selector.icon?.placeholder ||
|
||||
stateObj?.attributes.icon ||
|
||||
(stateObj &&
|
||||
until(
|
||||
entityIcon(
|
||||
this.hass.entities,
|
||||
this.hass.config,
|
||||
this.hass.connection,
|
||||
stateObj
|
||||
)
|
||||
));
|
||||
(stateObj && until(entityIcon(this.hass, stateObj)));
|
||||
|
||||
return html`
|
||||
<ha-icon-picker
|
||||
@@ -59,7 +51,11 @@ export class HaIconSelector extends LitElement {
|
||||
>
|
||||
${!placeholder && stateObj
|
||||
? html`
|
||||
<ha-state-icon slot="start" .stateObj=${stateObj}></ha-state-icon>
|
||||
<ha-state-icon
|
||||
slot="start"
|
||||
.hass=${this.hass}
|
||||
.stateObj=${stateObj}
|
||||
></ha-state-icon>
|
||||
`
|
||||
: nothing}
|
||||
</ha-icon-picker>
|
||||
|
||||
@@ -523,10 +523,7 @@ class HaSidebar extends SubscribeMixin(ScrollableFadeMixin(LitElement)) {
|
||||
}
|
||||
|
||||
private _renderUserItem(selectedPanel: string) {
|
||||
const isRTL = computeRTL(
|
||||
this.hass.language,
|
||||
this.hass.translationMetadata.translations
|
||||
);
|
||||
const isRTL = computeRTL(this.hass);
|
||||
const isSelected = selectedPanel === "profile";
|
||||
|
||||
return html`
|
||||
@@ -564,9 +561,9 @@ class HaSidebar extends SubscribeMixin(ScrollableFadeMixin(LitElement)) {
|
||||
id="sidebar-external-config"
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiCellphoneCog}></ha-svg-icon>
|
||||
<span class="item-text" slot="headline">
|
||||
${this.hass.localize("ui.sidebar.external_app_configuration")}
|
||||
</span>
|
||||
<span class="item-text" slot="headline"
|
||||
>${this.hass.localize("ui.sidebar.external_app_configuration")}</span
|
||||
>
|
||||
</ha-list-item-button>
|
||||
${!this.alwaysExpand
|
||||
? this._renderToolTip(
|
||||
@@ -743,7 +740,6 @@ class HaSidebar extends SubscribeMixin(ScrollableFadeMixin(LitElement)) {
|
||||
border-radius: var(--ha-border-radius-sm);
|
||||
--ha-row-item-min-height: var(--ha-space-10);
|
||||
--ha-row-item-padding-block: 0;
|
||||
--ha-row-item-padding-inline: var(--ha-space-3);
|
||||
width: var(--ha-space-12);
|
||||
position: relative;
|
||||
transition: width var(--ha-animation-duration-normal) ease;
|
||||
@@ -844,12 +840,21 @@ class HaSidebar extends SubscribeMixin(ScrollableFadeMixin(LitElement)) {
|
||||
}
|
||||
|
||||
ha-user-badge {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
width: var(--ha-space-10);
|
||||
height: var(--ha-space-10);
|
||||
}
|
||||
|
||||
ha-list-item-button.user {
|
||||
--ha-row-item-padding-inline: var(--ha-space-1) 0;
|
||||
--ha-row-item-padding-inline: var(--ha-space-2) var(--ha-space-3);
|
||||
}
|
||||
|
||||
ha-list-item-button.user.rtl {
|
||||
--ha-row-item-padding-inline: var(--ha-space-4) var(--ha-space-3);
|
||||
}
|
||||
|
||||
ha-user-badge {
|
||||
flex-shrink: 0;
|
||||
margin-right: calc(var(--ha-space-2) * -1);
|
||||
}
|
||||
|
||||
.spacer {
|
||||
|
||||
@@ -1,46 +1,31 @@
|
||||
import { consume, type ContextType } from "@lit/context";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { until } from "lit/directives/until";
|
||||
import { computeStateDomain } from "../common/entity/compute_state_domain";
|
||||
import {
|
||||
configContext,
|
||||
connectionContext,
|
||||
entitiesContext,
|
||||
} from "../data/context";
|
||||
import {
|
||||
DEFAULT_DOMAIN_ICON,
|
||||
entityIcon,
|
||||
FALLBACK_DOMAIN_ICONS,
|
||||
} from "../data/icons";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-icon";
|
||||
import "./ha-svg-icon";
|
||||
|
||||
@customElement("ha-state-icon")
|
||||
export class HaStateIcon extends LitElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public stateObj?: HassEntity;
|
||||
|
||||
@property({ attribute: false }) public stateValue?: string;
|
||||
|
||||
@property() public icon?: string;
|
||||
|
||||
@state()
|
||||
@consume({ context: configContext, subscribe: true })
|
||||
protected _config?: ContextType<typeof configContext>;
|
||||
|
||||
@state()
|
||||
@consume({ context: connectionContext, subscribe: true })
|
||||
protected _connection?: ContextType<typeof connectionContext>;
|
||||
|
||||
@state()
|
||||
@consume({ context: entitiesContext, subscribe: true })
|
||||
protected _entities?: ContextType<typeof entitiesContext>;
|
||||
|
||||
protected render() {
|
||||
const overrideIcon =
|
||||
this.icon ||
|
||||
(this.stateObj && this._entities?.[this.stateObj.entity_id]?.icon) ||
|
||||
(this.stateObj && this.hass?.entities[this.stateObj.entity_id]?.icon) ||
|
||||
this.stateObj?.attributes.icon;
|
||||
if (overrideIcon) {
|
||||
return html`<ha-icon .icon=${overrideIcon}></ha-icon>`;
|
||||
@@ -48,21 +33,17 @@ export class HaStateIcon extends LitElement {
|
||||
if (!this.stateObj) {
|
||||
return nothing;
|
||||
}
|
||||
if (!this._config || !this._connection || !this._entities) {
|
||||
if (!this.hass) {
|
||||
return this._renderFallback();
|
||||
}
|
||||
const icon = entityIcon(
|
||||
this._entities,
|
||||
this._config.config,
|
||||
this._connection.connection,
|
||||
this.stateObj,
|
||||
this.stateValue
|
||||
).then((icn) => {
|
||||
if (icn) {
|
||||
return html`<ha-icon .icon=${icn}></ha-icon>`;
|
||||
const icon = entityIcon(this.hass, this.stateObj, this.stateValue).then(
|
||||
(icn) => {
|
||||
if (icn) {
|
||||
return html`<ha-icon .icon=${icn}></ha-icon>`;
|
||||
}
|
||||
return this._renderFallback();
|
||||
}
|
||||
return this._renderFallback();
|
||||
});
|
||||
);
|
||||
return html`${until(icon)}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -1136,10 +1136,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
let rtl = false;
|
||||
let showEntityId = false;
|
||||
if (type === "area" || type === "floor") {
|
||||
rtl = computeRTL(
|
||||
this.hass.language,
|
||||
this.hass.translationMetadata.translations
|
||||
);
|
||||
rtl = computeRTL(this.hass);
|
||||
hasFloor =
|
||||
type === "area" && !!(item as FloorComboBoxItem).area?.floor_id;
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ class HaInputMulti extends LitElement {
|
||||
<div class="items">
|
||||
${repeat(
|
||||
this._items,
|
||||
(item, index) => `${item}-${index}`,
|
||||
(_item, index) => index,
|
||||
(item, index) => {
|
||||
const indexSuffix = `${this.itemIndex ? ` ${index + 1}` : ""}`;
|
||||
return html`
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { HasSlotController } from "@home-assistant/webawesome/dist/internal/slot";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
|
||||
/**
|
||||
* @element ha-row-item
|
||||
@@ -46,34 +46,13 @@ export class HaRowItem extends LitElement {
|
||||
|
||||
protected readonly _slotController = new HasSlotController(
|
||||
this,
|
||||
"start",
|
||||
"end",
|
||||
"headline",
|
||||
"supporting-text",
|
||||
"content"
|
||||
);
|
||||
|
||||
@state() private _hasStart = false;
|
||||
|
||||
@state() private _hasEnd = false;
|
||||
|
||||
private _onSlotChange(name: "start" | "end") {
|
||||
return (ev: Event) => {
|
||||
const slot = ev.target as HTMLSlotElement;
|
||||
const hasContent = slot
|
||||
.assignedNodes({ flatten: true })
|
||||
.some(
|
||||
(node) =>
|
||||
node.nodeType === Node.ELEMENT_NODE ||
|
||||
(node.nodeType === Node.TEXT_NODE &&
|
||||
(node as Text).textContent?.trim() !== "")
|
||||
);
|
||||
if (name === "start") {
|
||||
this._hasStart = hasContent;
|
||||
} else {
|
||||
this._hasEnd = hasContent;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return this._renderBase(this._renderInner());
|
||||
}
|
||||
@@ -86,16 +65,16 @@ export class HaRowItem extends LitElement {
|
||||
const hasContent = this._slotController.test("content");
|
||||
|
||||
return html`
|
||||
<div part="start" class="start" ?hidden=${!this._hasStart}>
|
||||
<slot name="start" @slotchange=${this._onSlotChange("start")}></slot>
|
||||
<div part="start" class="start">
|
||||
<slot name="start"></slot>
|
||||
</div>
|
||||
<div part="content" class="content">
|
||||
${hasContent
|
||||
? html`<slot name="content"></slot>`
|
||||
: this._renderDefaultContent()}
|
||||
</div>
|
||||
<div part="end" class="end" ?hidden=${!this._hasEnd}>
|
||||
<slot name="end" @slotchange=${this._onSlotChange("end")}></slot>
|
||||
<div part="end" class="end">
|
||||
<slot name="end"></slot>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -163,8 +142,8 @@ export class HaRowItem extends LitElement {
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.start[hidden],
|
||||
.end[hidden] {
|
||||
:host(:not(:has([slot="start"]))) .start,
|
||||
:host(:not(:has([slot="end"]))) .end {
|
||||
display: none;
|
||||
}
|
||||
.headline {
|
||||
|
||||
@@ -37,6 +37,7 @@ class HaEntityMarker extends LitElement {
|
||||
></div>`
|
||||
: this.showIcon && this.entityId
|
||||
? html`<ha-state-icon
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.hass?.states[this.entityId]}
|
||||
></ha-state-icon>`
|
||||
: !this.entityUnit
|
||||
|
||||
@@ -76,7 +76,12 @@ class DialogJoinMediaPlayers extends LitElement {
|
||||
|
||||
const entityId = this._entityId;
|
||||
return html`
|
||||
<ha-dialog .open=${this._open} flexcontent @closed=${this._dialogClosed}>
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
flexcontent
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
<ha-dialog-header show-border slot="header">
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize("ui.common.close")}
|
||||
|
||||
@@ -100,6 +100,7 @@ class DialogMediaManage extends LitElement {
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
?prevent-scrim-close=${this._uploading || this._deleting}
|
||||
@closed=${this._dialogClosed}
|
||||
|
||||
@@ -77,6 +77,7 @@ class DialogMediaPlayerBrowse extends LitElement {
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
width="large"
|
||||
flexcontent
|
||||
|
||||
@@ -59,10 +59,7 @@ class HaMediaPlayerToggle extends LitElement {
|
||||
icon = mdiSpeakerPause;
|
||||
}
|
||||
|
||||
const isRTL = computeRTL(
|
||||
this.hass.language,
|
||||
this.hass.translationMetadata.translations
|
||||
);
|
||||
const isRTL = computeRTL(this.hass);
|
||||
|
||||
const { primary, secondary } = this._computeDisplayData(
|
||||
this.entityId,
|
||||
|
||||
@@ -1,31 +1,15 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { ensureArray } from "../../../common/array/ensure-array";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import type { DeviceRegistryEntry } from "../../../data/device/device_registry";
|
||||
import { getDeviceIntegrationLookup } from "../../../data/device/device_registry";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "../../../data/entity/entity";
|
||||
import type { EntitySources } from "../../../data/entity/entity_sources";
|
||||
import { fetchEntitySourcesWithCache } from "../../../data/entity/entity_sources";
|
||||
import type { TargetSelector } from "../../../data/selector";
|
||||
import {
|
||||
filterSelectorDevices,
|
||||
filterSelectorEntities,
|
||||
} from "../../../data/selector";
|
||||
import type { HassDialog } from "../../../dialogs/make-dialog-manager";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "../../device/ha-device-picker";
|
||||
import "../../ha-adaptive-dialog";
|
||||
import "../../ha-dialog";
|
||||
import "../../ha-dialog-header";
|
||||
import "../../ha-icon-button";
|
||||
import "../../ha-icon-next";
|
||||
import "../../ha-md-list";
|
||||
import "../../ha-md-list-item";
|
||||
import "../../ha-svg-icon";
|
||||
import "../../list/ha-list-base";
|
||||
import "../ha-target-picker-item-row";
|
||||
import type { TargetDetailsDialogParams } from "./show-dialog-target-details";
|
||||
|
||||
@@ -37,12 +21,6 @@ class DialogTargetDetails extends LitElement implements HassDialog {
|
||||
|
||||
@state() private _opened = false;
|
||||
|
||||
@state() private _entitySources?: EntitySources;
|
||||
|
||||
@state() private _entitySourcesLoaded = false;
|
||||
|
||||
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
|
||||
|
||||
public showDialog(params: TargetDetailsDialogParams): void {
|
||||
this._params = params;
|
||||
this._opened = true;
|
||||
@@ -56,72 +34,6 @@ class DialogTargetDetails extends LitElement implements HassDialog {
|
||||
private _dialogClosed() {
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
this._params = undefined;
|
||||
this._entitySources = undefined;
|
||||
this._entitySourcesLoaded = false;
|
||||
}
|
||||
|
||||
private _hasIntegration(selector: TargetSelector) {
|
||||
return (
|
||||
(selector.target?.entity &&
|
||||
ensureArray(selector.target.entity).some((e) => e.integration)) ||
|
||||
(selector.target?.device &&
|
||||
ensureArray(selector.target.device).some((d) => d.integration))
|
||||
);
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
super.updated(changedProperties);
|
||||
if (!changedProperties.has("_params")) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
this._params?.selector &&
|
||||
this._hasIntegration(this._params.selector) &&
|
||||
!this._entitySourcesLoaded
|
||||
) {
|
||||
this._loadEntitySources();
|
||||
}
|
||||
}
|
||||
|
||||
private async _loadEntitySources(): Promise<void> {
|
||||
try {
|
||||
this._entitySources = await fetchEntitySourcesWithCache(this.hass);
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Failed to load entity sources for target details", err);
|
||||
} finally {
|
||||
this._entitySourcesLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
private _filterEntities = (entity: HassEntity): boolean => {
|
||||
const target = this._selectorTarget();
|
||||
if (!target?.entity) {
|
||||
return true;
|
||||
}
|
||||
return ensureArray(target.entity).some((e) =>
|
||||
filterSelectorEntities(e, entity, this._entitySources)
|
||||
);
|
||||
};
|
||||
|
||||
private _filterDevices = (device: DeviceRegistryEntry): boolean => {
|
||||
const target = this._selectorTarget();
|
||||
if (!target?.device) {
|
||||
return true;
|
||||
}
|
||||
const deviceIntegrations = this._entitySources
|
||||
? this._deviceIntegrationLookup(
|
||||
this._entitySources,
|
||||
Object.values(this.hass.entities)
|
||||
)
|
||||
: undefined;
|
||||
return ensureArray(target.device).some((d) =>
|
||||
filterSelectorDevices(d, device, deviceIntegrations)
|
||||
);
|
||||
};
|
||||
|
||||
private _selectorTarget() {
|
||||
return this._params?.selector?.target || null;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
@@ -129,86 +41,33 @@ class DialogTargetDetails extends LitElement implements HassDialog {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
let deviceFilter: HaDevicePickerDeviceFilterFunc | undefined;
|
||||
let entityFilter: HaEntityPickerEntityFilterFunc | undefined;
|
||||
let includeDomains: string[] | undefined;
|
||||
let includeDeviceClasses: string[] | undefined;
|
||||
let primaryEntitiesOnly: boolean | undefined;
|
||||
|
||||
if (this._params.selector) {
|
||||
deviceFilter = this._filterDevices;
|
||||
entityFilter = this._filterEntities;
|
||||
primaryEntitiesOnly = this._params.selector.target?.primary_entities_only;
|
||||
} else {
|
||||
deviceFilter = this._params.deviceFilter;
|
||||
entityFilter = this._params.entityFilter;
|
||||
includeDomains = this._params.includeDomains;
|
||||
includeDeviceClasses = this._params.includeDeviceClasses;
|
||||
primaryEntitiesOnly = this._params.primaryEntitiesOnly;
|
||||
}
|
||||
|
||||
const waitingForSources =
|
||||
this._params.selector &&
|
||||
this._hasIntegration(this._params.selector) &&
|
||||
!this._entitySourcesLoaded;
|
||||
|
||||
return html`
|
||||
<ha-adaptive-dialog
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._opened}
|
||||
header-title=${this.hass.localize(
|
||||
"ui.components.target-picker.target_details"
|
||||
)}
|
||||
header-subtitle=${`${this.hass.localize(
|
||||
`ui.components.target-picker.type.${this._params.type}`
|
||||
)}:
|
||||
${this._params.title}`}
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
<div class="type-wrapper">
|
||||
<div class="type-label">
|
||||
${this.hass.localize(
|
||||
`ui.components.target-picker.type.${this._params.type}`
|
||||
)}
|
||||
</div>
|
||||
<ha-list-base
|
||||
.ariaLabel=${`${this.hass.localize(`ui.components.target-picker.type.${this._params.type}`)}: ${this._params.title}`}
|
||||
wrap-focus
|
||||
>
|
||||
${waitingForSources
|
||||
? nothing
|
||||
: html`
|
||||
<ha-target-picker-item-row
|
||||
.hass=${this.hass}
|
||||
.type=${this._params.type}
|
||||
.itemId=${this._params.itemId}
|
||||
.deviceFilter=${deviceFilter}
|
||||
.entityFilter=${entityFilter}
|
||||
.includeDomains=${includeDomains}
|
||||
.includeDeviceClasses=${includeDeviceClasses}
|
||||
.primaryEntitiesOnly=${primaryEntitiesOnly}
|
||||
expand
|
||||
></ha-target-picker-item-row>
|
||||
`}
|
||||
</ha-list-base>
|
||||
</div>
|
||||
</ha-adaptive-dialog>
|
||||
<ha-target-picker-item-row
|
||||
.hass=${this.hass}
|
||||
.type=${this._params.type}
|
||||
.itemId=${this._params.itemId}
|
||||
.deviceFilter=${this._params.deviceFilter}
|
||||
.entityFilter=${this._params.entityFilter}
|
||||
.includeDomains=${this._params.includeDomains}
|
||||
.includeDeviceClasses=${this._params.includeDeviceClasses}
|
||||
.primaryEntitiesOnly=${this._params.primaryEntitiesOnly}
|
||||
expand
|
||||
></ha-target-picker-item-row>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.type-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: var(--ha-border-radius-xl);
|
||||
border: var(--ha-border-width-sm) solid
|
||||
var(--ha-color-border-neutral-normal);
|
||||
overflow: hidden;
|
||||
}
|
||||
.type-label {
|
||||
background-color: var(--ha-color-surface-low);
|
||||
padding: var(--ha-space-1) var(--ha-space-3);
|
||||
font-weight: var(--ha-font-weight-bold);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 20px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "../../../data/entity/entity";
|
||||
import type { TargetSelector } from "../../../data/selector";
|
||||
import type { TargetType } from "../../../data/target";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "../../device/ha-device-picker";
|
||||
|
||||
export type NewBackupType = "automatic" | "manual";
|
||||
|
||||
export interface TargetDetailsDialogParams {
|
||||
title: string;
|
||||
type: TargetType;
|
||||
itemId: string;
|
||||
selector?: TargetSelector;
|
||||
deviceFilter?: HaDevicePickerDeviceFilterFunc;
|
||||
entityFilter?: HaEntityPickerEntityFilterFunc;
|
||||
includeDomains?: string[];
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { TargetType, TargetTypeFloorless } from "../../data/target";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "../device/ha-device-picker";
|
||||
import "../ha-expansion-panel";
|
||||
import "../list/ha-list-base";
|
||||
import "../ha-md-list";
|
||||
import "./ha-target-picker-item-row";
|
||||
|
||||
@customElement("ha-target-picker-item-group")
|
||||
@@ -66,25 +66,23 @@ export class HaTargetPickerItemGroup extends LitElement {
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
<ha-list-base>
|
||||
${Object.entries(this.items).map(([type, items]) =>
|
||||
items
|
||||
? items.map(
|
||||
(item) =>
|
||||
html`<ha-target-picker-item-row
|
||||
.hass=${this.hass}
|
||||
.type=${type as TargetTypeFloorless}
|
||||
.itemId=${item}
|
||||
.deviceFilter=${this.deviceFilter}
|
||||
.entityFilter=${this.entityFilter}
|
||||
.includeDomains=${this.includeDomains}
|
||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||
.primaryEntitiesOnly=${this.primaryEntitiesOnly}
|
||||
></ha-target-picker-item-row>`
|
||||
)
|
||||
: nothing
|
||||
)}
|
||||
</ha-list-base>
|
||||
${Object.entries(this.items).map(([type, items]) =>
|
||||
items
|
||||
? items.map(
|
||||
(item) =>
|
||||
html`<ha-target-picker-item-row
|
||||
.hass=${this.hass}
|
||||
.type=${type as TargetTypeFloorless}
|
||||
.itemId=${item}
|
||||
.deviceFilter=${this.deviceFilter}
|
||||
.entityFilter=${this.entityFilter}
|
||||
.includeDomains=${this.includeDomains}
|
||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||
.primaryEntitiesOnly=${this.primaryEntitiesOnly}
|
||||
></ha-target-picker-item-row>`
|
||||
)
|
||||
: nothing
|
||||
)}
|
||||
</ha-expansion-panel>`;
|
||||
}
|
||||
|
||||
@@ -98,7 +96,7 @@ export class HaTargetPickerItemGroup extends LitElement {
|
||||
--expansion-panel-content-padding: 0;
|
||||
}
|
||||
ha-expansion-panel::part(summary) {
|
||||
background-color: var(--ha-color-surface-low);
|
||||
background-color: var(--ha-color-fill-neutral-quiet-resting);
|
||||
padding: var(--ha-space-1) var(--ha-space-2);
|
||||
font-weight: var(--ha-font-weight-bold);
|
||||
color: var(--secondary-text-color);
|
||||
@@ -106,6 +104,9 @@ export class HaTargetPickerItemGroup extends LitElement {
|
||||
justify-content: space-between;
|
||||
min-height: unset;
|
||||
}
|
||||
ha-md-list {
|
||||
padding: 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,14 @@
|
||||
import { consume } from "@lit/context";
|
||||
import {
|
||||
mdiChevronLeft,
|
||||
mdiChevronRight,
|
||||
mdiClose,
|
||||
mdiDevices,
|
||||
mdiHome,
|
||||
mdiLabel,
|
||||
mdiMinusBox,
|
||||
mdiTextureBox,
|
||||
} from "@mdi/js";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
html,
|
||||
LitElement,
|
||||
nothing,
|
||||
type PropertyValues,
|
||||
type TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { css, html, LitElement, nothing, type PropertyValues } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
@@ -48,17 +38,18 @@ import {
|
||||
type ExtractFromTargetResultReferenced,
|
||||
type TargetType,
|
||||
} from "../../data/target";
|
||||
import { showMoreInfoDialog } from "../../dialogs/more-info/show-ha-more-info-dialog";
|
||||
import { buttonLinkStyle } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { brandsUrl } from "../../util/brands-url";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "../device/ha-device-picker";
|
||||
import { floorDefaultIconPath } from "../ha-floor-icon";
|
||||
import "../ha-icon-button";
|
||||
import "../ha-md-list";
|
||||
import type { HaMdList } from "../ha-md-list";
|
||||
import "../ha-md-list-item";
|
||||
import type { HaMdListItem } from "../ha-md-list-item";
|
||||
import "../ha-state-icon";
|
||||
import "../ha-svg-icon";
|
||||
import "../item/ha-list-item-base";
|
||||
import "../item/ha-list-item-button";
|
||||
import { showTargetDetailsDialog } from "./dialog/show-dialog-target-details";
|
||||
|
||||
@customElement("ha-target-picker-item-row")
|
||||
@@ -74,9 +65,6 @@ export class HaTargetPickerItemRow extends LitElement {
|
||||
@property({ type: Boolean, attribute: "sub-entry", reflect: true })
|
||||
public subEntry = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
public subLevel = 0;
|
||||
|
||||
@property({ type: Boolean, attribute: "hide-context" })
|
||||
public hideContext = false;
|
||||
|
||||
@@ -118,6 +106,12 @@ export class HaTargetPickerItemRow extends LitElement {
|
||||
@consume({ context: labelsContext, subscribe: true })
|
||||
_labelRegistry!: LabelRegistryEntry[];
|
||||
|
||||
@query("ha-md-list-item") public item?: HaMdListItem;
|
||||
|
||||
@query("ha-md-list") public list?: HaMdList;
|
||||
|
||||
@query("ha-target-picker-item-row") public itemRow?: HaTargetPickerItemRow;
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues<this>) {
|
||||
if (!this.subEntry && changedProps.has("itemId")) {
|
||||
this._updateItemData();
|
||||
@@ -143,130 +137,101 @@ export class HaTargetPickerItemRow extends LitElement {
|
||||
|
||||
const replaceable = !this.subEntry && !this.expand;
|
||||
|
||||
const content = html`
|
||||
<div class="icon" slot="start">
|
||||
${iconPath
|
||||
? html`<ha-icon .icon=${iconPath}></ha-icon>`
|
||||
: this._iconImg
|
||||
? html`<img
|
||||
alt=${this._domainName || ""}
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
src=${this._iconImg}
|
||||
/>`
|
||||
: fallbackIconPath
|
||||
? html`<ha-svg-icon .path=${fallbackIconPath}></ha-svg-icon>`
|
||||
: this.type === "entity"
|
||||
? html`
|
||||
<ha-state-icon
|
||||
.stateObj=${stateObject ||
|
||||
({
|
||||
entity_id: this.itemId,
|
||||
attributes: {},
|
||||
} as HassEntity)}
|
||||
>
|
||||
</ha-state-icon>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
return html`
|
||||
<ha-md-list-item
|
||||
type=${replaceable ? "button" : "text"}
|
||||
class=${classMap({
|
||||
error: notFound,
|
||||
replaceable,
|
||||
})}
|
||||
@click=${replaceable ? this._replaceItem : undefined}
|
||||
>
|
||||
<div class="icon" slot="start">
|
||||
${this.subEntry
|
||||
? html`
|
||||
<div class="horizontal-line-wrapper">
|
||||
<div class="horizontal-line"></div>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
${iconPath
|
||||
? html`<ha-icon .icon=${iconPath}></ha-icon>`
|
||||
: this._iconImg
|
||||
? html`<img
|
||||
alt=${this._domainName || ""}
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
src=${this._iconImg}
|
||||
/>`
|
||||
: fallbackIconPath
|
||||
? html`<ha-svg-icon .path=${fallbackIconPath}></ha-svg-icon>`
|
||||
: this.type === "entity"
|
||||
? html`
|
||||
<ha-state-icon
|
||||
.hass=${this.hass}
|
||||
.stateObj=${stateObject ||
|
||||
({
|
||||
entity_id: this.itemId,
|
||||
attributes: {},
|
||||
} as HassEntity)}
|
||||
>
|
||||
</ha-state-icon>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
|
||||
<div slot="headline">${name}</div>
|
||||
${notFound || (context && !this.hideContext)
|
||||
? html`<span slot="supporting-text"
|
||||
>${notFound
|
||||
? this.hass.localize(
|
||||
`ui.components.target-picker.${this.type}_not_found`
|
||||
)
|
||||
: context}</span
|
||||
>`
|
||||
: nothing}
|
||||
${stateObject && this.subEntry
|
||||
? html`<span slot="supporting-text" class="state"
|
||||
>${this.hass.formatEntityState(stateObject)}</span
|
||||
>`
|
||||
: nothing}
|
||||
${!this.subEntry && entries && showEntities
|
||||
? html`
|
||||
<div slot="end" class="summary">
|
||||
${showEntities &&
|
||||
!this.expand &&
|
||||
entries?.referenced_entities.length
|
||||
? html`<button class="main link" @click=${this._openDetails}>
|
||||
${this.hass.localize(
|
||||
"ui.components.target-picker.entities_count",
|
||||
{
|
||||
count: entries?.referenced_entities.length,
|
||||
}
|
||||
)}
|
||||
</button>`
|
||||
: showEntities
|
||||
? html`<span class="main">
|
||||
<div slot="headline">${name}</div>
|
||||
${notFound || (context && !this.hideContext)
|
||||
? html`<span slot="supporting-text"
|
||||
>${notFound
|
||||
? this.hass.localize(
|
||||
`ui.components.target-picker.${this.type}_not_found`
|
||||
)
|
||||
: context}</span
|
||||
>`
|
||||
: nothing}
|
||||
${this._domainName && this.subEntry
|
||||
? html`<span slot="supporting-text" class="domain"
|
||||
>${this._domainName}</span
|
||||
>`
|
||||
: nothing}
|
||||
${!this.subEntry && entries && showEntities
|
||||
? html`
|
||||
<div slot="end" class="summary">
|
||||
${showEntities &&
|
||||
!this.expand &&
|
||||
entries?.referenced_entities.length
|
||||
? html`<button class="main link" @click=${this._openDetails}>
|
||||
${this.hass.localize(
|
||||
"ui.components.target-picker.entities_count",
|
||||
{
|
||||
count: entries?.referenced_entities.length,
|
||||
}
|
||||
)}
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
${!this.expand && !this.subEntry
|
||||
? html`
|
||||
<ha-icon-button
|
||||
.path=${mdiClose}
|
||||
slot="end"
|
||||
@click=${this._removeItem}
|
||||
></ha-icon-button>
|
||||
`
|
||||
: this.subEntry && this.type === "entity"
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
.path=${computeRTL(
|
||||
this.hass.language,
|
||||
this.hass.translationMetadata.translations
|
||||
)
|
||||
? mdiChevronLeft
|
||||
: mdiChevronRight}
|
||||
slot="end"
|
||||
></ha-svg-icon>
|
||||
</button>`
|
||||
: showEntities
|
||||
? html`<span class="main">
|
||||
${this.hass.localize(
|
||||
"ui.components.target-picker.entities_count",
|
||||
{
|
||||
count: entries?.referenced_entities.length,
|
||||
}
|
||||
)}
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
`;
|
||||
|
||||
let item: TemplateResult;
|
||||
|
||||
if (replaceable || (this.subEntry && this.type === "entity")) {
|
||||
item = html`
|
||||
<ha-list-item-button
|
||||
class=${classMap({
|
||||
error: notFound,
|
||||
replaceable,
|
||||
})}
|
||||
@click=${replaceable
|
||||
? this._replaceItem
|
||||
: this.subEntry && this.type === "entity"
|
||||
? this._openMoreInfo
|
||||
: undefined}
|
||||
>
|
||||
${content}
|
||||
</ha-list-item-button>
|
||||
`;
|
||||
} else {
|
||||
item = html`
|
||||
<ha-list-item-base
|
||||
class=${classMap({
|
||||
error: notFound,
|
||||
})}
|
||||
>
|
||||
${content}
|
||||
</ha-list-item-base>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
${item}
|
||||
${!this.expand && !this.subEntry
|
||||
? html`
|
||||
<ha-icon-button
|
||||
.path=${mdiClose}
|
||||
slot="end"
|
||||
@click=${this._removeItem}
|
||||
></ha-icon-button>
|
||||
`
|
||||
: nothing}
|
||||
</ha-md-list-item>
|
||||
${this.expand && entries && entries.referenced_entities
|
||||
? this._renderEntries()
|
||||
: nothing}
|
||||
@@ -276,10 +241,6 @@ export class HaTargetPickerItemRow extends LitElement {
|
||||
private _renderEntries() {
|
||||
const entries = this.parentEntries || this._entries;
|
||||
|
||||
if (!entries || entries.referenced_entities.length === 0) {
|
||||
return this._renderEmptyEntries();
|
||||
}
|
||||
|
||||
let nextType: TargetType =
|
||||
this.type === "floor"
|
||||
? "area"
|
||||
@@ -389,64 +350,54 @@ export class HaTargetPickerItemRow extends LitElement {
|
||||
) || ([] as string[]),
|
||||
}));
|
||||
|
||||
const nextSubLevel = this.subLevel + 1;
|
||||
|
||||
return html`
|
||||
${rows1.map(
|
||||
(itemId, index) => html`
|
||||
<ha-target-picker-item-row
|
||||
sub-entry
|
||||
.subLevel=${nextSubLevel}
|
||||
style=${`--sub-entry-indent: calc(${nextSubLevel} * var(--ha-space-10));`}
|
||||
.hass=${this.hass}
|
||||
.type=${nextType}
|
||||
.itemId=${itemId}
|
||||
.parentEntries=${rows1Entries?.[index]}
|
||||
.hideContext=${this.hideContext || this.type !== "label"}
|
||||
expand
|
||||
></ha-target-picker-item-row>
|
||||
`
|
||||
)}
|
||||
${deviceRows.map(
|
||||
(itemId, index) => html`
|
||||
<ha-target-picker-item-row
|
||||
sub-entry
|
||||
.subLevel=${nextSubLevel}
|
||||
style=${`--sub-entry-indent: calc(${nextSubLevel} * var(--ha-space-10));`}
|
||||
.hass=${this.hass}
|
||||
type="device"
|
||||
.itemId=${itemId}
|
||||
.parentEntries=${deviceRowsEntries?.[index]}
|
||||
.hideContext=${this.hideContext || this.type !== "label"}
|
||||
expand
|
||||
></ha-target-picker-item-row>
|
||||
`
|
||||
)}
|
||||
${entityRows.map(
|
||||
(itemId) => html`
|
||||
<ha-target-picker-item-row
|
||||
sub-entry
|
||||
.subLevel=${nextSubLevel}
|
||||
style=${`--sub-entry-indent: calc(${nextSubLevel} * var(--ha-space-10));`}
|
||||
.hass=${this.hass}
|
||||
type="entity"
|
||||
.itemId=${itemId}
|
||||
.hideContext=${this.hideContext || this.type !== "label"}
|
||||
></ha-target-picker-item-row>
|
||||
`
|
||||
)}
|
||||
<div class="entries-tree">
|
||||
<div class="line-wrapper">
|
||||
<div class="line"></div>
|
||||
</div>
|
||||
<ha-md-list class="entries">
|
||||
${rows1.map(
|
||||
(itemId, index) => html`
|
||||
<ha-target-picker-item-row
|
||||
sub-entry
|
||||
.hass=${this.hass}
|
||||
.type=${nextType}
|
||||
.itemId=${itemId}
|
||||
.parentEntries=${rows1Entries?.[index]}
|
||||
.hideContext=${this.hideContext || this.type !== "label"}
|
||||
expand
|
||||
></ha-target-picker-item-row>
|
||||
`
|
||||
)}
|
||||
${deviceRows.map(
|
||||
(itemId, index) => html`
|
||||
<ha-target-picker-item-row
|
||||
sub-entry
|
||||
.hass=${this.hass}
|
||||
type="device"
|
||||
.itemId=${itemId}
|
||||
.parentEntries=${deviceRowsEntries?.[index]}
|
||||
.hideContext=${this.hideContext || this.type !== "label"}
|
||||
expand
|
||||
></ha-target-picker-item-row>
|
||||
`
|
||||
)}
|
||||
${entityRows.map(
|
||||
(itemId) => html`
|
||||
<ha-target-picker-item-row
|
||||
sub-entry
|
||||
.hass=${this.hass}
|
||||
type="entity"
|
||||
.itemId=${itemId}
|
||||
.hideContext=${this.hideContext || this.type !== "label"}
|
||||
></ha-target-picker-item-row>
|
||||
`
|
||||
)}
|
||||
</ha-md-list>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderEmptyEntries() {
|
||||
return html`<ha-list-item-base>
|
||||
<ha-svg-icon .path=${mdiMinusBox} slot="start" class="icon"></ha-svg-icon>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize("ui.components.target-picker.no_targets")}</span
|
||||
>
|
||||
</ha-list-item-base>`;
|
||||
}
|
||||
|
||||
private async _updateItemData() {
|
||||
if (this.type === "entity") {
|
||||
this._entries = undefined;
|
||||
@@ -615,14 +566,7 @@ export class HaTargetPickerItemRow extends LitElement {
|
||||
const areaName = area ? computeAreaName(area) : undefined;
|
||||
const context = [areaName, entityName ? deviceName : undefined]
|
||||
.filter(Boolean)
|
||||
.join(
|
||||
computeRTL(
|
||||
this.hass.language,
|
||||
this.hass.translationMetadata.translations
|
||||
)
|
||||
? " ◂ "
|
||||
: " ▸ "
|
||||
);
|
||||
.join(computeRTL(this.hass) ? " ◂ " : " ▸ ");
|
||||
return {
|
||||
name: entityName || deviceName || item,
|
||||
context,
|
||||
@@ -696,12 +640,6 @@ export class HaTargetPickerItemRow extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _openMoreInfo = () => {
|
||||
showMoreInfoDialog(this, {
|
||||
entityId: this.itemId,
|
||||
});
|
||||
};
|
||||
|
||||
static styles = [
|
||||
buttonLinkStyle,
|
||||
css`
|
||||
@@ -713,6 +651,12 @@ export class HaTargetPickerItemRow extends LitElement {
|
||||
--md-list-item-two-line-container-height: 56px;
|
||||
}
|
||||
|
||||
:host([expand]:not([sub-entry])) ha-md-list-item {
|
||||
border: 2px solid var(--ha-color-border-neutral-loud);
|
||||
background-color: var(--ha-color-fill-neutral-quiet-resting);
|
||||
border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg));
|
||||
}
|
||||
|
||||
.error {
|
||||
background: var(--ha-color-fill-warning-quiet-resting);
|
||||
}
|
||||
@@ -736,7 +680,6 @@ export class HaTargetPickerItemRow extends LitElement {
|
||||
.icon {
|
||||
width: 24px;
|
||||
display: flex;
|
||||
color: var(--ha-color-on-neutral-normal);
|
||||
}
|
||||
|
||||
img {
|
||||
@@ -754,21 +697,53 @@ export class HaTargetPickerItemRow extends LitElement {
|
||||
line-height: var(--ha-line-height-condensed);
|
||||
}
|
||||
:host([sub-entry]) .summary {
|
||||
margin-inline-start: var(--ha-space-12);
|
||||
margin-right: var(--ha-space-12);
|
||||
}
|
||||
.summary .main {
|
||||
font-weight: var(--ha-font-weight-medium);
|
||||
}
|
||||
:host([expand]) .summary .main {
|
||||
color: var(--ha-color-text-secondary);
|
||||
font-size: var(--ha-font-size-s);
|
||||
font-weight: var(--ha-font-weight-normal);
|
||||
}
|
||||
.summary .secondary {
|
||||
font-size: var(--ha-font-size-s);
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.entries-tree {
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.entries-tree .line-wrapper {
|
||||
padding: var(--ha-space-5);
|
||||
}
|
||||
|
||||
.entries-tree .line-wrapper .line {
|
||||
border-left: 2px dashed var(--divider-color);
|
||||
height: calc(100% - 28px);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
:host([sub-entry]) .entries-tree .line-wrapper .line {
|
||||
height: calc(100% - 12px);
|
||||
top: -18px;
|
||||
}
|
||||
|
||||
.entries {
|
||||
padding: 0;
|
||||
--md-item-overflow: visible;
|
||||
}
|
||||
|
||||
.horizontal-line-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
.horizontal-line-wrapper .horizontal-line {
|
||||
position: absolute;
|
||||
top: 11px;
|
||||
margin-inline-start: -28px;
|
||||
width: 29px;
|
||||
border-top: 2px dashed var(--divider-color);
|
||||
}
|
||||
|
||||
button.link {
|
||||
text-decoration: none;
|
||||
color: var(--primary-color);
|
||||
@@ -779,19 +754,12 @@ export class HaTargetPickerItemRow extends LitElement {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.state {
|
||||
.domain {
|
||||
width: fit-content;
|
||||
font-size: var(--ha-font-size-s);
|
||||
color: var(--ha-color-text-secondary);
|
||||
}
|
||||
|
||||
ha-list-item-button::part(end) {
|
||||
gap: var(--ha-space-2);
|
||||
}
|
||||
|
||||
:host([sub-entry]) ha-list-item-button::part(base),
|
||||
:host([sub-entry]) ha-list-item-base::part(base) {
|
||||
padding-inline-start: var(--sub-entry-indent);
|
||||
border-radius: var(--ha-border-radius-md);
|
||||
background-color: var(--ha-color-fill-neutral-quiet-resting);
|
||||
padding: var(--ha-space-1);
|
||||
font-family: var(--ha-font-family-code);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -76,6 +76,7 @@ export class HaTargetPickerValueChip extends LitElement {
|
||||
? html`<ha-svg-icon .path=${fallbackIconPath}></ha-svg-icon>`
|
||||
: stateObject
|
||||
? html`<ha-state-icon
|
||||
.hass=${this.hass}
|
||||
.stateObj=${stateObject}
|
||||
></ha-state-icon>`
|
||||
: nothing}
|
||||
|
||||
@@ -99,8 +99,7 @@ export class HaTileContainer extends LitElement {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
min-height: var(--row-height, 56px);
|
||||
padding: 10px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { dump } from "js-yaml";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
|
||||
import type { Trigger } from "../../data/automation";
|
||||
import { migrateAutomationTrigger } from "../../data/automation";
|
||||
@@ -22,10 +23,9 @@ import "../../panels/logbook/ha-logbook-renderer";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-code-editor";
|
||||
import "../ha-icon-button";
|
||||
import "../ha-tab-group";
|
||||
import "../ha-tab-group-tab";
|
||||
import "./hat-logbook-note";
|
||||
import type { NodeInfo } from "./hat-script-graph";
|
||||
import { traceTabStyles } from "./trace-tab-styles";
|
||||
|
||||
const TRACE_PATH_TABS = [
|
||||
"step_config",
|
||||
@@ -66,21 +66,21 @@ export class HaTracePathDetails extends LitElement {
|
||||
${this._renderSelectedTraceInfo()}
|
||||
</div>
|
||||
|
||||
<ha-tab-group @wa-tab-show=${this._handleTabChanged}>
|
||||
<div class="tabs top">
|
||||
${TRACE_PATH_TABS.map(
|
||||
(view) => html`
|
||||
<ha-tab-group-tab
|
||||
slot="nav"
|
||||
.active=${this._view === view}
|
||||
.panel=${view}
|
||||
<button
|
||||
.view=${view}
|
||||
class=${classMap({ active: this._view === view })}
|
||||
@click=${this._showTab}
|
||||
>
|
||||
${this.hass!.localize(
|
||||
`ui.panel.config.automation.trace.tabs.${view}`
|
||||
)}
|
||||
</ha-tab-group-tab>
|
||||
</button>
|
||||
`
|
||||
)}
|
||||
</ha-tab-group>
|
||||
</div>
|
||||
${this._view === "step_config"
|
||||
? this._renderSelectedConfig()
|
||||
: this._view === "changed_variables"
|
||||
@@ -308,12 +308,7 @@ export class HaTracePathDetails extends LitElement {
|
||||
? this.hass!.localize(
|
||||
"ui.panel.config.automation.trace.path.no_variables_changed"
|
||||
)
|
||||
: html`<ha-code-editor
|
||||
read-only
|
||||
dir="ltr"
|
||||
.hass=${this.hass}
|
||||
.value=${dump(trace.changed_variables).trimEnd()}
|
||||
></ha-code-editor>`}
|
||||
: html`<pre>${dump(trace.changed_variables).trimEnd()}</pre>`}
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
@@ -388,12 +383,13 @@ export class HaTracePathDetails extends LitElement {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
private _handleTabChanged(ev: CustomEvent) {
|
||||
this._view = ev.detail.name as typeof this._view;
|
||||
private _showTab(ev) {
|
||||
this._view = ev.target.view;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
traceTabStyles,
|
||||
css`
|
||||
.padded-box {
|
||||
margin: 16px;
|
||||
@@ -410,16 +406,6 @@ export class HaTracePathDetails extends LitElement {
|
||||
.error {
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
ha-tab-group {
|
||||
background-color: var(--primary-background-color);
|
||||
border-top: 1px solid var(--divider-color);
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
}
|
||||
|
||||
ha-tab-group-tab::part(base) {
|
||||
padding: 2px 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { css } from "lit";
|
||||
|
||||
export const traceTabStyles = css`
|
||||
.tabs {
|
||||
background-color: var(--primary-background-color);
|
||||
border-top: 1px solid var(--divider-color);
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
display: flex;
|
||||
padding-left: 4px;
|
||||
padding-inline-start: 4px;
|
||||
padding-inline-end: initial;
|
||||
}
|
||||
|
||||
.tabs.top {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.tabs > * {
|
||||
padding: 2px 16px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
bottom: -1px;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
user-select: none;
|
||||
background: none;
|
||||
color: var(--primary-text-color);
|
||||
outline: none;
|
||||
transition: background 15ms linear;
|
||||
}
|
||||
|
||||
.tabs > *.active {
|
||||
border-bottom-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.tabs > *:focus,
|
||||
.tabs > *:hover {
|
||||
background: var(--secondary-background-color);
|
||||
}
|
||||
`;
|
||||
@@ -164,7 +164,6 @@ export interface BatterySourceTypeEnergyPreference {
|
||||
stat_energy_to: string;
|
||||
stat_rate?: string; // always available if power_config is set
|
||||
power_config?: PowerConfig;
|
||||
stat_soc?: string;
|
||||
}
|
||||
export interface GasSourceTypeEnergyPreference {
|
||||
type: "gas";
|
||||
|
||||
@@ -96,10 +96,7 @@ export const getEntities = (
|
||||
|
||||
const domainName = domainToName(hass.localize, computeDomain(entityId));
|
||||
|
||||
const isRTL = computeRTL(
|
||||
hass.language,
|
||||
hass.translationMetadata.translations
|
||||
);
|
||||
const isRTL = computeRTL(hass);
|
||||
|
||||
const primary = entityName || deviceName || entityId;
|
||||
const secondary = [areaName, entityName ? deviceName : undefined]
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
export interface HttpConfig {
|
||||
server_host?: string[];
|
||||
server_port?: number;
|
||||
ssl_certificate?: string;
|
||||
ssl_peer_certificate?: string;
|
||||
ssl_key?: string;
|
||||
cors_allowed_origins?: string[];
|
||||
use_x_forwarded_for?: boolean;
|
||||
trusted_proxies?: string[];
|
||||
use_x_frame_options?: boolean;
|
||||
ip_ban_enabled?: boolean;
|
||||
login_attempts_threshold?: number;
|
||||
ssl_profile?: "modern" | "intermediate";
|
||||
}
|
||||
|
||||
interface HttpConfigResponse {
|
||||
config: HttpConfig;
|
||||
}
|
||||
|
||||
export const fetchHttpConfig = (hass: HomeAssistant) =>
|
||||
hass.callWS<HttpConfigResponse>({ type: "http/config/get" });
|
||||
|
||||
export const saveHttpConfig = (hass: HomeAssistant, config: HttpConfig) =>
|
||||
hass.callWS<HttpConfigResponse>({
|
||||
type: "http/config/update",
|
||||
config,
|
||||
});
|
||||
+9
-26
@@ -456,13 +456,11 @@ const getIconFromTranslations = (
|
||||
};
|
||||
|
||||
export const entityIcon = async (
|
||||
entities: HomeAssistant["entities"],
|
||||
hassConfig: HomeAssistant["config"],
|
||||
hassConnection: Connection,
|
||||
hass: HomeAssistant,
|
||||
stateObj: HassEntity,
|
||||
state?: string
|
||||
) => {
|
||||
const entry = entities?.[stateObj.entity_id] as
|
||||
const entry = hass.entities?.[stateObj.entity_id] as
|
||||
| EntityRegistryDisplayEntry
|
||||
| undefined;
|
||||
if (entry?.icon) {
|
||||
@@ -470,14 +468,7 @@ export const entityIcon = async (
|
||||
}
|
||||
const domain = computeStateDomain(stateObj);
|
||||
|
||||
return getEntityIcon(
|
||||
hassConfig,
|
||||
hassConnection,
|
||||
domain,
|
||||
stateObj,
|
||||
state,
|
||||
entry
|
||||
);
|
||||
return getEntityIcon(hass, domain, stateObj, state, entry);
|
||||
};
|
||||
|
||||
export const entryIcon = async (
|
||||
@@ -489,19 +480,11 @@ export const entryIcon = async (
|
||||
}
|
||||
const stateObj = hass.states[entry.entity_id] as HassEntity | undefined;
|
||||
const domain = computeDomain(entry.entity_id);
|
||||
return getEntityIcon(
|
||||
hass.config,
|
||||
hass.connection,
|
||||
domain,
|
||||
stateObj,
|
||||
undefined,
|
||||
entry
|
||||
);
|
||||
return getEntityIcon(hass, domain, stateObj, undefined, entry);
|
||||
};
|
||||
|
||||
const getEntityIcon = async (
|
||||
hassConfig: HomeAssistant["config"],
|
||||
hassConnection: Connection,
|
||||
hass: HomeAssistant,
|
||||
domain: string,
|
||||
stateObj?: HassEntity,
|
||||
stateValue?: string,
|
||||
@@ -515,8 +498,8 @@ const getEntityIcon = async (
|
||||
let icon: string | undefined;
|
||||
if (translation_key && platform) {
|
||||
const platformIcons = await getPlatformIcons(
|
||||
hassConfig,
|
||||
hassConnection,
|
||||
hass.config,
|
||||
hass.connection,
|
||||
platform
|
||||
);
|
||||
if (platformIcons) {
|
||||
@@ -532,8 +515,8 @@ const getEntityIcon = async (
|
||||
|
||||
if (!icon) {
|
||||
const entityComponentIcons = await getComponentIcons(
|
||||
hassConnection,
|
||||
hassConfig,
|
||||
hass.connection,
|
||||
hass.config,
|
||||
domain
|
||||
);
|
||||
if (entityComponentIcons) {
|
||||
|
||||
@@ -58,6 +58,7 @@ class DialogConfigEntrySystemOptions extends LitElement {
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${this.hass.localize(
|
||||
"ui.dialogs.config_entry_system_options.title",
|
||||
|
||||
@@ -333,6 +333,7 @@ class DataEntryFlowDialog extends LitElement {
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
prevent-scrim-close
|
||||
@after-show=${this._focusFormStep}
|
||||
|
||||
@@ -18,7 +18,7 @@ import "../../../components/ha-slider";
|
||||
import "../../../components/ha-time-input";
|
||||
import "../../../components/input/ha-input";
|
||||
import { isTiltOnly } from "../../../data/cover";
|
||||
import { isUnavailableState, UNAVAILABLE } from "../../../data/entity/entity";
|
||||
import { isUnavailableState } from "../../../data/entity/entity";
|
||||
import type { ImageEntity } from "../../../data/image";
|
||||
import { computeImageUrl } from "../../../data/image";
|
||||
import "../../../panels/lovelace/components/hui-timestamp-display";
|
||||
@@ -266,7 +266,7 @@ class EntityPreviewRow extends LitElement {
|
||||
<div class="numberflex">
|
||||
<ha-slider
|
||||
labeled
|
||||
.disabled=${stateObj.state === UNAVAILABLE}
|
||||
.disabled=${isUnavailableState(stateObj.state)}
|
||||
.step=${Number(stateObj.attributes.step)}
|
||||
.min=${Number(stateObj.attributes.min)}
|
||||
.max=${Number(stateObj.attributes.max)}
|
||||
@@ -280,7 +280,7 @@ class EntityPreviewRow extends LitElement {
|
||||
: html`<div class="numberflex numberstate">
|
||||
<ha-input
|
||||
auto-validate
|
||||
.disabled=${stateObj.state === UNAVAILABLE}
|
||||
.disabled=${isUnavailableState(stateObj.state)}
|
||||
pattern="[0-9]+([\\.][0-9]+)?"
|
||||
.step=${Number(stateObj.attributes.step)}
|
||||
.min=${Number(stateObj.attributes.min)}
|
||||
@@ -303,7 +303,7 @@ class EntityPreviewRow extends LitElement {
|
||||
<ha-select
|
||||
.label=${computeStateName(stateObj)}
|
||||
.value=${stateObj.state}
|
||||
.disabled=${stateObj.state === UNAVAILABLE}
|
||||
.disabled=${isUnavailableState(stateObj.state)}
|
||||
.options=${stateObj.attributes.options?.map((option) => ({
|
||||
value: option,
|
||||
label: this.hass!.formatEntityState(stateObj, option),
|
||||
|
||||
@@ -103,6 +103,7 @@ export class ListItemsDialog
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${this._params.title ?? " "}
|
||||
@closed=${this._dialogClosed}
|
||||
|
||||
@@ -112,6 +112,7 @@ export class DialogEnterCode
|
||||
if (isText) {
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${this._dialogParams.title ??
|
||||
this.hass.localize("ui.dialogs.enter_code.title")}
|
||||
@@ -149,6 +150,7 @@ export class DialogEnterCode
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${this._dialogParams.title ?? "Enter code"}
|
||||
width="small"
|
||||
|
||||
@@ -140,6 +140,7 @@ export class DialogForm
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${this._params.title}
|
||||
prevent-scrim-close
|
||||
|
||||
@@ -96,6 +96,7 @@ export class HaImagecropperDialog
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${this.hass.localize(
|
||||
"ui.dialogs.image_cropper.crop_image"
|
||||
|
||||
@@ -148,6 +148,7 @@ class DialogLightColorFavorite extends LitElement {
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
.headerTitle=${this._dialogParams?.title}
|
||||
@closed=${this._dialogClosed}
|
||||
|
||||
@@ -65,6 +65,7 @@ class MoreInfoSirenAdvancedControls extends LitElement {
|
||||
return html`
|
||||
<ha-dialog
|
||||
.open=${this._open}
|
||||
.hass=${this.hass}
|
||||
header-title=${this.hass.localize(
|
||||
"ui.components.siren.advanced_controls"
|
||||
)}
|
||||
|
||||
@@ -46,7 +46,8 @@ class MoreInfoAlarmControlPanel extends LitElement {
|
||||
? html`
|
||||
<div class="status">
|
||||
<div class="icon">
|
||||
<ha-state-icon .stateObj=${this.stateObj}> </ha-state-icon>
|
||||
<ha-state-icon .hass=${this.hass} .stateObj=${this.stateObj}>
|
||||
</ha-state-icon>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
@@ -3,7 +3,7 @@ import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../components/ha-date-input";
|
||||
import "../../../components/ha-time-input";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../../../data/entity/entity";
|
||||
import { isUnavailableState, UNKNOWN } from "../../../data/entity/entity";
|
||||
import {
|
||||
setInputDateTimeValue,
|
||||
stateToIsoDateString,
|
||||
@@ -27,7 +27,7 @@ class MoreInfoInputDatetime extends LitElement {
|
||||
<ha-date-input
|
||||
.locale=${this.hass.locale}
|
||||
.value=${stateToIsoDateString(this.stateObj)}
|
||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||
.disabled=${isUnavailableState(this.stateObj.state)}
|
||||
@value-changed=${this._dateChanged}
|
||||
>
|
||||
</ha-date-input>
|
||||
@@ -42,7 +42,7 @@ class MoreInfoInputDatetime extends LitElement {
|
||||
? this.stateObj.state.split(" ")[1]
|
||||
: this.stateObj.state}
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||
.disabled=${isUnavailableState(this.stateObj.state)}
|
||||
@value-changed=${this._timeChanged}
|
||||
@click=${this._stopEventPropagation}
|
||||
></ha-time-input>
|
||||
|
||||
@@ -97,7 +97,10 @@ class MoreInfoLock extends LitElement {
|
||||
<div class="status">
|
||||
<span></span>
|
||||
<div class="icon">
|
||||
<ha-state-icon .stateObj=${this.stateObj}></ha-state-icon>
|
||||
<ha-state-icon
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
></ha-state-icon>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
@@ -190,6 +190,7 @@ class MoreInfoWeather extends LitElement {
|
||||
<ha-state-icon
|
||||
class="weather-icon"
|
||||
.stateObj=${this.stateObj}
|
||||
.hass=${this.hass}
|
||||
></ha-state-icon>
|
||||
`}
|
||||
</div>
|
||||
|
||||
@@ -594,13 +594,11 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
|
||||
? !favoritesHandler.hasCustomFavorites(favoritesContext.entry)
|
||||
: false;
|
||||
|
||||
const isRTL = computeRTL(
|
||||
this.hass.language,
|
||||
this.hass.translationMetadata.translations
|
||||
);
|
||||
const isRTL = computeRTL(this.hass);
|
||||
|
||||
return html`
|
||||
<ha-adaptive-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
.width=${this._fill ? "full" : this.large ? "large" : "medium"}
|
||||
@closed=${this._dialogClosed}
|
||||
|
||||
@@ -242,6 +242,7 @@ export class QuickBar extends LitElement {
|
||||
<ha-adaptive-dialog
|
||||
without-header
|
||||
flexcontent
|
||||
.hass=${this.hass}
|
||||
aria-label=${this.hass.localize("ui.dialogs.quick-bar.title")}
|
||||
.open=${this._open}
|
||||
hideActions
|
||||
@@ -252,6 +253,7 @@ export class QuickBar extends LitElement {
|
||||
${!this._loading && this._opened
|
||||
? html`<ha-picker-combo-box
|
||||
id="combo-box"
|
||||
.hass=${this.hass}
|
||||
@index-selected=${this._handleItemSelected}
|
||||
.notFoundLabel=${this.hass.localize(
|
||||
"ui.dialogs.quick-bar.nothing_found"
|
||||
|
||||
@@ -76,6 +76,7 @@ class DialogRestartWait extends LitElement {
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
.headerTitle=${this._title}
|
||||
@closed=${this._dialogClosed}
|
||||
|
||||
@@ -109,6 +109,7 @@ class DialogRestart extends LitElement {
|
||||
|
||||
return html`
|
||||
<ha-adaptive-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._dialogOpen}
|
||||
header-title=${dialogTitle}
|
||||
allow-mode-change
|
||||
|
||||
@@ -165,6 +165,7 @@ class DialogEditSidebar extends LitElement {
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${dialogTitle}
|
||||
header-subtitle=${!this._migrateToUserData
|
||||
|
||||
@@ -70,6 +70,7 @@ export class TTSTryDialog extends LitElement {
|
||||
}
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${this.hass.localize("ui.dialogs.tts-try.header")}
|
||||
@closed=${this._dialogClosed}
|
||||
|
||||
@@ -29,6 +29,7 @@ class DialogBox extends LitElement {
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${this.hass.localize("ui.dialogs.update_backup.title")}
|
||||
width="small"
|
||||
|
||||
@@ -143,6 +143,7 @@ export class HaVoiceAssistantSetupDialog extends LitElement {
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title="Voice Satellite setup"
|
||||
prevent-scrim-close
|
||||
|
||||
@@ -98,7 +98,12 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-dialog .open=${this._open} @closed=${this._dialogClosed} flexcontent>
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
@closed=${this._dialogClosed}
|
||||
flexcontent
|
||||
>
|
||||
<ha-dialog-header slot="header">
|
||||
<ha-icon-button
|
||||
slot="navigationIcon"
|
||||
|
||||
@@ -552,6 +552,7 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
|
||||
</hass-tabs-subpage>
|
||||
${this.showFilters && !showPane
|
||||
? html`<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${true}
|
||||
width="full"
|
||||
header-title=${localize("ui.components.subpage-data-table.filters")}
|
||||
|
||||
@@ -41,6 +41,7 @@ class ConfirmEventDialogBox extends LitElement {
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${this._params.title}
|
||||
width="small"
|
||||
|
||||
@@ -66,6 +66,7 @@ class DialogCalendarEventDetail extends LitElement {
|
||||
const stateObj = this.hass.states[this._calendarId!];
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${this._data!.summary}
|
||||
@closed=${this._dialogClosed}
|
||||
|
||||
@@ -149,6 +149,7 @@ class DialogCalendarEventEditor extends LitElement {
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${this.hass.localize(
|
||||
`ui.components.calendar.event.${isCreate ? "add" : "edit"}`
|
||||
|
||||
@@ -24,8 +24,8 @@ import "../../components/ha-two-pane-top-app-bar-fixed";
|
||||
import type {
|
||||
Calendar,
|
||||
CalendarEvent,
|
||||
CalendarEventApiData,
|
||||
CalendarEventSubscription,
|
||||
CalendarEventApiData,
|
||||
} from "../../data/calendar";
|
||||
import {
|
||||
getCalendars,
|
||||
@@ -144,6 +144,7 @@ class PanelCalendar extends SubscribeMixin(LitElement) {
|
||||
>
|
||||
<ha-state-icon
|
||||
slot="icon"
|
||||
.hass=${this.hass}
|
||||
.stateObj=${selCal}
|
||||
style="--icon-primary-color: ${selCal.backgroundColor}"
|
||||
></ha-state-icon>
|
||||
|
||||
@@ -98,6 +98,7 @@ export class DialogAddApplicationCredential extends LitElement {
|
||||
: "";
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
@closed=${this._abortDialog}
|
||||
.preventScrimClose=${!!this._domain ||
|
||||
|
||||
@@ -70,6 +70,7 @@ class AppsRegistriesDialog extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
@closed=${this._dialogClosed}
|
||||
header-title=${this.hass.localize(
|
||||
|
||||
@@ -87,6 +87,7 @@ class DialogAreasFloorsOrder extends LitElement {
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${dialogTitle}
|
||||
@closed=${this._dialogClosed}
|
||||
|
||||
@@ -108,6 +108,7 @@ class DialogFloorDetail extends LitElement {
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${entry
|
||||
? this.hass.localize("ui.panel.config.floors.editor.update_floor")
|
||||
|
||||
@@ -76,7 +76,6 @@ import type {
|
||||
} from "../../../../data/script";
|
||||
import { getActionType, isAction } from "../../../../data/script";
|
||||
import { describeAction } from "../../../../data/script_i18n";
|
||||
import type { TargetSelector } from "../../../../data/selector";
|
||||
import { callExecuteScript } from "../../../../data/service";
|
||||
import {
|
||||
showAlertDialog,
|
||||
@@ -289,12 +288,6 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
? { device_id: (this.action as DeviceAction).device_id }
|
||||
: undefined;
|
||||
|
||||
const serviceTargetSpec =
|
||||
type === "service" && action
|
||||
? this.hass.services?.[computeDomain(action)]?.[computeObjectId(action)]
|
||||
?.target
|
||||
: undefined;
|
||||
|
||||
return html`
|
||||
${type === "service" && "action" in this.action && this.action.action
|
||||
? html`
|
||||
@@ -324,11 +317,7 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
)
|
||||
)}
|
||||
${target !== undefined || (actionHasTarget && !this._isNew)
|
||||
? this._renderTargets(
|
||||
target,
|
||||
actionHasTarget && !this._isNew,
|
||||
serviceTargetSpec
|
||||
)
|
||||
? this._renderTargets(target, actionHasTarget && !this._isNew)
|
||||
: nothing}
|
||||
${type !== "condition" &&
|
||||
(this.action as NonConditionAction).continue_on_error === true
|
||||
@@ -692,16 +681,11 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
}
|
||||
|
||||
private _renderTargets = memoizeOne(
|
||||
(
|
||||
target?: HassServiceTarget,
|
||||
targetRequired = false,
|
||||
targetSpec?: TargetSelector["target"]
|
||||
) =>
|
||||
(target?: HassServiceTarget, targetRequired = false) =>
|
||||
html`<ha-automation-row-targets
|
||||
.hass=${this.hass}
|
||||
.target=${target}
|
||||
.targetRequired=${targetRequired}
|
||||
.selector=${targetSpec ? { target: targetSpec } : undefined}
|
||||
></ha-automation-row-targets>`
|
||||
);
|
||||
|
||||
|
||||
@@ -598,6 +598,7 @@ class DialogAddAutomationElement
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
width="large"
|
||||
.open=${this._open}
|
||||
@closed=${this._handleClosed}
|
||||
@@ -970,14 +971,7 @@ class DialogAddAutomationElement
|
||||
|
||||
subtitle = [areaName, entityName ? deviceName : undefined]
|
||||
.filter(Boolean)
|
||||
.join(
|
||||
computeRTL(
|
||||
this.hass.language,
|
||||
this.hass.translationMetadata.translations
|
||||
)
|
||||
? " ◂ "
|
||||
: " ▸ "
|
||||
);
|
||||
.join(computeRTL(this.hass) ? " ◂ " : " ▸ ");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-4
@@ -769,6 +769,7 @@ export default class HaAutomationAddFromTarget extends LitElement {
|
||||
private _renderEntityIcon =
|
||||
(stateObj: HassEntity) => (slot: string | undefined) =>
|
||||
html`<ha-state-icon
|
||||
.hass=${this.hass}
|
||||
slot=${ifDefined(slot)}
|
||||
.stateObj=${stateObj}
|
||||
></ha-state-icon>`;
|
||||
@@ -866,13 +867,10 @@ export default class HaAutomationAddFromTarget extends LitElement {
|
||||
undefined
|
||||
);
|
||||
|
||||
const filteredFloors = this._floorAreas.filter(
|
||||
({ id, areas }) => id !== undefined && areas.length
|
||||
);
|
||||
this._floorAreas.forEach((floor) => {
|
||||
this._entries[floor.id || `floor${TARGET_SEPARATOR}`] = {
|
||||
// auto expand if only one floor is present
|
||||
open: filteredFloors.length === 1 && filteredFloors[0].id === floor.id,
|
||||
open: this._floorAreas.length === 1,
|
||||
areas: {},
|
||||
};
|
||||
|
||||
|
||||
@@ -300,10 +300,7 @@ export class HaAutomationAddSearch extends LitElement {
|
||||
let showEntityId = false;
|
||||
|
||||
if (type === "area" || type === "floor") {
|
||||
rtl = computeRTL(
|
||||
this.hass.language,
|
||||
this.hass.translationMetadata.translations
|
||||
);
|
||||
rtl = computeRTL(this.hass);
|
||||
hasFloor =
|
||||
type === "area" && !!(item as FloorComboBoxItem).area?.floor_id;
|
||||
}
|
||||
|
||||
+1
@@ -57,6 +57,7 @@ class DialogAutomationSaveTimeout extends LitElement {
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._opened}
|
||||
header-title=${title}
|
||||
@closed=${this._dialogClosed}
|
||||
|
||||
@@ -60,7 +60,6 @@ import {
|
||||
import { fullEntitiesContext } from "../../../../data/context";
|
||||
import type { DeviceCondition } from "../../../../data/device/device_automation";
|
||||
import type { EntityRegistryEntry } from "../../../../data/entity/entity_registry";
|
||||
import type { TargetSelector } from "../../../../data/selector";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showPromptDialog,
|
||||
@@ -181,9 +180,6 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
? { device_id: [(this.condition as DeviceCondition).device_id] }
|
||||
: undefined;
|
||||
|
||||
const conditionTargetSpec =
|
||||
this.conditionDescriptions[this.condition.condition]?.target;
|
||||
|
||||
return html`
|
||||
<ha-condition-icon
|
||||
slot="leading-icon"
|
||||
@@ -195,11 +191,7 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
describeCondition(this.condition, this.hass, this._entityReg)
|
||||
)}
|
||||
${target !== undefined || (descriptionHasTarget && !this._isNew)
|
||||
? this._renderTargets(
|
||||
target,
|
||||
descriptionHasTarget && !this._isNew,
|
||||
conditionTargetSpec
|
||||
)
|
||||
? this._renderTargets(target, descriptionHasTarget && !this._isNew)
|
||||
: nothing}
|
||||
</h3>
|
||||
<ha-automation-row-event-chip
|
||||
@@ -513,16 +505,11 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
}
|
||||
|
||||
private _renderTargets = memoizeOne(
|
||||
(
|
||||
target?: HassServiceTarget,
|
||||
targetRequired = false,
|
||||
targetSpec?: TargetSelector["target"]
|
||||
) =>
|
||||
(target?: HassServiceTarget, targetRequired = false) =>
|
||||
html`<ha-automation-row-targets
|
||||
.hass=${this.hass}
|
||||
.target=${target}
|
||||
.targetRequired=${targetRequired}
|
||||
.selector=${targetSpec ? { target: targetSpec } : undefined}
|
||||
></ha-automation-row-targets>`
|
||||
);
|
||||
|
||||
|
||||
@@ -152,6 +152,7 @@ class DialogNewAutomation extends LitElement {
|
||||
|
||||
return html`
|
||||
<ha-adaptive-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
flexcontent
|
||||
header-title=${this.hass.localize(
|
||||
|
||||
@@ -302,6 +302,7 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
|
||||
showNarrow: true,
|
||||
template: (automation) =>
|
||||
html`<ha-state-icon
|
||||
.hass=${this.hass}
|
||||
.stateObj=${automation}
|
||||
style=${styleMap({
|
||||
color:
|
||||
|
||||
@@ -303,9 +303,7 @@ export default class HaAutomationSidebar extends LitElement {
|
||||
private _updateSize(clientX: number) {
|
||||
let delta = this._resizeStartX - clientX;
|
||||
|
||||
if (
|
||||
computeRTL(this.hass.language, this.hass.translationMetadata.translations)
|
||||
) {
|
||||
if (computeRTL(this.hass)) {
|
||||
delta = -delta;
|
||||
}
|
||||
|
||||
@@ -352,24 +350,14 @@ export default class HaAutomationSidebar extends LitElement {
|
||||
private _increaseSize = (ev: KeyboardEvent) => {
|
||||
ev.stopPropagation();
|
||||
|
||||
this._resizeStartX -= computeRTL(
|
||||
this.hass.language,
|
||||
this.hass.translationMetadata.translations
|
||||
)
|
||||
? 10
|
||||
: -10;
|
||||
this._resizeStartX -= computeRTL(this.hass) ? 10 : -10;
|
||||
this._keyboardResize();
|
||||
};
|
||||
|
||||
private _decreaseSize = (ev: KeyboardEvent) => {
|
||||
ev.stopPropagation();
|
||||
|
||||
this._resizeStartX += computeRTL(
|
||||
this.hass.language,
|
||||
this.hass.translationMetadata.translations
|
||||
)
|
||||
? 10
|
||||
: -10;
|
||||
this._resizeStartX += computeRTL(this.hass) ? 10 : -10;
|
||||
this._keyboardResize();
|
||||
};
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import type { CSSResultGroup, TemplateResult, PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
@@ -20,8 +21,6 @@ import "../../../components/ha-dropdown";
|
||||
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-tab-group";
|
||||
import "../../../components/ha-tab-group-tab";
|
||||
import "../../../components/trace/ha-trace-blueprint-config";
|
||||
import "../../../components/trace/ha-trace-config";
|
||||
import "../../../components/trace/ha-trace-logbook";
|
||||
@@ -32,6 +31,7 @@ import type {
|
||||
HatScriptGraph,
|
||||
NodeInfo,
|
||||
} from "../../../components/trace/hat-script-graph";
|
||||
import { traceTabStyles } from "../../../components/trace/trace-tab-styles";
|
||||
import type { AutomationEntity } from "../../../data/automation";
|
||||
import type { LogbookEntry } from "../../../data/logbook";
|
||||
import { getLogbookDataForContext } from "../../../data/logbook";
|
||||
@@ -172,10 +172,7 @@ export class HaAutomationTrace extends LitElement {
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.automation.trace.older_trace"
|
||||
)}
|
||||
.path=${computeRTL(
|
||||
this.hass!.language,
|
||||
this.hass!.translationMetadata.translations
|
||||
)
|
||||
.path=${computeRTL(this.hass!)
|
||||
? mdiRayStartArrow
|
||||
: mdiRayEndArrow}
|
||||
.disabled=${this._traces[this._traces.length - 1].run_id ===
|
||||
@@ -192,10 +189,7 @@ export class HaAutomationTrace extends LitElement {
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.automation.trace.newer_trace"
|
||||
)}
|
||||
.path=${computeRTL(
|
||||
this.hass!.language,
|
||||
this.hass!.translationMetadata.translations
|
||||
)
|
||||
.path=${computeRTL(this.hass!)
|
||||
? mdiRayEndArrow
|
||||
: mdiRayStartArrow}
|
||||
.disabled=${this._traces[0].run_id === this._runId}
|
||||
@@ -229,34 +223,40 @@ export class HaAutomationTrace extends LitElement {
|
||||
</div>
|
||||
|
||||
<div class="info">
|
||||
<ha-tab-group @wa-tab-show=${this._handleTabChanged}>
|
||||
<div class="tabs top">
|
||||
${TABS.map(
|
||||
(view) => html`
|
||||
<ha-tab-group-tab
|
||||
slot="nav"
|
||||
.active=${this._view === view}
|
||||
.panel=${view}
|
||||
<button
|
||||
tabindex="0"
|
||||
.view=${view}
|
||||
class=${classMap({
|
||||
active: this._view === view,
|
||||
})}
|
||||
@click=${this._showTab}
|
||||
>
|
||||
${this.hass!.localize(
|
||||
`ui.panel.config.automation.trace.tabs.${view}`
|
||||
)}
|
||||
</ha-tab-group-tab>
|
||||
</button>
|
||||
`
|
||||
)}
|
||||
${this._trace.blueprint_inputs
|
||||
? html`
|
||||
<ha-tab-group-tab
|
||||
slot="nav"
|
||||
.active=${this._view === "blueprint"}
|
||||
panel="blueprint"
|
||||
<button
|
||||
tabindex="0"
|
||||
.view=${"blueprint"}
|
||||
class=${classMap({
|
||||
active: this._view === "blueprint",
|
||||
})}
|
||||
@click=${this._showTab}
|
||||
>
|
||||
${this.hass!.localize(
|
||||
`ui.panel.config.automation.trace.tabs.blueprint_config`
|
||||
)}
|
||||
</ha-tab-group-tab>
|
||||
</button>
|
||||
`
|
||||
: ""}
|
||||
</ha-tab-group>
|
||||
</div>
|
||||
${this._selected === undefined ||
|
||||
this._logbookEntries === undefined ||
|
||||
trackedNodes === undefined
|
||||
@@ -483,8 +483,8 @@ export class HaAutomationTrace extends LitElement {
|
||||
this._logbookEntries = traceInfo.logbookEntries;
|
||||
}
|
||||
|
||||
private _handleTabChanged(ev: CustomEvent) {
|
||||
this._view = ev.detail.name as typeof this._view;
|
||||
private _showTab(ev: Event) {
|
||||
this._view = (ev.target as any).view;
|
||||
}
|
||||
|
||||
private _timelinePathPicked(ev: CustomEvent) {
|
||||
@@ -536,6 +536,7 @@ export class HaAutomationTrace extends LitElement {
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
traceTabStyles,
|
||||
css`
|
||||
.toolbar {
|
||||
display: flex;
|
||||
@@ -598,14 +599,6 @@ export class HaAutomationTrace extends LitElement {
|
||||
overflow-y: auto;
|
||||
background-color: var(--card-background-color);
|
||||
}
|
||||
ha-tab-group {
|
||||
background-color: var(--primary-background-color);
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
direction: var(--direction);
|
||||
}
|
||||
ha-tab-group-tab::part(base) {
|
||||
padding: 2px 16px;
|
||||
}
|
||||
:host([narrow]) .info {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ class DialogPasteReplace extends LitElement {
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
@closed=${this._dialogClosed}
|
||||
header-title=${this.hass.localize(
|
||||
|
||||
@@ -142,7 +142,6 @@ export default class HaAutomationSidebarCard extends ScrollableFadeMixin(
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
padding-bottom: max(var(--safe-area-inset-bottom, 0px), 32px);
|
||||
}
|
||||
|
||||
|
||||
@@ -241,6 +241,7 @@ export const automationRowsStyles = css`
|
||||
export const sidebarEditorStyles = css`
|
||||
.sidebar-editor {
|
||||
display: block;
|
||||
padding-top: 8px;
|
||||
}
|
||||
.description {
|
||||
padding-top: 16px;
|
||||
|
||||
@@ -14,8 +14,7 @@ export const getTargetIcon = (
|
||||
targetType: string,
|
||||
targetId: string | undefined,
|
||||
configEntryLookup: Record<string, ConfigEntry>,
|
||||
getLabel?: (id: string) => LabelRegistryEntry | undefined,
|
||||
slot?: string
|
||||
getLabel?: (id: string) => LabelRegistryEntry | undefined
|
||||
): TemplateResult | typeof nothing => {
|
||||
if (!targetId) {
|
||||
return nothing;
|
||||
@@ -23,7 +22,6 @@ export const getTargetIcon = (
|
||||
|
||||
if (targetType === "floor" && hass.floors[targetId]) {
|
||||
return html`<ha-floor-icon
|
||||
.slot=${slot}
|
||||
.floor=${hass.floors[targetId]}
|
||||
></ha-floor-icon>`;
|
||||
}
|
||||
@@ -31,12 +29,9 @@ export const getTargetIcon = (
|
||||
if (targetType === "area") {
|
||||
const area = hass.areas[targetId];
|
||||
if (area?.icon) {
|
||||
return html`<ha-icon .slot=${slot} .icon=${area.icon}></ha-icon>`;
|
||||
return html`<ha-icon .icon=${area.icon}></ha-icon>`;
|
||||
}
|
||||
return html`<ha-svg-icon
|
||||
.slot=${slot}
|
||||
.path=${mdiTextureBox}
|
||||
></ha-svg-icon>`;
|
||||
return html`<ha-svg-icon .path=${mdiTextureBox}></ha-svg-icon>`;
|
||||
}
|
||||
|
||||
if (targetType === "device" && hass.devices[targetId]) {
|
||||
@@ -50,24 +45,23 @@ export const getTargetIcon = (
|
||||
return html`<ha-domain-icon
|
||||
.domain=${domain}
|
||||
brand-fallback
|
||||
.slot=${slot}
|
||||
></ha-domain-icon>`;
|
||||
}
|
||||
}
|
||||
|
||||
if (targetType === "entity" && hass.states[targetId]) {
|
||||
return html`<ha-state-icon
|
||||
.hass=${hass}
|
||||
.stateObj=${hass.states[targetId]}
|
||||
.slot=${slot}
|
||||
></ha-state-icon>`;
|
||||
}
|
||||
|
||||
if (targetType === "label" && getLabel) {
|
||||
const label = getLabel(targetId);
|
||||
if (label?.icon) {
|
||||
return html`<ha-icon .slot=${slot} .icon=${label.icon}></ha-icon>`;
|
||||
return html`<ha-icon .icon=${label.icon}></ha-icon>`;
|
||||
}
|
||||
return html`<ha-svg-icon .slot=${slot} .path=${mdiLabel}></ha-svg-icon>`;
|
||||
return html`<ha-svg-icon .path=${mdiLabel}></ha-svg-icon>`;
|
||||
}
|
||||
|
||||
return nothing;
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import { consume, type ContextType } from "@lit/context";
|
||||
import {
|
||||
mdiAlert,
|
||||
mdiAlertOctagon,
|
||||
mdiCodeBraces,
|
||||
mdiFormatListBulleted,
|
||||
mdiMenuDown,
|
||||
mdiShape,
|
||||
} from "@mdi/js";
|
||||
import type { HassServiceTarget } from "home-assistant-js-websocket";
|
||||
@@ -14,13 +12,8 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { ensureArray } from "../../../../common/array/ensure-array";
|
||||
import { transform } from "../../../../common/decorators/transform";
|
||||
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||
import { isTemplate } from "../../../../common/string/has-template";
|
||||
import "../../../../components/ha-dropdown";
|
||||
import type { HaDropdownSelectEvent } from "../../../../components/ha-dropdown";
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import { showTargetDetailsDialog } from "../../../../components/target-picker/dialog/show-dialog-target-details";
|
||||
import type { ConfigEntry } from "../../../../data/config_entries";
|
||||
import {
|
||||
configEntriesContext,
|
||||
@@ -30,9 +23,6 @@ import {
|
||||
statesContext,
|
||||
} from "../../../../data/context";
|
||||
import type { LabelRegistryEntry } from "../../../../data/label/label_registry";
|
||||
import type { TargetSelector } from "../../../../data/selector";
|
||||
import type { TargetType } from "../../../../data/target";
|
||||
import { showMoreInfoDialog } from "../../../../dialogs/more-info/show-ha-more-info-dialog";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { getTargetIcon } from "./get_target_icon";
|
||||
import { getTargetText } from "./get_target_text";
|
||||
@@ -48,9 +38,6 @@ export class HaAutomationRowTargets extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public targetRequired = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
public selector?: TargetSelector;
|
||||
|
||||
@state()
|
||||
@consume({ context: internationalizationContext, subscribe: true })
|
||||
private _i18n!: ContextType<typeof internationalizationContext>;
|
||||
@@ -123,67 +110,17 @@ export class HaAutomationRowTargets extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
const rows = Object.entries(this.target!)
|
||||
.reduce<["floor" | "area" | "device" | "entity" | "label", string][]>(
|
||||
(acc, [targetType, targetId]) => {
|
||||
const type = targetType.replace("_id", "") as
|
||||
| "floor"
|
||||
| "area"
|
||||
| "device"
|
||||
| "entity"
|
||||
| "label";
|
||||
return [
|
||||
...acc,
|
||||
...ensureArray(targetId).map((id): [typeof type, string] => [
|
||||
type,
|
||||
id,
|
||||
]),
|
||||
];
|
||||
},
|
||||
[]
|
||||
)
|
||||
.sort(([typeA], [typeB]) => {
|
||||
const order = ["entity", "device", "area", "floor", "label"];
|
||||
return order.indexOf(typeA) - order.indexOf(typeB);
|
||||
});
|
||||
|
||||
let lastTargetType: string | null = null;
|
||||
|
||||
return html`
|
||||
<ha-dropdown
|
||||
@wa-select=${this._handleTargetSelect}
|
||||
@click=${stopPropagation}
|
||||
>
|
||||
<span slot="trigger" class="target interactive">
|
||||
<ha-svg-icon .path=${mdiFormatListBulleted}></ha-svg-icon>
|
||||
<div class="label">
|
||||
${this._i18n.localize(
|
||||
"ui.panel.config.automation.editor.target_summary.targets",
|
||||
{
|
||||
count: totalLength,
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
<ha-svg-icon .path=${mdiMenuDown}></ha-svg-icon>
|
||||
</span>
|
||||
${rows.map(([targetType, targetId]) => {
|
||||
const content = html`${lastTargetType !== null &&
|
||||
lastTargetType !== targetType
|
||||
? html`<wa-divider></wa-divider>`
|
||||
: nothing}
|
||||
${!lastTargetType || lastTargetType !== targetType
|
||||
? html`<h3>
|
||||
${this._i18n.localize(
|
||||
`ui.panel.config.automation.editor.target_summary.types.${targetType}`
|
||||
)}
|
||||
</h3>`
|
||||
: nothing}
|
||||
${this._renderTarget(targetType, targetId, true)}`;
|
||||
lastTargetType = targetType;
|
||||
return content;
|
||||
})}
|
||||
</ha-dropdown>
|
||||
`;
|
||||
return html`<span class="target">
|
||||
<ha-svg-icon .path=${mdiFormatListBulleted}></ha-svg-icon>
|
||||
<div class="label">
|
||||
${this._i18n.localize(
|
||||
"ui.panel.config.automation.editor.target_summary.targets",
|
||||
{
|
||||
count: totalLength,
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</span>`;
|
||||
}
|
||||
|
||||
private _getLabel = (id: string) =>
|
||||
@@ -215,22 +152,9 @@ export class HaAutomationRowTargets extends LitElement {
|
||||
icon: TemplateResult | typeof nothing,
|
||||
label: string,
|
||||
warning = false,
|
||||
error = false,
|
||||
targetId?: string,
|
||||
targetType?: string
|
||||
error = false
|
||||
) {
|
||||
return html`<div
|
||||
class=${classMap({
|
||||
target: true,
|
||||
warning,
|
||||
error,
|
||||
interactive: targetId && targetType,
|
||||
})}
|
||||
.targetId=${targetId}
|
||||
.targetType=${targetType}
|
||||
.label=${label}
|
||||
@click=${this._handleTargetClick}
|
||||
>
|
||||
return html`<div class=${classMap({ target: true, warning, error })}>
|
||||
${icon}
|
||||
<div class="label">${label}</div>
|
||||
</div>`;
|
||||
@@ -238,132 +162,48 @@ export class HaAutomationRowTargets extends LitElement {
|
||||
|
||||
private _renderTarget(
|
||||
targetType: "floor" | "area" | "device" | "entity" | "label",
|
||||
targetId: string,
|
||||
dropdownOption = false
|
||||
targetId: string
|
||||
) {
|
||||
let icon: string | undefined;
|
||||
let label: string;
|
||||
let warning = false;
|
||||
let badgeTargetId: string | undefined = targetId;
|
||||
let badgeTargetType: string | undefined = targetType;
|
||||
|
||||
if (targetType === "entity" && ["all", "none"].includes(targetId)) {
|
||||
icon = mdiShape;
|
||||
label = this._i18n.localize(
|
||||
`ui.panel.config.automation.editor.target_summary.${targetId as "all" | "none"}_entities`
|
||||
return this._renderTargetBadge(
|
||||
html`<ha-svg-icon .path=${mdiShape}></ha-svg-icon>`,
|
||||
this._i18n.localize(
|
||||
`ui.panel.config.automation.editor.target_summary.${targetId as "all" | "none"}_entities`
|
||||
)
|
||||
);
|
||||
badgeTargetId = undefined;
|
||||
badgeTargetType = undefined;
|
||||
} else if (isTemplate(targetId)) {
|
||||
// Check if the target is a template
|
||||
icon = mdiCodeBraces;
|
||||
label = this._i18n.localize(
|
||||
"ui.panel.config.automation.editor.target_summary.template"
|
||||
);
|
||||
badgeTargetId = undefined;
|
||||
badgeTargetType = undefined;
|
||||
} else {
|
||||
const exists = this._checkTargetExists(targetType, targetId);
|
||||
if (!exists) {
|
||||
icon = mdiAlert;
|
||||
label = getTargetText(this.hass, targetType, targetId, this._getLabel);
|
||||
warning = true;
|
||||
badgeTargetId = undefined;
|
||||
badgeTargetType = undefined;
|
||||
} else {
|
||||
label = getTargetText(this.hass, targetType, targetId, this._getLabel);
|
||||
}
|
||||
}
|
||||
|
||||
const iconTemplate = icon
|
||||
? html`<ha-svg-icon
|
||||
.slot=${dropdownOption ? "icon" : ""}
|
||||
.path=${icon}
|
||||
></ha-svg-icon>`
|
||||
: getTargetIcon(
|
||||
this.hass,
|
||||
targetType,
|
||||
targetId,
|
||||
this._configEntryLookup || {},
|
||||
this._getLabel,
|
||||
dropdownOption ? "icon" : ""
|
||||
);
|
||||
// Check if the target is a template
|
||||
if (isTemplate(targetId)) {
|
||||
return this._renderTargetBadge(
|
||||
html`<ha-svg-icon .path=${mdiCodeBraces}></ha-svg-icon>`,
|
||||
this._i18n.localize(
|
||||
"ui.panel.config.automation.editor.target_summary.template"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (dropdownOption) {
|
||||
return html`<ha-dropdown-item
|
||||
.value=${{
|
||||
targetId: badgeTargetId,
|
||||
targetType: badgeTargetType,
|
||||
label,
|
||||
}}
|
||||
class=${classMap({
|
||||
warning,
|
||||
})}
|
||||
>${iconTemplate} ${label}</ha-dropdown-item
|
||||
>`;
|
||||
const exists = this._checkTargetExists(targetType, targetId);
|
||||
if (!exists) {
|
||||
return this._renderTargetBadge(
|
||||
html`<ha-svg-icon .path=${mdiAlert}></ha-svg-icon>`,
|
||||
getTargetText(this.hass, targetType, targetId, this._getLabel),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
return this._renderTargetBadge(
|
||||
iconTemplate,
|
||||
label,
|
||||
warning,
|
||||
false,
|
||||
badgeTargetId,
|
||||
badgeTargetType
|
||||
getTargetIcon(
|
||||
this.hass,
|
||||
targetType,
|
||||
targetId,
|
||||
this._configEntryLookup || {},
|
||||
this._getLabel
|
||||
),
|
||||
getTargetText(this.hass, targetType, targetId, this._getLabel)
|
||||
);
|
||||
}
|
||||
|
||||
private _handleTargetClick(ev: Event) {
|
||||
const target = ev.currentTarget as HTMLDivElement & {
|
||||
targetId: string;
|
||||
targetType: TargetType;
|
||||
label: string;
|
||||
};
|
||||
|
||||
if (!target.targetId || !target.targetType) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._showTargetInfo(target.targetId, target.targetType, target.label, ev);
|
||||
}
|
||||
|
||||
private _handleTargetSelect(
|
||||
ev: HaDropdownSelectEvent<{
|
||||
targetId?: string;
|
||||
targetType?: TargetType;
|
||||
label: string;
|
||||
}>
|
||||
) {
|
||||
const value = ev.detail.item.value;
|
||||
|
||||
if (!value.targetId || !value.targetType) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._showTargetInfo(value.targetId, value.targetType, value.label);
|
||||
}
|
||||
|
||||
private _showTargetInfo(
|
||||
targetId: string,
|
||||
targetType: TargetType,
|
||||
label: string,
|
||||
ev?: Event
|
||||
) {
|
||||
ev?.stopPropagation();
|
||||
|
||||
if (targetType === "entity") {
|
||||
showMoreInfoDialog(this, { entityId: targetId });
|
||||
return;
|
||||
}
|
||||
|
||||
showTargetDetailsDialog(this, {
|
||||
title: label,
|
||||
type: targetType,
|
||||
itemId: targetId,
|
||||
selector: this.selector,
|
||||
});
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: contents;
|
||||
@@ -415,25 +255,6 @@ export class HaAutomationRowTargets extends LitElement {
|
||||
height: 32px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.target.interactive {
|
||||
cursor: pointer;
|
||||
}
|
||||
.target.interactive:hover {
|
||||
background: var(--ha-color-fill-neutral-normal-hover);
|
||||
}
|
||||
|
||||
ha-dropdown-item {
|
||||
padding: 0 var(--ha-space-2);
|
||||
}
|
||||
ha-dropdown-item.warning {
|
||||
background-color: var(--ha-color-fill-warning-quiet-resting);
|
||||
color: var(--ha-color-on-warning-normal);
|
||||
}
|
||||
ha-dropdown-item.warning:hover {
|
||||
background-color: var(--ha-color-fill-warning-quiet-hover);
|
||||
color: var(--ha-color-on-warning-normal);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,6 @@ import { validateConfig } from "../../../../data/config";
|
||||
import { fullEntitiesContext } from "../../../../data/context";
|
||||
import type { DeviceTrigger } from "../../../../data/device/device_automation";
|
||||
import type { EntityRegistryEntry } from "../../../../data/entity/entity_registry";
|
||||
import type { TargetSelector } from "../../../../data/selector";
|
||||
import type { TriggerDescriptions } from "../../../../data/trigger";
|
||||
import { isTriggerList } from "../../../../data/trigger";
|
||||
import {
|
||||
@@ -215,12 +214,6 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
? { device_id: (this.trigger as DeviceTrigger).device_id }
|
||||
: undefined;
|
||||
|
||||
const triggerTargetSpec =
|
||||
type === "platform"
|
||||
? this.triggerDescriptions[(this.trigger as PlatformTrigger).trigger]
|
||||
?.target
|
||||
: undefined;
|
||||
|
||||
return html`
|
||||
${type === "list"
|
||||
? html`<ha-svg-icon
|
||||
@@ -236,11 +229,7 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
<h3 slot="header">
|
||||
${describeTrigger(this.trigger, this.hass, this._entityReg)}
|
||||
${target !== undefined || (descriptionHasTarget && !this._isNew)
|
||||
? this._renderTargets(
|
||||
target,
|
||||
descriptionHasTarget && !this._isNew,
|
||||
triggerTargetSpec
|
||||
)
|
||||
? this._renderTargets(target, descriptionHasTarget && !this._isNew)
|
||||
: nothing}
|
||||
</h3>
|
||||
<ha-automation-row-event-chip
|
||||
@@ -518,16 +507,11 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
}
|
||||
|
||||
private _renderTargets = memoizeOne(
|
||||
(
|
||||
target?: HassServiceTarget,
|
||||
targetRequired = false,
|
||||
targetSpec?: TargetSelector["target"]
|
||||
) =>
|
||||
(target?: HassServiceTarget, targetRequired = false) =>
|
||||
html`<ha-automation-row-targets
|
||||
.hass=${this.hass}
|
||||
.target=${target}
|
||||
.targetRequired=${targetRequired}
|
||||
.selector=${targetSpec ? { target: targetSpec } : undefined}
|
||||
></ha-automation-row-targets>`
|
||||
);
|
||||
|
||||
|
||||
@@ -209,6 +209,7 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${this._stepTitle}
|
||||
prevent-scrim-close
|
||||
|
||||
@@ -89,6 +89,7 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${dialogTitle}
|
||||
prevent-scrim-close
|
||||
|
||||
@@ -56,6 +56,7 @@ class DialogDownloadDecryptedBackup extends LitElement implements HassDialog {
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.download.title"
|
||||
|
||||
@@ -181,7 +181,11 @@ class DialogGenerateBackup extends LitElement implements HassDialog {
|
||||
const selectedAgents = this._formData.agent_ids;
|
||||
|
||||
return html`
|
||||
<ha-dialog .open=${this._open} @closed=${this._dialogClosed}>
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
<ha-dialog-header slot="header">
|
||||
${isFirstStep
|
||||
? html`
|
||||
|
||||
@@ -64,6 +64,7 @@ class LocalBackupLocationDialog extends LitElement {
|
||||
}
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${this.hass.localize(
|
||||
`ui.panel.config.backup.dialogs.local_backup_location.title`
|
||||
|
||||
@@ -46,6 +46,7 @@ class DialogNewBackup extends LitElement implements HassDialog {
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._opened}
|
||||
header-title=${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.new.title"
|
||||
|
||||
@@ -137,6 +137,7 @@ class DialogRestoreBackup extends LitElement implements HassDialog {
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${dialogTitle}
|
||||
@closed=${this._dialogClosed}
|
||||
|
||||
@@ -86,6 +86,7 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${dialogTitle}
|
||||
prevent-scrim-close
|
||||
|
||||
@@ -47,6 +47,7 @@ class DialogShowBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.show_encryption_key.title"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user