Compare commits

..

2 Commits

Author SHA1 Message Date
Wendelin
1277dc303d remove unused attributes 2025-12-03 08:58:22 +01:00
Wendelin
5e3d38c073 Add unchecked icon support to ha-dropdown-item component 2025-12-03 08:53:58 +01:00
65 changed files with 1363 additions and 1868 deletions

View File

@@ -156,9 +156,7 @@ const createTestTranslation = () =>
*/
const createMasterTranslation = () =>
gulp
.src([EN_SRC, ...(mergeBackend ? [`${inBackendDir}/en.json`] : [])], {
allowEmpty: true,
})
.src([EN_SRC, ...(mergeBackend ? [`${inBackendDir}/en.json`] : [])])
.pipe(new CustomJSON(lokaliseTransform))
.pipe(new MergeJSON("en"))
.pipe(gulp.dest(workDir));

View File

@@ -44,24 +44,18 @@ export const mockEnergy = (hass: MockHomeAssistant) => {
number_energy_price: null,
},
],
power: [
{ stat_rate: "sensor.power_grid" },
{ stat_rate: "sensor.power_grid_return" },
],
cost_adjustment_day: 0,
},
{
type: "solar",
stat_energy_from: "sensor.solar_production",
stat_rate: "sensor.power_solar",
config_entry_solar_forecast: ["solar_forecast"],
},
{
/* {
type: "battery",
stat_energy_from: "sensor.battery_output",
stat_energy_to: "sensor.battery_input",
stat_rate: "sensor.power_battery",
},
}, */
{
type: "gas",
stat_energy_from: "sensor.energy_gas",
@@ -69,48 +63,28 @@ export const mockEnergy = (hass: MockHomeAssistant) => {
entity_energy_price: null,
number_energy_price: null,
},
{
type: "water",
stat_energy_from: "sensor.energy_water",
stat_cost: "sensor.energy_water_cost",
entity_energy_price: null,
number_energy_price: null,
},
],
device_consumption: [
{
stat_consumption: "sensor.energy_car",
stat_rate: "sensor.power_car",
},
{
stat_consumption: "sensor.energy_ac",
stat_rate: "sensor.power_ac",
},
{
stat_consumption: "sensor.energy_washing_machine",
stat_rate: "sensor.power_washing_machine",
},
{
stat_consumption: "sensor.energy_dryer",
stat_rate: "sensor.power_dryer",
},
{
stat_consumption: "sensor.energy_heat_pump",
stat_rate: "sensor.power_heat_pump",
},
{
stat_consumption: "sensor.energy_boiler",
stat_rate: "sensor.power_boiler",
},
],
device_consumption_water: [
{
stat_consumption: "sensor.water_kitchen",
},
{
stat_consumption: "sensor.water_garden",
},
],
device_consumption_water: [],
})
);
hass.mockWS(

View File

@@ -154,38 +154,6 @@ export const energyEntities = () =>
unit_of_measurement: "EUR",
},
},
"sensor.power_grid": {
entity_id: "sensor.power_grid",
state: "500",
attributes: {
state_class: "measurement",
unit_of_measurement: "W",
},
},
"sensor.power_grid_return": {
entity_id: "sensor.power_grid_return",
state: "-100",
attributes: {
state_class: "measurement",
unit_of_measurement: "W",
},
},
"sensor.power_solar": {
entity_id: "sensor.power_solar",
state: "200",
attributes: {
state_class: "measurement",
unit_of_measurement: "W",
},
},
"sensor.power_battery": {
entity_id: "sensor.power_battery",
state: "100",
attributes: {
state_class: "measurement",
unit_of_measurement: "W",
},
},
"sensor.energy_gas_cost": {
entity_id: "sensor.energy_gas_cost",
state: "2",
@@ -203,15 +171,6 @@ export const energyEntities = () =>
unit_of_measurement: "m³",
},
},
"sensor.energy_water": {
entity_id: "sensor.energy_water",
state: "4000",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Water",
unit_of_measurement: "L",
},
},
"sensor.energy_car": {
entity_id: "sensor.energy_car",
state: "4",
@@ -266,58 +225,4 @@ export const energyEntities = () =>
unit_of_measurement: "kWh",
},
},
"sensor.power_car": {
entity_id: "sensor.power_car",
state: "40",
attributes: {
state_class: "measurement",
friendly_name: "Electric car",
unit_of_measurement: "W",
},
},
"sensor.power_ac": {
entity_id: "sensor.power_ac",
state: "30",
attributes: {
state_class: "measurement",
friendly_name: "Air conditioning",
unit_of_measurement: "W",
},
},
"sensor.power_washing_machine": {
entity_id: "sensor.power_washing_machine",
state: "60",
attributes: {
state_class: "measurement",
friendly_name: "Washing machine",
unit_of_measurement: "W",
},
},
"sensor.power_dryer": {
entity_id: "sensor.power_dryer",
state: "55",
attributes: {
state_class: "measurement",
friendly_name: "Dryer",
unit_of_measurement: "W",
},
},
"sensor.power_heat_pump": {
entity_id: "sensor.power_heat_pump",
state: "60",
attributes: {
state_class: "measurement",
friendly_name: "Heat pump",
unit_of_measurement: "W",
},
},
"sensor.power_boiler": {
entity_id: "sensor.power_boiler",
state: "70",
attributes: {
state_class: "measurement",
friendly_name: "Boiler",
unit_of_measurement: "W",
},
},
});

View File

@@ -17,15 +17,17 @@ const generateMeanStatistics = (
end: Date,
// eslint-disable-next-line default-param-last
period: "5minute" | "hour" | "day" | "month" = "hour",
initValue: number,
maxDiff: number
): StatisticValue[] => {
const statistics: StatisticValue[] = [];
let currentDate = new Date(start);
currentDate.setMinutes(0, 0, 0);
let lastVal = initValue;
const now = new Date();
while (end > currentDate && currentDate < now) {
const delta = Math.random() * maxDiff;
const mean = delta;
const mean = lastVal + delta;
statistics.push({
start: currentDate.getTime(),
end: currentDate.getTime(),
@@ -36,6 +38,7 @@ const generateMeanStatistics = (
state: mean,
sum: null,
});
lastVal = mean;
currentDate =
period === "day"
? addDays(currentDate, 1)
@@ -333,6 +336,7 @@ export const mockRecorder = (mockHass: MockHomeAssistant) => {
start,
end,
period,
state,
state * (state > 80 ? 0.05 : 0.1)
);
}

View File

@@ -381,6 +381,10 @@ export class DemoHaWaDialog extends LitElement {
<td><code>--dialog-z-index</code></td>
<td>Z-index for the dialog.</td>
</tr>
<tr>
<td><code>--dialog-surface-position</code></td>
<td>CSS position of the dialog surface.</td>
</tr>
<tr>
<td><code>--dialog-surface-margin-top</code></td>
<td>Top margin for the dialog surface.</td>

View File

@@ -52,7 +52,7 @@
"@fullcalendar/list": "6.1.19",
"@fullcalendar/luxon3": "6.1.19",
"@fullcalendar/timegrid": "6.1.19",
"@home-assistant/webawesome": "3.0.0-ha.1",
"@home-assistant/webawesome": "3.0.0-ha.0",
"@lezer/highlight": "1.2.3",
"@lit-labs/motion": "1.0.9",
"@lit-labs/observers": "2.0.6",
@@ -157,7 +157,7 @@
"@octokit/auth-oauth-device": "8.0.3",
"@octokit/plugin-retry": "8.0.3",
"@octokit/rest": "22.0.1",
"@rsdoctor/rspack-plugin": "1.3.12",
"@rsdoctor/rspack-plugin": "1.3.11",
"@rspack/core": "1.6.5",
"@rspack/dev-server": "1.1.4",
"@types/babel__plugin-transform-runtime": "7.9.5",

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
version = "20251203.0"
version = "20251029.0"
license = "Apache-2.0"
license-files = ["LICENSE*"]
description = "The Home Assistant frontend"

View File

@@ -373,7 +373,6 @@ export class StateHistoryChartTimeline extends LitElement {
itemName: 3,
},
renderItem: this._renderItem,
progressive: 0,
});
});

View File

