mirror of
https://github.com/home-assistant/frontend.git
synced 2026-05-21 16:47:50 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cb84e3d608 | |||
| bbdde15a5c | |||
| 0b025293be | |||
| 1772dbf421 | |||
| 1f4dee984e | |||
| 74e312f529 |
@@ -5,7 +5,10 @@ export interface DataTableFilter {
|
||||
|
||||
export type DataTableFilters = Record<string, DataTableFilter>;
|
||||
|
||||
export type DataTableFiltersValue = string[] | { key: string[] } | undefined;
|
||||
export type DataTableFiltersValue =
|
||||
| string[]
|
||||
| Record<"key" | string, string[]>
|
||||
| undefined;
|
||||
|
||||
export type DataTableFiltersValues = Record<string, DataTableFiltersValue>;
|
||||
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
import { consume } from "@lit/context";
|
||||
import { mdiDelete, mdiDotsVertical, mdiImagePlus, mdiPencil } from "@mdi/js";
|
||||
import {
|
||||
mdiDelete,
|
||||
mdiDevices,
|
||||
mdiDotsVertical,
|
||||
mdiImagePlus,
|
||||
mdiPalette,
|
||||
mdiPencil,
|
||||
mdiRobot,
|
||||
mdiScriptText,
|
||||
mdiShape,
|
||||
mdiTools,
|
||||
} from "@mdi/js";
|
||||
import type { HassEntity } from "home-assistant-js-websocket/dist/types";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
@@ -10,7 +21,7 @@ import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { computeDeviceNameDisplay } from "../../../common/entity/compute_device_name";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { goBack } from "../../../common/navigate";
|
||||
import { goBack, navigate } from "../../../common/navigate";
|
||||
import { caseInsensitiveStringCompare } from "../../../common/string/compare";
|
||||
import { slugify } from "../../../common/string/slugify";
|
||||
import { groupBy } from "../../../common/util/group-by";
|
||||
@@ -18,6 +29,7 @@ import { afterNextRender } from "../../../common/util/render-status";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-dropdown";
|
||||
import type { HASSDomCurrentTargetEvent } from "../../../common/dom/fire_event";
|
||||
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import "../../../components/ha-icon-button";
|
||||
@@ -48,6 +60,7 @@ import "../../../layouts/hass-error-screen";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { isHelperDomain } from "../helpers/const";
|
||||
import "../../logbook/ha-logbook";
|
||||
import {
|
||||
loadAreaRegistryDetailDialog,
|
||||
@@ -59,6 +72,58 @@ declare interface NameAndEntity<EntityType extends HassEntity> {
|
||||
entity: EntityType;
|
||||
}
|
||||
|
||||
type AreaQuickLinkKey =
|
||||
| "devices"
|
||||
| "entities"
|
||||
| "helpers"
|
||||
| "automations"
|
||||
| "scenes"
|
||||
| "scripts";
|
||||
|
||||
const NAVIGATION_ACTIONS: {
|
||||
value: string;
|
||||
path: string;
|
||||
icon: string;
|
||||
countKey: AreaQuickLinkKey;
|
||||
}[] = [
|
||||
{
|
||||
value: "navigate-devices",
|
||||
path: "/config/devices/dashboard",
|
||||
icon: mdiDevices,
|
||||
countKey: "devices",
|
||||
},
|
||||
{
|
||||
value: "navigate-entities",
|
||||
path: "/config/entities",
|
||||
icon: mdiShape,
|
||||
countKey: "entities",
|
||||
},
|
||||
{
|
||||
value: "navigate-helpers",
|
||||
path: "/config/helpers",
|
||||
icon: mdiTools,
|
||||
countKey: "helpers",
|
||||
},
|
||||
{
|
||||
value: "navigate-automations",
|
||||
path: "/config/automation/dashboard",
|
||||
icon: mdiRobot,
|
||||
countKey: "automations",
|
||||
},
|
||||
{
|
||||
value: "navigate-scenes",
|
||||
path: "/config/scene/dashboard",
|
||||
icon: mdiPalette,
|
||||
countKey: "scenes",
|
||||
},
|
||||
{
|
||||
value: "navigate-scripts",
|
||||
path: "/config/script/dashboard",
|
||||
icon: mdiScriptText,
|
||||
countKey: "scripts",
|
||||
},
|
||||
] as const;
|
||||
|
||||
@customElement("ha-config-area-page")
|
||||
class HaConfigAreaPage extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -128,6 +193,31 @@ class HaConfigAreaPage extends LitElement {
|
||||
.concat(memberships.indirectEntities.map((entry) => entry.entity_id))
|
||||
);
|
||||
|
||||
private _getQuickLinkCounts = memoizeOne(
|
||||
(
|
||||
memberships: {
|
||||
devices: DeviceRegistryEntry[];
|
||||
entities: EntityRegistryEntry[];
|
||||
indirectEntities: EntityRegistryEntry[];
|
||||
},
|
||||
related?: RelatedResult
|
||||
) => {
|
||||
const allEntityIds = this._allEntities(memberships);
|
||||
const entityIds = related?.entity ?? allEntityIds;
|
||||
|
||||
return {
|
||||
devices: related?.device?.length ?? memberships.devices.length,
|
||||
entities: entityIds.length,
|
||||
helpers: entityIds.filter((entityId) =>
|
||||
isHelperDomain(computeDomain(entityId))
|
||||
).length,
|
||||
automations: related?.automation?.length ?? 0,
|
||||
scenes: related?.scene?.length ?? 0,
|
||||
scripts: related?.script?.length ?? 0,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues<this>) {
|
||||
super.firstUpdated(changedProps);
|
||||
loadAreaRegistryDetailDialog();
|
||||
@@ -162,6 +252,10 @@ class HaConfigAreaPage extends LitElement {
|
||||
this._entityReg
|
||||
);
|
||||
const { devices, entities } = memberships;
|
||||
const quickLinkCounts = this._getQuickLinkCounts(
|
||||
memberships,
|
||||
this._related
|
||||
);
|
||||
|
||||
// Pre-compute the entity and device names, so we can sort by them
|
||||
if (devices) {
|
||||
@@ -238,6 +332,21 @@ class HaConfigAreaPage extends LitElement {
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
|
||||
${NAVIGATION_ACTIONS.map(
|
||||
(action) => html`
|
||||
<ha-dropdown-item value=${action.value}>
|
||||
<ha-svg-icon slot="icon" .path=${action.icon}></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.areas.quick_links.${action.countKey}`,
|
||||
{ count: quickLinkCounts[action.countKey] }
|
||||
)}
|
||||
<ha-icon-next slot="details"></ha-icon-next>
|
||||
</ha-dropdown-item>
|
||||
`
|
||||
)}
|
||||
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
<ha-dropdown-item value="edit" .data=${area}>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPencil}> </ha-svg-icon>
|
||||
${this.hass.localize("ui.panel.config.areas.edit_settings")}
|
||||
@@ -609,9 +718,18 @@ class HaConfigAreaPage extends LitElement {
|
||||
this._related = await findRelated(this.hass, "area", this.areaId);
|
||||
}
|
||||
|
||||
private _handleMenuAction(ev: HaDropdownSelectEvent) {
|
||||
private _handleMenuAction(
|
||||
ev: HaDropdownSelectEvent<string, AreaRegistryEntry>
|
||||
) {
|
||||
const action = ev.detail?.item?.value;
|
||||
const entry = (ev.detail?.item as any)?.data as AreaRegistryEntry;
|
||||
const entry = ev.detail?.item?.data;
|
||||
|
||||
const navAction = NAVIGATION_ACTIONS.find((a) => a.value === action);
|
||||
if (navAction) {
|
||||
navigate(`${navAction.path}?historyBack=1&area=${this.areaId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "edit":
|
||||
this._openDialog(entry);
|
||||
@@ -622,15 +740,19 @@ class HaConfigAreaPage extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _showSettings(ev: MouseEvent) {
|
||||
const entry: AreaRegistryEntry = (ev.currentTarget! as any).entry;
|
||||
this._openDialog(entry);
|
||||
private _showSettings(
|
||||
ev: HASSDomCurrentTargetEvent<
|
||||
HTMLButtonElement & { entry: AreaRegistryEntry }
|
||||
>
|
||||
) {
|
||||
this._openDialog(ev.currentTarget.entry);
|
||||
}
|
||||
|
||||
private _openEntity(ev) {
|
||||
const entry: EntityRegistryEntry = (ev.currentTarget as any).entity;
|
||||
private _openEntity(
|
||||
ev: HASSDomCurrentTargetEvent<HTMLElement & { entity: EntityRegistryEntry }>
|
||||
) {
|
||||
showMoreInfoDialog(this, {
|
||||
entityId: entry.entity_id,
|
||||
entityId: ev.currentTarget.entity.entity_id,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -768,10 +768,15 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
|
||||
super.willUpdate(changedProps);
|
||||
if (!this.hasUpdated) {
|
||||
const hasUrlFilter =
|
||||
this._searchParms.has("blueprint") || this._searchParms.has("label");
|
||||
this._searchParms.has("area") ||
|
||||
this._searchParms.has("blueprint") ||
|
||||
this._searchParms.has("label");
|
||||
if (!hasUrlFilter) {
|
||||
this._filters = this._storageFilters;
|
||||
}
|
||||
if (this._searchParms.has("area")) {
|
||||
this._filterArea();
|
||||
}
|
||||
if (this._searchParms.has("blueprint")) {
|
||||
this._filterBlueprint();
|
||||
}
|
||||
@@ -871,6 +876,22 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
|
||||
this._filteredEntityIds = filteredEntityIds;
|
||||
}
|
||||
|
||||
private _filterArea() {
|
||||
const area = this._searchParms.get("area");
|
||||
if (!area) {
|
||||
return;
|
||||
}
|
||||
this._fromUrl = true;
|
||||
this._filters = {
|
||||
...this._filters,
|
||||
"ha-filter-floor-areas": {
|
||||
value: { areas: [area] },
|
||||
items: undefined,
|
||||
},
|
||||
};
|
||||
this._applyFilters();
|
||||
}
|
||||
|
||||
private _filterLabel() {
|
||||
const label = this._searchParms.get("label");
|
||||
if (!label) {
|
||||
|
||||
@@ -249,12 +249,13 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
}
|
||||
|
||||
private _setFiltersFromUrl() {
|
||||
const area = this._searchParms.get("area");
|
||||
const domain = this._searchParms.get("domain");
|
||||
const configEntry = this._searchParms.get("config_entry");
|
||||
const subEntry = this._searchParms.get("sub_entry");
|
||||
const label = this._searchParms.has("label");
|
||||
|
||||
if (!domain && !configEntry && !label) {
|
||||
if (!area && !domain && !configEntry && !label) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -269,6 +270,10 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
],
|
||||
items: undefined,
|
||||
},
|
||||
"ha-filter-floor-areas": {
|
||||
value: area ? { areas: [area] } : undefined,
|
||||
items: undefined,
|
||||
},
|
||||
"ha-filter-integrations": {
|
||||
value: domain ? [domain] : [],
|
||||
items: undefined,
|
||||
|
||||
@@ -1092,6 +1092,7 @@ export class HaConfigEntities extends LitElement {
|
||||
}
|
||||
|
||||
private _setFiltersFromUrl() {
|
||||
const area = this._searchParms.get("area");
|
||||
const domain = this._searchParms.get("domain");
|
||||
const configEntry = this._searchParms.get("config_entry");
|
||||
const subEntry = this._searchParms.get("sub_entry");
|
||||
@@ -1099,7 +1100,7 @@ export class HaConfigEntities extends LitElement {
|
||||
const label = this._searchParms.get("label");
|
||||
const voiceAssistant = this._searchParms.get("voice_assistant");
|
||||
|
||||
if (!domain && !configEntry && !label && !device) {
|
||||
if (!area && !domain && !configEntry && !label && !device) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1108,6 +1109,7 @@ export class HaConfigEntities extends LitElement {
|
||||
|
||||
this._filters = {
|
||||
"ha-filter-states": [],
|
||||
"ha-filter-floor-areas": area ? { areas: [area] } : undefined,
|
||||
"ha-filter-integrations": domain ? [domain] : [],
|
||||
"ha-filter-devices": device ? [device] : [],
|
||||
"ha-filter-labels": label ? [label] : [],
|
||||
|
||||
@@ -625,7 +625,9 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
||||
<hass-tabs-subpage-data-table
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
back-path="/config"
|
||||
.backPath=${this._searchParms.has("historyBack")
|
||||
? undefined
|
||||
: "/config"}
|
||||
.route=${this.route}
|
||||
.tabs=${configSections.devices}
|
||||
.searchLabel=${this.hass.localize(
|
||||
@@ -964,12 +966,13 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
||||
};
|
||||
|
||||
private _setFiltersFromUrl() {
|
||||
const area = this._searchParms.get("area");
|
||||
const device = this._searchParms.get("device");
|
||||
const label = this._searchParms.get("label");
|
||||
const category = this._searchParms.get("category");
|
||||
const voiceAssistant = this._searchParms.get("voice_assistant");
|
||||
|
||||
if (!category && !label && !device) {
|
||||
if (!area && !category && !label && !device && !voiceAssistant) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -977,6 +980,7 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
||||
this._filter = history.state?.filter || "";
|
||||
|
||||
this._filters = {
|
||||
"ha-filter-floor-areas": area ? { areas: [area] } : undefined,
|
||||
"ha-filter-devices": device ? [device] : [],
|
||||
"ha-filter-labels": label ? [label] : [],
|
||||
"ha-filter-categories": category ? [category] : [],
|
||||
|
||||
@@ -410,9 +410,10 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
|
||||
protected willUpdate(changedProps: PropertyValues) {
|
||||
super.willUpdate(changedProps);
|
||||
if (!this.hasUpdated) {
|
||||
if (!this._searchParms.has("label")) {
|
||||
if (!this._searchParms.has("area") && !this._searchParms.has("label")) {
|
||||
this._filters = this._storageFilters;
|
||||
}
|
||||
this._filterArea();
|
||||
this._filterLabel();
|
||||
}
|
||||
}
|
||||
@@ -785,6 +786,22 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
|
||||
this._applyFilters();
|
||||
}
|
||||
|
||||
private _filterArea() {
|
||||
const area = this._searchParms.get("area");
|
||||
if (!area) {
|
||||
return;
|
||||
}
|
||||
this._fromUrl = true;
|
||||
this._filters = {
|
||||
...this._filters,
|
||||
"ha-filter-floor-areas": {
|
||||
value: { areas: [area] },
|
||||
items: undefined,
|
||||
},
|
||||
};
|
||||
this._applyFilters();
|
||||
}
|
||||
|
||||
private _filterLabel() {
|
||||
const label = this._searchParms.get("label");
|
||||
if (!label) {
|
||||
|
||||
@@ -783,10 +783,15 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
|
||||
super.willUpdate(changedProps);
|
||||
if (!this.hasUpdated) {
|
||||
const hasUrlFilter =
|
||||
this._searchParms.has("blueprint") || this._searchParms.has("label");
|
||||
this._searchParms.has("area") ||
|
||||
this._searchParms.has("blueprint") ||
|
||||
this._searchParms.has("label");
|
||||
if (!hasUrlFilter) {
|
||||
this._filters = this._storageFilters;
|
||||
}
|
||||
if (this._searchParms.has("area")) {
|
||||
this._filterArea();
|
||||
}
|
||||
if (this._searchParms.has("blueprint")) {
|
||||
this._filterBlueprint();
|
||||
}
|
||||
@@ -803,6 +808,22 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
private _filterArea() {
|
||||
const area = this._searchParms.get("area");
|
||||
if (!area) {
|
||||
return;
|
||||
}
|
||||
this._fromUrl = true;
|
||||
this._filters = {
|
||||
...this._filters,
|
||||
"ha-filter-floor-areas": {
|
||||
value: { areas: [area] },
|
||||
items: undefined,
|
||||
},
|
||||
};
|
||||
this._applyFilters();
|
||||
}
|
||||
|
||||
private _filterLabel() {
|
||||
const label = this._searchParms.get("label");
|
||||
if (!label) {
|
||||
|
||||
@@ -3126,6 +3126,14 @@
|
||||
"caption": "Areas",
|
||||
"description": "Group devices and entities into areas",
|
||||
"edit_settings": "Area settings",
|
||||
"quick_links": {
|
||||
"devices": "{count} {count, plural,\n one {device}\n other {devices}\n}",
|
||||
"entities": "{count} {count, plural,\n one {entity}\n other {entities}\n}",
|
||||
"helpers": "{count} {count, plural,\n one {helper}\n other {helpers}\n}",
|
||||
"automations": "{count} {count, plural,\n one {automation}\n other {automations}\n}",
|
||||
"scenes": "{count} {count, plural,\n one {scene}\n other {scenes}\n}",
|
||||
"scripts": "{count} {count, plural,\n one {script}\n other {scripts}\n}"
|
||||
},
|
||||
"add_picture": "Add a picture",
|
||||
"assigned_to_area": "Assigned to this area",
|
||||
"targeting_area": "Targeting this area",
|
||||
|
||||
Reference in New Issue
Block a user