Compare commits

...
Author SHA1 Message Date
Petar Petrov 6d11fbe250 Turn the category group menu into a context element
The menu stored hass on its dropdown for its handlers; it now consumes localize and the API context.
2026-10-28 10:27:20 +02:00
6 changed files with 119 additions and 90 deletions

No files matched your search

@@ -566,7 +566,6 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
@click=${this._showHelp}
></ha-icon-button>
${renderCategoryGroupActions(
this.hass,
"automation",
this._categories,
this._tableSettings.grouping,
@@ -0,0 +1,116 @@
import type { ContextType } from "@lit/context";
import { mdiDelete, mdiDotsVertical, mdiPencil } from "@mdi/js";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { consume } from "../../../common/decorators/consume";
import { consumeLocalize } from "../../../common/decorators/consume-context-entry";
import type { LocalizeFunc } from "../../../common/translations/localize";
import "../../../components/ha-dropdown";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import "../../../components/ha-icon-button";
import "../../../components/ha-svg-icon";
import type { CategoryRegistryEntry } from "../../../data/category_registry";
import {
deleteCategoryRegistryEntry,
updateCategoryRegistryEntry,
} from "../../../data/category_registry";
import { apiContext } from "../../../data/context";
import {
showAlertDialog,
showConfirmationDialog,
} from "../../../dialogs/generic/show-dialog-box";
import { showCategoryRegistryDetailDialog } from "./show-dialog-category-registry-detail";
@customElement("ha-category-group-actions")
export class HaCategoryGroupActions extends LitElement {
@property({ attribute: false }) public category?: CategoryRegistryEntry;
@property() public scope?: string;
@property({ attribute: false }) public onDeleted?: (
categoryId: string
) => void;
@state()
@consumeLocalize()
private _localize!: LocalizeFunc;
@consume({ context: apiContext, subscribe: true })
private _api!: ContextType<typeof apiContext>;
protected render() {
if (!this.category) {
return nothing;
}
return html`<ha-dropdown @wa-select=${this._handleAction}>
<ha-icon-button
slot="trigger"
.label=${this._localize("ui.common.overflow_menu_for", {
name: this.category.name,
})}
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-dropdown-item value="edit">
<ha-svg-icon slot="icon" .path=${mdiPencil}></ha-svg-icon>
${this._localize("ui.panel.config.category.editor.edit")}
</ha-dropdown-item>
<ha-dropdown-item value="delete" variant="danger">
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
${this._localize("ui.panel.config.category.editor.delete")}
</ha-dropdown-item>
</ha-dropdown>`;
}
private _handleAction(ev: HaDropdownSelectEvent<"edit" | "delete">) {
if (ev.detail.item.value === "delete") {
this._deleteCategory();
return;
}
const category = this.category!;
const scope = this.scope!;
showCategoryRegistryDetailDialog(this, {
scope,
entry: category,
updateEntry: (updates) =>
updateCategoryRegistryEntry(
this._api.callWS,
scope,
category.category_id,
updates
),
});
}
private async _deleteCategory() {
const category = this.category!;
const scope = this.scope!;
const confirmed = await showConfirmationDialog(this, {
title: this._localize("ui.panel.config.category.editor.confirm_delete"),
text: this._localize(
"ui.panel.config.category.editor.confirm_delete_text"
),
confirmText: this._localize("ui.common.delete"),
destructive: true,
});
if (!confirmed) {
return;
}
try {
await deleteCategoryRegistryEntry(
this._api.callWS,
scope,
category.category_id
);
this.onDeleted?.(category.category_id);
} catch (err: any) {
showAlertDialog(this, { text: err.message });
}
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-category-group-actions": HaCategoryGroupActions;
}
}
@@ -1,79 +1,14 @@
import { mdiDelete, mdiDotsVertical, mdiPencil } from "@mdi/js";
import { html, nothing } from "lit";
import { repeat } from "lit/directives/repeat";
import { groupActionsSlot } from "../../../components/data-table/ha-data-table";
import "../../../components/ha-dropdown";
import type {
HaDropdown,
HaDropdownSelectEvent,
} from "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import "../../../components/ha-icon-button";
import "../../../components/ha-svg-icon";
import type { CategoryRegistryEntry } from "../../../data/category_registry";
import {
deleteCategoryRegistryEntry,
updateCategoryRegistryEntry,
} from "../../../data/category_registry";
import {
showAlertDialog,
showConfirmationDialog,
} from "../../../dialogs/generic/show-dialog-box";
import type { HomeAssistant } from "../../../types";
import { showCategoryRegistryDetailDialog } from "./show-dialog-category-registry-detail";
type CategoryMenu = HaDropdown & {
category: CategoryRegistryEntry;
scope: string;
hass: HomeAssistant;
onDeleted: (categoryId: string) => void;
};
const deleteCategory = async (menu: CategoryMenu) => {
const { category, scope, hass, onDeleted } = menu;
const confirmed = await showConfirmationDialog(menu, {
title: hass.localize("ui.panel.config.category.editor.confirm_delete"),
text: hass.localize("ui.panel.config.category.editor.confirm_delete_text"),
confirmText: hass.localize("ui.common.delete"),
destructive: true,
});
if (!confirmed) {
return;
}
try {
await deleteCategoryRegistryEntry(hass.callWS, scope, category.category_id);
onDeleted(category.category_id);
} catch (err: any) {
showAlertDialog(menu, { text: err.message });
}
};
const handleAction = (ev: HaDropdownSelectEvent<"edit" | "delete">) => {
const menu = ev.currentTarget as CategoryMenu;
if (ev.detail.item.value === "delete") {
deleteCategory(menu);
return;
}
const { category, scope, hass } = menu;
showCategoryRegistryDetailDialog(menu, {
scope,
entry: category,
updateEntry: (updates) =>
updateCategoryRegistryEntry(
hass.callWS,
scope,
category.category_id,
updates
),
});
};
import "./ha-category-group-actions";
// The same edit and delete menu as the categories filter, on the category
// group headers of a table grouped by category, which names its groups after
// the categories. Other groupings can have groups with the same names.
// onDeleted lets the page take a deleted category out of its filter.
export const renderCategoryGroupActions = (
hass: HomeAssistant,
scope: string,
categories: CategoryRegistryEntry[] | undefined,
grouping: string | undefined,
@@ -84,29 +19,11 @@ export const renderCategoryGroupActions = (
categories ?? [],
(category) => category.category_id,
(category) =>
html`<ha-dropdown
html`<ha-category-group-actions
slot=${groupActionsSlot(category.name)}
.category=${category}
.scope=${scope}
.hass=${hass}
.onDeleted=${onDeleted}
@wa-select=${handleAction}
>
<ha-icon-button
slot="trigger"
.label=${hass.localize("ui.common.overflow_menu_for", {
name: category.name,
})}
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-dropdown-item value="edit">
<ha-svg-icon slot="icon" .path=${mdiPencil}></ha-svg-icon>
${hass.localize("ui.panel.config.category.editor.edit")}
</ha-dropdown-item>
<ha-dropdown-item value="delete" variant="danger">
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
${hass.localize("ui.panel.config.category.editor.delete")}
</ha-dropdown-item>
</ha-dropdown>`
></ha-category-group-actions>`
)
: nothing;
@@ -881,7 +881,6 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
slot="toolbar-icon"
></ha-integration-overflow-menu>
${renderCategoryGroupActions(
this.hass,
"helpers",
this._categories,
this._tableSettings.grouping,
@@ -564,7 +564,6 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
.path=${mdiHelpCircleOutline}
></ha-icon-button>
${renderCategoryGroupActions(
this.hass,
"scene",
this._categories,
this._tableSettings.grouping,
@@ -498,7 +498,6 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
@click=${this._showHelp}
></ha-icon-button>
${renderCategoryGroupActions(
this.hass,
"script",
this._categories,
this._tableSettings.grouping,