@@ -4,6 +4,7 @@ import { LitElement, html } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import { getAreaContext } from "../common/entity/context/get_area_context";
import { areaCompare } from "../data/area_registry";
import type { HomeAssistant } from "../types";
import "./ha-expansion-panel";
import "./ha-items-display-editor";
@@ -36,7 +37,11 @@ export class HaAreasDisplayEditor extends LitElement {
public showNavigationButton = false;
protected render(): TemplateResult {
const areas = Object.values(this.hass.areas);
const compare = areaCompare(this.hass.areas);
const areas = Object.values(this.hass.areas).sort((areaA, areaB) =>
compare(areaA.area_id, areaB.area_id)
);
const items: DisplayItem[] = areas.map((area) => {
const { floor } = getAreaContext(area, this.hass.floors);

View File

@@ -7,6 +7,7 @@ import memoizeOne from "memoize-one";
import { fireEvent } from "../common/dom/fire_event";
import { computeFloorName } from "../common/entity/compute_floor_name";
import { getAreaContext } from "../common/entity/context/get_area_context";
import { areaCompare } from "../data/area_registry";
import type { FloorRegistryEntry } from "../data/floor_registry";
import { getFloors } from "../panels/lovelace/strategies/areas/helpers/areas-strategy-helper";
import type { HomeAssistant } from "../types";
@@ -130,8 +131,11 @@ export class HaAreasFloorsDisplayEditor extends LitElement {
// update items if floors change
_hassFloors: HomeAssistant["floors"]
): Record<string, DisplayItem[]> => {
const areas = Object.values(hassAreas);
const compare = areaCompare(hassAreas);
const areas = Object.values(hassAreas).sort((areaA, areaB) =>
compare(areaA.area_id, areaB.area_id)
);
const groupedItems: Record<string, DisplayItem[]> = areas.reduce(
(acc, area) => {
const { floor } = getAreaContext(area, this.hass.floors);

View File

@@ -1,6 +1,9 @@
import DropdownItem from "@home-assistant/webawesome/dist/components/dropdown-item/dropdown-item";
import { css, type CSSResultGroup } from "lit";
import "@home-assistant/webawesome/dist/components/icon/icon";
import { mdiCheckboxBlankOutline, mdiCheckboxMarked } from "@mdi/js";
import { css, type CSSResultGroup, html, nothing } from "lit";
import { customElement } from "lit/decorators";
import "./ha-svg-icon";
/**
* Home Assistant dropdown item component
@@ -14,6 +17,62 @@ import { customElement } from "lit/decorators";
*/
@customElement("ha-dropdown-item")
export class HaDropdownItem extends DropdownItem {
// overwritten wa-dropdown-item render function, to add unchecked icon support
render() {
return html`
${this.type === "checkbox"
? html`
<ha-svg-icon
id="check"
part="checkmark"
.path=${this.checked
? mdiCheckboxMarked
: mdiCheckboxBlankOutline}
></ha-svg-icon>
`
: nothing}
<span id="icon" part="icon">
<slot name="icon"></slot>
</span>
<span id="label" part="label">
<slot></slot>
</span>
<span id="details" part="details">
<slot name="details"></slot>
</span>
${this.hasSubmenu
? html`
<wa-icon
id="submenu-indicator"
part="submenu-icon"
exportparts="svg:submenu-icon__svg"
library="system"
name="chevron-right"
></wa-icon>
`
: nothing}
${this.hasSubmenu
? html`
<div
id="submenu"
part="submenu"
popover="manual"
role="menu"
tabindex="-1"
aria-orientation="vertical"
hidden
>
<slot name="submenu"></slot>
</div>
`
: nothing}
`;
}
static get styles(): CSSResultGroup {
return [
DropdownItem.styles,
@@ -22,6 +81,10 @@ export class HaDropdownItem extends DropdownItem {
min-height: var(--ha-space-10);
}
#check {
visibility: visible;
}
#icon ::slotted(*) {
color: var(--ha-color-on-neutral-normal);
}

View File

@@ -167,33 +167,30 @@ export class HaFilterLabels extends SubscribeMixin(LitElement) {
}
private async _labelSelected(ev: CustomEvent<SelectedDetail<Set<number>>>) {
if (!ev.detail.index.size) {
fireEvent(this, "data-table-filter-changed", {
value: [],
items: undefined,
});
this.value = [];
return;
}
const value: string[] = [];
const filteredLabels = this._filteredLabels(
this._labels,
this._filter,
this.value
);
const filteredLabelIds = new Set(filteredLabels.map((l) => l.label_id));
// Keep previously selected labels that are not in the current filtered view
const preservedLabels = (this.value || []).filter(
(id) => !filteredLabelIds.has(id)
);
// Build the new selection from the filtered labels based on selected indices
const newlySelectedLabels: string[] = [];
for (const index of ev.detail.index) {
const labelId = filteredLabels[index]?.label_id;
if (labelId) {
newlySelectedLabels.push(labelId);
}
const labelId = filteredLabels[index].label_id;
value.push(labelId);
}
const value = [...preservedLabels, ...newlySelectedLabels];
this.value = value.length ? value : [];
this.value = value;
fireEvent(this, "data-table-filter-changed", {
value: value.length ? value : undefined,
value,
items: undefined,
});
}

View File

@@ -99,7 +99,10 @@ class HaMarkdownElement extends ReactiveElement {
}
);
render(h(unsafeHTML(elements.join(""))), this.renderRoot);
render(
elements.map((e) => h(unsafeHTML(e))),
this.renderRoot
);
this._resize();

View File

@@ -25,11 +25,11 @@ export class HaMarkdown extends LitElement {
@property({ type: Boolean }) public cache = false;
@query("ha-markdown-element") private _markdownElement?: ReactiveElement;
@query("ha-markdown-element") private _markdownElement!: ReactiveElement;
protected async getUpdateComplete() {
const result = await super.getUpdateComplete();
await this._markdownElement?.updateComplete;
await this._markdownElement.updateComplete;
return result;
}

File diff suppressed because one or more lines are too long

View File

@@ -49,6 +49,7 @@ export type DialogWidth = "small" | "medium" | "large" | "full";
* @cssprop --ha-dialog-surface-background - Dialog background color.
* @cssprop --ha-dialog-border-radius - Border radius of the dialog surface.
* @cssprop --dialog-z-index - Z-index for the dialog.
* @cssprop --dialog-surface-position - CSS position of the dialog surface.
* @cssprop --dialog-surface-margin-top - Top margin for the dialog surface.
*
* @attr {boolean} open - Controls the dialog open state.
@@ -243,6 +244,7 @@ export class HaWaDialog extends LitElement {
calc(var(--safe-height) - var(--ha-space-20))
);
min-height: var(--ha-dialog-min-height);
position: var(--dialog-surface-position, relative);
margin-top: var(--dialog-surface-margin-top, auto);
/* Used to offset the dialog from the safe areas when space is limited */
transform: translate(

View File

@@ -223,7 +223,6 @@ const getAreasAndFloorsItems = (
}
let outputAreas = areas;
let outputFloors = floors;
let areaIds: string[] | undefined;
@@ -255,29 +254,9 @@ const getAreasAndFloorsItems = (
outputAreas = outputAreas.filter(
(area) => !area.floor_id || !excludeFloors!.includes(area.floor_id)
);
outputFloors = outputFloors.filter(
(floor) => !excludeFloors!.includes(floor.floor_id)
);
}
if (
entityFilter ||
deviceFilter ||
includeDomains ||
excludeDomains ||
includeDeviceClasses
) {
// Ensure we only include floors that have areas with the filtered entities/devices
const validFloorIds = new Set(
outputAreas.map((area) => area.floor_id).filter((id) => id)
);
outputFloors = outputFloors.filter((floor) =>
validFloorIds.has(floor.floor_id)
);
}
const hierarchy = getAreasFloorHierarchy(outputFloors, outputAreas);
const hierarchy = getAreasFloorHierarchy(floors, outputAreas);
const items: (
| FloorComboBoxItem

View File

@@ -1,3 +1,4 @@
import { stringCompare } from "../common/string/compare";
import type { HomeAssistant } from "../types";
import type { DeviceRegistryEntry } from "./device_registry";
import type {
@@ -104,3 +105,22 @@ export const getAreaDeviceLookup = (
}
return areaDeviceLookup;
};
export const areaCompare =
(entries?: HomeAssistant["areas"], order?: string[]) =>
(a: string, b: string) => {
const indexA = order ? order.indexOf(a) : -1;
const indexB = order ? order.indexOf(b) : -1;
if (indexA === -1 && indexB === -1) {
const nameA = entries?.[a]?.name ?? a;
const nameB = entries?.[b]?.name ?? b;
return stringCompare(nameA, nameB);
}
if (indexA === -1) {
return 1;
}
if (indexB === -1) {
return -1;
}
return indexA - indexB;
};

View File

@@ -11,7 +11,7 @@ import {
isLastDayOfMonth,
addYears,
} from "date-fns";
import type { Collection, HassEntity } from "home-assistant-js-websocket";
import type { Collection } from "home-assistant-js-websocket";
import { getCollection } from "home-assistant-js-websocket";
import memoizeOne from "memoize-one";
import {
@@ -1361,37 +1361,3 @@ export const calculateSolarConsumedGauge = (
}
return undefined;
};
/**
* Get current power value from entity state, normalized to kW
* @param stateObj - The entity state object to get power value from
* @returns Power value in kW, or 0 if entity not found or invalid
*/
export const getPowerFromState = (stateObj: HassEntity): number | undefined => {
if (!stateObj) {
return undefined;
}
const value = parseFloat(stateObj.state);
if (isNaN(value)) {
return undefined;
}
// Normalize to kW based on unit of measurement (case-sensitive)
// Supported units: GW, kW, MW, mW, TW, W
const unit = stateObj.attributes.unit_of_measurement;
switch (unit) {
case "W":
return value / 1000;
case "mW":
return value / 1000000;
case "MW":
return value * 1000;
case "GW":
return value * 1000000;
case "TW":
return value * 1000000000;
default:
// Assume kW if no unit or unit is kW
return value;
}
};

View File

@@ -18,11 +18,6 @@ export interface LabPreviewFeaturesResponse {
features: LabPreviewFeature[];
}
/**
* Fetch all lab features
* @param hass - The Home Assistant instance
* @returns A promise to fetch the lab features
*/
export const fetchLabFeatures = async (
hass: HomeAssistant
): Promise<LabPreviewFeature[]> => {
@@ -32,15 +27,6 @@ export const fetchLabFeatures = async (
return response.features;
};
/**
* Update a specific lab feature
* @param hass - The Home Assistant instance
* @param domain - The domain of the lab feature
* @param preview_feature - The preview feature of the lab feature
* @param enabled - Whether the lab feature is enabled
* @param create_backup - Whether to create a backup of the lab feature
* @returns A promise to update the lab feature
*/
export const labsUpdatePreviewFeature = (
hass: HomeAssistant,
domain: string,
@@ -79,12 +65,6 @@ const subscribeLabUpdates = (
"labs_updated"
);
/**
* Subscribe to a collection of lab features
* @param conn - The connection to the Home Assistant instance
* @param onChange - The function to call when the lab features change
* @returns The unsubscribe function
*/
export const subscribeLabFeatures = (
conn: Connection,
onChange: (features: LabPreviewFeature[]) => void
@@ -96,27 +76,3 @@ export const subscribeLabFeatures = (
conn,
onChange
);
/**
* Subscribe to a specific lab feature
* @param conn - The connection to the Home Assistant instance
* @param domain - The domain of the lab feature
* @param previewFeature - The preview feature of the lab feature
* @param onChange - The function to call when the lab feature changes
* @returns The unsubscribe function
*/
export const subscribeLabFeature = (
conn: Connection,
domain: string,
previewFeature: string,
onChange: (enabled: boolean) => void
) =>
subscribeLabFeatures(conn, (features) => {
const enabled =
features.find(
(feature) =>
feature.domain === domain &&
feature.preview_feature === previewFeature
)?.enabled ?? false;
onChange(enabled);
});

View File

@@ -82,12 +82,6 @@ export interface WeatherEntity extends HassEntityBase {
attributes: WeatherEntityAttributes;
}
export const WEATHER_TEMPERATURE_ATTRIBUTES = new Set<string>([
"temperature",
"apparent_temperature",
"dew_point",
]);
export const weatherSVGs = new Set<string>([
"clear-night",
"cloudy",
@@ -262,15 +256,9 @@ export const getWeatherUnit = (
export const getSecondaryWeatherAttribute = (
hass: HomeAssistant,
stateObj: WeatherEntity,
forecast: ForecastAttribute[],
temperatureFractionDigits?: number
forecast: ForecastAttribute[]
): TemplateResult | undefined => {
const extrema = getWeatherExtrema(
hass,
stateObj,
forecast,
temperatureFractionDigits
);
const extrema = getWeatherExtrema(hass, stateObj, forecast);
if (extrema) {
return extrema;
@@ -310,8 +298,7 @@ export const getSecondaryWeatherAttribute = (
const getWeatherExtrema = (
hass: HomeAssistant,
stateObj: WeatherEntity,
forecast: ForecastAttribute[],
temperatureFractionDigits?: number
forecast: ForecastAttribute[]
): TemplateResult | undefined => {
if (!forecast?.length) {
return undefined;
@@ -326,22 +313,13 @@ const getWeatherExtrema = (
break;
}
if (!tempHigh || fc.temperature > tempHigh) {
tempHigh =
temperatureFractionDigits === undefined
? fc.temperature
: round(fc.temperature, temperatureFractionDigits);
tempHigh = fc.temperature;
}
if (fc.templow !== undefined && (!tempLow || fc.templow < tempLow)) {
tempLow =
temperatureFractionDigits === undefined
? fc.templow
: round(fc.templow, temperatureFractionDigits);
if (!tempLow || (fc.templow && fc.templow < tempLow)) {
tempLow = fc.templow;
}
if (!fc.templow && (!tempLow || fc.temperature < tempLow)) {
tempLow =
temperatureFractionDigits === undefined
? fc.temperature
: round(fc.temperature, temperatureFractionDigits);
tempLow = fc.temperature;
}
}

View File

@@ -81,11 +81,8 @@ class DialogAreasFloorsOrder extends LitElement {
return nothing;
}
const hasFloors = this._hierarchy.floors.length > 0;
const dialogTitle = this.hass.localize(
hasFloors
? "ui.panel.config.areas.dialog.reorder_floors_areas_title"
: "ui.panel.config.areas.dialog.reorder_areas_title"
"ui.panel.config.areas.dialog.reorder_title"
);
return html`
@@ -175,7 +172,7 @@ class DialogAreasFloorsOrder extends LitElement {
? html`<div class="floor-header">
<span class="floor-name">
${this.hass.localize(
"ui.panel.config.areas.dialog.other_areas"
"ui.panel.config.areas.dialog.unassigned_areas"
)}
</span>
</div>`
@@ -421,6 +418,7 @@ class DialogAreasFloorsOrder extends LitElement {
}
.floor.unassigned {
border-style: dashed;
margin-top: 16px;
}

View File

@@ -175,12 +175,21 @@ export class HaConfigAreasDashboard extends LitElement {
.route=${this.route}
has-fab
>
<ha-icon-button
slot="toolbar-icon"
.label=${this.hass.localize("ui.common.help")}
.path=${mdiHelpCircle}
@click=${this._showHelp}
></ha-icon-button>
<ha-button-menu slot="toolbar-icon">
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-list-item graphic="icon" @click=${this._showReorderDialog}>
<ha-svg-icon .path=${mdiSort} slot="graphic"></ha-svg-icon>
${this.hass.localize("ui.panel.config.areas.picker.reorder")}
</ha-list-item>
<ha-list-item graphic="icon" @click=${this._showHelp}>
<ha-svg-icon .path=${mdiHelpCircle} slot="graphic"></ha-svg-icon>
${this.hass.localize("ui.common.help")}
</ha-list-item>
</ha-button-menu>
<div class="container">
<div class="floors">
${this._hierarchy.floors.map(({ areas, id }) => {
@@ -204,16 +213,6 @@ export class HaConfigAreasDashboard extends LitElement {
slot="trigger"
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-list-item graphic="icon"
><ha-svg-icon
.path=${mdiSort}
slot="graphic"
></ha-svg-icon
>${this.hass.localize(
"ui.panel.config.areas.picker.reorder"
)}</ha-list-item
>
<li divider role="separator"></li>
<ha-list-item graphic="icon"
><ha-svg-icon
.path=${mdiPencil}
@@ -267,30 +266,9 @@ export class HaConfigAreasDashboard extends LitElement {
<div class="header">
<h2>
${this.hass.localize(
this._hierarchy.floors.length
? "ui.panel.config.areas.picker.other_areas"
: "ui.panel.config.areas.picker.header"
"ui.panel.config.areas.picker.unassigned_areas"
)}
</h2>
<div class="actions">
<ha-button-menu
@action=${this._handleUnassignedAreasAction}
>
<ha-icon-button
slot="trigger"
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-list-item graphic="icon"
><ha-svg-icon
.path=${mdiSort}
slot="graphic"
></ha-svg-icon
>${this.hass.localize(
"ui.panel.config.areas.picker.reorder"
)}</ha-list-item
>
</ha-button-menu>
</div>
</div>
<ha-sortable
handle-selector="a"
@@ -537,23 +515,14 @@ export class HaConfigAreasDashboard extends LitElement {
const floor = (ev.currentTarget as any).floor;
switch (ev.detail.index) {
case 0:
this._showReorderDialog();
break;
case 1:
this._editFloor(floor);
break;
case 2:
case 1:
this._deleteFloor(floor);
break;
}
}
private _handleUnassignedAreasAction(ev: CustomEvent<ActionDetail>) {
if (ev.detail.index === 0) {
this._showReorderDialog();
}
}
private _createFloor() {
this._openFloorDialog();
}

View File

@@ -1,4 +1,3 @@
import "@home-assistant/webawesome/dist/components/divider/divider";
import { consume } from "@lit/context";
import {
mdiAlertCircleCheck,
@@ -33,11 +32,11 @@ import { copyToClipboard } from "../../../../common/util/copy-clipboard";
import "../../../../components/ha-automation-row";
import type { HaAutomationRow } from "../../../../components/ha-automation-row";
import "../../../../components/ha-card";
import "../../../../components/ha-dropdown";
import "../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
import "../../../../components/ha-expansion-panel";
import "../../../../components/ha-icon-button";
import "../../../../components/ha-md-button-menu";
import "../../../../components/ha-md-divider";
import "../../../../components/ha-md-menu-item";
import "../../../../components/ha-service-icon";
import "../../../../components/ha-tooltip";
import {
@@ -289,12 +288,15 @@ export default class HaAutomationActionRow extends LitElement {
</ha-tooltip>`
: nothing}
<ha-dropdown
<ha-md-button-menu
quick
slot="icons"
@click=${preventDefaultStopPropagation}
@keydown=${stopPropagation}
@wa-select=${this._handleDropdownSelect}
placement="bottom-end"
@closed=${stopPropagation}
positioning="fixed"
anchor-corner="end-end"
menu-corner="start-end"
>
<ha-icon-button
slot="trigger"
@@ -302,24 +304,30 @@ export default class HaAutomationActionRow extends LitElement {
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-dropdown-item value="run">
<ha-svg-icon slot="icon" .path=${mdiPlay}></ha-svg-icon>
<ha-md-menu-item .clickAction=${this._runAction}>
<ha-svg-icon slot="start" .path=${mdiPlay}></ha-svg-icon>
${this._renderOverflowLabel(
this.hass.localize("ui.panel.config.automation.editor.actions.run")
)}
</ha-dropdown-item>
<ha-dropdown-item value="rename" .disabled=${this.disabled}>
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
</ha-md-menu-item>
<ha-md-menu-item
.clickAction=${this._renameAction}
.disabled=${this.disabled}
>
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
${this._renderOverflowLabel(
this.hass.localize(
"ui.panel.config.automation.editor.triggers.rename"
)
)}
</ha-dropdown-item>
<wa-divider></wa-divider>
<ha-dropdown-item value="duplicate" .disabled=${this.disabled}>
</ha-md-menu-item>
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
<ha-md-menu-item
.clickAction=${this._duplicateAction}
.disabled=${this.disabled}
>
<ha-svg-icon
slot="icon"
slot="start"
.path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon>
@@ -328,10 +336,13 @@ export default class HaAutomationActionRow extends LitElement {
"ui.panel.config.automation.editor.actions.duplicate"
)
)}
</ha-dropdown-item>
</ha-md-menu-item>
<ha-dropdown-item value="copy" .disabled=${this.disabled}>
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
<ha-md-menu-item
.clickAction=${this._copyAction}
.disabled=${this.disabled}
>
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
${this._renderOverflowLabel(
this.hass.localize(
"ui.panel.config.automation.editor.triggers.copy"
@@ -340,6 +351,7 @@ export default class HaAutomationActionRow extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -350,10 +362,13 @@ export default class HaAutomationActionRow extends LitElement {
<span>C</span>
</span>`
)}
</ha-dropdown-item>
</ha-md-menu-item>
<ha-dropdown-item value="cut" .disabled=${this.disabled}>
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon>
<ha-md-menu-item
.clickAction=${this._cutAction}
.disabled=${this.disabled}
>
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
${this._renderOverflowLabel(
this.hass.localize(
"ui.panel.config.automation.editor.triggers.cut"
@@ -362,6 +377,7 @@ export default class HaAutomationActionRow extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -372,48 +388,51 @@ export default class HaAutomationActionRow extends LitElement {
<span>X</span>
</span>`
)}
</ha-dropdown-item>
</ha-md-menu-item>
${!this.optionsInSidebar
? html`
<ha-dropdown-item
value="move_up"
<ha-md-menu-item
.clickAction=${this._moveUp}
.disabled=${this.disabled || !!this.first}
>
${this.hass.localize(
"ui.panel.config.automation.editor.move_up"
)}
<ha-svg-icon slot="icon" .path=${mdiArrowUp}></ha-svg-icon
></ha-dropdown-item>
<ha-dropdown-item
value="move_down"
<ha-svg-icon slot="start" .path=${mdiArrowUp}></ha-svg-icon
></ha-md-menu-item>
<ha-md-menu-item
.clickAction=${this._moveDown}
.disabled=${this.disabled || !!this.last}
>
${this.hass.localize(
"ui.panel.config.automation.editor.move_down"
)}
<ha-svg-icon slot="icon" .path=${mdiArrowDown}></ha-svg-icon
></ha-dropdown-item>
<ha-svg-icon slot="start" .path=${mdiArrowDown}></ha-svg-icon
></ha-md-menu-item>
`
: nothing}
<ha-dropdown-item
value="toggle_yaml_mode"
<ha-md-menu-item
.clickAction=${this._toggleYamlMode}
.disabled=${!this._uiModeAvailable || !!this._warnings}
>
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
${this._renderOverflowLabel(
this.hass.localize(
`ui.panel.config.automation.editor.edit_${!this._yamlMode ? "yaml" : "ui"}`
)
)}
</ha-dropdown-item>
</ha-md-menu-item>
<wa-divider></wa-divider>
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
<ha-dropdown-item value="disable" .disabled=${this.disabled}>
<ha-md-menu-item
.clickAction=${this._onDisable}
.disabled=${this.disabled}
>
<ha-svg-icon
slot="icon"
slot="start"
.path=${this.action.enabled === false
? mdiPlayCircleOutline
: mdiStopCircleOutline}
@@ -424,15 +443,15 @@ export default class HaAutomationActionRow extends LitElement {
`ui.panel.config.automation.editor.actions.${this.action.enabled === false ? "enable" : "disable"}`
)
)}
</ha-dropdown-item>
<ha-dropdown-item
value="delete"
variant="danger"
</ha-md-menu-item>
<ha-md-menu-item
class="warning"
.clickAction=${this._onDelete}
.disabled=${this.disabled}
>
<ha-svg-icon
class="warning"
slot="icon"
slot="start"
.path=${mdiDelete}
></ha-svg-icon>
@@ -444,6 +463,7 @@ export default class HaAutomationActionRow extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -458,8 +478,8 @@ export default class HaAutomationActionRow extends LitElement {
>
</span>`
)}
</ha-dropdown-item>
</ha-dropdown>
</ha-md-menu-item>
</ha-md-button-menu>
${!this.optionsInSidebar
? html`${this._warnings
@@ -870,47 +890,6 @@ export default class HaAutomationActionRow extends LitElement {
this._automationRowElement?.focus();
}
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "run":
this._runAction();
break;
case "rename":
this._renameAction();
break;
case "duplicate":
this._duplicateAction();
break;
case "copy":
this._copyAction();
break;
case "cut":
this._cutAction();
break;
case "move_up":
this._moveUp();
break;
case "move_down":
this._moveDown();
break;
case "toggle_yaml_mode":
this._toggleYamlMode(ev.target as HTMLElement);
break;
case "disable":
this._onDisable();
break;
case "delete":
this._onDelete();
break;
}
}
static styles = [rowStyles, overflowStyles];
}

View File

@@ -97,7 +97,7 @@ import {
fetchIntegrationManifests,
} from "../../../data/integration";
import type { LabelRegistryEntry } from "../../../data/label_registry";
import { subscribeLabFeature } from "../../../data/labs";
import { subscribeLabFeatures } from "../../../data/labs";
import {
TARGET_SEPARATOR,
getConditionsForTarget,
@@ -281,12 +281,15 @@ class DialogAddAutomationElement
this._fetchManifests();
this._calculateUsedDomains();
this._unsubscribeLabFeatures = subscribeLabFeature(
this._unsubscribeLabFeatures = subscribeLabFeatures(
this.hass.connection,
"automation",
"new_triggers_conditions",
(enabled) => {
this._newTriggersAndConditions = enabled;
(features) => {
this._newTriggersAndConditions =
features.find(
(feature) =>
feature.domain === "automation" &&
feature.preview_feature === "new_triggers_conditions"
)?.enabled ?? false;
this._tab = this._newTriggersAndConditions ? "targets" : "groups";
}
);
@@ -683,7 +686,6 @@ class DialogAddAutomationElement
<ha-automation-add-items
.hass=${this.hass}
.items=${this._getItems()}
.scrollable=${!this._narrow}
.error=${this._tab === "targets" && this._loadItemsError
? this.hass.localize(
"ui.panel.config.automation.editor.load_target_items_failed"
@@ -2144,7 +2146,7 @@ class DialogAddAutomationElement
min-height: 160px;
}
.content.column ha-automation-add-from-target {
overflow: clip;
overflow: hidden;
}
ha-wa-dialog ha-automation-add-items {

View File

@@ -911,10 +911,6 @@ export default class HaAutomationAddFromTarget extends LitElement {
const services: Record<string, Level3Entries> = {};
unassignedDevices.forEach(({ id: deviceId, entry_type }) => {
const device = this.devices[deviceId];
if (!device || device.disabled_by) {
return;
}
const deviceEntry = {
open: false,
entities:
@@ -1016,10 +1012,6 @@ export default class HaAutomationAddFromTarget extends LitElement {
const devices: Record<string, Level3Entries> = {};
referenced_devices.forEach(({ id: deviceId }) => {
const device = this.devices[deviceId];
if (!device || device.disabled_by) {
return;
}
devices[deviceId] = {
open: false,
entities:

View File

@@ -60,8 +60,6 @@ export class HaAutomationAddItems extends LitElement {
@property({ type: Boolean, attribute: "tooltip-description" })
public tooltipDescription = false;
@property({ type: Boolean, reflect: true }) scrollable = false;
@state() private _itemsScrolled = false;
@query(".items")
@@ -262,12 +260,11 @@ export class HaAutomationAddItems extends LitElement {
:host {
display: flex;
}
:host([scrollable]) .items {
overflow: auto;
}
.items {
display: flex;
flex-direction: column;
overflow: auto;
flex: 1;
}
.items.blank {

View File

@@ -1,4 +1,3 @@
import "@home-assistant/webawesome/dist/components/divider/divider";
import { consume } from "@lit/context";
import {
mdiAppleKeyboardCommand,
@@ -34,11 +33,11 @@ import "../../../../components/ha-automation-row";
import type { HaAutomationRow } from "../../../../components/ha-automation-row";
import "../../../../components/ha-card";
import "../../../../components/ha-condition-icon";
import "../../../../components/ha-dropdown";
import "../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
import "../../../../components/ha-expansion-panel";
import "../../../../components/ha-icon-button";
import "../../../../components/ha-md-button-menu";
import "../../../../components/ha-md-divider";
import "../../../../components/ha-md-menu-item";
import type {
AutomationClipboard,
Condition,
@@ -195,12 +194,15 @@ export default class HaAutomationConditionRow extends LitElement {
<slot name="icons" slot="icons"></slot>
<ha-dropdown
<ha-md-button-menu
quick
slot="icons"
@click=${preventDefaultStopPropagation}
@keydown=${stopPropagation}
@wa-select=${this._handleDropdownSelect}
placement="bottom-end"
@closed=${stopPropagation}
positioning="fixed"
anchor-corner="end-end"
menu-corner="start-end"
>
<ha-icon-button
slot="trigger"
@@ -209,28 +211,34 @@ export default class HaAutomationConditionRow extends LitElement {
>
</ha-icon-button>
<ha-dropdown-item value="test">
<ha-svg-icon slot="icon" .path=${mdiFlask}></ha-svg-icon>
<ha-md-menu-item .clickAction=${this._testCondition}>
<ha-svg-icon slot="start" .path=${mdiFlask}></ha-svg-icon>
${this._renderOverflowLabel(
this.hass.localize(
"ui.panel.config.automation.editor.conditions.test"
)
)}
</ha-dropdown-item>
<ha-dropdown-item value="rename" .disabled=${this.disabled}>
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
</ha-md-menu-item>
<ha-md-menu-item
.clickAction=${this._renameCondition}
.disabled=${this.disabled}
>
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
${this._renderOverflowLabel(
this.hass.localize(
"ui.panel.config.automation.editor.conditions.rename"
)
)}
</ha-dropdown-item>
</ha-md-menu-item>
<wa-divider></wa-divider>
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
<ha-dropdown-item value="duplicate" .disabled=${this.disabled}>
<ha-md-menu-item
.clickAction=${this._duplicateCondition}
.disabled=${this.disabled}
>
<ha-svg-icon
slot="icon"
slot="start"
.path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon>
${this._renderOverflowLabel(
@@ -238,10 +246,13 @@ export default class HaAutomationConditionRow extends LitElement {
"ui.panel.config.automation.editor.actions.duplicate"
)
)}
</ha-dropdown-item>
</ha-md-menu-item>
<ha-dropdown-item value="copy" .disabled=${this.disabled}>
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon
<ha-md-menu-item
.clickAction=${this._copyCondition}
.disabled=${this.disabled}
>
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon
>${this._renderOverflowLabel(
this.hass.localize(
"ui.panel.config.automation.editor.triggers.copy"
@@ -250,6 +261,7 @@ export default class HaAutomationConditionRow extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -260,10 +272,13 @@ export default class HaAutomationConditionRow extends LitElement {
<span>C</span>
</span>`
)}
</ha-dropdown-item>
</ha-md-menu-item>
<ha-dropdown-item value="cut" .disabled=${this.disabled}>
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon
<ha-md-menu-item
.clickAction=${this._cutCondition}
.disabled=${this.disabled}
>
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon
>${this._renderOverflowLabel(
this.hass.localize(
"ui.panel.config.automation.editor.triggers.cut"
@@ -272,6 +287,7 @@ export default class HaAutomationConditionRow extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -282,45 +298,48 @@ export default class HaAutomationConditionRow extends LitElement {
<span>X</span>
</span>`
)}
</ha-dropdown-item>
</ha-md-menu-item>
${!this.optionsInSidebar
? html`
<ha-dropdown-item
value="move_up"
.disabled=${this.disabled || !!this.first}
<ha-md-menu-item
.clickAction=${this._moveUp}
.disabled=${this.disabled || this.first}
>
${this.hass.localize(
"ui.panel.config.automation.editor.move_up"
)}
<ha-svg-icon slot="icon" .path=${mdiArrowUp}></ha-svg-icon
></ha-dropdown-item>
<ha-dropdown-item
value="move_down"
.disabled=${this.disabled || !!this.last}
<ha-svg-icon slot="start" .path=${mdiArrowUp}></ha-svg-icon
></ha-md-menu-item>
<ha-md-menu-item
.clickAction=${this._moveDown}
.disabled=${this.disabled || this.last}
>
${this.hass.localize(
"ui.panel.config.automation.editor.move_down"
)}
<ha-svg-icon slot="icon" .path=${mdiArrowDown}></ha-svg-icon
></ha-dropdown-item>
<ha-svg-icon slot="start" .path=${mdiArrowDown}></ha-svg-icon
></ha-md-menu-item>
`
: nothing}
<ha-dropdown-item value="toggle_yaml_mode">
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
<ha-md-menu-item .clickAction=${this._toggleYamlMode}>
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
${this._renderOverflowLabel(
this.hass.localize(
`ui.panel.config.automation.editor.edit_${!this._yamlMode ? "yaml" : "ui"}`
)
)}
</ha-dropdown-item>
</ha-md-menu-item>
<wa-divider></wa-divider>
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
<ha-dropdown-item value="disable" .disabled=${this.disabled}>
<ha-md-menu-item
.clickAction=${this._onDisable}
.disabled=${this.disabled}
>
<ha-svg-icon
slot="icon"
slot="start"
.path=${this.condition.enabled === false
? mdiPlayCircleOutline
: mdiStopCircleOutline}
@@ -331,15 +350,15 @@ export default class HaAutomationConditionRow extends LitElement {
`ui.panel.config.automation.editor.actions.${this.condition.enabled === false ? "enable" : "disable"}`
)
)}
</ha-dropdown-item>
<ha-dropdown-item
variant="danger"
value="delete"
</ha-md-menu-item>
<ha-md-menu-item
class="warning"
.clickAction=${this._onDelete}
.disabled=${this.disabled}
>
<ha-svg-icon
class="warning"
slot="icon"
slot="start"
.path=${mdiDelete}
></ha-svg-icon>
${this._renderOverflowLabel(
@@ -350,6 +369,7 @@ export default class HaAutomationConditionRow extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -364,8 +384,8 @@ export default class HaAutomationConditionRow extends LitElement {
>
</span>`
)}
</ha-dropdown-item>
</ha-dropdown>
</ha-md-menu-item>
</ha-md-button-menu>
${!this.optionsInSidebar
? html`${this._warnings
? html`<ha-automation-editor-warning
@@ -817,47 +837,6 @@ export default class HaAutomationConditionRow extends LitElement {
this._automationRowElement?.focus();
}
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "test":
this._testCondition();
break;
case "rename":
this._renameCondition();
break;
case "duplicate":
this._duplicateCondition();
break;
case "copy":
this._copyCondition();
break;
case "cut":
this._cutCondition();
break;
case "move_up":
this._moveUp();
break;
case "move_down":
this._moveDown();
break;
case "toggle_yaml_mode":
this._toggleYamlMode(ev.target as HTMLElement);
break;
case "disable":
this._onDisable();
break;
case "delete":
this._onDelete();
break;
}
}
static get styles(): CSSResultGroup {
return [
rowStyles,

View File

@@ -28,7 +28,7 @@ import {
CONDITION_BUILDING_BLOCKS,
subscribeConditions,
} from "../../../../data/condition";
import { subscribeLabFeature } from "../../../../data/labs";
import { subscribeLabFeatures } from "../../../../data/labs";
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
import type { HomeAssistant } from "../../../../types";
import {
@@ -90,14 +90,14 @@ export default class HaAutomationCondition extends SubscribeMixin(LitElement) {
protected hassSubscribe() {
return [
subscribeLabFeature(
this.hass!.connection,
"automation",
"new_triggers_conditions",
(enabled) => {
this._newTriggersAndConditions = enabled;
}
),
subscribeLabFeatures(this.hass!.connection, (features) => {
this._newTriggersAndConditions =
features.find(
(feature) =>
feature.domain === "automation" &&
feature.preview_feature === "new_triggers_conditions"
)?.enabled ?? false;
}),
];
}

View File

@@ -1,4 +1,3 @@
import "@home-assistant/webawesome/dist/components/divider/divider";
import { consume } from "@lit/context";
import {
mdiAppleKeyboardCommand,
@@ -26,20 +25,18 @@ import type { CSSResultGroup, PropertyValues, TemplateResult } 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 { UndoRedoController } from "../../../common/controllers/undo-redo-controller";
import { transform } from "../../../common/decorators/transform";
import { fireEvent } from "../../../common/dom/fire_event";
import { goBack, navigate } from "../../../common/navigate";
import { promiseTimeout } from "../../../common/util/promise-timeout";
import { afterNextRender } from "../../../common/util/render-status";
import "../../../components/ha-button";
import "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
import "../../../components/ha-button-menu";
import "../../../components/ha-fab";
import "../../../components/ha-fade-in";
import "../../../components/ha-icon";
import "../../../components/ha-icon-button";
import "../../../components/ha-list-item";
import "../../../components/ha-spinner";
import "../../../components/ha-svg-icon";
import "../../../components/ha-yaml-editor";
@@ -77,6 +74,7 @@ import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info
import "../../../layouts/hass-subpage";
import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin";
import { PreventUnsavedMixin } from "../../../mixins/prevent-unsaved-mixin";
import { UndoRedoController } from "../../../common/controllers/undo-redo-controller";
import { haStyle } from "../../../resources/styles";
import type { Entries, HomeAssistant, Route } from "../../../types";
import { isMac } from "../../../util/is_mac";
@@ -88,10 +86,10 @@ import {
type EntityRegistryUpdate,
showAutomationSaveDialog,
} from "./automation-save-dialog/show-dialog-automation-save";
import { showAutomationSaveTimeoutDialog } from "./automation-save-timeout-dialog/show-dialog-automation-save-timeout";
import "./blueprint-automation-editor";
import "./manual-automation-editor";
import type { HaManualAutomationEditor } from "./manual-automation-editor";
import { showAutomationSaveTimeoutDialog } from "./automation-save-timeout-dialog/show-dialog-automation-save-timeout";
declare global {
interface HTMLElementTagNameMap {
@@ -294,10 +292,7 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
</ha-button>
`
: ""}
<ha-dropdown
slot="toolbar-icon"
@wa-select=${this._handleDropdownSelect}
>
<ha-button-menu slot="toolbar-icon">
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
@@ -305,73 +300,99 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
></ha-icon-button>
${this._mode === "gui" && this.narrow
? html`<ha-dropdown-item
value="undo"
? html`<ha-list-item
graphic="icon"
@click=${this._undo}
.disabled=${!this._undoRedoController.canUndo}
>
${this.hass.localize("ui.common.undo")}
<ha-svg-icon slot="icon" .path=${mdiUndo}></ha-svg-icon>
</ha-dropdown-item>
<ha-dropdown-item
value="redo"
<ha-svg-icon slot="graphic" .path=${mdiUndo}></ha-svg-icon>
</ha-list-item>
<ha-list-item
graphic="icon"
@click=${this._redo}
.disabled=${!this._undoRedoController.canRedo}
>
${this.hass.localize("ui.common.redo")}
<ha-svg-icon slot="icon" .path=${mdiRedo}></ha-svg-icon>
</ha-dropdown-item>`
<ha-svg-icon slot="graphic" .path=${mdiRedo}></ha-svg-icon>
</ha-list-item>`
: nothing}
<ha-dropdown-item .disabled=${!stateObj} value="info">
<ha-list-item
graphic="icon"
.disabled=${!stateObj}
@click=${this._showInfo}
>
${this.hass.localize("ui.panel.config.automation.editor.show_info")}
<ha-svg-icon
slot="icon"
slot="graphic"
.path=${mdiInformationOutline}
></ha-svg-icon>
</ha-dropdown-item>
</ha-list-item>
<ha-dropdown-item .disabled=${!stateObj} value="settings">
<ha-list-item
graphic="icon"
.disabled=${!stateObj}
@click=${this._showSettings}
>
${this.hass.localize(
"ui.panel.config.automation.picker.show_settings"
)}
<ha-svg-icon slot="icon" .path=${mdiCog}></ha-svg-icon>
</ha-dropdown-item>
<ha-svg-icon slot="graphic" .path=${mdiCog}></ha-svg-icon>
</ha-list-item>
<ha-dropdown-item .disabled=${!stateObj} value="category">
<ha-list-item
graphic="icon"
.disabled=${!stateObj}
@click=${this._editCategory}
>
${this.hass.localize(
`ui.panel.config.scene.picker.${this._registryEntry?.categories?.automation ? "edit_category" : "assign_category"}`
)}
<ha-svg-icon slot="icon" .path=${mdiTag}></ha-svg-icon>
</ha-dropdown-item>
<ha-svg-icon slot="graphic" .path=${mdiTag}></ha-svg-icon>
</ha-list-item>
<ha-dropdown-item .disabled=${!stateObj} value="run">
<ha-list-item
graphic="icon"
.disabled=${!stateObj}
@click=${this._runActions}
>
${this.hass.localize("ui.panel.config.automation.editor.run")}
<ha-svg-icon slot="icon" .path=${mdiPlay}></ha-svg-icon>
</ha-dropdown-item>
<ha-svg-icon slot="graphic" .path=${mdiPlay}></ha-svg-icon>
</ha-list-item>
${stateObj && this.narrow
? html`<ha-dropdown-item value="trace">
${this.hass.localize(
"ui.panel.config.automation.editor.show_trace"
)}
<ha-svg-icon
slot="icon"
.path=${mdiTransitConnection}
></ha-svg-icon>
</ha-dropdown-item>`
? html`<a
href="/config/automation/trace/${encodeURIComponent(
this._config.id!
)}"
>
<ha-list-item graphic="icon">
${this.hass.localize(
"ui.panel.config.automation.editor.show_trace"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiTransitConnection}
></ha-svg-icon>
</ha-list-item>
</a>`
: nothing}
<ha-dropdown-item
value="rename"
<ha-list-item
graphic="icon"
@click=${this._promptAutomationAlias}
.disabled=${this._readOnly ||
!this.automationId ||
this._mode === "yaml"}
>
${this.hass.localize("ui.panel.config.automation.editor.rename")}
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
</ha-dropdown-item>
<ha-svg-icon slot="graphic" .path=${mdiRenameBox}></ha-svg-icon>
</ha-list-item>
${!useBlueprint
? html`
<ha-dropdown-item
<ha-list-item
graphic="icon"
@click=${this._promptAutomationMode}
.disabled=${this._readOnly || this._mode === "yaml"}
>
@@ -379,17 +400,18 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
"ui.panel.config.automation.editor.change_mode"
)}
<ha-svg-icon
slot="icon"
slot="graphic"
.path=${mdiDebugStepOver}
></ha-svg-icon>
</ha-dropdown-item>
</ha-list-item>
`
: nothing}
<ha-dropdown-item
.disabled=${!!this._blueprintConfig ||
<ha-list-item
.disabled=${this._blueprintConfig ||
(!this._readOnly && !this.automationId)}
value="duplicate"
graphic="icon"
@click=${this._duplicate}
>
${this.hass.localize(
this._readOnly
@@ -397,60 +419,74 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
: "ui.panel.config.automation.editor.duplicate"
)}
<ha-svg-icon
slot="icon"
slot="graphic"
.path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon>
</ha-dropdown-item>
</ha-list-item>
${useBlueprint
? html`
<ha-dropdown-item
value="take_control"
<ha-list-item
graphic="icon"
@click=${this._takeControl}
.disabled=${this._readOnly}
>
${this.hass.localize(
"ui.panel.config.automation.editor.take_control"
)}
<ha-svg-icon slot="icon" .path=${mdiFileEdit}></ha-svg-icon>
</ha-dropdown-item>
<ha-svg-icon
slot="graphic"
.path=${mdiFileEdit}
></ha-svg-icon>
</ha-list-item>
`
: nothing}
<ha-dropdown-item value="toggle_yaml_mode">
<ha-list-item
graphic="icon"
@click=${this._mode === "gui"
? this._switchYamlMode
: this._switchUiMode}
>
${this.hass.localize(
`ui.panel.config.automation.editor.edit_${this._mode === "gui" ? "yaml" : "ui"}`
)}
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
</ha-dropdown-item>
<ha-svg-icon slot="graphic" .path=${mdiPlaylistEdit}></ha-svg-icon>
</ha-list-item>
<wa-divider></wa-divider>
<li divider role="separator"></li>
<ha-dropdown-item .disabled=${!stateObj} value="disable">
<ha-list-item
graphic="icon"
.disabled=${!stateObj}
@click=${this._toggle}
>
${stateObj?.state === "off"
? this.hass.localize("ui.panel.config.automation.editor.enable")
: this.hass.localize("ui.panel.config.automation.editor.disable")}
<ha-svg-icon
slot="icon"
slot="graphic"
.path=${stateObj?.state === "off"
? mdiPlayCircleOutline
: mdiStopCircleOutline}
></ha-svg-icon>
</ha-dropdown-item>
</ha-list-item>
<ha-dropdown-item
<ha-list-item
.disabled=${!this.automationId}
.variant=${this.automationId ? "danger" : "default"}
value="delete"
class=${classMap({ warning: Boolean(this.automationId) })}
graphic="icon"
@click=${this._deleteConfirm}
>
${this.hass.localize("ui.panel.config.automation.picker.delete")}
<ha-svg-icon
class=${classMap({ warning: Boolean(this.automationId) })}
slot="icon"
slot="graphic"
.path=${mdiDelete}
>
</ha-svg-icon>
</ha-dropdown-item>
</ha-dropdown>
</ha-list-item>
</ha-button-menu>
<div
class=${this._mode === "yaml" ? "yaml-mode" : ""}
@subscribe-automation-config=${this._subscribeAutomationConfig}
@@ -1213,63 +1249,6 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
this._undoRedoController.redo();
}
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "undo":
this._undo();
break;
case "redo":
this._redo();
break;
case "info":
this._showInfo();
break;
case "settings":
this._showSettings();
break;
case "category":
this._editCategory();
break;
case "run":
this._runActions();
break;
case "rename":
this._promptAutomationAlias();
break;
case "change_mode":
this._promptAutomationMode();
break;
case "duplicate":
this._duplicate();
break;
case "take_control":
this._takeControl();
break;
case "toggle_yaml_mode":
if (this._mode === "gui") {
this._switchYamlMode();
break;
}
this._switchUiMode();
break;
case "disable":
this._toggle();
break;
case "delete":
this._deleteConfirm();
break;
case "trace":
this._showTrace();
break;
}
}
static get styles(): CSSResultGroup {
return [
haStyle,
@@ -1322,6 +1301,13 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
margin-inline-end: 8px;
margin-inline-start: initial;
}
li[role="separator"] {
border-bottom-color: var(--divider-color);
}
ha-button-menu a {
text-decoration: none;
color: var(--primary-color);
}
h1 {
margin: 0;
}

View File

@@ -1,4 +1,3 @@
import "@home-assistant/webawesome/dist/components/divider/divider";
import {
mdiDotsVertical,
mdiDownload,
@@ -16,13 +15,11 @@ import { repeat } from "lit/directives/repeat";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { formatDateTimeWithSeconds } from "../../../common/datetime/format_date_time";
import { fireEvent } from "../../../common/dom/fire_event";
import { navigate } from "../../../common/navigate";
import { computeRTL } from "../../../common/util/compute_rtl";
import "../../../components/ha-button";
import "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
import "../../../components/ha-button-menu";
import "../../../components/ha-icon-button";
import "../../../components/ha-list-item";
import "../../../components/trace/ha-trace-blueprint-config";
import "../../../components/trace/ha-trace-config";
import "../../../components/trace/ha-trace-logbook";
@@ -107,7 +104,9 @@ export class HaAutomationTrace extends LitElement {
appearance="plain"
size="small"
class="trace-link"
@click=${this._navigateToAutomation}
href="/config/automation/edit/${encodeURIComponent(
stateObj.attributes.id
)}"
slot="toolbar-icon"
>
${this.hass.localize(
@@ -115,50 +114,65 @@ export class HaAutomationTrace extends LitElement {
)}
</ha-button>
`
: nothing}
<ha-dropdown
slot="toolbar-icon"
@wa-select=${this._handleDropdownSelect}
>
: ""}
<ha-button-menu slot="toolbar-icon">
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-dropdown-item .disabled=${!stateObj} value="show_info">
<ha-list-item
graphic="icon"
.disabled=${!stateObj}
@click=${this._showInfo}
>
${this.hass.localize("ui.panel.config.automation.editor.show_info")}
<ha-svg-icon
slot="icon"
slot="graphic"
.path=${mdiInformationOutline}
></ha-svg-icon>
</ha-dropdown-item>
</ha-list-item>
${stateObj?.attributes.id && this.narrow
? html`
<ha-dropdown-item value="edit_automation">
${this.hass.localize(
"ui.panel.config.automation.trace.edit_automation"
)}
<ha-svg-icon slot="icon" .path=${mdiPencil}></ha-svg-icon>
</ha-dropdown-item>
<a
class="trace-link"
href="/config/automation/edit/${encodeURIComponent(
stateObj.attributes.id
)}"
>
<ha-list-item graphic="icon">
${this.hass.localize(
"ui.panel.config.automation.trace.edit_automation"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiPencil}
></ha-svg-icon>
</ha-list-item>
</a>
`
: nothing}
: ""}
<wa-divider></wa-divider>
<li divider role="separator"></li>
<ha-dropdown-item value="refresh">
<ha-list-item graphic="icon" @click=${this._refreshTraces}>
${this.hass.localize("ui.panel.config.automation.trace.refresh")}
<ha-svg-icon slot="icon" .path=${mdiRefresh}></ha-svg-icon>
</ha-dropdown-item>
<ha-svg-icon slot="graphic" .path=${mdiRefresh}></ha-svg-icon>
</ha-list-item>
<ha-dropdown-item .disabled=${!this._trace} value="download_trace">
<ha-list-item
graphic="icon"
.disabled=${!this._trace}
@click=${this._downloadTrace}
>
${this.hass.localize(
"ui.panel.config.automation.trace.download_trace"
)}
<ha-svg-icon slot="icon" .path=${mdiDownload}></ha-svg-icon>
</ha-dropdown-item>
</ha-dropdown>
<ha-svg-icon slot="graphic" .path=${mdiDownload}></ha-svg-icon>
</ha-list-item>
</ha-button-menu>
<div class="toolbar">
${this._traces && this._traces.length > 0
@@ -506,37 +520,6 @@ export class HaAutomationTrace extends LitElement {
fireEvent(this, "hass-more-info", { entityId: this._entityId });
}
private _navigateToAutomation() {
if (this._entityId && this.hass.states[this._entityId]) {
navigate(
`/config/automation/edit/${encodeURIComponent(this.hass.states[this._entityId].attributes.id)}`
);
}
}
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "show_info":
this._showInfo();
break;
case "refresh":
this._refreshTraces();
break;
case "download_trace":
this._downloadTrace();
break;
case "edit_automation":
this._navigateToAutomation();
break;
}
}
static get styles(): CSSResultGroup {
return [
haStyle,

View File

@@ -20,11 +20,10 @@ import { capitalizeFirstLetter } from "../../../../common/string/capitalize-firs
import "../../../../components/ha-automation-row";
import type { HaAutomationRow } from "../../../../components/ha-automation-row";
import "../../../../components/ha-card";
import "../../../../components/ha-dropdown";
import "../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
import "../../../../components/ha-expansion-panel";
import "../../../../components/ha-icon-button";
import "../../../../components/ha-md-button-menu";
import "../../../../components/ha-md-menu-item";
import "../../../../components/ha-svg-icon";
import type {
Condition,
@@ -37,7 +36,6 @@ import type { Action, Option } from "../../../../data/script";
import { showPromptDialog } from "../../../../dialogs/generic/show-dialog-box";
import type { HomeAssistant } from "../../../../types";
import { isMac } from "../../../../util/is_mac";
import { showToast } from "../../../../util/toast";
import "../action/ha-automation-action";
import type HaAutomationAction from "../action/ha-automation-action";
import "../condition/ha-automation-condition";
@@ -48,6 +46,7 @@ import {
overflowStyles,
rowStyles,
} from "../styles";
import { showToast } from "../../../../util/toast";
@customElement("ha-automation-option-row")
export default class HaAutomationOptionRow extends LitElement {
@@ -156,12 +155,15 @@ export default class HaAutomationOptionRow extends LitElement {
${this.option
? html`
<ha-dropdown
<ha-md-button-menu
quick
slot="icons"
@click=${preventDefaultStopPropagation}
@closed=${stopPropagation}
@keydown=${stopPropagation}
@wa-select=${this._handleDropdownSelect}
placement="bottom-end"
positioning="fixed"
anchor-corner="end-end"
menu-corner="start-end"
>
<ha-icon-button
slot="trigger"
@@ -169,18 +171,24 @@ export default class HaAutomationOptionRow extends LitElement {
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-dropdown-item value="rename" .disabled=${this.disabled}>
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
<ha-md-menu-item
@click=${this._renameOption}
.disabled=${this.disabled}
>
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
${this._renderOverflowLabel(
this.hass.localize(
"ui.panel.config.automation.editor.triggers.rename"
)
)}
</ha-dropdown-item>
</ha-md-menu-item>
<ha-dropdown-item value="duplicate" .disabled=${this.disabled}>
<ha-md-menu-item
@click=${this._duplicateOption}
.disabled=${this.disabled}
>
<ha-svg-icon
slot="icon"
slot="start"
.path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon>
@@ -189,42 +197,45 @@ export default class HaAutomationOptionRow extends LitElement {
"ui.panel.config.automation.editor.actions.duplicate"
)
)}
</ha-dropdown-item>
</ha-md-menu-item>
${!this.optionsInSidebar
? html`
<ha-dropdown-item
value="move_up"
<ha-md-menu-item
.clickAction=${this._moveUp}
.disabled=${this.disabled || !!this.first}
>
${this.hass.localize(
"ui.panel.config.automation.editor.move_up"
)}
<ha-svg-icon slot="icon" .path=${mdiArrowUp}></ha-svg-icon
></ha-dropdown-item>
<ha-dropdown-item
value="move_down"
<ha-svg-icon
slot="start"
.path=${mdiArrowUp}
></ha-svg-icon
></ha-md-menu-item>
<ha-md-menu-item
.clickAction=${this._moveDown}
.disabled=${this.disabled || !!this.last}
>
${this.hass.localize(
"ui.panel.config.automation.editor.move_down"
)}
<ha-svg-icon
slot="icon"
slot="start"
.path=${mdiArrowDown}
></ha-svg-icon
></ha-dropdown-item>
></ha-md-menu-item>
`
: nothing}
<ha-dropdown-item
value="delete"
variant="danger"
<ha-md-menu-item
@click=${this._removeOption}
class="warning"
.disabled=${this.disabled}
>
<ha-svg-icon
class="warning"
slot="icon"
slot="start"
.path=${mdiDelete}
></ha-svg-icon>
${this._renderOverflowLabel(
@@ -235,6 +246,7 @@ export default class HaAutomationOptionRow extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -249,8 +261,8 @@ export default class HaAutomationOptionRow extends LitElement {
>
</span>`
)}
</ha-dropdown-item>
</ha-dropdown>
</ha-md-menu-item>
</ha-md-button-menu>
`
: nothing}
${!this.optionsInSidebar ? this._renderContent() : nothing}
@@ -349,32 +361,6 @@ export default class HaAutomationOptionRow extends LitElement {
fireEvent(this, "move-down");
}
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "rename":
this._renameOption();
break;
case "delete":
this._removeOption();
break;
case "duplicate":
this._duplicateOption();
break;
case "move_up":
this._moveUp();
break;
case "move_down":
this._moveDown();
break;
}
}
private _removeOption = () => {
if (this.option) {
fireEvent(this, "value-changed", {
@@ -527,6 +513,9 @@ export default class HaAutomationOptionRow extends LitElement {
overflowStyles,
indentStyle,
css`
li[role="separator"] {
border-bottom-color: var(--divider-color);
}
h4 {
color: var(--ha-color-text-secondary);
}

View File

@@ -1,4 +1,3 @@
import "@home-assistant/webawesome/dist/components/divider/divider";
import {
mdiAppleKeyboardCommand,
mdiContentCopy,
@@ -17,8 +16,8 @@ import { keyed } from "lit/directives/keyed";
import { fireEvent } from "../../../../common/dom/fire_event";
import { handleStructError } from "../../../../common/structs/handle-errors";
import type { LocalizeKeys } from "../../../../common/translations/localize";
import "../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
import "../../../../components/ha-md-divider";
import "../../../../components/ha-md-menu-item";
import { ACTION_BUILDING_BLOCKS } from "../../../../data/action";
import type { ActionSidebarConfig } from "../../../../data/automation";
import { domainToName } from "../../../../data/integration";
@@ -117,7 +116,6 @@ export default class HaAutomationSidebarAction extends LitElement {
.yamlMode=${this.yamlMode}
.warnings=${this._warnings}
.narrow=${this.narrow}
@wa-select=${this._handleDropdownSelect}
>
<span slot="title">${title}</span>
<span slot="subtitle"
@@ -128,35 +126,38 @@ export default class HaAutomationSidebarAction extends LitElement {
: ""}</span
>
<ha-dropdown-item slot="menu-items" value="run">
<ha-svg-icon slot="icon" .path=${mdiPlay}></ha-svg-icon>
<ha-md-menu-item slot="menu-items" .clickAction=${this.config.run}>
<ha-svg-icon slot="start" .path=${mdiPlay}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize("ui.panel.config.automation.editor.actions.run")}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
<ha-dropdown-item
</ha-md-menu-item>
<ha-md-menu-item
slot="menu-items"
value="rename"
.clickAction=${this.config.rename}
.disabled=${this.disabled}
>
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.rename"
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
<wa-divider slot="menu-items"></wa-divider>
<ha-dropdown-item
</ha-md-menu-item>
<ha-md-divider
slot="menu-items"
value="duplicate"
role="separator"
tabindex="-1"
></ha-md-divider>
<ha-md-menu-item
slot="menu-items"
.clickAction=${this.config.duplicate}
.disabled=${this.disabled}
>
<ha-svg-icon
slot="icon"
slot="start"
.path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon>
<div class="overflow-label">
@@ -165,9 +166,9 @@ export default class HaAutomationSidebarAction extends LitElement {
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
<ha-dropdown-item slot="menu-items" value="copy">
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
</ha-md-menu-item>
<ha-md-menu-item slot="menu-items" .clickAction=${this.config.copy}>
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.copy"
@@ -177,6 +178,7 @@ export default class HaAutomationSidebarAction extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -188,13 +190,13 @@ export default class HaAutomationSidebarAction extends LitElement {
</span>`
: nothing}
</div>
</ha-dropdown-item>
<ha-dropdown-item
</ha-md-menu-item>
<ha-md-menu-item
slot="menu-items"
value="cut"
.clickAction=${this.config.cut}
.disabled=${this.disabled}
>
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.cut"
@@ -204,6 +206,7 @@ export default class HaAutomationSidebarAction extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -215,29 +218,32 @@ export default class HaAutomationSidebarAction extends LitElement {
</span>`
: nothing}
</div>
</ha-dropdown-item>
<ha-dropdown-item
</ha-md-menu-item>
<ha-md-menu-item
slot="menu-items"
value="toggle_yaml_mode"
.clickAction=${this._toggleYamlMode}
.disabled=${!this.config.uiSupported || !!this._warnings}
>
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
<wa-divider slot="menu-items"></wa-divider>
<ha-dropdown-item
</ha-md-menu-item>
<ha-md-divider
slot="menu-items"
value="disable"
role="separator"
tabindex="-1"
></ha-md-divider>
<ha-md-menu-item
slot="menu-items"
.clickAction=${this.config.disable}
.disabled=${this.disabled}
>
<ha-svg-icon
slot="icon"
slot="start"
.path=${rowDisabled ? mdiPlayCircleOutline : mdiStopCircleOutline}
></ha-svg-icon>
<div class="overflow-label">
@@ -246,14 +252,14 @@ export default class HaAutomationSidebarAction extends LitElement {
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
<ha-dropdown-item
</ha-md-menu-item>
<ha-md-menu-item
slot="menu-items"
value="delete"
variant="danger"
.clickAction=${this.config.delete}
.disabled=${this.disabled}
class="warning"
>
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
@@ -263,6 +269,7 @@ export default class HaAutomationSidebarAction extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -278,7 +285,7 @@ export default class HaAutomationSidebarAction extends LitElement {
</span>`
: nothing}
</div>
</ha-dropdown-item>
</ha-md-menu-item>
${description && !this.yamlMode
? html`<div class="description">${description}</div>`
: keyed(
@@ -334,41 +341,6 @@ export default class HaAutomationSidebarAction extends LitElement {
fireEvent(this, "toggle-yaml-mode");
};
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "rename":
this.config.rename();
break;
case "run":
this.config.run();
break;
case "duplicate":
this.config.duplicate();
break;
case "copy":
this.config.copy();
break;
case "cut":
this.config.cut();
break;
case "toggle_yaml_mode":
this._toggleYamlMode();
break;
case "disable":
this.config.disable();
break;
case "delete":
this.config.delete();
break;
}
}
static styles = [sidebarEditorStyles, overflowStyles];
}

View File

@@ -3,16 +3,16 @@ import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { fireEvent } from "../../../../common/dom/fire_event";
import { preventDefaultStopPropagation } from "../../../../common/dom/prevent_default_stop_propagation";
import { stopPropagation } from "../../../../common/dom/stop_propagation";
import "../../../../components/ha-card";
import "../../../../components/ha-dialog-header";
import "../../../../components/ha-dropdown";
import "../../../../components/ha-icon-button";
import { ScrollableFadeMixin } from "../../../../mixins/scrollable-fade-mixin";
import "../../../../components/ha-md-button-menu";
import "../../../../components/ha-md-divider";
import { haStyleScrollbar } from "../../../../resources/styles";
import type { HomeAssistant } from "../../../../types";
import "../ha-automation-editor-warning";
import { ScrollableFadeMixin } from "../../../../mixins/scrollable-fade-mixin";
export interface SidebarOverflowMenuEntry {
clickAction: () => void;
@@ -36,10 +36,6 @@ export default class HaAutomationSidebarCard extends ScrollableFadeMixin(
@property({ attribute: false }) public warnings?: string[];
@property({ attribute: false }) public handleDropdownSelect!: (
ev: CustomEvent
) => void;
@property({ type: Boolean }) public narrow = false;
@query(".card-content") private _contentElement!: HTMLDivElement;
@@ -67,10 +63,14 @@ export default class HaAutomationSidebarCard extends ScrollableFadeMixin(
<slot slot="title" name="title"></slot>
<slot slot="subtitle" name="subtitle"></slot>
<slot name="overflow-menu" slot="actionItems">
<ha-dropdown
@click=${preventDefaultStopPropagation}
<ha-md-button-menu
quick
@click=${this._openOverflowMenu}
@keydown=${stopPropagation}
placement="bottom-end"
@closed=${stopPropagation}
.positioning=${this.narrow ? "absolute" : "fixed"}
anchor-corner="end-end"
menu-corner="start-end"
>
<ha-icon-button
slot="trigger"
@@ -78,7 +78,7 @@ export default class HaAutomationSidebarCard extends ScrollableFadeMixin(
.path=${mdiDotsVertical}
></ha-icon-button>
<slot name="menu-items"></slot>
</ha-dropdown>
</ha-md-button-menu>
</slot>
</ha-dialog-header>
${this.warnings
@@ -100,6 +100,11 @@ export default class HaAutomationSidebarCard extends ScrollableFadeMixin(
fireEvent(this, "close-sidebar");
}
private _openOverflowMenu(ev: MouseEvent) {
ev.stopPropagation();
ev.preventDefault();
}
static get styles() {
return [
...super.styles,

View File

@@ -1,4 +1,3 @@
import "@home-assistant/webawesome/dist/components/divider/divider";
import {
mdiAppleKeyboardCommand,
mdiContentCopy,
@@ -17,11 +16,9 @@ import { classMap } from "lit/directives/class-map";
import { keyed } from "lit/directives/keyed";
import { fireEvent } from "../../../../common/dom/fire_event";
import { handleStructError } from "../../../../common/structs/handle-errors";
import "../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
import type {
ConditionSidebarConfig,
LegacyCondition,
ConditionSidebarConfig,
} from "../../../../data/automation";
import { testCondition } from "../../../../data/automation";
import {
@@ -120,7 +117,6 @@ export default class HaAutomationSidebarCondition extends LitElement {
.yamlMode=${this.yamlMode}
.warnings=${this._warnings}
.narrow=${this.narrow}
@wa-select=${this._handleDropdownSelect}
>
<span slot="title">${title}</span>
<span slot="subtitle"
@@ -128,38 +124,42 @@ export default class HaAutomationSidebarCondition extends LitElement {
? ` (${this.hass.localize("ui.panel.config.automation.editor.actions.disabled")})`
: ""}</span
>
<ha-dropdown-item slot="menu-items" value="test">
<ha-svg-icon slot="icon" .path=${mdiFlask}></ha-svg-icon>
<ha-md-menu-item slot="menu-items" .clickAction=${this._testCondition}>
<ha-svg-icon slot="start" .path=${mdiFlask}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.conditions.test"
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
<ha-dropdown-item
</ha-md-menu-item>
<ha-md-menu-item
slot="menu-items"
value="rename"
.clickAction=${this.config.rename}
.disabled=${this.disabled}
>
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.rename"
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
</ha-md-menu-item>
<wa-divider slot="menu-items"></wa-divider>
<ha-dropdown-item
<ha-md-divider
slot="menu-items"
value="duplicate"
role="separator"
tabindex="-1"
></ha-md-divider>
<ha-md-menu-item
slot="menu-items"
.clickAction=${this.config.duplicate}
.disabled=${this.disabled}
>
<ha-svg-icon
slot="icon"
slot="start"
.path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon>
<div class="overflow-label">
@@ -168,10 +168,10 @@ export default class HaAutomationSidebarCondition extends LitElement {
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
</ha-md-menu-item>
<ha-dropdown-item slot="menu-items" value="copy">
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
<ha-md-menu-item slot="menu-items" .clickAction=${this.config.copy}>
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.copy"
@@ -181,6 +181,7 @@ export default class HaAutomationSidebarCondition extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -192,14 +193,14 @@ export default class HaAutomationSidebarCondition extends LitElement {
</span>`
: nothing}
</div>
</ha-dropdown-item>
</ha-md-menu-item>
<ha-dropdown-item
<ha-md-menu-item
slot="menu-items"
value="cut"
.clickAction=${this.config.cut}
.disabled=${this.disabled}
>
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.cut"
@@ -209,6 +210,7 @@ export default class HaAutomationSidebarCondition extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -220,29 +222,32 @@ export default class HaAutomationSidebarCondition extends LitElement {
</span>`
: nothing}
</div>
</ha-dropdown-item>
<ha-dropdown-item
</ha-md-menu-item>
<ha-md-menu-item
slot="menu-items"
value="toggle_yaml_mode"
.clickAction=${this._toggleYamlMode}
.disabled=${!this.config.uiSupported || !!this._warnings}
>
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
<wa-divider slot="menu-items"></wa-divider>
<ha-dropdown-item
</ha-md-menu-item>
<ha-md-divider
slot="menu-items"
value="disable"
role="separator"
tabindex="-1"
></ha-md-divider>
<ha-md-menu-item
slot="menu-items"
.clickAction=${this.config.disable}
.disabled=${this.disabled}
>
<ha-svg-icon
slot="icon"
slot="start"
.path=${rowDisabled ? mdiPlayCircleOutline : mdiStopCircleOutline}
></ha-svg-icon>
<div class="overflow-label">
@@ -251,14 +256,14 @@ export default class HaAutomationSidebarCondition extends LitElement {
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
<ha-dropdown-item
</ha-md-menu-item>
<ha-md-menu-item
slot="menu-items"
value="delete"
variant="danger"
.clickAction=${this.config.delete}
.disabled=${this.disabled}
class="warning"
>
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
@@ -268,6 +273,7 @@ export default class HaAutomationSidebarCondition extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -283,7 +289,7 @@ export default class HaAutomationSidebarCondition extends LitElement {
</span>`
: nothing}
</div>
</ha-dropdown-item>
</ha-md-menu-item>
${description && !this.yamlMode
? html`<div class="description">${description}</div>`
: keyed(
@@ -413,41 +419,6 @@ export default class HaAutomationSidebarCondition extends LitElement {
fireEvent(this, "toggle-yaml-mode");
};
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "rename":
this.config.rename();
break;
case "test":
this._testCondition();
break;
case "duplicate":
this.config.duplicate();
break;
case "copy":
this.config.copy();
break;
case "cut":
this.config.cut();
break;
case "toggle_yaml_mode":
this._toggleYamlMode();
break;
case "disable":
this.config.disable();
break;
case "delete":
this.config.delete();
break;
}
}
static styles = [
sidebarEditorStyles,
overflowStyles,

View File

@@ -1,4 +1,3 @@
import "@home-assistant/webawesome/dist/components/divider/divider";
import {
mdiAppleKeyboardCommand,
mdiDelete,
@@ -7,9 +6,8 @@ import {
} from "@mdi/js";
import { html, LitElement, nothing } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
import "../../../../components/ha-md-divider";
import "../../../../components/ha-md-menu-item";
import "../../../../components/ha-svg-icon";
import type { OptionSidebarConfig } from "../../../../data/automation";
import type { HomeAssistant } from "../../../../types";
@@ -52,34 +50,33 @@ export default class HaAutomationSidebarOption extends LitElement {
.hass=${this.hass}
.isWide=${this.isWide}
.narrow=${this.narrow}
@wa-select=${this._handleDropdownSelect}
>
<span slot="title">${title}</span>
<span slot="subtitle">${subtitle}</span>
${this.config.defaultOption
? html`<span slot="overflow-menu"></span>`
: html`
<ha-dropdown-item
<ha-md-menu-item
slot="menu-items"
value="rename"
.clickAction=${this.config.rename}
.disabled=${!!disabled}
>
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.rename"
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
</ha-md-menu-item>
<ha-dropdown-item
<ha-md-menu-item
slot="menu-items"
value="duplicate"
@click=${this.config.duplicate}
.disabled=${this.disabled}
>
<ha-svg-icon
slot="icon"
slot="start"
.path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon>
<div class="overflow-label">
@@ -88,15 +85,19 @@ export default class HaAutomationSidebarOption extends LitElement {
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
<wa-divider slot="menu-items"></wa-divider>
<ha-dropdown-item
</ha-md-menu-item>
<ha-md-divider
slot="menu-items"
value="delete"
role="separator"
tabindex="-1"
></ha-md-divider>
<ha-md-menu-item
slot="menu-items"
.clickAction=${this.config.delete}
.disabled=${this.disabled}
variant="danger"
class="warning"
>
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.type.choose.remove_option"
@@ -122,33 +123,13 @@ export default class HaAutomationSidebarOption extends LitElement {
</span>`
: nothing}
</div>
</ha-dropdown-item>
</ha-md-menu-item>
`}
<div class="description">${description}</div>
</ha-automation-sidebar-card>`;
}
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "rename":
this.config.rename();
break;
case "duplicate":
this.config.duplicate();
break;
case "delete":
this.config.delete();
break;
}
}
static styles = [sidebarEditorStyles, overflowStyles];
}

View File

@@ -4,8 +4,6 @@ import { customElement, property, query, state } from "lit/decorators";
import { keyed } from "lit/directives/keyed";
import { fireEvent } from "../../../../common/dom/fire_event";
import type { LocalizeKeys } from "../../../../common/translations/localize";
import "../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
import type { ScriptFieldSidebarConfig } from "../../../../data/automation";
import type { HomeAssistant } from "../../../../types";
import { isMac } from "../../../../util/is_mac";
@@ -64,30 +62,29 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
.yamlMode=${this.yamlMode}
.warnings=${this._warnings}
.narrow=${this.narrow}
@wa-select=${this._handleDropdownSelect}
>
<span slot="title">${title}</span>
<span slot="subtitle">${subtitle}</span>
<ha-dropdown-item
<ha-md-menu-item
slot="menu-items"
value="toggle_yaml_mode"
.clickAction=${this._toggleYamlMode}
.disabled=${!!this._warnings}
>
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
<ha-dropdown-item
</ha-md-menu-item>
<ha-md-menu-item
slot="menu-items"
value="delete"
.clickAction=${this.config.delete}
.disabled=${this.disabled}
variant="danger"
class="warning"
>
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
@@ -97,6 +94,7 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -112,7 +110,7 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
</span>`
: nothing}
</div>
</ha-dropdown-item>
</ha-md-menu-item>
${keyed(
this.sidebarKey,
html`<ha-script-field-selector-editor
@@ -162,23 +160,6 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
fireEvent(this, "toggle-yaml-mode");
};
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "toggle_yaml_mode":
this._toggleYamlMode();
break;
case "delete":
this.config.delete();
break;
}
}
static styles = sidebarEditorStyles;
}

View File

@@ -3,8 +3,6 @@ import { html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { keyed } from "lit/directives/keyed";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
import type { ScriptFieldSidebarConfig } from "../../../../data/automation";
import type { HomeAssistant } from "../../../../types";
import { isMac } from "../../../../util/is_mac";
@@ -58,29 +56,28 @@ export default class HaAutomationSidebarScriptField extends LitElement {
.yamlMode=${this.yamlMode}
.warnings=${this._warnings}
.narrow=${this.narrow}
@wa-select=${this._handleDropdownSelect}
>
<span slot="title">${title}</span>
<ha-dropdown-item
<ha-md-menu-item
slot="menu-items"
value="toggle_yaml_mode"
.clickAction=${this._toggleYamlMode}
.disabled=${!!this._warnings}
>
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
<ha-dropdown-item
</ha-md-menu-item>
<ha-md-menu-item
slot="menu-items"
value="delete"
.clickAction=${this.config.delete}
.disabled=${this.disabled}
variant="danger"
class="warning"
>
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
@@ -90,6 +87,7 @@ export default class HaAutomationSidebarScriptField extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -105,7 +103,7 @@ export default class HaAutomationSidebarScriptField extends LitElement {
</span>`
: nothing}
</div>
</ha-dropdown-item>
</ha-md-menu-item>
${keyed(
this.sidebarKey,
html`<ha-script-field-editor
@@ -156,23 +154,6 @@ export default class HaAutomationSidebarScriptField extends LitElement {
fireEvent(this, "toggle-yaml-mode");
};
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "toggle_yaml_mode":
this._toggleYamlMode();
break;
case "delete":
this.config.delete();
break;
}
}
static styles = [sidebarEditorStyles, overflowStyles];
}

View File

@@ -1,4 +1,3 @@
import "@home-assistant/webawesome/dist/components/divider/divider";
import {
mdiAppleKeyboardCommand,
mdiContentCopy,
@@ -16,8 +15,6 @@ import { customElement, property, query, state } from "lit/decorators";
import { keyed } from "lit/directives/keyed";
import { fireEvent } from "../../../../common/dom/fire_event";
import { handleStructError } from "../../../../common/structs/handle-errors";
import "../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
import type {
LegacyTrigger,
TriggerSidebarConfig,
@@ -102,7 +99,6 @@ export default class HaAutomationSidebarTrigger extends LitElement {
.yamlMode=${this.yamlMode}
.warnings=${this._warnings}
.narrow=${this.narrow}
@wa-select=${this._handleDropdownSelect}
>
<span slot="title">${title}</span>
<span slot="subtitle"
@@ -110,56 +106,60 @@ export default class HaAutomationSidebarTrigger extends LitElement {
? ` (${this.hass.localize("ui.panel.config.automation.editor.actions.disabled")})`
: ""}</span
>
<ha-dropdown-item
<ha-md-menu-item
slot="menu-items"
value="rename"
.clickAction=${this.config.rename}
.disabled=${this.disabled || type === "list"}
>
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.rename"
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
</ha-md-menu-item>
${!this.yamlMode &&
!("id" in this.config.config) &&
!this._requestShowId
? html`<ha-dropdown-item
? html`<ha-md-menu-item
slot="menu-items"
value="show_id"
.clickAction=${this._showTriggerId}
.disabled=${this.disabled || type === "list"}
>
<ha-svg-icon slot="icon" .path=${mdiIdentifier}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiIdentifier}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.edit_id"
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>`
</ha-md-menu-item>`
: nothing}
<wa-divider slot="menu-items"></wa-divider>
<ha-dropdown-item
<ha-md-divider
slot="menu-items"
value="duplicate"
role="separator"
tabindex="-1"
></ha-md-divider>
<ha-md-menu-item
slot="menu-items"
.clickAction=${this.config.duplicate}
.disabled=${this.disabled}
>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.duplicate"
)}
<ha-svg-icon
slot="icon"
slot="start"
.path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon>
</ha-dropdown-item>
</ha-md-menu-item>
<ha-dropdown-item slot="menu-items" value="copy">
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
<ha-md-menu-item slot="menu-items" .clickAction=${this.config.copy}>
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.copy"
@@ -169,6 +169,7 @@ export default class HaAutomationSidebarTrigger extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -180,14 +181,14 @@ export default class HaAutomationSidebarTrigger extends LitElement {
</span>`
: nothing}
</div>
</ha-dropdown-item>
</ha-md-menu-item>
<ha-dropdown-item
<ha-md-menu-item
slot="menu-items"
value="cut"
.clickAction=${this.config.cut}
.disabled=${this.disabled}
>
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.cut"
@@ -197,6 +198,7 @@ export default class HaAutomationSidebarTrigger extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -208,28 +210,32 @@ export default class HaAutomationSidebarTrigger extends LitElement {
</span>`
: nothing}
</div>
</ha-dropdown-item>
<ha-dropdown-item
</ha-md-menu-item>
<ha-md-menu-item
slot="menu-items"
value="toggle_yaml_mode"
.clickAction=${this._toggleYamlMode}
.disabled=${!this.config.uiSupported || !!this._warnings}
>
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
<wa-divider slot="menu-items"></wa-divider>
<ha-dropdown-item
</ha-md-menu-item>
<ha-md-divider
slot="menu-items"
value="disable"
role="separator"
tabindex="-1"
></ha-md-divider>
<ha-md-menu-item
slot="menu-items"
.clickAction=${this.config.disable}
.disabled=${this.disabled || type === "list"}
>
<ha-svg-icon
slot="icon"
slot="start"
.path=${rowDisabled ? mdiPlayCircleOutline : mdiStopCircleOutline}
></ha-svg-icon>
<div class="overflow-label">
@@ -238,14 +244,14 @@ export default class HaAutomationSidebarTrigger extends LitElement {
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
<ha-dropdown-item
</ha-md-menu-item>
<ha-md-menu-item
slot="menu-items"
value="delete"
.clickAction=${this.config.delete}
.disabled=${this.disabled}
variant="danger"
class="warning"
>
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
@@ -255,6 +261,7 @@ export default class HaAutomationSidebarTrigger extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -270,7 +277,7 @@ export default class HaAutomationSidebarTrigger extends LitElement {
</span>`
: nothing}
</div>
</ha-dropdown-item>
</ha-md-menu-item>
${keyed(
this.sidebarKey,
html`<ha-automation-trigger-editor
@@ -328,41 +335,6 @@ export default class HaAutomationSidebarTrigger extends LitElement {
this._requestShowId = true;
};
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "rename":
this.config.rename();
break;
case "show_id":
this._showTriggerId();
break;
case "duplicate":
this.config.duplicate();
break;
case "copy":
this.config.copy();
break;
case "cut":
this.config.cut();
break;
case "toggle_yaml_mode":
this._toggleYamlMode();
break;
case "disable":
this.config.disable();
break;
case "delete":
this.config.delete();
break;
}
}
static styles = [sidebarEditorStyles, overflowStyles];
}

View File

@@ -1,4 +1,3 @@
import "@home-assistant/webawesome/dist/components/divider/divider";
import { consume } from "@lit/context";
import {
mdiAppleKeyboardCommand,
@@ -35,11 +34,11 @@ import "../../../../components/ha-alert";
import "../../../../components/ha-automation-row";
import type { HaAutomationRow } from "../../../../components/ha-automation-row";
import "../../../../components/ha-card";
import "../../../../components/ha-dropdown";
import "../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
import "../../../../components/ha-expansion-panel";
import "../../../../components/ha-icon-button";
import "../../../../components/ha-md-button-menu";
import "../../../../components/ha-md-divider";
import "../../../../components/ha-md-menu-item";
import "../../../../components/ha-svg-icon";
import { TRIGGER_ICONS } from "../../../../components/ha-trigger-icon";
import type {
@@ -209,35 +208,41 @@ export default class HaAutomationTriggerRow extends LitElement {
<slot name="icons" slot="icons"></slot>
<ha-dropdown
<ha-md-button-menu
quick
slot="icons"
@click=${preventDefaultStopPropagation}
@keydown=${stopPropagation}
@wa-select=${this._handleDropdownSelect}
placement="bottom-end"
@closed=${stopPropagation}
positioning="fixed"
anchor-corner="end-end"
menu-corner="start-end"
>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-dropdown-item
value="rename"
<ha-md-menu-item
.clickAction=${this._renameTrigger}
.disabled=${this.disabled || type === "list"}
>
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
${this._renderOverflowLabel(
this.hass.localize(
"ui.panel.config.automation.editor.triggers.rename"
)
)}
</ha-dropdown-item>
</ha-md-menu-item>
<wa-divider></wa-divider>
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
<ha-dropdown-item value="duplicate" .disabled=${this.disabled}>
<ha-md-menu-item
.clickAction=${this._duplicateTrigger}
.disabled=${this.disabled}
>
<ha-svg-icon
slot="icon"
slot="start"
.path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon>
@@ -246,10 +251,13 @@ export default class HaAutomationTriggerRow extends LitElement {
"ui.panel.config.automation.editor.actions.duplicate"
)
)}
</ha-dropdown-item>
</ha-md-menu-item>
<ha-dropdown-item value="copy" .disabled=${this.disabled}>
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
<ha-md-menu-item
.clickAction=${this._copyTrigger}
.disabled=${this.disabled}
>
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
${this._renderOverflowLabel(
this.hass.localize(
"ui.panel.config.automation.editor.triggers.copy"
@@ -258,6 +266,7 @@ export default class HaAutomationTriggerRow extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -268,10 +277,13 @@ export default class HaAutomationTriggerRow extends LitElement {
<span>C</span>
</span>`
)}
</ha-dropdown-item>
</ha-md-menu-item>
<ha-dropdown-item value="cut" .disabled=${this.disabled}>
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon>
<ha-md-menu-item
.clickAction=${this._cutTrigger}
.disabled=${this.disabled}
>
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
${this._renderOverflowLabel(
this.hass.localize(
"ui.panel.config.automation.editor.triggers.cut"
@@ -280,6 +292,7 @@ export default class HaAutomationTriggerRow extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -290,51 +303,51 @@ export default class HaAutomationTriggerRow extends LitElement {
<span>X</span>
</span>`
)}
</ha-dropdown-item>
</ha-md-menu-item>
${!this.optionsInSidebar
? html`
<ha-dropdown-item
value="move_up"
<ha-md-menu-item
.clickAction=${this._moveUp}
.disabled=${this.disabled || !!this.first}
>
${this.hass.localize(
"ui.panel.config.automation.editor.move_up"
)}
<ha-svg-icon slot="icon" .path=${mdiArrowUp}></ha-svg-icon
></ha-dropdown-item>
<ha-dropdown-item
value="move_down"
<ha-svg-icon slot="start" .path=${mdiArrowUp}></ha-svg-icon
></ha-md-menu-item>
<ha-md-menu-item
.clickAction=${this._moveDown}
.disabled=${this.disabled || !!this.last}
>
${this.hass.localize(
"ui.panel.config.automation.editor.move_down"
)}
<ha-svg-icon slot="icon" .path=${mdiArrowDown}></ha-svg-icon
></ha-dropdown-item>
<ha-svg-icon slot="start" .path=${mdiArrowDown}></ha-svg-icon
></ha-md-menu-item>
`
: nothing}
<ha-dropdown-item
value="toggle_yaml_mode"
<ha-md-menu-item
.clickAction=${this._toggleYamlMode}
.disabled=${!supported || !!this._warnings}
>
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
${this._renderOverflowLabel(
this.hass.localize(
`ui.panel.config.automation.editor.edit_${!yamlMode ? "yaml" : "ui"}`
)
)}
</ha-dropdown-item>
</ha-md-menu-item>
<wa-divider></wa-divider>
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
<ha-dropdown-item
value="disable"
<ha-md-menu-item
.clickAction=${this._onDisable}
.disabled=${this.disabled || type === "list"}
>
<ha-svg-icon
slot="icon"
slot="start"
.path=${"enabled" in this.trigger && this.trigger.enabled === false
? mdiPlayCircleOutline
: mdiStopCircleOutline}
@@ -345,15 +358,15 @@ export default class HaAutomationTriggerRow extends LitElement {
`ui.panel.config.automation.editor.actions.${"enabled" in this.trigger && this.trigger.enabled === false ? "enable" : "disable"}`
)
)}
</ha-dropdown-item>
<ha-dropdown-item
value="delete"
variant="danger"
</ha-md-menu-item>
<ha-md-menu-item
.clickAction=${this._onDelete}
class="warning"
.disabled=${this.disabled}
>
<ha-svg-icon
class="warning"
slot="icon"
slot="start"
.path=${mdiDelete}
></ha-svg-icon>
${this._renderOverflowLabel(
@@ -364,6 +377,7 @@ export default class HaAutomationTriggerRow extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -378,8 +392,8 @@ export default class HaAutomationTriggerRow extends LitElement {
>
</span>`
)}
</ha-dropdown-item>
</ha-dropdown>
</ha-md-menu-item>
</ha-md-button-menu>
${!this.optionsInSidebar
? html`${this._warnings
? html`<ha-automation-editor-warning
@@ -790,44 +804,6 @@ export default class HaAutomationTriggerRow extends LitElement {
this._automationRowElement?.focus();
}
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "rename":
this._renameTrigger();
break;
case "duplicate":
this._duplicateTrigger();
break;
case "copy":
this._copyTrigger();
break;
case "cut":
this._cutTrigger();
break;
case "move_up":
this._moveUp();
break;
case "move_down":
this._moveDown();
break;
case "toggle_yaml_mode":
this._toggleYamlMode(ev.target as HTMLElement);
break;
case "disable":
this._onDisable();
break;
case "delete":
this._onDelete();
break;
}
}
static get styles(): CSSResultGroup {
return [
rowStyles,

View File

@@ -24,7 +24,7 @@ import {
type Trigger,
type TriggerList,
} from "../../../../data/automation";
import { subscribeLabFeature } from "../../../../data/labs";
import { subscribeLabFeatures } from "../../../../data/labs";
import type { TriggerDescriptions } from "../../../../data/trigger";
import { isTriggerList, subscribeTriggers } from "../../../../data/trigger";
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
@@ -85,14 +85,14 @@ export default class HaAutomationTrigger extends SubscribeMixin(LitElement) {
protected hassSubscribe() {
return [
subscribeLabFeature(
this.hass!.connection,
"automation",
"new_triggers_conditions",
(enabled) => {
this._newTriggersAndConditions = enabled;
}
),
subscribeLabFeatures(this.hass!.connection, (features) => {
this._newTriggersAndConditions =
features.find(
(feature) =>
feature.domain === "automation" &&
feature.preview_feature === "new_triggers_conditions"
)?.enabled ?? false;
}),
];
}

View File

@@ -1,4 +1,3 @@
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical, mdiRefresh } from "@mdi/js";
import type { HassEntities } from "home-assistant-js-websocket";
import type { TemplateResult } from "lit";
@@ -6,13 +5,9 @@ import { LitElement, css, html } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
import "../../../components/ha-alert";
import "../../../components/ha-bar";
import "../../../components/ha-button-menu";
import "../../../components/ha-card";
import "../../../components/ha-check-list-item";
import "../../../components/ha-list-item";
import "../../../components/ha-metric";
import { extractApiErrorMessage } from "../../../data/hassio/common";
import type {
@@ -33,6 +28,9 @@ import "../../../layouts/hass-subpage";
import type { HomeAssistant } from "../../../types";
import "../dashboard/ha-config-updates";
import { showJoinBetaDialog } from "./updates/show-dialog-join-beta";
import "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import "@home-assistant/webawesome/dist/components/divider/divider";
@customElement("ha-config-section-updates")
class HaConfigSectionUpdates extends LitElement {
@@ -73,24 +71,25 @@ class HaConfigSectionUpdates extends LitElement {
.path=${mdiRefresh}
@click=${this._checkUpdates}
></ha-icon-button>
<ha-button-menu multi>
<ha-dropdown @wa-select=${this._handleOverflowAction}>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-check-list-item
left
@request-selected=${this._toggleSkipped}
.selected=${this._showSkipped}
<ha-dropdown-item
type="checkbox"
value="show_skipped"
.checked=${this._showSkipped}
>
${this.hass.localize("ui.panel.config.updates.show_skipped")}
</ha-check-list-item>
</ha-dropdown-item>
${this._supervisorInfo
? html`
<li divider role="separator"></li>
<ha-list-item
@request-selected=${this._toggleBeta}
<wa-divider></wa-divider>
<ha-dropdown-item
value="toggle_beta"
.disabled=${this._supervisorInfo.channel === "dev"}
>
${this._supervisorInfo.channel === "stable"
@@ -98,10 +97,10 @@ class HaConfigSectionUpdates extends LitElement {
: this.hass.localize(
"ui.panel.config.updates.leave_beta"
)}
</ha-list-item>
</ha-dropdown-item>
`
: ""}
</ha-button-menu>
</ha-dropdown>
</div>
<div class="content">
<ha-card outlined>
@@ -133,27 +132,19 @@ class HaConfigSectionUpdates extends LitElement {
this._supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
}
private _toggleSkipped(ev: CustomEvent<RequestSelectedDetail>): void {
if (ev.detail.source !== "property") {
return;
}
this._showSkipped = !this._showSkipped;
}
private async _toggleBeta(
ev: CustomEvent<RequestSelectedDetail>
private async _handleOverflowAction(
ev: CustomEvent<{ item: { value: string } }>
): Promise<void> {
if (!shouldHandleRequestSelectedEvent(ev)) {
return;
}
if (this._supervisorInfo!.channel === "stable") {
showJoinBetaDialog(this, {
join: async () => this._setChannel("beta"),
});
} else {
this._setChannel("stable");
if (ev.detail.item.value === "toggle_beta") {
if (this._supervisorInfo!.channel === "stable") {
showJoinBetaDialog(this, {
join: async () => this._setChannel("beta"),
});
} else {
this._setChannel("stable");
}
} else if (ev.detail.item.value === "show_skipped") {
this._showSkipped = !this._showSkipped;
}
}

View File

@@ -9,10 +9,10 @@ export class MatterAddDevice extends HTMLElement {
public hass!: HomeAssistant;
connectedCallback() {
navigate("/config/devices/dashboard", {
showMatterAddDeviceDialog(this);
navigate(`/config/devices`, {
replace: true,
});
showMatterAddDeviceDialog(this);
}
}

View File

@@ -1,4 +1,3 @@
import "@home-assistant/webawesome/dist/components/divider/divider";
import { consume } from "@lit/context";
import {
mdiAppleKeyboardCommand,
@@ -24,19 +23,18 @@ import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { UndoRedoController } from "../../../common/controllers/undo-redo-controller";
import { transform } from "../../../common/decorators/transform";
import { fireEvent } from "../../../common/dom/fire_event";
import { goBack, navigate } from "../../../common/navigate";
import { slugify } from "../../../common/string/slugify";
import { promiseTimeout } from "../../../common/util/promise-timeout";
import { afterNextRender } from "../../../common/util/render-status";
import "../../../components/ha-button";
import "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
import "../../../components/ha-button-menu";
import "../../../components/ha-fab";
import { transform } from "../../../common/decorators/transform";
import "../../../components/ha-icon-button";
import "../../../components/ha-list-item";
import "../../../components/ha-svg-icon";
import "../../../components/ha-yaml-editor";
import { substituteBlueprint } from "../../../data/blueprint";
@@ -67,6 +65,7 @@ import "../../../layouts/hass-subpage";
import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin";
import { PreventUnsavedMixin } from "../../../mixins/prevent-unsaved-mixin";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { UndoRedoController } from "../../../common/controllers/undo-redo-controller";
import { haStyle } from "../../../resources/styles";
import type { Entries, HomeAssistant, Route } from "../../../types";
import { isMac } from "../../../util/is_mac";
@@ -74,11 +73,11 @@ import { showToast } from "../../../util/toast";
import { showAutomationModeDialog } from "../automation/automation-mode-dialog/show-dialog-automation-mode";
import type { EntityRegistryUpdate } from "../automation/automation-save-dialog/show-dialog-automation-save";
import { showAutomationSaveDialog } from "../automation/automation-save-dialog/show-dialog-automation-save";
import { showAutomationSaveTimeoutDialog } from "../automation/automation-save-timeout-dialog/show-dialog-automation-save-timeout";
import { showAssignCategoryDialog } from "../category/show-dialog-assign-category";
import "./blueprint-script-editor";
import "./manual-script-editor";
import type { HaManualScriptEditor } from "./manual-script-editor";
import { showAutomationSaveTimeoutDialog } from "../automation/automation-save-timeout-dialog/show-dialog-automation-save-timeout";
@customElement("ha-script-editor")
export class HaScriptEditor extends SubscribeMixin(
@@ -242,10 +241,7 @@ export class HaScriptEditor extends SubscribeMixin(
</ha-button>
`
: ""}
<ha-dropdown
slot="toolbar-icon"
@wa-select=${this._handleDropdownSelect}
>
<ha-button-menu slot="toolbar-icon">
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
@@ -253,107 +249,133 @@ export class HaScriptEditor extends SubscribeMixin(
></ha-icon-button>
${this._mode === "gui" && this.narrow
? html`<ha-dropdown-item
value="undo"
? html`<ha-list-item
graphic="icon"
@click=${this._undo}
.disabled=${!this._undoRedoController.canUndo}
>
${this.hass.localize("ui.common.undo")}
<ha-svg-icon slot="icon" .path=${mdiUndo}></ha-svg-icon>
</ha-dropdown-item>
<ha-dropdown-item
value="redo"
<ha-svg-icon slot="graphic" .path=${mdiUndo}></ha-svg-icon>
</ha-list-item>
<ha-list-item
graphic="icon"
@click=${this._redo}
.disabled=${!this._undoRedoController.canRedo}
>
${this.hass.localize("ui.common.redo")}
<ha-svg-icon slot="icon" .path=${mdiRedo}></ha-svg-icon>
</ha-dropdown-item>`
<ha-svg-icon slot="graphic" .path=${mdiRedo}></ha-svg-icon>
</ha-list-item>`
: nothing}
<ha-dropdown-item .disabled=${!this.scriptId} value="info">
<ha-list-item
graphic="icon"
.disabled=${!this.scriptId}
@click=${this._showInfo}
>
${this.hass.localize("ui.panel.config.script.editor.show_info")}
<ha-svg-icon
slot="icon"
slot="graphic"
.path=${mdiInformationOutline}
></ha-svg-icon>
</ha-dropdown-item>
</ha-list-item>
<ha-dropdown-item .disabled=${!stateObj} value="settings">
<ha-list-item
graphic="icon"
.disabled=${!stateObj}
@click=${this._showSettings}
>
${this.hass.localize(
"ui.panel.config.automation.picker.show_settings"
)}
<ha-svg-icon slot="icon" .path=${mdiCog}></ha-svg-icon>
</ha-dropdown-item>
<ha-svg-icon slot="graphic" .path=${mdiCog}></ha-svg-icon>
</ha-list-item>
<ha-dropdown-item .disabled=${!stateObj} value="category">
<ha-list-item
graphic="icon"
.disabled=${!stateObj}
@click=${this._editCategory}
>
${this.hass.localize(
`ui.panel.config.scene.picker.${this._registryEntry?.categories?.script ? "edit_category" : "assign_category"}`
)}
<ha-svg-icon slot="icon" .path=${mdiTag}></ha-svg-icon>
</ha-dropdown-item>
<ha-svg-icon slot="graphic" .path=${mdiTag}></ha-svg-icon>
</ha-list-item>
<ha-dropdown-item .disabled=${!this.scriptId} value="run">
<ha-list-item
graphic="icon"
.disabled=${!this.scriptId}
@click=${this._runScript}
>
${this.hass.localize("ui.panel.config.script.picker.run_script")}
<ha-svg-icon slot="icon" .path=${mdiPlay}></ha-svg-icon>
</ha-dropdown-item>
<ha-svg-icon slot="graphic" .path=${mdiPlay}></ha-svg-icon>
</ha-list-item>
${this.scriptId && this.narrow
? html`<ha-dropdown-item value="trace">
${this.hass.localize(
"ui.panel.config.automation.editor.show_trace"
)}
<ha-svg-icon
slot="icon"
.path=${mdiTransitConnection}
></ha-svg-icon>
</ha-dropdown-item>`
? html`
<a href="/config/script/trace/${this.scriptId}">
<ha-list-item graphic="icon">
${this.hass.localize(
"ui.panel.config.script.editor.show_trace"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiTransitConnection}
></ha-svg-icon>
</ha-list-item>
</a>
`
: nothing}
${!useBlueprint && !("fields" in this._config)
? html`
<ha-dropdown-item
<ha-list-item
graphic="icon"
.disabled=${this._readOnly || this._mode === "yaml"}
value="add_fields"
@click=${this._addFields}
>
${this.hass.localize(
"ui.panel.config.script.editor.field.add_fields"
)}
<ha-svg-icon
slot="icon"
slot="graphic"
.path=${mdiFormTextbox}
></ha-svg-icon>
</ha-dropdown-item>
</ha-list-item>
`
: nothing}
<ha-dropdown-item
value="rename"
<ha-list-item
graphic="icon"
@click=${this._promptScriptAlias}
.disabled=${!this.scriptId ||
this._readOnly ||
this._mode === "yaml"}
>
${this.hass.localize("ui.panel.config.script.editor.rename")}
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
</ha-dropdown-item>
<ha-svg-icon slot="graphic" .path=${mdiRenameBox}></ha-svg-icon>
</ha-list-item>
${!useBlueprint
? html`
<ha-dropdown-item
value="change_mode"
<ha-list-item
graphic="icon"
@click=${this._promptScriptMode}
.disabled=${this._readOnly || this._mode === "yaml"}
>
${this.hass.localize(
"ui.panel.config.script.editor.change_mode"
)}
<ha-svg-icon
slot="icon"
slot="graphic"
.path=${mdiDebugStepOver}
></ha-svg-icon>
</ha-dropdown-item>
</ha-list-item>
`
: nothing}
<ha-dropdown-item
.disabled=${!!this._blueprintConfig ||
<ha-list-item
.disabled=${this._blueprintConfig ||
(!this._readOnly && !this.scriptId)}
value="duplicate"
graphic="icon"
@click=${this._duplicate}
>
${this.hass.localize(
this._readOnly
@@ -361,48 +383,58 @@ export class HaScriptEditor extends SubscribeMixin(
: "ui.panel.config.script.editor.duplicate"
)}
<ha-svg-icon
slot="icon"
slot="graphic"
.path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon>
</ha-dropdown-item>
</ha-list-item>
${useBlueprint
? html`
<ha-dropdown-item
value="take_control"
<ha-list-item
graphic="icon"
@click=${this._takeControl}
.disabled=${this._readOnly}
>
${this.hass.localize(
"ui.panel.config.script.editor.take_control"
)}
<ha-svg-icon slot="icon" .path=${mdiFileEdit}></ha-svg-icon>
</ha-dropdown-item>
<ha-svg-icon
slot="graphic"
.path=${mdiFileEdit}
></ha-svg-icon>
</ha-list-item>
`
: nothing}
<ha-dropdown-item value="toggle_yaml_mode">
<ha-list-item
graphic="icon"
@click=${this._mode === "gui"
? this._switchYamlMode
: this._switchUiMode}
>
${this.hass.localize(
`ui.panel.config.automation.editor.edit_${this._mode === "gui" ? "yaml" : "ui"}`
)}
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
</ha-dropdown-item>
<ha-svg-icon slot="graphic" .path=${mdiPlaylistEdit}></ha-svg-icon>
</ha-list-item>
<wa-divider></wa-divider>
<li divider role="separator"></li>
<ha-dropdown-item
<ha-list-item
.disabled=${this._readOnly || !this.scriptId}
value="delete"
.variant=${this.scriptId ? "danger" : "default"}
class=${classMap({ warning: Boolean(this.scriptId) })}
graphic="icon"
@click=${this._deleteConfirm}
>
${this.hass.localize("ui.panel.config.script.picker.delete")}
<ha-svg-icon
class=${classMap({ warning: Boolean(this.scriptId) })}
slot="icon"
slot="graphic"
.path=${mdiDelete}
>
</ha-svg-icon>
</ha-dropdown-item>
</ha-dropdown>
</ha-list-item>
</ha-button-menu>
<div class=${this._mode === "yaml" ? "yaml-mode" : ""}>
${this._mode === "gui"
? html`
@@ -656,7 +688,9 @@ export class HaScriptEditor extends SubscribeMixin(
this._dirty = true;
}
private async _runScript() {
private async _runScript(ev: CustomEvent) {
ev.stopPropagation();
if (hasScriptFields(this.hass, this._entityId!)) {
showMoreInfoDialog(this, {
entityId: this._entityId!,
@@ -1121,63 +1155,6 @@ export class HaScriptEditor extends SubscribeMixin(
this._undoRedoController.redo();
}
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "undo":
this._undo();
break;
case "redo":
this._redo();
break;
case "info":
this._showInfo();
break;
case "settings":
this._showSettings();
break;
case "category":
this._editCategory();
break;
case "run":
this._runScript();
break;
case "add_fields":
this._addFields();
break;
case "rename":
this._promptScriptAlias();
break;
case "change_mode":
this._promptScriptMode();
break;
case "duplicate":
this._duplicate();
break;
case "take_control":
this._takeControl();
break;
case "toggle_yaml_mode":
if (this._mode === "gui") {
this._switchYamlMode();
break;
}
this._switchUiMode();
break;
case "delete":
this._deleteConfirm();
break;
case "trace":
this._showTrace();
break;
}
}
static get styles(): CSSResultGroup {
return [
haStyle,
@@ -1268,6 +1245,9 @@ export class HaScriptEditor extends SubscribeMixin(
ha-fab.dirty {
bottom: calc(16px + var(--safe-area-inset-bottom, 0px));
}
li[role="separator"] {
border-bottom-color: var(--divider-color);
}
.header {
display: flex;
margin: 16px 0;
@@ -1281,6 +1261,10 @@ export class HaScriptEditor extends SubscribeMixin(
.header a {
color: var(--secondary-text-color);
}
ha-button-menu a {
text-decoration: none;
color: var(--primary-color);
}
ha-tooltip ha-svg-icon {
width: 12px;
}

View File

@@ -15,19 +15,18 @@ import type { LocalizeKeys } from "../../../common/translations/localize";
import "../../../components/ha-automation-row";
import type { HaAutomationRow } from "../../../components/ha-automation-row";
import "../../../components/ha-card";
import "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
import "../../../components/ha-md-button-menu";
import "../../../components/ha-md-menu-item";
import type { ScriptFieldSidebarConfig } from "../../../data/automation";
import type { Field } from "../../../data/script";
import { SELECTOR_SELECTOR_BUILDING_BLOCKS } from "../../../data/selector/selector_selector";
import { haStyle } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
import { isMac } from "../../../util/is_mac";
import { showToast } from "../../../util/toast";
import { indentStyle, overflowStyles } from "../automation/styles";
import "./ha-script-field-selector-editor";
import type HaScriptFieldSelectorEditor from "./ha-script-field-selector-editor";
import { showToast } from "../../../util/toast";
@customElement("ha-script-field-row")
export default class HaScriptFieldRow extends LitElement {
@@ -80,33 +79,36 @@ export default class HaScriptFieldRow extends LitElement {
.highlight=${this.highlight}
@delete-row=${this._onDelete}
>
<ha-dropdown
<ha-md-button-menu
quick
slot="icons"
@click=${preventDefaultStopPropagation}
@keydown=${stopPropagation}
@wa-select=${this._handleDropdownSelect}
placement="bottom-end"
@closed=${stopPropagation}
positioning="fixed"
anchor-corner="end-end"
menu-corner="start-end"
>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-dropdown-item value="toggle_yaml_mode">
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
<ha-md-menu-item .clickAction=${this._toggleYamlMode}>
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
`ui.panel.config.automation.editor.edit_${!this._yamlMode ? "yaml" : "ui"}`
)}
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
</div>
</ha-dropdown-item>
<ha-dropdown-item
value="delete"
</ha-md-menu-item>
<ha-md-menu-item
.clickAction=${this._onDelete}
.disabled=${this.disabled}
variant="danger"
class="warning"
>
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
@@ -116,6 +118,7 @@ export default class HaScriptFieldRow extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -131,8 +134,8 @@ export default class HaScriptFieldRow extends LitElement {
</span>`
: nothing}
</div>
</ha-dropdown-item>
</ha-dropdown>
</ha-md-menu-item>
</ha-md-button-menu>
<h3 slot="header">${this.key}</h3>
@@ -167,21 +170,27 @@ export default class HaScriptFieldRow extends LitElement {
"ui.panel.config.script.editor.field.selector"
)}
</h3>
<ha-dropdown
<ha-md-button-menu
quick
slot="icons"
@click=${preventDefaultStopPropagation}
@keydown=${stopPropagation}
@wa-select=${this._handleDropdownSelect}
placement="bottom-end"
@closed=${stopPropagation}
positioning="fixed"
anchor-corner="end-end"
menu-corner="start-end"
>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-dropdown-item value="toggle_yaml_mode" selector-row>
<ha-md-menu-item
.clickAction=${this._toggleYamlMode}
selector-row
>
<ha-svg-icon
slot="icon"
slot="start"
.path=${mdiPlaylistEdit}
></ha-svg-icon>
<div class="overflow-label">
@@ -192,13 +201,16 @@ export default class HaScriptFieldRow extends LitElement {
class="shortcut-placeholder ${isMac ? "mac" : ""}"
></span>
</div>
</ha-dropdown-item>
<ha-dropdown-item
value="delete"
</ha-md-menu-item>
<ha-md-menu-item
.clickAction=${this._onDelete}
.disabled=${this.disabled}
variant="danger"
class="warning"
>
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
<ha-svg-icon
slot="start"
.path=${mdiDelete}
></ha-svg-icon>
<div class="overflow-label">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
@@ -208,6 +220,7 @@ export default class HaScriptFieldRow extends LitElement {
<span
>${isMac
? html`<ha-svg-icon
slot="start"
.path=${mdiAppleKeyboardCommand}
></ha-svg-icon>`
: this.hass.localize(
@@ -223,8 +236,8 @@ export default class HaScriptFieldRow extends LitElement {
</span>`
: nothing}
</div>
</ha-dropdown-item>
</ha-dropdown>
</ha-md-menu-item>
</ha-md-button-menu>
</ha-automation-row>
</ha-card>
${typeof this.field.selector === "object" &&
@@ -407,23 +420,6 @@ export default class HaScriptFieldRow extends LitElement {
this._selectorRowElement?.focus();
}
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "toggle_yaml_mode":
this._toggleYamlMode(ev.target as HTMLElement);
break;
case "delete":
this._onDelete();
break;
}
}
static get styles(): CSSResultGroup {
return [
haStyle,
@@ -481,6 +477,9 @@ export default class HaScriptFieldRow extends LitElement {
.selected_menu_item {
color: var(--primary-color);
}
li[role="separator"] {
border-bottom-color: var(--divider-color);
}
.selector-row {
padding-top: 12px;
padding-bottom: 16px;

View File

@@ -1,4 +1,3 @@
import "@home-assistant/webawesome/dist/components/divider/divider";
import {
mdiDotsVertical,
mdiDownload,
@@ -9,19 +8,17 @@ import {
mdiRefresh,
} from "@mdi/js";
import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { repeat } from "lit/directives/repeat";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { formatDateTimeWithSeconds } from "../../../common/datetime/format_date_time";
import { fireEvent } from "../../../common/dom/fire_event";
import { navigate } from "../../../common/navigate";
import "../../../components/ha-button-menu";
import "../../../components/ha-button";
import "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
import "../../../components/ha-icon-button";
import "../../../components/ha-list-item";
import "../../../components/trace/ha-trace-blueprint-config";
import "../../../components/trace/ha-trace-config";
import "../../../components/trace/ha-trace-logbook";
@@ -107,7 +104,7 @@ export class HaScriptTrace extends LitElement {
? html`
<ha-button
class="trace-link"
@click=${this._navigateToScript}
href="/config/script/edit/${this.scriptId}"
slot="toolbar-icon"
appearance="plain"
>
@@ -116,49 +113,64 @@ export class HaScriptTrace extends LitElement {
)}
</ha-button>
`
: nothing}
: ""}
<ha-dropdown
slot="toolbar-icon"
@wa-select=${this._handleDropdownSelect}
>
<ha-button-menu slot="toolbar-icon">
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-dropdown-item .disabled=${!stateObj} value="show_info">
<ha-list-item
graphic="icon"
.disabled=${!stateObj}
@click=${this._showInfo}
>
${this.hass.localize("ui.panel.config.script.editor.show_info")}
<ha-svg-icon
slot="icon"
slot="graphic"
.path=${mdiInformationOutline}
></ha-svg-icon>
</ha-dropdown-item>
</ha-list-item>
${this.narrow && this.scriptId
? html`<ha-dropdown-item value="edit_script">
${this.hass.localize(
"ui.panel.config.script.trace.edit_script"
)}
<ha-svg-icon slot="icon" .path=${mdiPencil}></ha-svg-icon>
</ha-dropdown-item> `
: nothing}
? html`
<a
class="trace-link"
href="/config/script/edit/${this.scriptId}"
>
<ha-list-item graphic="icon">
${this.hass.localize(
"ui.panel.config.script.trace.edit_script"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiPencil}
></ha-svg-icon>
</ha-list-item>
</a>
`
: ""}
<wa-divider></wa-divider>
<li divider role="separator"></li>
<ha-dropdown-item value="refresh">
<ha-list-item graphic="icon" @click=${this._refreshTraces}>
${this.hass.localize("ui.panel.config.automation.trace.refresh")}
<ha-svg-icon slot="icon" .path=${mdiRefresh}></ha-svg-icon>
</ha-dropdown-item>
<ha-svg-icon slot="graphic" .path=${mdiRefresh}></ha-svg-icon>
</ha-list-item>
<ha-dropdown-item .disabled=${!this._trace} value="download_trace">
<ha-list-item
graphic="icon"
.disabled=${!this._trace}
@click=${this._downloadTrace}
>
${this.hass.localize(
"ui.panel.config.automation.trace.download_trace"
)}
<ha-svg-icon slot="icon" .path=${mdiDownload}></ha-svg-icon>
</ha-dropdown-item>
</ha-dropdown>
<ha-svg-icon slot="graphic" .path=${mdiDownload}></ha-svg-icon>
</ha-list-item>
</ha-button-menu>
<div class="toolbar">
${this._traces && this._traces.length > 0
@@ -518,35 +530,6 @@ export class HaScriptTrace extends LitElement {
fireEvent(this, "hass-more-info", { entityId: this._entityId });
}
private _navigateToScript() {
if (this.scriptId) {
navigate(`/config/script/edit/${this.scriptId}`);
}
}
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "show_info":
this._showInfo();
break;
case "refresh":
this._refreshTraces();
break;
case "download_trace":
this._downloadTrace();
break;
case "edit_script":
this._navigateToScript();
break;
}
}
static get styles(): CSSResultGroup {
return [
haStyle,

View File

@@ -215,62 +215,57 @@ export class AssistPipelineDebug extends LitElement {
? html`
<div class="messages">
${messages.map((content) =>
content.role === "system"
? content.content
? html`
<ha-expansion-panel
class="content-expansion ${content.role}"
>
<div slot="header">System</div>
<pre>${content.content}</pre>
</ha-expansion-panel>
`
: nothing
: content.role === "tool_result"
? html`
<ha-expansion-panel
class="content-expansion ${content.role}"
>
<div slot="header">
Result for ${content.tool_name}
</div>
<ha-yaml-editor
read-only
auto-update
.value=${content}
></ha-yaml-editor>
</ha-expansion-panel>
`
: html`
${content.content
? html`
<div class=${`message ${content.role}`}>
${content.content}
</div>
`
: nothing}
${content.role === "assistant" &&
content.tool_calls?.length
? html`
<ha-expansion-panel
class="content-expansion assistant"
>
<span slot="header">
Call
${content.tool_calls.length === 1
? content.tool_calls[0].tool_name
: `${content.tool_calls.length} tools`}
</span>
content.role === "system" || content.role === "tool_result"
? html`
<ha-expansion-panel
class="content-expansion ${content.role}"
>
<div slot="header">
${content.role === "system"
? "System"
: `Result for ${content.tool_name}`}
</div>
${content.role === "system"
? html`<pre>${content.content}</pre>`
: html`
<ha-yaml-editor
read-only
auto-update
.value=${content}
></ha-yaml-editor>
`}
</ha-expansion-panel>
`
: html`
${content.content
? html`
<div class=${`message ${content.role}`}>
${content.content}
</div>
`
: nothing}
${content.role === "assistant" &&
content.tool_calls?.length
? html`
<ha-expansion-panel
class="content-expansion assistant"
>
<span slot="header">
Call
${content.tool_calls.length === 1
? content.tool_calls[0].tool_name
: `${content.tool_calls.length} tools`}
</span>
<ha-yaml-editor
read-only
auto-update
.value=${content.tool_calls}
></ha-yaml-editor>
</ha-expansion-panel>
`
: nothing}
`
<ha-yaml-editor
read-only
auto-update
.value=${content.tool_calls}
></ha-yaml-editor>
</ha-expansion-panel>
`
: nothing}
`
)}
</div>
<div style="clear:both"></div>

View File

@@ -268,10 +268,8 @@ class PanelEnergy extends LitElement {
(source) => source.type === "gas"
);
const hasDeviceConsumption = this._prefs.device_consumption.length > 0;
const views: LovelaceViewConfig[] = [];
if (hasEnergy || hasDeviceConsumption) {
if (hasEnergy) {
views.push(ENERGY_VIEW);
}
if (hasGas) {

View File

@@ -8,6 +8,14 @@ import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strat
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../ha-panel-energy";
const sourceHasCost = (source: Record<string, any>): boolean =>
Boolean(
source.stat_cost ||
source.stat_compensation ||
source.entity_energy_price ||
source.number_energy_price
);
@customElement("energy-overview-view-strategy")
export class EnergyOverviewViewStrategy extends ReactiveElement {
static async generate(
@@ -60,6 +68,13 @@ export class EnergyOverviewViewStrategy extends ReactiveElement {
(source.type === "battery" && source.stat_rate) ||
(source.type === "grid" && source.power?.length)
);
const hasCost = prefs.energy_sources.some(
(source) =>
sourceHasCost(source) ||
(source.type === "grid" &&
(source.flow_from?.some(sourceHasCost) ||
source.flow_to?.some(sourceHasCost)))
);
const overviewSection: LovelaceSectionConfig = {
type: "grid",
@@ -80,7 +95,7 @@ export class EnergyOverviewViewStrategy extends ReactiveElement {
}
view.sections!.push(overviewSection);
if (prefs.energy_sources.length) {
if (hasCost) {
view.sections!.push({
type: "grid",
cards: [

View File

@@ -56,19 +56,6 @@ export function getSuggestedPeriod(
return dayDifference > 35 ? "month" : dayDifference > 2 ? "day" : "hour";
}
function createYAxisLabelFormatter(locale: FrontendLocaleData) {
let previousValue: number | undefined;
return (value: number): string => {
const maximumFractionDigits = Math.max(
1,
-Math.floor(Math.log10(Math.abs(value - (previousValue ?? value) || 1)))
);
previousValue = value;
return formatNumber(value, locale, { maximumFractionDigits });
};
}
export function getCommonOptions(
start: Date,
end: Date,
@@ -99,7 +86,7 @@ export function getCommonOptions(
align: "left",
},
axisLabel: {
formatter: createYAxisLabelFormatter(locale),
formatter: (value: number) => formatNumber(Math.abs(value), locale),
},
splitLine: {
show: true,

View File

@@ -6,10 +6,7 @@ import { classMap } from "lit/directives/class-map";
import "../../../../components/ha-card";
import "../../../../components/ha-svg-icon";
import type { EnergyData, EnergyPreferences } from "../../../../data/energy";
import {
getEnergyDataCollection,
getPowerFromState,
} from "../../../../data/energy";
import { getEnergyDataCollection } from "../../../../data/energy";
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
import type { HomeAssistant } from "../../../../types";
import type { LovelaceCard, LovelaceGridOptions } from "../../types";
@@ -727,7 +724,33 @@ class HuiPowerSankeyCard
// Track this entity for state change detection
this._entities.add(entityId);
return getPowerFromState(this.hass.states[entityId]) ?? 0;
const stateObj = this.hass.states[entityId];
if (!stateObj) {
return 0;
}
const value = parseFloat(stateObj.state);
if (isNaN(value)) {
return 0;
}
// Normalize to kW based on unit of measurement (case-sensitive)
// Supported units: GW, kW, MW, mW, TW, W
const unit = stateObj.attributes.unit_of_measurement;
switch (unit) {
case "W":
return value / 1000;
case "mW":
return value / 1000000;
case "MW":
return value * 1000;
case "GW":
return value * 1000000;
case "TW":
return value * 1000000000;
default:
// Assume kW if no unit or unit is kW
return value;
}
}
/**

View File

@@ -10,10 +10,7 @@ import { LinearGradient } from "../../../../resources/echarts/echarts";
import "../../../../components/chart/ha-chart-base";
import "../../../../components/ha-card";
import type { EnergyData } from "../../../../data/energy";
import {
getEnergyDataCollection,
getPowerFromState,
} from "../../../../data/energy";
import { getEnergyDataCollection } from "../../../../data/energy";
import type { StatisticValue } from "../../../../data/recorder";
import type { FrontendLocaleData } from "../../../../data/translation";
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
@@ -200,7 +197,6 @@ export class HuiPowerSourcesGraphCard
},
};
const now = Date.now();
Object.keys(statIds).forEach((key, keyIndex) => {
if (statIds[key].stats.length) {
const colorHex = computedStyles.getPropertyValue(statIds[key].color);
@@ -208,14 +204,7 @@ export class HuiPowerSourcesGraphCard
// Echarts is supposed to handle that but it is bugged when you use it together with stacking.
// The interpolation breaks the stacking, so this positive/negative is a workaround
const { positive, negative } = this._processData(
statIds[key].stats.map((id: string) => {
const stats = energyData.stats[id] ?? [];
const currentState = getPowerFromState(this.hass.states[id]);
if (currentState !== undefined) {
stats.push({ start: now, end: now, mean: currentState });
}
return stats;
})
statIds[key].stats.map((id: string) => energyData.stats[id] ?? [])
);
datasets.push({
...commonSeriesOptions,

View File

@@ -96,6 +96,8 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
`;
}
const entityState = Number(stateObj.state);
if (stateObj.state === UNAVAILABLE) {
return html`
<hui-warning
@@ -162,7 +164,7 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
.unit_of_measurement ||
""}
style=${styleMap({
"--gauge-color": this._computeSeverity(Number(valueToDisplay)),
"--gauge-color": this._computeSeverity(entityState),
})}
.needle=${this._config!.needle}
.levels=${this._config!.needle ? this._severityLevels() : undefined}

View File

@@ -1,4 +1,5 @@
import type { List } from "@material/mwc-list/mwc-list";
import type { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import {
mdiClock,
mdiDelete,
@@ -17,16 +18,15 @@ import { classMap } from "lit/directives/class-map";
import { repeat } from "lit/directives/repeat";
import memoizeOne from "memoize-one";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import { supportsFeature } from "../../../common/entity/supports-feature";
import { caseInsensitiveStringCompare } from "../../../common/string/compare";
import "../../../components/ha-card";
import "../../../components/ha-check-list-item";
import "../../../components/ha-checkbox";
import "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
import "../../../components/ha-icon-button";
import "../../../components/ha-list";
import "../../../components/ha-list-item";
import "../../../components/ha-markdown-element";
import "../../../components/ha-relative-time";
import "../../../components/ha-select";
@@ -378,29 +378,28 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {
${this._todoListSupportsFeature(
TodoListEntityFeature.DELETE_TODO_ITEM
)
? html`<ha-dropdown
@wa-select=${this._handleCompletedMenuSelect}
placement="bottom-end"
? html`<ha-button-menu
@closed=${stopPropagation}
fixed
@action=${this._handleCompletedMenuAction}
>
<ha-icon-button
slot="trigger"
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-dropdown-item
value="clear"
variant="danger"
>
<ha-list-item graphic="icon" class="warning">
${this.hass!.localize(
"ui.panel.lovelace.cards.todo-list.clear_items"
)}
<ha-svg-icon
class="warning"
slot="icon"
slot="graphic"
.path=${mdiDeleteSweep}
.disabled=${unavailable}
>
</ha-svg-icon>
</ha-dropdown-item>
</ha-dropdown>`
</ha-list-item>
</ha-button-menu>`
: nothing}
</div>`
: nothing}
@@ -414,27 +413,33 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {
`;
}
private _renderMenu(config: TodoListCardConfig, _unavailable: boolean) {
private _renderMenu(config: TodoListCardConfig, unavailable: boolean) {
return (!config.display_order ||
config.display_order === TodoSortMode.NONE) &&
this._todoListSupportsFeature(TodoListEntityFeature.MOVE_TODO_ITEM)
? html`<ha-dropdown
@wa-select=${this._handlePrimaryMenuSelect}
placement="bottom-end"
? html`<ha-button-menu
@closed=${stopPropagation}
fixed
@action=${this._handlePrimaryMenuAction}
>
<ha-icon-button
slot="trigger"
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-dropdown-item value="reorder">
<ha-list-item graphic="icon">
${this.hass!.localize(
this._reordering
? "ui.panel.lovelace.cards.todo-list.exit_reorder_items"
: "ui.panel.lovelace.cards.todo-list.reorder_items"
)}
<ha-svg-icon slot="icon" .path=${mdiSort}> </ha-svg-icon>
</ha-dropdown-item>
</ha-dropdown>`
<ha-svg-icon
slot="graphic"
.path=${mdiSort}
.disabled=${unavailable}
>
</ha-svg-icon>
</ha-list-item>
</ha-button-menu>`
: nothing;
}
@@ -636,11 +641,11 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {
}
}
private _handleCompletedMenuSelect(
ev: CustomEvent<{ item: HaDropdownItem }>
) {
if (ev.detail?.item?.value === "clear") {
this._clearCompletedItems();
private _handleCompletedMenuAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
this._clearCompletedItems();
break;
}
}
@@ -699,9 +704,11 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {
}
}
private _handlePrimaryMenuSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
if (ev.detail?.item?.value === "reorder") {
this._toggleReorder();
private _handlePrimaryMenuAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
this._toggleReorder();
break;
}
}

View File

@@ -23,10 +23,8 @@ import {
subscribeForecast,
weatherAttrIcons,
weatherSVGStyles,
WEATHER_TEMPERATURE_ATTRIBUTES,
} from "../../../data/weather";
import type { HomeAssistant } from "../../../types";
import { round } from "../../../common/number/round";
import { actionHandler } from "../common/directives/action-handler-directive";
import { computeLovelaceEntityName } from "../common/entity/compute-lovelace-entity-name";
import { findEntities } from "../common/find-entities";
@@ -268,20 +266,6 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
this._config.name
);
const temperatureFractionDigits = this._config.round_temperature
? 0
: undefined;
const isSecondaryInfoAttributeTemperature =
this._config?.secondary_info_attribute &&
WEATHER_TEMPERATURE_ATTRIBUTES.has(this._config.secondary_info_attribute);
const isSecondaryInfoNumber =
this._config.secondary_info_attribute &&
!Number.isNaN(
+stateObj.attributes[this._config.secondary_info_attribute]
);
return html`
<ha-card
class=${classMap({
@@ -328,11 +312,7 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
? html`
${formatNumber(
stateObj.attributes.temperature,
this.hass.locale,
{
maximumFractionDigits:
temperatureFractionDigits,
}
this.hass.locale
)}&nbsp;<span
>${getWeatherUnit(
this.hass.config,
@@ -370,26 +350,14 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
: html`
${this.hass.formatEntityAttributeValue(
stateObj,
this._config.secondary_info_attribute,
temperatureFractionDigits === 0 &&
isSecondaryInfoNumber &&
isSecondaryInfoAttributeTemperature
? round(
stateObj.attributes[
this._config
.secondary_info_attribute
],
temperatureFractionDigits
)
: undefined
this._config.secondary_info_attribute
)}
`}
`
: getSecondaryWeatherAttribute(
this.hass,
stateObj,
forecast!,
temperatureFractionDigits
forecast!
)}
</div>
</div>
@@ -457,11 +425,7 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
${this._showValue(item.temperature)
? html`${formatNumber(
item.temperature,
this.hass!.locale,
{
maximumFractionDigits:
temperatureFractionDigits,
}
this.hass!.locale
)}°`
: "—"}
</div>
@@ -469,11 +433,7 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
${this._showValue(item.templow)
? html`${formatNumber(
item.templow!,
this.hass!.locale,
{
maximumFractionDigits:
temperatureFractionDigits,
}
this.hass!.locale
)}°`
: hourly
? ""

View File

@@ -597,7 +597,6 @@ export interface WeatherForecastCardConfig extends LovelaceCardConfig {
forecast_type?: ForecastType;
forecast_slots?: number;
secondary_info_attribute?: keyof TranslationDict["ui"]["card"]["weather"]["attributes"];
round_temperature?: boolean;
theme?: string;
tap_action?: ActionConfig;
hold_action?: ActionConfig;

View File

@@ -21,8 +21,8 @@ export const computeLovelaceEntityName = (
if (!config) {
return stateObj ? computeStateName(stateObj) : "";
}
if (typeof config !== "object") {
return String(config);
if (typeof config === "string") {
return config;
}
if (stateObj) {
return hass.formatEntityName(stateObj, config);

View File

@@ -5,9 +5,10 @@ import { computeStateDomain } from "../../../common/entity/compute_state_domain"
import { computeStateName } from "../../../common/entity/compute_state_name";
import { splitByGroups } from "../../../common/entity/split_by_groups";
import { stripPrefixFromEntityName } from "../../../common/entity/strip_prefix_from_entity_name";
import { orderCompare, stringCompare } from "../../../common/string/compare";
import { stringCompare } from "../../../common/string/compare";
import type { LocalizeFunc } from "../../../common/translations/localize";
import type { AreasDisplayValue } from "../../../components/ha-areas-display-editor";
import { areaCompare } from "../../../data/area_registry";
import type {
EnergyPreferences,
GridSourceTypeEnergyPreference,
@@ -571,21 +572,13 @@ export const generateDefaultViewConfig = (
const areaCards: LovelaceCardConfig[] = [];
const areaIds = Object.keys(areaEntries);
const sortedAreas = Object.keys(splittedByAreaDevice.areasWithEntities).sort(
areaCompare(areaEntries, areasPrefs?.order)
);
if (areasPrefs?.order) {
const areaOrder = areasPrefs.order;
areaIds.sort(orderCompare(areaOrder));
}
for (const areaId of areaIds) {
// Skip areas with no entities
if (!(areaId in splittedByAreaDevice.areasWithEntities)) {
continue;
}
for (const areaId of sortedAreas) {
const areaEntities = splittedByAreaDevice.areasWithEntities[areaId];
const area = areaEntries[areaId];
areaCards.push(
...computeCards(
hass,

View File

@@ -1,4 +1,3 @@
import "@home-assistant/webawesome/dist/components/divider/divider";
import {
mdiContentCopy,
mdiContentCut,
@@ -13,10 +12,9 @@ import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
import "../../../components/ha-button-menu";
import "../../../components/ha-icon-button";
import "../../../components/ha-list-item";
import "../../../components/ha-svg-icon";
import { haStyle } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
@@ -43,6 +41,9 @@ export class HuiCardEditMode extends LitElement {
@property({ type: Boolean, attribute: "no-move" })
public noMove = false;
@state()
public _menuOpened = false;
@state()
public _hover = false;
@@ -90,7 +91,8 @@ export class HuiCardEditMode extends LitElement {
};
protected render(): TemplateResult {
const showOverlay = (this._hover || this._focused) && !this.hiddenOverlay;
const showOverlay =
(this._hover || this._menuOpened || this._focused) && !this.hiddenOverlay;
return html`
<div class="card-wrapper" inert><slot></slot></div>
@@ -113,71 +115,107 @@ export class HuiCardEditMode extends LitElement {
<ha-svg-icon .path=${mdiPencil}> </ha-svg-icon>
</div>
`}
<ha-dropdown
<ha-button-menu
class="more"
placement="bottom-end"
@wa-select=${this._handleDropdownSelect}
corner="BOTTOM_END"
menu-corner="END"
.path=${[this.path!]}
@action=${this._handleAction}
@opened=${this._handleOpened}
@closed=${this._handleClosed}
>
<ha-icon-button slot="trigger" .path=${mdiDotsVertical}>
</ha-icon-button>
${this.noEdit
? nothing
: html`
<ha-dropdown-item value="edit">
<ha-svg-icon slot="icon" .path=${mdiPencil}></ha-svg-icon>
<ha-list-item
graphic="icon"
@click=${this._handleAction}
.action=${"edit"}
>
<ha-svg-icon slot="graphic" .path=${mdiPencil}></ha-svg-icon>
${this.hass.localize(
"ui.panel.lovelace.editor.edit_card.edit"
)}
</ha-dropdown-item>
</ha-list-item>
`}
${this.noDuplicate
? nothing
: html`
<ha-dropdown-item value="duplicate">
<ha-list-item
graphic="icon"
@click=${this._handleAction}
.action=${"duplicate"}
>
<ha-svg-icon
slot="icon"
slot="graphic"
.path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon>
${this.hass.localize(
"ui.panel.lovelace.editor.edit_card.duplicate"
)}
</ha-dropdown-item>
</ha-list-item>
`}
${this.noMove
? nothing
: html`
<ha-dropdown-item value="copy">
<ha-list-item
graphic="icon"
@click=${this._handleAction}
.action=${"copy"}
>
<ha-svg-icon
slot="icon"
slot="graphic"
.path=${mdiContentCopy}
></ha-svg-icon>
${this.hass.localize(
"ui.panel.lovelace.editor.edit_card.copy"
)}
</ha-dropdown-item>
<ha-dropdown-item value="cut">
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon>
</ha-list-item>
<ha-list-item
graphic="icon"
@click=${this._handleAction}
.action=${"cut"}
>
<ha-svg-icon
slot="graphic"
.path=${mdiContentCut}
></ha-svg-icon>
${this.hass.localize(
"ui.panel.lovelace.editor.edit_card.cut"
)}
</ha-dropdown-item>
</ha-list-item>
`}
${this.noDuplicate && this.noEdit && this.noMove
? nothing
: html`<wa-divider></wa-divider>`}
<ha-dropdown-item value="delete" variant="danger">
: html`<li divider role="separator"></li>`}
<ha-list-item
graphic="icon"
class="warning"
@click=${this._handleAction}
.action=${"delete"}
>
${this.hass.localize("ui.panel.lovelace.editor.edit_card.delete")}
<ha-svg-icon
class="warning"
slot="icon"
slot="graphic"
.path=${mdiDelete}
></ha-svg-icon>
</ha-dropdown-item>
</ha-dropdown>
</ha-list-item>
</ha-button-menu>
</div>
`;
}
private _handleOpened() {
this._menuOpened = true;
}
private _handleClosed() {
this._menuOpened = false;
}
private _handleOverlayClick(ev): void {
if (ev.defaultPrevented) {
return;
@@ -190,14 +228,8 @@ export class HuiCardEditMode extends LitElement {
this._editCard();
}
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
private _handleAction(ev) {
switch (ev.currentTarget.action) {
case "edit":
this._editCard();
break;
@@ -298,12 +330,14 @@ export class HuiCardEditMode extends LitElement {
background: var(--secondary-background-color);
--mdc-icon-size: 20px;
}
.more ha-icon-button {
.more {
position: absolute;
right: -6px;
top: -6px;
inset-inline-end: -6px;
inset-inline-start: initial;
}
.more ha-icon-button {
cursor: pointer;
border-radius: var(--ha-border-radius-circle);
background: var(--secondary-background-color);

View File

@@ -1,4 +1,4 @@
import "@home-assistant/webawesome/dist/components/divider/divider";
import type { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import {
mdiContentCopy,
mdiContentCut,
@@ -15,11 +15,10 @@ import { customElement, property, queryAssignedElements } from "lit/decorators";
import { storage } from "../../../common/decorators/storage";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/ha-button";
import "../../../components/ha-button-menu";
import "../../../components/ha-card";
import "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
import "../../../components/ha-icon-button";
import "../../../components/ha-list-item";
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
import { saveConfig } from "../../../data/lovelace/config/types";
import { isStrategyView } from "../../../data/lovelace/config/view";
@@ -133,10 +132,7 @@ export class HuiCardOptions extends LitElement {
></ha-icon-button>
`
: nothing}
<ha-dropdown
@wa-select=${this._handleDropdownSelect}
placement="bottom-end"
>
<ha-button-menu @action=${this._handleAction}>
<ha-icon-button
slot="trigger"
.label=${this.hass!.localize(
@@ -144,46 +140,52 @@ export class HuiCardOptions extends LitElement {
)}
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-dropdown-item value="move">
<ha-list-item graphic="icon">
<ha-svg-icon
slot="icon"
slot="graphic"
.path=${mdiFileMoveOutline}
></ha-svg-icon>
${this.hass!.localize(
"ui.panel.lovelace.editor.edit_card.move"
)}
</ha-dropdown-item>
<ha-dropdown-item value="duplicate">
</ha-list-item>
<ha-list-item graphic="icon">
<ha-svg-icon
slot="icon"
slot="graphic"
.path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon>
${this.hass!.localize(
"ui.panel.lovelace.editor.edit_card.duplicate"
)}
</ha-dropdown-item>
<ha-dropdown-item value="copy">
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
</ha-list-item>
<ha-list-item graphic="icon">
<ha-svg-icon
slot="graphic"
.path=${mdiContentCopy}
></ha-svg-icon>
${this.hass!.localize(
"ui.panel.lovelace.editor.edit_card.copy"
)}
</ha-dropdown-item>
<ha-dropdown-item value="cut">
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon>
</ha-list-item>
<ha-list-item graphic="icon">
<ha-svg-icon
slot="graphic"
.path=${mdiContentCut}
></ha-svg-icon>
${this.hass!.localize("ui.panel.lovelace.editor.edit_card.cut")}
</ha-dropdown-item>
<wa-divider></wa-divider>
<ha-dropdown-item value="delete" variant="danger">
</ha-list-item>
<li divider role="separator"></li>
<ha-list-item class="warning" graphic="icon">
<ha-svg-icon
class="warning"
slot="icon"
slot="graphic"
.path=${mdiDelete}
></ha-svg-icon>
${this.hass!.localize(
"ui.panel.lovelace.editor.edit_card.delete"
)}
</ha-dropdown-item>
</ha-dropdown>
</ha-list-item>
</ha-button-menu>
</div>
</div>
</ha-card>
@@ -242,31 +244,30 @@ export class HuiCardOptions extends LitElement {
ha-icon-button.move-arrow[disabled] {
color: var(--disabled-text-color);
}
ha-list-item {
cursor: pointer;
white-space: nowrap;
}
`,
];
}
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "move":
private _handleAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
this._moveCard();
break;
case "duplicate":
case 1:
this._duplicateCard();
break;
case "copy":
case 2:
this._copyCard();
break;
case "cut":
case 3:
this._cutCard();
break;
case "delete":
case 4:
this._deleteCard();
break;
}

View File

@@ -37,7 +37,6 @@ const cardConfigStruct = assign(
forecast_type: optional(string()),
forecast_slots: optional(number()),
secondary_info_attribute: optional(string()),
round_temperature: optional(boolean()),
tap_action: optional(actionConfigStruct),
hold_action: optional(actionConfigStruct),
double_tap_action: optional(actionConfigStruct),
@@ -157,10 +156,6 @@ export class HuiWeatherForecastCardEditor
},
context: { entity: "entity" },
},
{
name: "round_temperature",
selector: { boolean: {} },
},
{
name: "",
type: "grid",

View File

@@ -5,6 +5,7 @@ import { generateEntityFilter } from "../../../../../common/entity/entity_filter
import { stripPrefixFromEntityName } from "../../../../../common/entity/strip_prefix_from_entity_name";
import { orderCompare } from "../../../../../common/string/compare";
import type { AreaRegistryEntry } from "../../../../../data/area_registry";
import { areaCompare } from "../../../../../data/area_registry";
import type { FloorRegistryEntry } from "../../../../../data/floor_registry";
import type { LovelaceCardConfig } from "../../../../../data/lovelace/config/card";
import type { HomeAssistant } from "../../../../../types";
@@ -286,11 +287,7 @@ export const getAreas = (
? areas.filter((area) => !hiddenAreas!.includes(area.area_id))
: areas.concat();
if (!areasOrder) {
return filteredAreas;
}
const compare = orderCompare(areasOrder);
const compare = areaCompare(entries, areasOrder);
const sortedAreas = filteredAreas.sort((areaA, areaB) =>
compare(areaA.area_id, areaB.area_id)

View File

@@ -1,4 +1,3 @@
import "@home-assistant/webawesome/dist/components/divider/divider";
import { ResizeController } from "@lit-labs/observers/resize-controller";
import {
mdiChevronDown,
@@ -24,10 +23,6 @@ import {
extractSearchParam,
} from "../../common/url/search-params";
import "../../components/ha-button";
import "../../components/ha-button-menu";
import "../../components/ha-dropdown";
import "../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../components/ha-dropdown-item";
import "../../components/ha-fab";
import "../../components/ha-icon-button";
import "../../components/ha-list";
@@ -227,41 +222,47 @@ class PanelTodo extends LitElement {
${this.hass.localize("ui.panel.todo.create_list")}
</ha-list-item>`
: nothing}
<ha-dropdown
slot="actionItems"
@wa-select=${this._handleDropdownSelect}
>
<ha-button-menu slot="actionItems">
<ha-icon-button
slot="trigger"
.label=${""}
.path=${mdiDotsVertical}
></ha-icon-button>
${this._conversation(this.hass.config.components)
? html`<ha-dropdown-item value="info" .disabled=${!this._entityId}>
<ha-svg-icon .path=${mdiInformationOutline} slot="icon">
? html`<ha-list-item
graphic="icon"
@click=${this._showMoreInfoDialog}
.disabled=${!this._entityId}
>
<ha-svg-icon .path=${mdiInformationOutline} slot="graphic">
</ha-svg-icon>
${this.hass.localize("ui.panel.todo.information")}
</ha-dropdown-item>`
</ha-list-item>`
: nothing}
<wa-divider></wa-divider>
<ha-dropdown-item value="assist">
<ha-svg-icon .path=${mdiCommentProcessingOutline} slot="icon">
<li divider role="separator"></li>
<ha-list-item graphic="icon" @click=${this._showVoiceCommandDialog}>
<ha-svg-icon .path=${mdiCommentProcessingOutline} slot="graphic">
</ha-svg-icon>
${this.hass.localize("ui.panel.todo.assist")}
</ha-dropdown-item>
</ha-list-item>
${entityRegistryEntry?.platform === "local_todo"
? html` <wa-divider></wa-divider>
<ha-dropdown-item
value="delete"
variant="danger"
? html` <li divider role="separator"></li>
<ha-list-item
graphic="icon"
@click=${this._deleteList}
class="warning"
.disabled=${!this._entityId}
>
<ha-svg-icon .path=${mdiDelete} slot="icon" class="warning">
<ha-svg-icon
.path=${mdiDelete}
slot="graphic"
class="warning"
>
</ha-svg-icon>
${this.hass.localize("ui.panel.todo.delete_list")}
</ha-dropdown-item>`
</ha-list-item>`
: nothing}
</ha-dropdown>
</ha-button-menu>
<div id="columns">
<div class="column">
${this._entityId
@@ -362,26 +363,6 @@ class PanelTodo extends LitElement {
showTodoItemEditDialog(this, { entity: this._entityId! });
}
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
return;
}
switch (action) {
case "info":
this._showMoreInfoDialog();
break;
case "assist":
this._showVoiceCommandDialog();
break;
case "delete":
this._deleteList();
break;
}
}
static get styles(): CSSResultGroup {
return [
haStyle,

View File

@@ -304,7 +304,6 @@
},
"weather": {
"attributes": {
"dew_point": "Dew point",
"air_pressure": "Air pressure",
"humidity": "Humidity",
"temperature": "Temperature",
@@ -831,6 +830,7 @@
"add_new": "Add new area…",
"no_areas": "No areas available",
"no_match": "No areas found for {term}",
"unassigned_areas": "Unassigned areas",
"failed_create_area": "Failed to create area."
},
"floor-picker": {
@@ -2463,7 +2463,7 @@
"introduction2": "To place devices in an area, use the link below to navigate to the integrations page and then click on a configured integration to get to the device cards.",
"integrations_page": "Integrations page",
"no_areas": "Looks like you have no areas yet!",
"other_areas": "Other areas",
"unassigned_areas": "Unassigned areas",
"create_area": "Create area",
"create_floor": "Create floor",
"floor": {
@@ -2475,12 +2475,11 @@
"area_reorder_failed": "Failed to reorder areas",
"area_move_failed": "Failed to move area",
"floor_reorder_failed": "Failed to reorder floors",
"reorder": "Reorder"
"reorder": "Reorder floors and areas"
},
"dialog": {
"reorder_areas_title": "Reorder areas",
"reorder_floors_areas_title": "Reorder floors and areas",
"other_areas": "Other areas",
"reorder_title": "Reorder floors and areas",
"unassigned_areas": "Unassigned areas",
"reorder_failed": "Failed to save order",
"empty_floor": "No areas on this floor",
"empty_unassigned": "All your areas are assigned to floors"
@@ -8027,7 +8026,6 @@
"suggested_cards": "Suggested cards",
"other_cards": "Other cards",
"custom_cards": "Custom cards",
"round_temperature": "Round temperature",
"features": "Features",
"actions": "Actions",
"content": "Content"

100
yarn.lock
View File

@@ -1940,9 +1940,9 @@ __metadata:
languageName: node
linkType: hard
"@home-assistant/webawesome@npm:3.0.0-ha.1":
version: 3.0.0-ha.1
resolution: "@home-assistant/webawesome@npm:3.0.0-ha.1"
"@home-assistant/webawesome@npm:3.0.0-ha.0":
version: 3.0.0-ha.0
resolution: "@home-assistant/webawesome@npm:3.0.0-ha.0"
dependencies:
"@ctrl/tinycolor": "npm:4.1.0"
"@floating-ui/dom": "npm:^1.6.13"
@@ -1953,7 +1953,7 @@ __metadata:
lit: "npm:^3.2.1"
nanoid: "npm:^5.1.5"
qr-creator: "npm:^1.0.0"
checksum: 10/281f16c2c6c28d95a381de6fca05948a9c67d8184f20844d64ce33dc2caf9e6761d2cf8337b97e7487a71be011ab04f2a021b20b823a20e3c049cc68205de86a
checksum: 10/2034d498d5b26bb0573ebc2c9aadd144604bb48c04becbae0c67b16857d8e5d6562626e795974362c3fc41e9b593a9005595d8b5ff434b1569b2d724af13043b
languageName: node
linkType: hard
@@ -3893,22 +3893,22 @@ __metadata:
languageName: node
linkType: hard
"@rsdoctor/client@npm:1.3.12":
version: 1.3.12
resolution: "@rsdoctor/client@npm:1.3.12"
checksum: 10/3abd14af2bd9a34da2199bad3a9c9aef34381beebebf3d0aceb90e4e9dc9ea19e804372bcdb8e082888dedb268fe544b658537c510df7ae9160df822ced9712f
"@rsdoctor/client@npm:1.3.11":
version: 1.3.11
resolution: "@rsdoctor/client@npm:1.3.11"
checksum: 10/17b769e8b6ae23e508816be05fb5b9ab235bd4380f2bc3ff7a8add8ae088eee0663627a637d695f304841fb708f19b245ab20a4924f3b0b9f35e341649028490
languageName: node
linkType: hard
"@rsdoctor/core@npm:1.3.12":
version: 1.3.12
resolution: "@rsdoctor/core@npm:1.3.12"
"@rsdoctor/core@npm:1.3.11":
version: 1.3.11
resolution: "@rsdoctor/core@npm:1.3.11"
dependencies:
"@rsbuild/plugin-check-syntax": "npm:1.5.0"
"@rsdoctor/graph": "npm:1.3.12"
"@rsdoctor/sdk": "npm:1.3.12"
"@rsdoctor/types": "npm:1.3.12"
"@rsdoctor/utils": "npm:1.3.12"
"@rsdoctor/graph": "npm:1.3.11"
"@rsdoctor/sdk": "npm:1.3.11"
"@rsdoctor/types": "npm:1.3.11"
"@rsdoctor/utils": "npm:1.3.11"
browserslist-load-config: "npm:^1.0.1"
enhanced-resolve: "npm:5.12.0"
es-toolkit: "npm:^1.41.0"
@@ -3916,59 +3916,59 @@ __metadata:
fs-extra: "npm:^11.1.1"
semver: "npm:^7.7.3"
source-map: "npm:^0.7.6"
checksum: 10/fda3d1a0acb0e57888a5bbe28649c1b439951e915d6411982116bdaabb72185d4f339009fd748681fe1c9c341c3c46fad597b587d8c3273b2024d682f6b40aca
checksum: 10/1e11f76f00ef4148743c5b5ef8dcefdc57f658ffefcf6de6455888b4546000ebf6a9d03623734bbb7e3b5cd619bb460c668c38e6ffa0f747890018bbc3fc005a
languageName: node
linkType: hard
"@rsdoctor/graph@npm:1.3.12":
version: 1.3.12
resolution: "@rsdoctor/graph@npm:1.3.12"
"@rsdoctor/graph@npm:1.3.11":
version: 1.3.11
resolution: "@rsdoctor/graph@npm:1.3.11"
dependencies:
"@rsdoctor/types": "npm:1.3.12"
"@rsdoctor/utils": "npm:1.3.12"
"@rsdoctor/types": "npm:1.3.11"
"@rsdoctor/utils": "npm:1.3.11"
es-toolkit: "npm:^1.41.0"
path-browserify: "npm:1.0.1"
source-map: "npm:^0.7.6"
checksum: 10/38b07882c0e90fc9a97c68ee571f7b649c454624d403e8745ef85b6c2ad2c698f922aa1fb313a23cc1ceadfe391f48b23d381f73cf089ab5762a14a095dd26f3
checksum: 10/e32444685f98cad184eb9d07bc9ebd4a3668c6dfaa352ce6912cabc4cdc63bdf15efc57a3d145b35e14724215230bd6657b7b8de30aa2c674a64c2c7983a3739
languageName: node
linkType: hard
"@rsdoctor/rspack-plugin@npm:1.3.12":
version: 1.3.12
resolution: "@rsdoctor/rspack-plugin@npm:1.3.12"
"@rsdoctor/rspack-plugin@npm:1.3.11":
version: 1.3.11
resolution: "@rsdoctor/rspack-plugin@npm:1.3.11"
dependencies:
"@rsdoctor/core": "npm:1.3.12"
"@rsdoctor/graph": "npm:1.3.12"
"@rsdoctor/sdk": "npm:1.3.12"
"@rsdoctor/types": "npm:1.3.12"
"@rsdoctor/utils": "npm:1.3.12"
"@rsdoctor/core": "npm:1.3.11"
"@rsdoctor/graph": "npm:1.3.11"
"@rsdoctor/sdk": "npm:1.3.11"
"@rsdoctor/types": "npm:1.3.11"
"@rsdoctor/utils": "npm:1.3.11"
peerDependencies:
"@rspack/core": "*"
peerDependenciesMeta:
"@rspack/core":
optional: true
checksum: 10/2825034e62d89d9e9eeb5d4fa68ab3b19a12ee6b270fb103fd694e649dcaccfe08016879271c5d2cd2ee961179df43e9b124f965d2933b24dfaadc98441c2cc3
checksum: 10/973626b9387d85814839c7a8f08b21b60da079c4f755dd5ada5fae8aba61910d27ead333c35d3f9154ba71253c420bed43481bd68c09dae618d42f47a6d8c080
languageName: node
linkType: hard
"@rsdoctor/sdk@npm:1.3.12":
version: 1.3.12
resolution: "@rsdoctor/sdk@npm:1.3.12"
"@rsdoctor/sdk@npm:1.3.11":
version: 1.3.11
resolution: "@rsdoctor/sdk@npm:1.3.11"
dependencies:
"@rsdoctor/client": "npm:1.3.12"
"@rsdoctor/graph": "npm:1.3.12"
"@rsdoctor/types": "npm:1.3.12"
"@rsdoctor/utils": "npm:1.3.12"
"@rsdoctor/client": "npm:1.3.11"
"@rsdoctor/graph": "npm:1.3.11"
"@rsdoctor/types": "npm:1.3.11"
"@rsdoctor/utils": "npm:1.3.11"
safer-buffer: "npm:2.1.2"
socket.io: "npm:4.8.1"
tapable: "npm:2.2.3"
checksum: 10/5b60197500b73b1ae671565a2c2886a40df36fcf0e7277457840f569cb845d2c725fbfe43a4f6496ac5a26d749e0249e1a2a79bdb582a0fe16beeaa9bea0cdaa
checksum: 10/6e98b51259178af26cad8852a2ac4f35a404a34978d60262f6100320b02c47fdcd11df2080c384d765eca28edfed65c8f0e1b49bf6cdac462d5c0cff0ec599f9
languageName: node
linkType: hard
"@rsdoctor/types@npm:1.3.12":
version: 1.3.12
resolution: "@rsdoctor/types@npm:1.3.12"
"@rsdoctor/types@npm:1.3.11":
version: 1.3.11
resolution: "@rsdoctor/types@npm:1.3.11"
dependencies:
"@types/connect": "npm:3.4.38"
"@types/estree": "npm:1.0.5"
@@ -3982,16 +3982,16 @@ __metadata:
optional: true
webpack:
optional: true
checksum: 10/1d8891362056332289dfefd22a9ab0daf977ef8d82cffa1bc2abf507423ddf628242188b826bcf34e8fd908bb1b22f093c840675e77593a7f93e671e91261c5f
checksum: 10/383cc2182737d6a9ab49a43f826bc16c5a2688f1135fe44586b835153c24fcae1bac5ea16683d252df91ec269608aa8e8926c07b0ed17f3d954e7a17d30a4650
languageName: node
linkType: hard
"@rsdoctor/utils@npm:1.3.12":
version: 1.3.12
resolution: "@rsdoctor/utils@npm:1.3.12"
"@rsdoctor/utils@npm:1.3.11":
version: 1.3.11
resolution: "@rsdoctor/utils@npm:1.3.11"
dependencies:
"@babel/code-frame": "npm:7.26.2"
"@rsdoctor/types": "npm:1.3.12"
"@rsdoctor/types": "npm:1.3.11"
"@types/estree": "npm:1.0.5"
acorn: "npm:^8.10.0"
acorn-import-attributes: "npm:^1.9.5"
@@ -4005,7 +4005,7 @@ __metadata:
picocolors: "npm:^1.1.1"
rslog: "npm:^1.2.11"
strip-ansi: "npm:^6.0.1"
checksum: 10/24d46e84abf2f85514f9feabb44db9b7a1e9e161052c8cdb8edc561db797928fd0d257249e1fb228f2b9346dc446e0ec59e03d46307f5b1e4bf5554a73274ee6
checksum: 10/f5d1bc0dbfc39753adbe2ee89d524a97d7568c6e1ad8059e479993fdb79e4b753e6cfb714bf641e5f336071215cb76c9b965a2ea004944d6c137e60d8285a1e7
languageName: node
linkType: hard
@@ -9213,7 +9213,7 @@ __metadata:
"@fullcalendar/list": "npm:6.1.19"
"@fullcalendar/luxon3": "npm:6.1.19"
"@fullcalendar/timegrid": "npm:6.1.19"
"@home-assistant/webawesome": "npm:3.0.0-ha.1"
"@home-assistant/webawesome": "npm:3.0.0-ha.0"
"@lezer/highlight": "npm:1.2.3"
"@lit-labs/motion": "npm:1.0.9"
"@lit-labs/observers": "npm:2.0.6"
@@ -9250,7 +9250,7 @@ __metadata:
"@octokit/plugin-retry": "npm:8.0.3"
"@octokit/rest": "npm:22.0.1"
"@replit/codemirror-indentation-markers": "npm:6.5.3"
"@rsdoctor/rspack-plugin": "npm:1.3.12"
"@rsdoctor/rspack-plugin": "npm:1.3.11"
"@rspack/core": "npm:1.6.5"
"@rspack/dev-server": "npm:1.1.4"
"@swc/helpers": "npm:0.5.17"