mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-23 09:16:38 +00:00
Related blueprints (#16618)
This commit is contained in:
parent
e0c1f98803
commit
a70d7d8de3
@ -1,31 +1,30 @@
|
||||
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import "@material/mwc-list/mwc-list";
|
||||
import { mdiDevices, mdiPaletteSwatch, mdiSofa } from "@mdi/js";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
nothing,
|
||||
PropertyValues,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import {
|
||||
AreaRegistryEntry,
|
||||
subscribeAreaRegistry,
|
||||
} from "../data/area_registry";
|
||||
import { Blueprints, fetchBlueprints } from "../data/blueprint";
|
||||
import { ConfigEntry, getConfigEntries } from "../data/config_entries";
|
||||
import {
|
||||
DeviceRegistryEntry,
|
||||
subscribeDeviceRegistry,
|
||||
} from "../data/device_registry";
|
||||
import { SceneEntity } from "../data/scene";
|
||||
import { findRelated, ItemType, RelatedResult } from "../data/search";
|
||||
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { brandsUrl } from "../util/brands-url";
|
||||
import "./ha-icon-next";
|
||||
import "./ha-list-item";
|
||||
import "./ha-state-icon";
|
||||
import "./ha-switch";
|
||||
|
||||
@customElement("ha-related-items")
|
||||
export class HaRelatedItems extends SubscribeMixin(LitElement) {
|
||||
export class HaRelatedItems extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public itemType!: ItemType;
|
||||
@ -34,29 +33,31 @@ export class HaRelatedItems extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _entries?: ConfigEntry[];
|
||||
|
||||
@state() private _devices?: DeviceRegistryEntry[];
|
||||
|
||||
@state() private _areas?: AreaRegistryEntry[];
|
||||
@state() private _blueprints?: Record<"automation" | "script", Blueprints>;
|
||||
|
||||
@state() private _related?: RelatedResult;
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeDeviceRegistry(this.hass.connection!, (devices) => {
|
||||
this._devices = devices;
|
||||
}),
|
||||
subscribeAreaRegistry(this.hass.connection!, (areas) => {
|
||||
this._areas = areas;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
getConfigEntries(this.hass).then((configEntries) => {
|
||||
this._entries = configEntries;
|
||||
});
|
||||
}
|
||||
|
||||
private async _fetchConfigEntries() {
|
||||
if (this._entries) {
|
||||
return;
|
||||
}
|
||||
this.hass.loadBackendTranslation("title");
|
||||
this._entries = await getConfigEntries(this.hass);
|
||||
}
|
||||
|
||||
private async _fetchBlueprints() {
|
||||
if (this._blueprints) {
|
||||
return;
|
||||
}
|
||||
const [automation, script] = await Promise.all([
|
||||
fetchBlueprints(this.hass, "automation"),
|
||||
fetchBlueprints(this.hass, "script"),
|
||||
]);
|
||||
this._blueprints = { automation, script };
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
@ -81,7 +82,10 @@ export class HaRelatedItems extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
return html`
|
||||
${this._related.config_entry && this._entries
|
||||
? this._related.config_entry.map((relatedConfigEntryId) => {
|
||||
? html`<h3>
|
||||
${this.hass.localize("ui.components.related-items.integration")}:
|
||||
</h3><mwc-list>${this._related.config_entry.map(
|
||||
(relatedConfigEntryId) => {
|
||||
const entry: ConfigEntry | undefined = this._entries!.find(
|
||||
(configEntry) => configEntry.entry_id === relatedConfigEntryId
|
||||
);
|
||||
@ -89,69 +93,101 @@ export class HaRelatedItems extends SubscribeMixin(LitElement) {
|
||||
return "";
|
||||
}
|
||||
return html`
|
||||
<h3>
|
||||
${this.hass.localize(
|
||||
"ui.components.related-items.integration"
|
||||
)}:
|
||||
</h3>
|
||||
<a
|
||||
href=${`/config/integrations#config_entry=${relatedConfigEntryId}`}
|
||||
@click=${this._navigateAwayClose}
|
||||
>
|
||||
<ha-list-item hasMeta graphic="icon">
|
||||
<img
|
||||
.src=${brandsUrl({
|
||||
domain: entry.domain,
|
||||
type: "icon",
|
||||
useFallback: true,
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
})}
|
||||
alt=${entry.domain}
|
||||
slot="graphic"
|
||||
/>
|
||||
${this.hass.localize(`component.${entry.domain}.title`)}:
|
||||
${entry.title}
|
||||
${entry.title} <ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</a>
|
||||
`;
|
||||
})
|
||||
}
|
||||
)}</mw-list>`
|
||||
: ""}
|
||||
${this._related.device && this._devices
|
||||
? this._related.device.map((relatedDeviceId) => {
|
||||
const device: DeviceRegistryEntry | undefined = this._devices!.find(
|
||||
(dev) => dev.id === relatedDeviceId
|
||||
);
|
||||
${this._related.device
|
||||
? html`<h3>
|
||||
${this.hass.localize("ui.components.related-items.device")}:
|
||||
</h3>
|
||||
${this._related.device.map((relatedDeviceId) => {
|
||||
const device = this.hass.devices[relatedDeviceId];
|
||||
if (!device) {
|
||||
return "";
|
||||
}
|
||||
return html`
|
||||
<h3>
|
||||
${this.hass.localize("ui.components.related-items.device")}:
|
||||
</h3>
|
||||
<a
|
||||
href="/config/devices/device/${relatedDeviceId}"
|
||||
@click=${this._navigateAwayClose}
|
||||
>
|
||||
<ha-list-item hasMeta graphic="icon">
|
||||
<ha-svg-icon
|
||||
.path=${mdiDevices}
|
||||
slot="graphic"
|
||||
></ha-svg-icon>
|
||||
${device.name_by_user || device.name}
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</a>
|
||||
`;
|
||||
})
|
||||
})} </mwc-list>
|
||||
`
|
||||
: ""}
|
||||
${this._related.area && this._areas
|
||||
? this._related.area.map((relatedAreaId) => {
|
||||
const area: AreaRegistryEntry | undefined = this._areas!.find(
|
||||
(ar) => ar.area_id === relatedAreaId
|
||||
);
|
||||
${this._related.area
|
||||
? html`<h3>
|
||||
${this.hass.localize("ui.components.related-items.area")}:
|
||||
</h3>
|
||||
<mwc-list
|
||||
>${this._related.area.map((relatedAreaId) => {
|
||||
const area = this.hass.areas[relatedAreaId];
|
||||
if (!area) {
|
||||
return "";
|
||||
}
|
||||
return html`
|
||||
<h3>
|
||||
${this.hass.localize("ui.components.related-items.area")}:
|
||||
</h3>
|
||||
<a
|
||||
href="/config/areas/area/${relatedAreaId}"
|
||||
@click=${this._navigateAwayClose}
|
||||
>
|
||||
<ha-list-item
|
||||
hasMeta
|
||||
.graphic=${area.picture ? "avatar" : "icon"}
|
||||
>
|
||||
${area.picture
|
||||
? html` <div
|
||||
class="avatar"
|
||||
style=${styleMap({
|
||||
backgroundImage: `url(${area.picture})`,
|
||||
})}
|
||||
slot="graphic"
|
||||
></div>`
|
||||
: html`<ha-svg-icon
|
||||
.path=${mdiSofa}
|
||||
slot="graphic"
|
||||
></ha-svg-icon>`}
|
||||
${area.name}
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</a>
|
||||
`;
|
||||
})
|
||||
})}</mwc-list
|
||||
>`
|
||||
: ""}
|
||||
${this._related.entity
|
||||
? html`
|
||||
<h3>
|
||||
${this.hass.localize("ui.components.related-items.entity")}:
|
||||
</h3>
|
||||
<ul>
|
||||
<mwc-list>
|
||||
${this._related.entity.map((entityId) => {
|
||||
const entity: HassEntity | undefined =
|
||||
this.hass.states[entityId];
|
||||
@ -159,48 +195,56 @@ export class HaRelatedItems extends SubscribeMixin(LitElement) {
|
||||
return "";
|
||||
}
|
||||
return html`
|
||||
<li>
|
||||
<button
|
||||
<ha-list-item
|
||||
@click=${this._openMoreInfo}
|
||||
.entityId=${entityId}
|
||||
class="link"
|
||||
hasMeta
|
||||
graphic="icon"
|
||||
>
|
||||
${entity.attributes.friendly_name || entityId}
|
||||
</button>
|
||||
</li>
|
||||
<ha-state-icon
|
||||
.state=${entity}
|
||||
slot="graphic"
|
||||
></ha-state-icon>
|
||||
${entity.attributes.friendly_name || entity.entity_id}
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
`;
|
||||
})}
|
||||
</ul>
|
||||
</mwc-list>
|
||||
`
|
||||
: ""}
|
||||
${this._related.group
|
||||
? html`
|
||||
<h3>${this.hass.localize("ui.components.related-items.group")}:</h3>
|
||||
<ul>
|
||||
<mwc-list>
|
||||
${this._related.group.map((groupId) => {
|
||||
const group: HassEntity | undefined = this.hass.states[groupId];
|
||||
if (!group) {
|
||||
return "";
|
||||
}
|
||||
return html`
|
||||
<li>
|
||||
<button
|
||||
class="link"
|
||||
<ha-list-item
|
||||
@click=${this._openMoreInfo}
|
||||
.entityId=${groupId}
|
||||
hasMeta
|
||||
graphic="icon"
|
||||
>
|
||||
<ha-state-icon
|
||||
.state=${group}
|
||||
slot="graphic"
|
||||
></ha-state-icon>
|
||||
${group.attributes.friendly_name || group.entity_id}
|
||||
</button>
|
||||
</li>
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
`;
|
||||
})}
|
||||
</ul>
|
||||
</mwc-list>
|
||||
`
|
||||
: ""}
|
||||
${this._related.scene
|
||||
? html`
|
||||
<h3>${this.hass.localize("ui.components.related-items.scene")}:</h3>
|
||||
<ul>
|
||||
<mwc-list>
|
||||
${this._related.scene.map((sceneId) => {
|
||||
const scene: SceneEntity | undefined =
|
||||
this.hass.states[sceneId];
|
||||
@ -208,18 +252,51 @@ export class HaRelatedItems extends SubscribeMixin(LitElement) {
|
||||
return "";
|
||||
}
|
||||
return html`
|
||||
<li>
|
||||
<button
|
||||
class="link"
|
||||
<ha-list-item
|
||||
@click=${this._openMoreInfo}
|
||||
.entityId=${sceneId}
|
||||
hasMeta
|
||||
graphic="icon"
|
||||
>
|
||||
<ha-state-icon
|
||||
.state=${scene}
|
||||
slot="graphic"
|
||||
></ha-state-icon>
|
||||
${scene.attributes.friendly_name || scene.entity_id}
|
||||
</button>
|
||||
</li>
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
`;
|
||||
})}
|
||||
</ul>
|
||||
</mwc-list>
|
||||
`
|
||||
: ""}
|
||||
${this._related.automation_blueprint
|
||||
? html`
|
||||
<h3>
|
||||
${this.hass.localize("ui.components.related-items.blueprint")}:
|
||||
</h3>
|
||||
<mwc-list>
|
||||
${this._related.automation_blueprint.map((path) => {
|
||||
const blueprintMeta = this._blueprints
|
||||
? this._blueprints.automation[path]
|
||||
: undefined;
|
||||
return html`<a
|
||||
href="/config/blueprint/dashboard"
|
||||
@click=${this._navigateAwayClose}
|
||||
>
|
||||
<ha-list-item hasMeta graphic="icon">
|
||||
<ha-svg-icon
|
||||
.path=${mdiPaletteSwatch}
|
||||
slot="graphic"
|
||||
></ha-svg-icon>
|
||||
${!blueprintMeta || "error" in blueprintMeta
|
||||
? path
|
||||
: blueprintMeta.metadata.name || path}
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</a>`;
|
||||
})}
|
||||
</mwc-list>
|
||||
`
|
||||
: ""}
|
||||
${this._related.automation
|
||||
@ -227,7 +304,7 @@ export class HaRelatedItems extends SubscribeMixin(LitElement) {
|
||||
<h3>
|
||||
${this.hass.localize("ui.components.related-items.automation")}:
|
||||
</h3>
|
||||
<ul>
|
||||
<mwc-list>
|
||||
${this._related.automation.map((automationId) => {
|
||||
const automation: HassEntity | undefined =
|
||||
this.hass.states[automationId];
|
||||
@ -235,19 +312,52 @@ export class HaRelatedItems extends SubscribeMixin(LitElement) {
|
||||
return "";
|
||||
}
|
||||
return html`
|
||||
<li>
|
||||
<button
|
||||
class="link"
|
||||
<ha-list-item
|
||||
@click=${this._openMoreInfo}
|
||||
.entityId=${automationId}
|
||||
hasMeta
|
||||
graphic="icon"
|
||||
>
|
||||
<ha-state-icon
|
||||
.state=${automation}
|
||||
slot="graphic"
|
||||
></ha-state-icon>
|
||||
${automation.attributes.friendly_name ||
|
||||
automation.entity_id}
|
||||
</button>
|
||||
</li>
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
`;
|
||||
})}
|
||||
</ul>
|
||||
</mwc-list>
|
||||
`
|
||||
: ""}
|
||||
${this._related.script_blueprint
|
||||
? html`
|
||||
<h3>
|
||||
${this.hass.localize("ui.components.related-items.blueprint")}:
|
||||
</h3>
|
||||
<mwc-list>
|
||||
${this._related.script_blueprint.map((path) => {
|
||||
const blueprintMeta = this._blueprints
|
||||
? this._blueprints.script[path]
|
||||
: undefined;
|
||||
return html`<a
|
||||
href="/config/blueprint/dashboard"
|
||||
@click=${this._navigateAwayClose}
|
||||
>
|
||||
<ha-list-item hasMeta graphic="icon">
|
||||
<ha-svg-icon
|
||||
.path=${mdiPaletteSwatch}
|
||||
slot="graphic"
|
||||
></ha-svg-icon>
|
||||
${!blueprintMeta || "error" in blueprintMeta
|
||||
? path
|
||||
: blueprintMeta.metadata.name || path}
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</a>`;
|
||||
})}
|
||||
</mwc-list>
|
||||
`
|
||||
: ""}
|
||||
${this._related.script
|
||||
@ -255,7 +365,7 @@ export class HaRelatedItems extends SubscribeMixin(LitElement) {
|
||||
<h3>
|
||||
${this.hass.localize("ui.components.related-items.script")}:
|
||||
</h3>
|
||||
<ul>
|
||||
<mwc-list>
|
||||
${this._related.script.map((scriptId) => {
|
||||
const script: HassEntity | undefined =
|
||||
this.hass.states[scriptId];
|
||||
@ -263,18 +373,22 @@ export class HaRelatedItems extends SubscribeMixin(LitElement) {
|
||||
return "";
|
||||
}
|
||||
return html`
|
||||
<li>
|
||||
<button
|
||||
class="link"
|
||||
<ha-list-item
|
||||
@click=${this._openMoreInfo}
|
||||
.entityId=${scriptId}
|
||||
hasMeta
|
||||
graphic="icon"
|
||||
>
|
||||
<ha-state-icon
|
||||
.state=${script}
|
||||
slot="graphic"
|
||||
></ha-state-icon>
|
||||
${script.attributes.friendly_name || script.entity_id}
|
||||
</button>
|
||||
</li>
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
`;
|
||||
})}
|
||||
</ul>
|
||||
</mwc-list>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
@ -290,8 +404,12 @@ export class HaRelatedItems extends SubscribeMixin(LitElement) {
|
||||
|
||||
private async _findRelated() {
|
||||
this._related = await findRelated(this.hass, this.itemType, this.itemId);
|
||||
await this.updateComplete;
|
||||
fireEvent(this, "iron-resize");
|
||||
if (this._related.config_entry) {
|
||||
this._fetchConfigEntries();
|
||||
}
|
||||
if (this._related.script_blueprint || this._related.automation_blueprint) {
|
||||
this._fetchBlueprints();
|
||||
}
|
||||
}
|
||||
|
||||
private _openMoreInfo(ev: CustomEvent) {
|
||||
@ -303,19 +421,10 @@ export class HaRelatedItems extends SubscribeMixin(LitElement) {
|
||||
return css`
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
button.link {
|
||||
color: var(--primary-color);
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border-width: initial;
|
||||
border-style: none;
|
||||
border-color: initial;
|
||||
border-image: initial;
|
||||
padding: 0px;
|
||||
font: inherit;
|
||||
text-decoration: underline;
|
||||
ha-list-item {
|
||||
--mdc-list-side-padding: 24px;
|
||||
}
|
||||
h3 {
|
||||
font-family: var(--paper-font-title_-_font-family);
|
||||
@ -327,10 +436,15 @@ export class HaRelatedItems extends SubscribeMixin(LitElement) {
|
||||
letter-spacing: var(--paper-font-title_-_letter-spacing);
|
||||
line-height: var(--paper-font-title_-_line-height);
|
||||
opacity: var(--dark-primary-opacity);
|
||||
padding: 0 24px;
|
||||
}
|
||||
h3:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
.avatar {
|
||||
background-position: center center;
|
||||
background-size: cover;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@ -3,14 +3,23 @@ import { HomeAssistant } from "../types";
|
||||
export interface RelatedResult {
|
||||
area?: string[];
|
||||
automation?: string[];
|
||||
automation_blueprint?: string[];
|
||||
config_entry?: string[];
|
||||
device?: string[];
|
||||
entity?: string[];
|
||||
group?: string[];
|
||||
scene?: string[];
|
||||
script?: string[];
|
||||
script_blueprint?: string[];
|
||||
}
|
||||
|
||||
export const SearchableDomains = new Set([
|
||||
"automation",
|
||||
"script",
|
||||
"scene",
|
||||
"group",
|
||||
]);
|
||||
|
||||
export type ItemType =
|
||||
| "area"
|
||||
| "automation"
|
||||
@ -19,7 +28,9 @@ export type ItemType =
|
||||
| "entity"
|
||||
| "group"
|
||||
| "scene"
|
||||
| "script";
|
||||
| "script"
|
||||
| "automation_blueprint"
|
||||
| "script_blueprint";
|
||||
|
||||
export const findRelated = (
|
||||
hass: HomeAssistant,
|
||||
|
@ -30,6 +30,7 @@ import {
|
||||
ExtEntityRegistryEntry,
|
||||
getExtendedEntityRegistryEntry,
|
||||
} from "../../data/entity_registry";
|
||||
import { SearchableDomains } from "../../data/search";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import "../../state-summary/state-card-content";
|
||||
import { HomeAssistant } from "../../types";
|
||||
@ -406,7 +407,9 @@ export class MoreInfoDialog extends LitElement {
|
||||
<ha-related-items
|
||||
.hass=${this.hass}
|
||||
.itemId=${entityId}
|
||||
itemType="entity"
|
||||
.itemType=${SearchableDomains.has(domain)
|
||||
? domain
|
||||
: "entity"}
|
||||
></ha-related-items>
|
||||
`
|
||||
: nothing
|
||||
@ -464,7 +467,6 @@ export class MoreInfoDialog extends LitElement {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
ha-related-items,
|
||||
ha-more-info-history-and-logbook {
|
||||
padding: 8px 24px 24px 24px;
|
||||
display: block;
|
||||
|
@ -50,6 +50,8 @@ import { HomeAssistant, Route } from "../../../types";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import { showNewAutomationDialog } from "./show-dialog-new-automation";
|
||||
import { findRelated } from "../../../data/search";
|
||||
import { fetchBlueprints } from "../../../data/blueprint";
|
||||
|
||||
@customElement("ha-automation-picker")
|
||||
class HaAutomationPicker extends LitElement {
|
||||
@ -65,6 +67,8 @@ class HaAutomationPicker extends LitElement {
|
||||
|
||||
@property() private _activeFilters?: string[];
|
||||
|
||||
@state() private _searchParms = new URLSearchParams(window.location.search);
|
||||
|
||||
@state() private _filteredAutomations?: string[] | null;
|
||||
|
||||
@state() private _filterValue?;
|
||||
@ -308,6 +312,34 @@ class HaAutomationPicker extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
if (this._searchParms.has("blueprint")) {
|
||||
this._filterBlueprint();
|
||||
}
|
||||
}
|
||||
|
||||
private async _filterBlueprint() {
|
||||
const blueprint = this._searchParms.get("blueprint");
|
||||
if (!blueprint) {
|
||||
return;
|
||||
}
|
||||
const [related, blueprints] = await Promise.all([
|
||||
findRelated(this.hass, "automation_blueprint", blueprint),
|
||||
fetchBlueprints(this.hass, "automation"),
|
||||
]);
|
||||
this._filteredAutomations = related.automation || [];
|
||||
const blueprintMeta = blueprints[blueprint];
|
||||
this._activeFilters = [
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.picker.filtered_by_blueprint",
|
||||
"name",
|
||||
!blueprintMeta || "error" in blueprintMeta
|
||||
? blueprint
|
||||
: blueprintMeta.metadata.name || blueprint
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
private _relatedFilterChanged(ev: CustomEvent) {
|
||||
this._filterValue = ev.detail.value;
|
||||
if (!this._filterValue) {
|
||||
|
@ -1,11 +1,13 @@
|
||||
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
|
||||
import {
|
||||
mdiAlertCircle,
|
||||
mdiDelete,
|
||||
mdiDownload,
|
||||
mdiEye,
|
||||
mdiHelpCircle,
|
||||
mdiRobot,
|
||||
mdiPlus,
|
||||
mdiShareVariant,
|
||||
} from "@mdi/js";
|
||||
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
|
||||
import {
|
||||
CSSResultGroup,
|
||||
html,
|
||||
@ -15,13 +17,18 @@ import {
|
||||
} from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { extractSearchParam } from "../../../common/url/search-params";
|
||||
import { DataTableColumnContainer } from "../../../components/data-table/ha-data-table";
|
||||
import {
|
||||
DataTableColumnContainer,
|
||||
RowClickedEvent,
|
||||
} from "../../../components/data-table/ha-data-table";
|
||||
import "../../../components/entity/ha-entity-toggle";
|
||||
import "../../../components/ha-fab";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-icon-overflow-menu";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { showAutomationEditor } from "../../../data/automation";
|
||||
import {
|
||||
@ -31,6 +38,7 @@ import {
|
||||
deleteBlueprint,
|
||||
} from "../../../data/blueprint";
|
||||
import { showScriptEditor } from "../../../data/script";
|
||||
import { findRelated } from "../../../data/search";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
@ -73,7 +81,7 @@ class HaBlueprintOverview extends LitElement {
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
@property({ attribute: false }) public blueprints!: Record<
|
||||
string,
|
||||
"automation" | "script",
|
||||
Blueprints
|
||||
>;
|
||||
|
||||
@ -104,7 +112,7 @@ class HaBlueprintOverview extends LitElement {
|
||||
);
|
||||
|
||||
private _columns = memoizeOne(
|
||||
(narrow, _language): DataTableColumnContainer => ({
|
||||
(narrow, _language): DataTableColumnContainer<BlueprintMetaDataPath> => ({
|
||||
name: {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.blueprint.overview.headers.name"
|
||||
@ -146,64 +154,60 @@ class HaBlueprintOverview extends LitElement {
|
||||
direction: "asc",
|
||||
width: "25%",
|
||||
},
|
||||
create: {
|
||||
actions: {
|
||||
title: "",
|
||||
width: narrow ? undefined : "20%",
|
||||
type: narrow ? "icon-button" : undefined,
|
||||
template: (_, blueprint: BlueprintMetaDataPath) =>
|
||||
width: this.narrow ? undefined : "10%",
|
||||
type: "overflow-menu",
|
||||
template: (_: string, blueprint) =>
|
||||
blueprint.error
|
||||
? ""
|
||||
: narrow
|
||||
? html`<ha-icon-button
|
||||
.blueprint=${blueprint}
|
||||
.label=${this.hass.localize(
|
||||
? html`<ha-svg-icon
|
||||
style="color: var(--error-color); display: block; margin-inline-end: 12px; margin-inline-start: auto;"
|
||||
.path=${mdiAlertCircle}
|
||||
></ha-svg-icon>`
|
||||
: html`
|
||||
<ha-icon-overflow-menu
|
||||
.hass=${this.hass}
|
||||
narrow
|
||||
.items=${[
|
||||
{
|
||||
path: mdiPlus,
|
||||
label: this.hass.localize(
|
||||
`ui.panel.config.blueprint.overview.create_${blueprint.domain}`
|
||||
)}
|
||||
@click=${this._createNew}
|
||||
.path=${mdiRobot}
|
||||
>
|
||||
</ha-icon-button>`
|
||||
: html`<mwc-button
|
||||
.blueprint=${blueprint}
|
||||
@click=${this._createNew}
|
||||
>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.blueprint.overview.create_${blueprint.domain}`
|
||||
)}
|
||||
</mwc-button>`,
|
||||
),
|
||||
action: () => this._createNew(blueprint),
|
||||
},
|
||||
share: {
|
||||
title: "",
|
||||
type: "icon-button",
|
||||
template: (_, blueprint: any) =>
|
||||
blueprint.error
|
||||
? ""
|
||||
: html`<ha-icon-button
|
||||
.blueprint=${blueprint}
|
||||
.disabled=${!blueprint.source_url}
|
||||
.label=${this.hass.localize(
|
||||
{
|
||||
path: mdiEye,
|
||||
label: this.hass.localize(
|
||||
`ui.panel.config.blueprint.overview.view_${blueprint.domain}`
|
||||
),
|
||||
action: () => this._showUsed(blueprint),
|
||||
},
|
||||
{
|
||||
path: mdiShareVariant,
|
||||
disabled: !blueprint.source_url,
|
||||
label: this.hass.localize(
|
||||
blueprint.source_url
|
||||
? "ui.panel.config.blueprint.overview.share_blueprint"
|
||||
: "ui.panel.config.blueprint.overview.share_blueprint_no_url"
|
||||
)}
|
||||
.path=${mdiShareVariant}
|
||||
@click=${this._share}
|
||||
></ha-icon-button>`,
|
||||
),
|
||||
action: () => this._share(blueprint),
|
||||
},
|
||||
delete: {
|
||||
title: "",
|
||||
type: "icon-button",
|
||||
template: (_, blueprint: any) =>
|
||||
blueprint.error
|
||||
? ""
|
||||
: html`<ha-icon-button
|
||||
.blueprint=${blueprint}
|
||||
.label=${this.hass.localize(
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
label: this.hass.localize(
|
||||
"ui.panel.config.blueprint.overview.delete_blueprint"
|
||||
)}
|
||||
.path=${mdiDelete}
|
||||
@click=${this._delete}
|
||||
></ha-icon-button>`,
|
||||
),
|
||||
path: mdiDelete,
|
||||
action: () => this._delete(blueprint),
|
||||
warning: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
</ha-icon-overflow-menu>
|
||||
`,
|
||||
},
|
||||
})
|
||||
);
|
||||
@ -229,11 +233,13 @@ class HaBlueprintOverview extends LitElement {
|
||||
.tabs=${configSections.automations}
|
||||
.columns=${this._columns(this.narrow, this.hass.language)}
|
||||
.data=${this._processedBlueprints(this.blueprints)}
|
||||
id="entity_id"
|
||||
id="path"
|
||||
.noDataText=${this.hass.localize(
|
||||
"ui.panel.config.blueprint.overview.no_blueprints"
|
||||
)}
|
||||
hasFab
|
||||
clickable
|
||||
@row-click=${this._handleRowClicked}
|
||||
.appendRow=${html` <div
|
||||
class="mdc-data-table__cell"
|
||||
style="width: 100%; text-align: center;"
|
||||
@ -310,23 +316,81 @@ class HaBlueprintOverview extends LitElement {
|
||||
fireEvent(this, "reload-blueprints");
|
||||
}
|
||||
|
||||
private _createNew = (ev) => {
|
||||
const blueprint = ev.currentTarget.blueprint as BlueprintMetaDataPath;
|
||||
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
|
||||
const blueprint = this._processedBlueprints(this.blueprints).find(
|
||||
(b) => b.path === ev.detail.id
|
||||
);
|
||||
if (blueprint.error) {
|
||||
return;
|
||||
}
|
||||
this._createNew(blueprint);
|
||||
}
|
||||
|
||||
private _showUsed = (blueprint: BlueprintMetaDataPath) => {
|
||||
navigate(
|
||||
`/config/${blueprint.domain}/dashboard?blueprint=${encodeURIComponent(
|
||||
blueprint.path
|
||||
)}`
|
||||
);
|
||||
};
|
||||
|
||||
private _createNew = (blueprint: BlueprintMetaDataPath) => {
|
||||
createNewFunctions[blueprint.domain](blueprint);
|
||||
};
|
||||
|
||||
private _share = (ev) => {
|
||||
const blueprint = ev.currentTarget.blueprint;
|
||||
private _share = (blueprint: BlueprintMetaDataPath) => {
|
||||
const params = new URLSearchParams();
|
||||
params.append("redirect", "blueprint_import");
|
||||
params.append("blueprint_url", blueprint.source_url);
|
||||
params.append("blueprint_url", blueprint.source_url!);
|
||||
window.open(
|
||||
`https://my.home-assistant.io/create-link/?${params.toString()}`
|
||||
);
|
||||
};
|
||||
|
||||
private _delete = async (ev) => {
|
||||
const blueprint = ev.currentTarget.blueprint;
|
||||
private _delete = async (blueprint: BlueprintMetaDataPath) => {
|
||||
const related = await findRelated(
|
||||
this.hass,
|
||||
`${blueprint.domain}_blueprint`,
|
||||
blueprint.path
|
||||
);
|
||||
if (related.automation?.length || related.script?.length) {
|
||||
const type = this.hass.localize(
|
||||
`ui.panel.config.blueprint.overview.types_plural.${blueprint.domain}`
|
||||
);
|
||||
const result = await showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.blueprint.overview.blueprint_in_use_title"
|
||||
),
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.blueprint.overview.blueprint_in_use_text",
|
||||
{
|
||||
type,
|
||||
list: html`<ul>
|
||||
${[...(related.automation || []), ...(related.script || [])].map(
|
||||
(item) => {
|
||||
const state = this.hass.states[item];
|
||||
return html`<li>
|
||||
${state ? `${computeStateName(state)} (${item})` : item}
|
||||
</li>`;
|
||||
}
|
||||
)}
|
||||
</ul>`,
|
||||
}
|
||||
),
|
||||
confirmText: this.hass!.localize(
|
||||
"ui.panel.config.blueprint.overview.blueprint_in_use_view",
|
||||
{ type }
|
||||
),
|
||||
});
|
||||
if (result) {
|
||||
navigate(
|
||||
`/config/${blueprint.domain}/dashboard?blueprint=${encodeURIComponent(
|
||||
blueprint.path
|
||||
)}`
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
|
@ -45,6 +45,8 @@ import { documentationUrl } from "../../../util/documentation-url";
|
||||
import { showToast } from "../../../util/toast";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||
import { findRelated } from "../../../data/search";
|
||||
import { fetchBlueprints } from "../../../data/blueprint";
|
||||
|
||||
@customElement("ha-script-picker")
|
||||
class HaScriptPicker extends LitElement {
|
||||
@ -60,6 +62,8 @@ class HaScriptPicker extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public entityRegistry!: EntityRegistryEntry[];
|
||||
|
||||
@state() private _searchParms = new URLSearchParams(window.location.search);
|
||||
|
||||
@state() private _activeFilters?: string[];
|
||||
|
||||
@state() private _filteredScripts?: string[] | null;
|
||||
@ -251,6 +255,34 @@ class HaScriptPicker extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
if (this._searchParms.has("blueprint")) {
|
||||
this._filterBlueprint();
|
||||
}
|
||||
}
|
||||
|
||||
private async _filterBlueprint() {
|
||||
const blueprint = this._searchParms.get("blueprint");
|
||||
if (!blueprint) {
|
||||
return;
|
||||
}
|
||||
const [related, blueprints] = await Promise.all([
|
||||
findRelated(this.hass, "script_blueprint", blueprint),
|
||||
fetchBlueprints(this.hass, "script"),
|
||||
]);
|
||||
this._filteredScripts = related.script || [];
|
||||
const blueprintMeta = blueprints[blueprint];
|
||||
this._activeFilters = [
|
||||
this.hass.localize(
|
||||
"ui.panel.config.script.picker.filtered_by_blueprint",
|
||||
"name",
|
||||
!blueprintMeta || "error" in blueprintMeta
|
||||
? blueprint
|
||||
: blueprintMeta.metadata.name || blueprint
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
private _relatedFilterChanged(ev: CustomEvent) {
|
||||
this._filterValue = ev.detail.value;
|
||||
if (!this._filterValue) {
|
||||
|
@ -540,7 +540,8 @@
|
||||
"group": "Part of the following groups",
|
||||
"scene": "Part of the following scenes",
|
||||
"script": "Part of the following scripts",
|
||||
"automation": "Part of the following automations"
|
||||
"automation": "Part of the following automations",
|
||||
"blueprint": "Using the following blueprints"
|
||||
},
|
||||
"data-table": {
|
||||
"search": "Search",
|
||||
@ -2147,6 +2148,7 @@
|
||||
"delete_confirm_text": "{name} will be permanently deleted.",
|
||||
"duplicate": "[%key:ui::common::duplicate%]",
|
||||
"disabled": "Disabled",
|
||||
"filtered_by_blueprint": "blueprint: {name}",
|
||||
"headers": {
|
||||
"toggle": "Enable/disable",
|
||||
"name": "Name",
|
||||
@ -2622,12 +2624,21 @@
|
||||
"automation": "Automation",
|
||||
"script": "Script"
|
||||
},
|
||||
"types_plural": {
|
||||
"automation": "automations",
|
||||
"script": "scripts"
|
||||
},
|
||||
"blueprint_in_use_title": "This blueprint is in use, and can not be deleted",
|
||||
"blueprint_in_use_text": "Please remove all below {type} that use this blueprint before deleting it. {list}",
|
||||
"blueprint_in_use_view": "view {type}",
|
||||
"confirm_delete_title": "Delete blueprint?",
|
||||
"confirm_delete_text": "{name} will be permanently deleted.",
|
||||
"add_blueprint": "Import blueprint",
|
||||
"no_blueprints": "[%key:ui::panel::config::automation::editor::blueprint::no_blueprints%]",
|
||||
"create_automation": "Create automation",
|
||||
"create_script": "Create script",
|
||||
"view_automation": "Show automations using this blueprint",
|
||||
"view_script": "Show scripts using this blueprint",
|
||||
"delete_blueprint": "Delete blueprint",
|
||||
"share_blueprint": "Share blueprint",
|
||||
"share_blueprint_no_url": "Unable to share blueprint: no source url",
|
||||
@ -2662,6 +2673,7 @@
|
||||
"run": "[%key:ui::panel::config::automation::editor::actions::run%]",
|
||||
"show_trace": "[%key:ui::panel::config::automation::editor::show_trace%]",
|
||||
"show_info": "[%key:ui::panel::config::automation::editor::show_info%]",
|
||||
"filtered_by_blueprint": "[%key:ui::panel::config::automation::picker::filtered_by_blueprint%]",
|
||||
"headers": {
|
||||
"name": "Name",
|
||||
"state": "State"
|
||||
|
Loading…
x
Reference in New Issue
Block a user