mirror of
https://github.com/home-assistant/frontend.git
synced 2026-02-04 01:10:33 -06:00
Add support for vacuum segment mapping to areas
This commit is contained in:
parent
a7f9b93018
commit
ea88cd8f9c
258
src/components/ha-vacuum-segment-area-mapper.ts
Normal file
258
src/components/ha-vacuum-segment-area-mapper.ts
Normal file
@ -0,0 +1,258 @@
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { Segment } from "../data/vacuum";
|
||||
import { getVacuumSegments } from "../data/vacuum";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-alert";
|
||||
import "./ha-area-picker";
|
||||
|
||||
type AreaSegmentMapping = Record<string, string[]>; // area ID -> segment IDs
|
||||
|
||||
@customElement("ha-vacuum-segment-area-mapper")
|
||||
export class HaVacuumSegmentAreaMapper extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: "entity-id" }) public entityId!: string;
|
||||
|
||||
@property({ attribute: false }) public value?: AreaSegmentMapping;
|
||||
|
||||
@state() private _segments?: Segment[];
|
||||
|
||||
@state() private _loading = false;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
public get lastSeenSegments() {
|
||||
return this._segments?.map((seg: Segment) => ({
|
||||
id: seg.id,
|
||||
name: seg.name,
|
||||
...(seg.group && { group: seg.group }),
|
||||
}));
|
||||
}
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues): void {
|
||||
super.willUpdate(changedProps);
|
||||
|
||||
if (changedProps.has("entityId") && this.entityId) {
|
||||
this._loadSegments();
|
||||
}
|
||||
}
|
||||
|
||||
private async _loadSegments() {
|
||||
this._loading = true;
|
||||
this._error = undefined;
|
||||
|
||||
try {
|
||||
const result = await getVacuumSegments(this.hass, this.entityId);
|
||||
this._segments = result.segments;
|
||||
} catch (err: any) {
|
||||
this._error = err.message || "Failed to load segments";
|
||||
this._segments = undefined;
|
||||
} finally {
|
||||
this._loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (this._loading) {
|
||||
return html`
|
||||
<div class="loading">${this.hass.localize("ui.common.loading")}...</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (this._error) {
|
||||
return html` <ha-alert alert-type="error">${this._error}</ha-alert> `;
|
||||
}
|
||||
|
||||
if (!this._segments || this._segments.length === 0) {
|
||||
return html`
|
||||
<ha-alert alert-type="info"> No segments available </ha-alert>
|
||||
`;
|
||||
}
|
||||
|
||||
// Group segments by group (if available)
|
||||
const groupedSegments = this._groupSegments(this._segments);
|
||||
|
||||
return html`
|
||||
<div class="segments">
|
||||
${Object.entries(groupedSegments).map(
|
||||
([groupName, segments]) => html`
|
||||
${groupName !== "undefined"
|
||||
? html`<div class="group-header">${groupName}</div>`
|
||||
: nothing}
|
||||
${segments.map((segment) => this._renderSegment(segment))}
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _groupSegments(segments: Segment[]): Record<string, Segment[]> {
|
||||
const grouped: Record<string, Segment[]> = {};
|
||||
|
||||
for (const segment of segments) {
|
||||
const group = segment.group || "undefined";
|
||||
if (!grouped[group]) {
|
||||
grouped[group] = [];
|
||||
}
|
||||
grouped[group].push(segment);
|
||||
}
|
||||
|
||||
return grouped;
|
||||
}
|
||||
|
||||
private _renderSegment(segment: Segment) {
|
||||
const mappedAreas = this._getSegmentAreas(segment.id);
|
||||
|
||||
return html`
|
||||
<div class="segment-row">
|
||||
<div class="segment-info">
|
||||
<div class="segment-name">${segment.name}</div>
|
||||
<div class="segment-id">${segment.id}</div>
|
||||
</div>
|
||||
<ha-area-picker
|
||||
.hass=${this.hass}
|
||||
.value=${mappedAreas}
|
||||
.label=${"Area"}
|
||||
allow-custom-entity
|
||||
@value-changed=${this._handleAreaChanged}
|
||||
data-segment-id=${segment.id}
|
||||
></ha-area-picker>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleAreaChanged = (ev: CustomEvent) => {
|
||||
const target = ev.currentTarget as HTMLElement;
|
||||
const segmentId = target.dataset.segmentId;
|
||||
if (segmentId) {
|
||||
this._areaChanged(segmentId, ev);
|
||||
}
|
||||
};
|
||||
|
||||
private _getSegmentAreas(segmentId: string): string | undefined {
|
||||
if (!this.value) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Find which area(s) contain this segment
|
||||
for (const [areaId, segmentIds] of Object.entries(this.value)) {
|
||||
if (segmentIds.includes(segmentId)) {
|
||||
return areaId;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _areaChanged(segmentId: string, ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const newAreaId = ev.detail.value as string | undefined;
|
||||
|
||||
// Create a copy of the current mapping
|
||||
const newMapping: AreaSegmentMapping = { ...this.value };
|
||||
|
||||
// Remove segment from all areas
|
||||
for (const areaId of Object.keys(newMapping)) {
|
||||
newMapping[areaId] = newMapping[areaId].filter((id) => id !== segmentId);
|
||||
// Remove empty area entries
|
||||
if (newMapping[areaId].length === 0) {
|
||||
delete newMapping[areaId];
|
||||
}
|
||||
}
|
||||
|
||||
// Add segment to new area if specified
|
||||
if (newAreaId) {
|
||||
if (!newMapping[newAreaId]) {
|
||||
newMapping[newAreaId] = [];
|
||||
}
|
||||
newMapping[newAreaId].push(segmentId);
|
||||
}
|
||||
|
||||
fireEvent(this, "value-changed", { value: newMapping });
|
||||
}
|
||||
|
||||
static styles: CSSResultGroup = css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.loading {
|
||||
padding: var(--ha-space-4);
|
||||
text-align: center;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.segments {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--ha-space-2);
|
||||
}
|
||||
|
||||
.group-header {
|
||||
font-weight: 500;
|
||||
color: var(--primary-text-color);
|
||||
padding: var(--ha-space-3) var(--ha-space-2);
|
||||
margin-top: var(--ha-space-2);
|
||||
background-color: var(--secondary-background-color);
|
||||
border-radius: var(--ha-card-border-radius, 12px);
|
||||
}
|
||||
|
||||
.segment-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--ha-space-4);
|
||||
padding: var(--ha-space-2);
|
||||
border-radius: var(--ha-card-border-radius, 12px);
|
||||
background-color: var(--card-background-color);
|
||||
border: 1px solid var(--divider-color);
|
||||
}
|
||||
|
||||
.segment-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.segment-name {
|
||||
font-weight: 500;
|
||||
color: var(--primary-text-color);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.segment-id {
|
||||
font-size: 0.875rem;
|
||||
color: var(--secondary-text-color);
|
||||
font-family: var(--ha-font-family-code);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
ha-area-picker {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.segment-row {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
ha-area-picker {
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-vacuum-segment-area-mapper": HaVacuumSegmentAreaMapper;
|
||||
}
|
||||
}
|
||||
@ -9,6 +9,7 @@ import { debounce } from "../../common/util/debounce";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { LightColor } from "../light";
|
||||
import type { RegistryEntry } from "../registry";
|
||||
import type { Segment } from "../vacuum";
|
||||
|
||||
type EntityCategory = "config" | "diagnostic";
|
||||
|
||||
@ -120,6 +121,11 @@ export interface SwitchAsXEntityOptions {
|
||||
invert: boolean;
|
||||
}
|
||||
|
||||
export interface VacuumEntityOptions {
|
||||
area_mapping?: Record<string, string[]>;
|
||||
last_seen_segments?: Segment[];
|
||||
}
|
||||
|
||||
export interface EntityRegistryOptions {
|
||||
number?: NumberEntityOptions;
|
||||
sensor?: SensorEntityOptions;
|
||||
@ -128,6 +134,7 @@ export interface EntityRegistryOptions {
|
||||
lock?: LockEntityOptions;
|
||||
weather?: WeatherEntityOptions;
|
||||
light?: LightEntityOptions;
|
||||
vacuum?: VacuumEntityOptions;
|
||||
switch_as_x?: SwitchAsXEntityOptions;
|
||||
conversation?: Record<string, unknown>;
|
||||
"cloud.alexa"?: Record<string, unknown>;
|
||||
@ -150,7 +157,8 @@ export interface EntityRegistryEntryUpdateParams {
|
||||
| AlarmControlPanelEntityOptions
|
||||
| CalendarEntityOptions
|
||||
| WeatherEntityOptions
|
||||
| LightEntityOptions;
|
||||
| LightEntityOptions
|
||||
| VacuumEntityOptions;
|
||||
aliases?: string[];
|
||||
labels?: string[];
|
||||
categories?: Record<string, string | null>;
|
||||
|
||||
@ -2,6 +2,7 @@ import type {
|
||||
HassEntityAttributeBase,
|
||||
HassEntityBase,
|
||||
} from "home-assistant-js-websocket";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { UNAVAILABLE } from "./entity/entity";
|
||||
|
||||
export type VacuumEntityState =
|
||||
@ -29,6 +30,7 @@ export const enum VacuumEntityFeature {
|
||||
MAP = 2048,
|
||||
STATE = 4096,
|
||||
START = 8192,
|
||||
CLEAN_AREA = 16384,
|
||||
}
|
||||
|
||||
interface VacuumEntityAttributes extends HassEntityAttributeBase {
|
||||
@ -62,3 +64,18 @@ export function canReturnHome(stateObj: VacuumEntity): boolean {
|
||||
}
|
||||
return stateObj.state !== "returning";
|
||||
}
|
||||
|
||||
export interface Segment {
|
||||
id: string;
|
||||
name: string;
|
||||
group?: string;
|
||||
}
|
||||
|
||||
export const getVacuumSegments = (
|
||||
hass: HomeAssistant,
|
||||
entity_id: string
|
||||
): Promise<{ segments: Segment[] }> =>
|
||||
hass.callWS({
|
||||
type: "vacuum/get_segments",
|
||||
entity_id,
|
||||
});
|
||||
|
||||
@ -0,0 +1,152 @@
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-vacuum-segment-area-mapper";
|
||||
import type { HaVacuumSegmentAreaMapper } from "../../../../components/ha-vacuum-segment-area-mapper";
|
||||
import type {
|
||||
ExtEntityRegistryEntry,
|
||||
VacuumEntityOptions,
|
||||
} from "../../../../data/entity/entity_registry";
|
||||
import {
|
||||
getExtendedEntityRegistryEntry,
|
||||
updateEntityRegistryEntry,
|
||||
} from "../../../../data/entity/entity_registry";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
|
||||
@customElement("ha-more-info-view-vacuum-segment-mapping")
|
||||
export class HaMoreInfoViewVacuumSegmentMapping extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public params!: { entityId: string };
|
||||
|
||||
@state() private _areaMapping?: Record<string, string[]>;
|
||||
|
||||
@state() private _submitting = false;
|
||||
|
||||
@state() private _dirty = false;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
private _entry?: ExtEntityRegistryEntry;
|
||||
|
||||
protected firstUpdated() {
|
||||
this._loadCurrentMapping();
|
||||
}
|
||||
|
||||
private async _loadCurrentMapping() {
|
||||
if (!this.params.entityId) return;
|
||||
|
||||
this._entry = await getExtendedEntityRegistryEntry(
|
||||
this.hass,
|
||||
this.params.entityId
|
||||
);
|
||||
|
||||
if (this._entry?.options?.vacuum) {
|
||||
this._areaMapping = this._entry.options.vacuum.area_mapping || {};
|
||||
} else {
|
||||
this._areaMapping = {};
|
||||
}
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
this._areaMapping = ev.detail.value;
|
||||
this._dirty = true;
|
||||
}
|
||||
|
||||
private async _save() {
|
||||
if (!this.params.entityId || !this._areaMapping) return;
|
||||
this._error = undefined;
|
||||
this._submitting = true;
|
||||
|
||||
// Get current segments from the mapper component
|
||||
const mapper = this.shadowRoot!.querySelector(
|
||||
"ha-vacuum-segment-area-mapper"
|
||||
) as HaVacuumSegmentAreaMapper;
|
||||
|
||||
const options: VacuumEntityOptions = {
|
||||
...(this._entry?.options?.vacuum ?? {}),
|
||||
area_mapping: this._areaMapping,
|
||||
last_seen_segments: mapper.lastSeenSegments,
|
||||
};
|
||||
|
||||
try {
|
||||
await updateEntityRegistryEntry(this.hass, this.params.entityId, {
|
||||
options_domain: "vacuum",
|
||||
options: options,
|
||||
});
|
||||
this._dirty = false;
|
||||
} catch (err: any) {
|
||||
this._error = err.message;
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._areaMapping) {
|
||||
return html`<ha-spinner active></ha-spinner>`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="content">
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: nothing}
|
||||
|
||||
<ha-vacuum-segment-area-mapper
|
||||
.hass=${this.hass}
|
||||
.entityId=${this.params.entityId}
|
||||
.value=${this._areaMapping}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-vacuum-segment-area-mapper>
|
||||
|
||||
<div class="footer">
|
||||
<ha-button
|
||||
@click=${this._save}
|
||||
.disabled=${!this._dirty || this._submitting}
|
||||
>
|
||||
${this.hass.localize("ui.common.save")}
|
||||
</ha-button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles: CSSResultGroup = css`
|
||||
:host {
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
ha-spinner {
|
||||
margin: var(--ha-space-8);
|
||||
display: flex;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
ha-vacuum-segment-area-mapper {
|
||||
flex: 1;
|
||||
padding: var(--ha-space-4);
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: var(--ha-space-4);
|
||||
border-top: 1px solid var(--divider-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-more-info-view-vacuum-segment-mapping": HaMoreInfoViewVacuumSegmentMapping;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
|
||||
export const loadVacuumSegmentMappingView = () =>
|
||||
import("./ha-more-info-view-vacuum-segment-mapping");
|
||||
|
||||
export const showVacuumSegmentMappingView = (
|
||||
element: HTMLElement,
|
||||
entityId: string
|
||||
): void => {
|
||||
fireEvent(element, "show-child-view", {
|
||||
viewTag: "ha-more-info-view-vacuum-segment-mapping",
|
||||
viewImport: loadVacuumSegmentMappingView,
|
||||
viewTitle: "Map vacuum segments to areas",
|
||||
viewParams: { entityId },
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,203 @@
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { computeAreaName } from "../../../../common/entity/compute_area_name";
|
||||
import { computeDeviceName } from "../../../../common/entity/compute_device_name";
|
||||
import {
|
||||
computeEntityEntryName,
|
||||
computeEntityName,
|
||||
} from "../../../../common/entity/compute_entity_name";
|
||||
import {
|
||||
getEntityContext,
|
||||
getEntityEntryContext,
|
||||
} from "../../../../common/entity/context/get_entity_context";
|
||||
import { computeRTL } from "../../../../common/util/compute_rtl";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-dialog-footer";
|
||||
import "../../../../components/ha-vacuum-segment-area-mapper";
|
||||
import type { HaVacuumSegmentAreaMapper } from "../../../../components/ha-vacuum-segment-area-mapper";
|
||||
import "../../../../components/ha-wa-dialog";
|
||||
import type {
|
||||
ExtEntityRegistryEntry,
|
||||
VacuumEntityOptions,
|
||||
} from "../../../../data/entity/entity_registry";
|
||||
import {
|
||||
getExtendedEntityRegistryEntry,
|
||||
updateEntityRegistryEntry,
|
||||
} from "../../../../data/entity/entity_registry";
|
||||
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
||||
import { haStyleDialog } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
|
||||
export interface VacuumSegmentMappingDialogParams {
|
||||
entityId: string;
|
||||
}
|
||||
|
||||
@customElement("dialog-vacuum-segment-mapping")
|
||||
export class DialogVacuumSegmentMapping
|
||||
extends LitElement
|
||||
implements HassDialog<VacuumSegmentMappingDialogParams>
|
||||
{
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _params?: VacuumSegmentMappingDialogParams;
|
||||
|
||||
@state() private _open = false;
|
||||
|
||||
@state() private _areaMapping?: Record<string, string[]>;
|
||||
|
||||
@state() private _submitting = false;
|
||||
|
||||
private _entry?: ExtEntityRegistryEntry;
|
||||
|
||||
public async showDialog(
|
||||
params: VacuumSegmentMappingDialogParams
|
||||
): Promise<void> {
|
||||
this._params = params;
|
||||
this._open = true;
|
||||
await this._loadCurrentMapping();
|
||||
}
|
||||
|
||||
public closeDialog(): boolean {
|
||||
this._open = false;
|
||||
this._params = undefined;
|
||||
this._areaMapping = undefined;
|
||||
return true;
|
||||
}
|
||||
|
||||
private async _loadCurrentMapping() {
|
||||
if (!this._params) return;
|
||||
|
||||
const entityId = this._params.entityId;
|
||||
this._entry = await getExtendedEntityRegistryEntry(this.hass, entityId);
|
||||
|
||||
if (this._entry?.options?.vacuum) {
|
||||
this._areaMapping = this._entry.options.vacuum.area_mapping || {};
|
||||
} else {
|
||||
this._areaMapping = {};
|
||||
}
|
||||
}
|
||||
|
||||
private _dialogClosed(): void {
|
||||
this._params = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
this._areaMapping = ev.detail.value;
|
||||
}
|
||||
|
||||
private async _save() {
|
||||
if (!this._params || !this._areaMapping) return;
|
||||
|
||||
this._submitting = true;
|
||||
|
||||
try {
|
||||
const mapper = this.renderRoot.querySelector(
|
||||
"ha-vacuum-segment-area-mapper"
|
||||
) as HaVacuumSegmentAreaMapper;
|
||||
|
||||
const options: VacuumEntityOptions = {
|
||||
area_mapping: this._areaMapping,
|
||||
last_seen_segments: mapper.lastSeenSegments,
|
||||
};
|
||||
|
||||
await updateEntityRegistryEntry(this.hass, this._params.entityId, {
|
||||
options_domain: "vacuum",
|
||||
options: options,
|
||||
});
|
||||
|
||||
this.closeDialog();
|
||||
} catch (_err: any) {
|
||||
// Error will be shown by the system
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._params) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const stateObj = this.hass.states[this._params.entityId];
|
||||
|
||||
const context = stateObj
|
||||
? getEntityContext(
|
||||
stateObj,
|
||||
this.hass.entities,
|
||||
this.hass.devices,
|
||||
this.hass.areas,
|
||||
this.hass.floors
|
||||
)
|
||||
: this._entry
|
||||
? getEntityEntryContext(
|
||||
this._entry,
|
||||
this.hass.entities,
|
||||
this.hass.devices,
|
||||
this.hass.areas,
|
||||
this.hass.floors
|
||||
)
|
||||
: undefined;
|
||||
|
||||
const entityName = stateObj
|
||||
? computeEntityName(stateObj, this.hass.entities, this.hass.devices)
|
||||
: this._entry
|
||||
? computeEntityEntryName(this._entry, this.hass.devices)
|
||||
: this._params.entityId;
|
||||
|
||||
const deviceName = context?.device
|
||||
? computeDeviceName(context.device)
|
||||
: undefined;
|
||||
const areaName = context?.area ? computeAreaName(context.area) : undefined;
|
||||
|
||||
const breadcrumb = [areaName, deviceName, entityName].filter(
|
||||
(v): v is string => Boolean(v)
|
||||
);
|
||||
|
||||
return html`
|
||||
<ha-wa-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
@closed=${this._dialogClosed}
|
||||
header-title="Map vacuum segments to areas"
|
||||
.headerSubtitle=${breadcrumb.join(
|
||||
computeRTL(this.hass) ? " ◂ " : " ▸ "
|
||||
)}
|
||||
>
|
||||
<ha-vacuum-segment-area-mapper
|
||||
.hass=${this.hass}
|
||||
entity-id=${this._params.entityId}
|
||||
.value=${this._areaMapping}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-vacuum-segment-area-mapper>
|
||||
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-button
|
||||
slot="secondaryAction"
|
||||
appearance="plain"
|
||||
@click=${this.closeDialog}
|
||||
>
|
||||
${this.hass.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._save}
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this.hass.localize("ui.common.save")}
|
||||
</ha-button>
|
||||
</ha-dialog-footer>
|
||||
</ha-wa-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles: CSSResultGroup = [haStyleDialog];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-vacuum-segment-mapping": DialogVacuumSegmentMapping;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
|
||||
export interface VacuumSegmentMappingDialogParams {
|
||||
entityId: string;
|
||||
}
|
||||
|
||||
export const loadVacuumSegmentMappingDialog = () =>
|
||||
import("./dialog-vacuum-segment-mapping");
|
||||
|
||||
export const showVacuumSegmentMappingDialog = (
|
||||
element: HTMLElement,
|
||||
params: VacuumSegmentMappingDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-vacuum-segment-mapping",
|
||||
dialogImport: loadVacuumSegmentMappingDialog,
|
||||
dialogParams: params,
|
||||
});
|
||||
};
|
||||
@ -80,6 +80,7 @@ import {
|
||||
getSensorDeviceClassConvertibleUnits,
|
||||
getSensorNumericDeviceClasses,
|
||||
} from "../../../data/sensor";
|
||||
import { VacuumEntityFeature } from "../../../data/vacuum";
|
||||
import type { WeatherUnits } from "../../../data/weather";
|
||||
import { getWeatherConvertibleUnits } from "../../../data/weather";
|
||||
import { showOptionsFlowDialog } from "../../../dialogs/config-flow/show-dialog-options-flow";
|
||||
@ -87,6 +88,7 @@ import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../dialogs/generic/show-dialog-box";
|
||||
import { showVacuumSegmentMappingView } from "../../../dialogs/more-info/components/vacuum/show-view-vacuum-segment-mapping";
|
||||
import { showVoiceAssistantsView } from "../../../dialogs/more-info/components/voice/show-view-voice-assistants";
|
||||
import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info-dialog";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
@ -925,6 +927,25 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
|
||||
${domain === "vacuum" &&
|
||||
stateObj &&
|
||||
supportsFeature(stateObj, VacuumEntityFeature.CLEAN_AREA)
|
||||
? html`
|
||||
<ha-list-item
|
||||
class="menu-item"
|
||||
twoline
|
||||
hasMeta
|
||||
.disabled=${this.disabled}
|
||||
@click=${this._handleVacuumSegmentMappingClicked}
|
||||
>
|
||||
<span>Map vacuum segments to areas</span>
|
||||
<span slot="secondary">
|
||||
Configure which areas each vacuum segment should clean
|
||||
</span>
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
`
|
||||
: ""}
|
||||
${this._disabledBy &&
|
||||
this._disabledBy !== "user" &&
|
||||
this._disabledBy !== "integration"
|
||||
@ -1512,6 +1533,10 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
private _handleVacuumSegmentMappingClicked() {
|
||||
showVacuumSegmentMappingView(this, this.entry.entity_id);
|
||||
}
|
||||
|
||||
private async _showOptionsFlow() {
|
||||
showOptionsFlowDialog(this, this.helperConfigEntry!, {
|
||||
manifest: await fetchIntegrationManifest(
|
||||
|
||||
@ -5,6 +5,11 @@ import { capitalizeFirstLetter } from "../../../common/string/capitalize-first-l
|
||||
import "../../../components/ha-md-list";
|
||||
import "../../../components/ha-md-list-item";
|
||||
import { domainToName } from "../../../data/integration";
|
||||
import type { StatisticsValidationResult } from "../../../data/recorder";
|
||||
import {
|
||||
STATISTIC_TYPES,
|
||||
updateStatisticsIssues,
|
||||
} from "../../../data/recorder";
|
||||
import {
|
||||
fetchRepairsIssueData,
|
||||
type RepairsIssue,
|
||||
@ -15,11 +20,7 @@ import { brandsUrl } from "../../../util/brands-url";
|
||||
import { fixStatisticsIssue } from "../developer-tools/statistics/fix-statistics";
|
||||
import { showRepairsFlowDialog } from "./show-dialog-repair-flow";
|
||||
import { showRepairsIssueDialog } from "./show-repair-issue-dialog";
|
||||
import type { StatisticsValidationResult } from "../../../data/recorder";
|
||||
import {
|
||||
STATISTIC_TYPES,
|
||||
updateStatisticsIssues,
|
||||
} from "../../../data/recorder";
|
||||
import { showVacuumSegmentMappingDialog } from "../entities/dialogs/show-dialog-vacuum-segment-mapping";
|
||||
|
||||
@customElement("ha-config-repairs")
|
||||
class HaConfigRepairs extends LitElement {
|
||||
@ -139,6 +140,23 @@ class HaConfigRepairs extends LitElement {
|
||||
continueFlowId: data.issue_data.flow_id as string,
|
||||
});
|
||||
}
|
||||
} else if (
|
||||
issue.domain === "vacuum" &&
|
||||
issue.translation_key === "segments_changed"
|
||||
) {
|
||||
const data = await fetchRepairsIssueData(
|
||||
this.hass.connection,
|
||||
issue.domain,
|
||||
issue.issue_id
|
||||
);
|
||||
if (
|
||||
"entity_id" in data.issue_data &&
|
||||
typeof data.issue_data.entity_id === "string"
|
||||
) {
|
||||
showVacuumSegmentMappingDialog(this, {
|
||||
entityId: data.issue_data.entity_id,
|
||||
});
|
||||
}
|
||||
} else if (
|
||||
issue.domain === "sensor" &&
|
||||
issue.translation_key &&
|
||||
|
||||
@ -1358,6 +1358,9 @@
|
||||
}
|
||||
},
|
||||
"dialogs": {
|
||||
"vacuum_segment_mapping": {
|
||||
"title": "Map vacuum segments to areas"
|
||||
},
|
||||
"safe_mode": {
|
||||
"title": "Safe mode",
|
||||
"text": "Home Assistant is running in safe mode, custom integrations and frontend modules are not available. Restart Home Assistant to exit safe mode."
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user