mirror of
https://github.com/home-assistant/frontend.git
synced 2025-12-20 10:48:44 +00:00
Add Add entity to feature for external_app (#26346)
* Add Add entity to feature for external_app * Update icon from plus to plusboxmultiple * Apply suggestion on the name * Add missing shouldHandleRequestSelectedEvent that caused duplicate * WIP * Rework the logic to match the agreed design * Rename property * Apply PR comments * Apply prettier * Merge MessageWithAnswer * Apply PR comments
This commit is contained in:
152
src/dialogs/more-info/ha-more-info-add-to.ts
Normal file
152
src/dialogs/more-info/ha-more-info-add-to.ts
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
import { LitElement, css, html, nothing } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import "../../components/ha-alert";
|
||||||
|
import "../../components/ha-icon";
|
||||||
|
import "../../components/ha-list-item";
|
||||||
|
import "../../components/ha-spinner";
|
||||||
|
import type {
|
||||||
|
ExternalEntityAddToActions,
|
||||||
|
ExternalEntityAddToAction,
|
||||||
|
} from "../../external_app/external_messaging";
|
||||||
|
import { showToast } from "../../util/toast";
|
||||||
|
|
||||||
|
import type { HomeAssistant } from "../../types";
|
||||||
|
|
||||||
|
@customElement("ha-more-info-add-to")
|
||||||
|
export class HaMoreInfoAddTo extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public entityId!: string;
|
||||||
|
|
||||||
|
@state() private _externalActions?: ExternalEntityAddToActions = {
|
||||||
|
actions: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
@state() private _loading = true;
|
||||||
|
|
||||||
|
private async _loadExternalActions() {
|
||||||
|
if (this.hass.auth.external?.config.hasEntityAddTo) {
|
||||||
|
this._externalActions =
|
||||||
|
await this.hass.auth.external?.sendMessage<"entity/add_to/get_actions">(
|
||||||
|
{
|
||||||
|
type: "entity/add_to/get_actions",
|
||||||
|
payload: { entity_id: this.entityId },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _actionSelected(ev: CustomEvent) {
|
||||||
|
const action = (ev.currentTarget as any)
|
||||||
|
.action as ExternalEntityAddToAction;
|
||||||
|
if (!action.enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.hass.auth.external!.fireMessage({
|
||||||
|
type: "entity/add_to",
|
||||||
|
payload: {
|
||||||
|
entity_id: this.entityId,
|
||||||
|
app_payload: action.app_payload,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (err: any) {
|
||||||
|
showToast(this, {
|
||||||
|
message: this.hass.localize(
|
||||||
|
"ui.dialogs.more_info_control.add_to.action_failed",
|
||||||
|
{
|
||||||
|
error: err.message || err,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async firstUpdated() {
|
||||||
|
await this._loadExternalActions();
|
||||||
|
this._loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (this._loading) {
|
||||||
|
return html`
|
||||||
|
<div class="loading">
|
||||||
|
<ha-spinner></ha-spinner>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._externalActions?.actions.length) {
|
||||||
|
return html`
|
||||||
|
<ha-alert alert-type="info">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.dialogs.more_info_control.add_to.no_actions"
|
||||||
|
)}
|
||||||
|
</ha-alert>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="actions-list">
|
||||||
|
${this._externalActions.actions.map(
|
||||||
|
(action) => html`
|
||||||
|
<ha-list-item
|
||||||
|
graphic="icon"
|
||||||
|
.disabled=${!action.enabled}
|
||||||
|
.action=${action}
|
||||||
|
.twoline=${!!action.details}
|
||||||
|
@click=${this._actionSelected}
|
||||||
|
>
|
||||||
|
<span>${action.name}</span>
|
||||||
|
${action.details
|
||||||
|
? html`<span slot="secondary">${action.details}</span>`
|
||||||
|
: nothing}
|
||||||
|
<ha-icon slot="graphic" .icon=${action.mdi_icon}></ha-icon>
|
||||||
|
</ha-list-item>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
padding: var(--ha-space-2) var(--ha-space-6) var(--ha-space-6)
|
||||||
|
var(--ha-space-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: var(--ha-space-8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-list-item {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-list-item[disabled] {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-more-info-add-to": HaMoreInfoAddTo;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
mdiPencil,
|
mdiPencil,
|
||||||
mdiPencilOff,
|
mdiPencilOff,
|
||||||
mdiPencilOutline,
|
mdiPencilOutline,
|
||||||
|
mdiPlusBoxMultipleOutline,
|
||||||
mdiTransitConnectionVariant,
|
mdiTransitConnectionVariant,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import type { HassEntity } from "home-assistant-js-websocket";
|
import type { HassEntity } from "home-assistant-js-websocket";
|
||||||
@@ -60,6 +61,7 @@ import {
|
|||||||
computeShowLogBookComponent,
|
computeShowLogBookComponent,
|
||||||
} from "./const";
|
} from "./const";
|
||||||
import "./controls/more-info-default";
|
import "./controls/more-info-default";
|
||||||
|
import "./ha-more-info-add-to";
|
||||||
import "./ha-more-info-history-and-logbook";
|
import "./ha-more-info-history-and-logbook";
|
||||||
import "./ha-more-info-info";
|
import "./ha-more-info-info";
|
||||||
import "./ha-more-info-settings";
|
import "./ha-more-info-settings";
|
||||||
@@ -73,7 +75,7 @@ export interface MoreInfoDialogParams {
|
|||||||
data?: Record<string, any>;
|
data?: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type View = "info" | "history" | "settings" | "related";
|
type View = "info" | "history" | "settings" | "related" | "add_to";
|
||||||
|
|
||||||
interface ChildView {
|
interface ChildView {
|
||||||
viewTag: string;
|
viewTag: string;
|
||||||
@@ -194,6 +196,10 @@ export class MoreInfoDialog extends LitElement {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _shouldShowAddEntityTo(): boolean {
|
||||||
|
return !!this.hass.auth.external?.config.hasEntityAddTo;
|
||||||
|
}
|
||||||
|
|
||||||
private _getDeviceId(): string | null {
|
private _getDeviceId(): string | null {
|
||||||
const entity = this.hass.entities[this._entityId!] as
|
const entity = this.hass.entities[this._entityId!] as
|
||||||
| EntityRegistryEntry
|
| EntityRegistryEntry
|
||||||
@@ -295,6 +301,11 @@ export class MoreInfoDialog extends LitElement {
|
|||||||
this._setView("related");
|
this._setView("related");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _goToAddEntityTo(ev) {
|
||||||
|
if (!shouldHandleRequestSelectedEvent(ev)) return;
|
||||||
|
this._setView("add_to");
|
||||||
|
}
|
||||||
|
|
||||||
private _breadcrumbClick(ev: Event) {
|
private _breadcrumbClick(ev: Event) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this._setView("related");
|
this._setView("related");
|
||||||
@@ -521,6 +532,22 @@ export class MoreInfoDialog extends LitElement {
|
|||||||
.path=${mdiInformationOutline}
|
.path=${mdiInformationOutline}
|
||||||
></ha-svg-icon>
|
></ha-svg-icon>
|
||||||
</ha-list-item>
|
</ha-list-item>
|
||||||
|
${this._shouldShowAddEntityTo()
|
||||||
|
? html`
|
||||||
|
<ha-list-item
|
||||||
|
graphic="icon"
|
||||||
|
@request-selected=${this._goToAddEntityTo}
|
||||||
|
>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.dialogs.more_info_control.add_entity_to"
|
||||||
|
)}
|
||||||
|
<ha-svg-icon
|
||||||
|
slot="graphic"
|
||||||
|
.path=${mdiPlusBoxMultipleOutline}
|
||||||
|
></ha-svg-icon>
|
||||||
|
</ha-list-item>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
</ha-button-menu>
|
</ha-button-menu>
|
||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
@@ -613,6 +640,13 @@ export class MoreInfoDialog extends LitElement {
|
|||||||
: "entity"}
|
: "entity"}
|
||||||
></ha-related-items>
|
></ha-related-items>
|
||||||
`
|
`
|
||||||
|
: this._currView === "add_to"
|
||||||
|
? html`
|
||||||
|
<ha-more-info-add-to
|
||||||
|
.hass=${this.hass}
|
||||||
|
.entityId=${entityId}
|
||||||
|
></ha-more-info-add-to>
|
||||||
|
`
|
||||||
: nothing
|
: nothing
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -36,6 +36,13 @@ interface EMOutgoingMessageConfigGet extends EMMessage {
|
|||||||
type: "config/get";
|
type: "config/get";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface EMOutgoingMessageEntityAddToGetActions extends EMMessage {
|
||||||
|
type: "entity/add_to/get_actions";
|
||||||
|
payload: {
|
||||||
|
entity_id: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface EMOutgoingMessageBarCodeScan extends EMMessage {
|
interface EMOutgoingMessageBarCodeScan extends EMMessage {
|
||||||
type: "bar_code/scan";
|
type: "bar_code/scan";
|
||||||
payload: {
|
payload: {
|
||||||
@@ -75,6 +82,10 @@ interface EMOutgoingMessageWithAnswer {
|
|||||||
request: EMOutgoingMessageConfigGet;
|
request: EMOutgoingMessageConfigGet;
|
||||||
response: ExternalConfig;
|
response: ExternalConfig;
|
||||||
};
|
};
|
||||||
|
"entity/add_to/get_actions": {
|
||||||
|
request: EMOutgoingMessageEntityAddToGetActions;
|
||||||
|
response: ExternalEntityAddToActions;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EMOutgoingMessageExoplayerPlayHLS extends EMMessage {
|
interface EMOutgoingMessageExoplayerPlayHLS extends EMMessage {
|
||||||
@@ -157,6 +168,14 @@ interface EMOutgoingMessageThreadStoreInPlatformKeychain extends EMMessage {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface EMOutgoingMessageAddEntityTo extends EMMessage {
|
||||||
|
type: "entity/add_to";
|
||||||
|
payload: {
|
||||||
|
entity_id: string;
|
||||||
|
app_payload: string; // Opaque string received from get_actions
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
type EMOutgoingMessageWithoutAnswer =
|
type EMOutgoingMessageWithoutAnswer =
|
||||||
| EMMessageResultError
|
| EMMessageResultError
|
||||||
| EMMessageResultSuccess
|
| EMMessageResultSuccess
|
||||||
@@ -177,7 +196,8 @@ type EMOutgoingMessageWithoutAnswer =
|
|||||||
| EMOutgoingMessageThemeUpdate
|
| EMOutgoingMessageThemeUpdate
|
||||||
| EMOutgoingMessageThreadStoreInPlatformKeychain
|
| EMOutgoingMessageThreadStoreInPlatformKeychain
|
||||||
| EMOutgoingMessageImprovScan
|
| EMOutgoingMessageImprovScan
|
||||||
| EMOutgoingMessageImprovConfigureDevice;
|
| EMOutgoingMessageImprovConfigureDevice
|
||||||
|
| EMOutgoingMessageAddEntityTo;
|
||||||
|
|
||||||
export interface EMIncomingMessageRestart {
|
export interface EMIncomingMessageRestart {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -305,6 +325,19 @@ export interface ExternalConfig {
|
|||||||
canSetupImprov?: boolean;
|
canSetupImprov?: boolean;
|
||||||
downloadFileSupported?: boolean;
|
downloadFileSupported?: boolean;
|
||||||
appVersion?: string;
|
appVersion?: string;
|
||||||
|
hasEntityAddTo?: boolean; // Supports "Add to" from more-info dialog, with action coming from external app
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExternalEntityAddToAction {
|
||||||
|
enabled: boolean;
|
||||||
|
name: string; // Translated name of the action to be displayed in the UI
|
||||||
|
details?: string; // Optional translated details of the action to be displayed in the UI
|
||||||
|
mdi_icon: string; // MDI icon name to be displayed in the UI (e.g., "mdi:car")
|
||||||
|
app_payload: string; // Opaque string to be sent back when the action is selected
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExternalEntityAddToActions {
|
||||||
|
actions: ExternalEntityAddToAction[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ExternalMessaging {
|
export class ExternalMessaging {
|
||||||
|
|||||||
@@ -1434,6 +1434,7 @@
|
|||||||
"back_to_info": "Back to info",
|
"back_to_info": "Back to info",
|
||||||
"info": "Information",
|
"info": "Information",
|
||||||
"related": "Related",
|
"related": "Related",
|
||||||
|
"add_entity_to": "Add to",
|
||||||
"history": "History",
|
"history": "History",
|
||||||
"aggregate": "5-minute aggregated",
|
"aggregate": "5-minute aggregated",
|
||||||
"logbook": "Activity",
|
"logbook": "Activity",
|
||||||
@@ -1450,6 +1451,10 @@
|
|||||||
"last_action": "Last action",
|
"last_action": "Last action",
|
||||||
"last_triggered": "Last triggered"
|
"last_triggered": "Last triggered"
|
||||||
},
|
},
|
||||||
|
"add_to": {
|
||||||
|
"no_actions": "No actions available",
|
||||||
|
"action_failed": "Failed to perform the action {error}"
|
||||||
|
},
|
||||||
"sun": {
|
"sun": {
|
||||||
"azimuth": "Azimuth",
|
"azimuth": "Azimuth",
|
||||||
"elevation": "Elevation",
|
"elevation": "Elevation",
|
||||||
|
|||||||
Reference in New Issue
Block a user