mirror of
https://github.com/home-assistant/frontend.git
synced 2026-10-25 07:04:11 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c7c64d7a2 | ||
|
|
3946006918 |
No files matched your search
@@ -16,9 +16,31 @@ import "./ha-svg-icon";
|
||||
*
|
||||
*/
|
||||
@customElement("ha-dropdown-item")
|
||||
// @ts-ignore Allow to override the private notifyParentOfOpening
|
||||
export class HaDropdownItem extends DropdownItem {
|
||||
@property({ type: Boolean, reflect: true }) selected = false;
|
||||
|
||||
// Webawesome only closes open sibling submenus of `wa-dropdown-item`
|
||||
// elements, so hovering from one submenu item to another kept the first
|
||||
// one open and highlighted.
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
private override notifyParentOfOpening() {
|
||||
// @ts-ignore
|
||||
super.notifyParentOfOpening();
|
||||
const siblings = this.parentElement?.children ?? [];
|
||||
for (const sibling of siblings) {
|
||||
if (
|
||||
sibling !== this &&
|
||||
sibling.localName === this.localName &&
|
||||
sibling.getAttribute("slot") === this.getAttribute("slot") &&
|
||||
(sibling as HaDropdownItem).submenuOpen
|
||||
) {
|
||||
(sibling as HaDropdownItem).submenuOpen = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected renderCheckboxIcon() {
|
||||
return html`
|
||||
<ha-svg-icon
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
mdiEye,
|
||||
mdiEyeOff,
|
||||
mdiMenuDown,
|
||||
mdiMicrophoneMessage,
|
||||
mdiMicrophoneMessageOff,
|
||||
mdiPencilOff,
|
||||
mdiPlus,
|
||||
mdiRestore,
|
||||
@@ -22,6 +24,7 @@ import memoize from "memoize-one";
|
||||
import { consume } from "../../../common/decorators/consume";
|
||||
import { storage } from "../../../common/decorators/storage";
|
||||
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { computeAreaName } from "../../../common/entity/compute_area_name";
|
||||
import {
|
||||
computeDeviceName,
|
||||
@@ -29,6 +32,7 @@ import {
|
||||
} from "../../../common/entity/compute_device_name";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { computeEntityEntryName } from "../../../common/entity/compute_entity_name_display";
|
||||
import { isEmptyEntityDomainFilter } from "../../../common/entity/entity_domain_filter";
|
||||
import {
|
||||
deleteEntity,
|
||||
isDeletableEntity,
|
||||
@@ -87,7 +91,11 @@ import { updateEntityRegistryEntry } from "../../../data/entity/entity_registry"
|
||||
import type { EntitySources } from "../../../data/entity/entity_sources";
|
||||
import { fetchEntitySourcesWithCache } from "../../../data/entity/entity_sources";
|
||||
import type { ExposeEntitySettings } from "../../../data/expose";
|
||||
import { listExposedEntities, voiceAssistants } from "../../../data/expose";
|
||||
import {
|
||||
exposeEntities,
|
||||
listExposedEntities,
|
||||
voiceAssistants,
|
||||
} from "../../../data/expose";
|
||||
import { HELPERS_CRUD } from "../../../data/helpers_crud";
|
||||
import type { IntegrationManifest } from "../../../data/integration";
|
||||
import {
|
||||
@@ -493,6 +501,7 @@ export class HaConfigEntities extends LitElement {
|
||||
devices: HomeAssistant["devices"],
|
||||
areas: HomeAssistant["areas"],
|
||||
entitiesWithoutUniqueId: StateEntity[],
|
||||
exposedEntities: Record<string, ExposeEntitySettings> | undefined,
|
||||
filters: DataTableFiltersValues,
|
||||
filteredItems: DataTableFiltersItems,
|
||||
entries?: ConfigEntry[],
|
||||
@@ -632,9 +641,10 @@ export class HaConfigEntities extends LitElement {
|
||||
filter.length
|
||||
) {
|
||||
filteredEntities = filteredEntities.filter((entity) =>
|
||||
this._getExposedEntityVoiceAssistantIds(entity.entity_id).some(
|
||||
(va) => (filter as string[]).includes(va)
|
||||
)
|
||||
this._getExposedEntityVoiceAssistantIds(
|
||||
exposedEntities,
|
||||
entity.entity_id
|
||||
).some((va) => (filter as string[]).includes(va))
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -690,6 +700,7 @@ export class HaConfigEntities extends LitElement {
|
||||
: deviceName
|
||||
: undefined;
|
||||
const assistants = this._getExposedEntityVoiceAssistantIds(
|
||||
exposedEntities,
|
||||
entry.entity_id
|
||||
);
|
||||
|
||||
@@ -791,12 +802,18 @@ export class HaConfigEntities extends LitElement {
|
||||
this.hass.devices,
|
||||
this.hass.areas,
|
||||
this._entitiesWithoutUniqueId,
|
||||
this._exposedEntities,
|
||||
this._filters,
|
||||
this._filteredItems,
|
||||
this._entries,
|
||||
this._labels
|
||||
);
|
||||
|
||||
const exposableAssistants = this._exposableAssistants(
|
||||
this.cloudStatus,
|
||||
this.hass
|
||||
);
|
||||
|
||||
const includeAddDeviceFab =
|
||||
filteredDomains.size === 1 &&
|
||||
(PROTOCOL_INTEGRATIONS as readonly string[]).includes(
|
||||
@@ -941,6 +958,39 @@ export class HaConfigEntities extends LitElement {
|
||||
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
${
|
||||
exposableAssistants.length
|
||||
? html`<ha-dropdown-item>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${mdiMicrophoneMessage}
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.entities.picker.expose_selected.button"
|
||||
)}
|
||||
${this._renderVoiceAssistantItems(
|
||||
"expose",
|
||||
exposableAssistants
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${mdiMicrophoneMessageOff}
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.entities.picker.unexpose_selected.button"
|
||||
)}
|
||||
${this._renderVoiceAssistantItems(
|
||||
"unexpose",
|
||||
exposableAssistants
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
|
||||
<wa-divider></wa-divider>`
|
||||
: nothing
|
||||
}
|
||||
|
||||
<ha-dropdown-item value="restore_entity_id_selected">
|
||||
<ha-svg-icon slot="icon" .path=${mdiRestore}></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
@@ -1141,9 +1191,12 @@ export class HaConfigEntities extends LitElement {
|
||||
|
||||
// local variant of data/expose/getExposedEntityVoiceAssistantIds
|
||||
// which works for both entities with and without unique id
|
||||
private _getExposedEntityVoiceAssistantIds(entityId: string) {
|
||||
private _getExposedEntityVoiceAssistantIds(
|
||||
exposedEntities: Record<string, ExposeEntitySettings> | undefined,
|
||||
entityId: string
|
||||
) {
|
||||
return Object.keys(voiceAssistants).filter(
|
||||
(vaId) => this._exposedEntities?.[entityId]?.[vaId]
|
||||
(vaId) => exposedEntities?.[entityId]?.[vaId]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1539,6 +1592,7 @@ ${rejected
|
||||
this.hass.devices,
|
||||
this.hass.areas,
|
||||
this._entitiesWithoutUniqueId,
|
||||
this._exposedEntities,
|
||||
this._filters,
|
||||
this._filteredItems,
|
||||
this._entries,
|
||||
@@ -1608,7 +1662,112 @@ ${rejected
|
||||
|
||||
if (action.startsWith("label_")) {
|
||||
this._handleBulkLabel(ev);
|
||||
return;
|
||||
}
|
||||
|
||||
if (action.startsWith("expose_") || action.startsWith("unexpose_")) {
|
||||
const assistant = action.substring(action.indexOf("_") + 1);
|
||||
this._exposeSelected(
|
||||
assistant === "all"
|
||||
? this._exposableAssistants(this.cloudStatus, this.hass)
|
||||
: [assistant],
|
||||
action.startsWith("expose_")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Alexa and Google Assistant can't be exposed to from the UI when they
|
||||
// are configured manually in YAML, same as on the voice assistants expose page
|
||||
private _exposableAssistants = memoize(
|
||||
(cloudStatus: CloudStatus | undefined, hass: HomeAssistant) =>
|
||||
getAvailableAssistants(cloudStatus, hass).filter(
|
||||
(assistant) =>
|
||||
!(
|
||||
cloudStatus?.logged_in &&
|
||||
((assistant === "cloud.alexa" &&
|
||||
!isEmptyEntityDomainFilter(cloudStatus.alexa_entities)) ||
|
||||
(assistant === "cloud.google_assistant" &&
|
||||
!isEmptyEntityDomainFilter(cloudStatus.google_entities)))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
private _renderVoiceAssistantItems(
|
||||
action: "expose" | "unexpose",
|
||||
assistants: string[]
|
||||
) {
|
||||
return html`${assistants.map(
|
||||
(assistant) =>
|
||||
html`<ha-dropdown-item
|
||||
slot="submenu"
|
||||
.value=${`${action}_${assistant}`}
|
||||
>
|
||||
${voiceAssistants[assistant].name}
|
||||
</ha-dropdown-item>`
|
||||
)}
|
||||
${
|
||||
assistants.length > 1
|
||||
? html`<ha-dropdown-item slot="submenu" .value=${`${action}_all`}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.entities.picker.expose_selected.all_assistants"
|
||||
)}
|
||||
</ha-dropdown-item>`
|
||||
: nothing
|
||||
}`;
|
||||
}
|
||||
|
||||
private _exposeSelected(assistants: string[], shouldExpose: boolean) {
|
||||
// Only entities with a state can be exposed, same as the expose page
|
||||
const entityIds = this._selected.filter(
|
||||
(entityId) => entityId in this.hass.states
|
||||
);
|
||||
|
||||
if (!entityIds.length) {
|
||||
showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.entities.picker.expose_selected.no_entities"
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const params = {
|
||||
assistants: assistants
|
||||
.map((assistant) => voiceAssistants[assistant].name)
|
||||
.join(", "),
|
||||
entities: entityIds.length,
|
||||
};
|
||||
|
||||
showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
shouldExpose
|
||||
? "ui.panel.config.voice_assistants.expose.expose_confirm_title"
|
||||
: "ui.panel.config.voice_assistants.expose.unexpose_confirm_title"
|
||||
),
|
||||
text: this.hass.localize(
|
||||
shouldExpose
|
||||
? "ui.panel.config.voice_assistants.expose.expose_confirm_text"
|
||||
: "ui.panel.config.voice_assistants.expose.unexpose_confirm_text",
|
||||
params
|
||||
),
|
||||
confirmText: this.hass.localize(
|
||||
shouldExpose
|
||||
? "ui.panel.config.voice_assistants.expose.expose"
|
||||
: "ui.panel.config.voice_assistants.expose.unexpose"
|
||||
),
|
||||
dismissText: this.hass.localize("ui.common.cancel"),
|
||||
confirm: async () => {
|
||||
try {
|
||||
await exposeEntities(this.hass, assistants, entityIds, shouldExpose);
|
||||
fireEvent(this, "exposed-entities-changed");
|
||||
this._clearSelection();
|
||||
} catch (err: any) {
|
||||
showAlertDialog(this, {
|
||||
text: err.message || err.code || String(err),
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
|
||||
@@ -7499,6 +7499,14 @@
|
||||
"confirm_title": "Do you want to disable {number} {number, plural,\n one {entity}\n other {entities}\n}?",
|
||||
"confirm_text": "Disabled entities will not be added to Home Assistant."
|
||||
},
|
||||
"expose_selected": {
|
||||
"button": "Expose to voice assistant",
|
||||
"all_assistants": "All assistants",
|
||||
"no_entities": "None of the selected entities can be exposed."
|
||||
},
|
||||
"unexpose_selected": {
|
||||
"button": "Stop exposing to voice assistant"
|
||||
},
|
||||
"restore_entity_id_selected": {
|
||||
"button": "Recreate entity IDs of selected",
|
||||
"confirm_title": "Recreate entity IDs?",
|
||||
|
||||
Reference in new issue
Block a user