Collapsible blueprint input sections (#19946)

* Deduplicate blueprint editor code

* Collapsible blueprint sections

* add description

* renamed collapsed

* unused import

* unused import

* Don't allow collapsing sections with required

* Update to new schema
This commit is contained in:
karwosts 2024-05-29 06:44:11 -07:00 committed by GitHub
parent f1345af526
commit a629f01300
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 134 additions and 44 deletions

View File

@ -21,6 +21,8 @@ export class HaExpansionPanel extends LitElement {
@property({ type: Boolean, reflect: true }) leftChevron = false; @property({ type: Boolean, reflect: true }) leftChevron = false;
@property({ type: Boolean, reflect: true }) noCollapse = false;
@property() header?: string; @property() header?: string;
@property() secondary?: string; @property() secondary?: string;
@ -34,16 +36,17 @@ export class HaExpansionPanel extends LitElement {
<div class="top ${classMap({ expanded: this.expanded })}"> <div class="top ${classMap({ expanded: this.expanded })}">
<div <div
id="summary" id="summary"
class=${classMap({ noCollapse: this.noCollapse })}
@click=${this._toggleContainer} @click=${this._toggleContainer}
@keydown=${this._toggleContainer} @keydown=${this._toggleContainer}
@focus=${this._focusChanged} @focus=${this._focusChanged}
@blur=${this._focusChanged} @blur=${this._focusChanged}
role="button" role="button"
tabindex="0" tabindex=${this.noCollapse ? -1 : 0}
aria-expanded=${this.expanded} aria-expanded=${this.expanded}
aria-controls="sect1" aria-controls="sect1"
> >
${this.leftChevron ${this.leftChevron && !this.noCollapse
? html` ? html`
<ha-svg-icon <ha-svg-icon
.path=${mdiChevronDown} .path=${mdiChevronDown}
@ -57,7 +60,7 @@ export class HaExpansionPanel extends LitElement {
<slot class="secondary" name="secondary">${this.secondary}</slot> <slot class="secondary" name="secondary">${this.secondary}</slot>
</div> </div>
</slot> </slot>
${!this.leftChevron ${!this.leftChevron && !this.noCollapse
? html` ? html`
<ha-svg-icon <ha-svg-icon
.path=${mdiChevronDown} .path=${mdiChevronDown}
@ -106,6 +109,9 @@ export class HaExpansionPanel extends LitElement {
return; return;
} }
ev.preventDefault(); ev.preventDefault();
if (this.noCollapse) {
return;
}
const newExpanded = !this.expanded; const newExpanded = !this.expanded;
fireEvent(this, "expanded-will-change", { expanded: newExpanded }); fireEvent(this, "expanded-will-change", { expanded: newExpanded });
this._container.style.overflow = "hidden"; this._container.style.overflow = "hidden";
@ -130,6 +136,9 @@ export class HaExpansionPanel extends LitElement {
} }
private _focusChanged(ev) { private _focusChanged(ev) {
if (this.noCollapse) {
return;
}
this.shadowRoot!.querySelector(".top")!.classList.toggle( this.shadowRoot!.querySelector(".top")!.classList.toggle(
"focused", "focused",
ev.type === "focus" ev.type === "focus"
@ -191,6 +200,9 @@ export class HaExpansionPanel extends LitElement {
font-weight: 500; font-weight: 500;
outline: none; outline: none;
} }
#summary.noCollapse {
cursor: default;
}
.summary-icon.expanded { .summary-icon.expanded {
transform: rotate(180deg); transform: rotate(180deg);

View File

@ -13,7 +13,7 @@ export interface Blueprint {
export interface BlueprintMetaData { export interface BlueprintMetaData {
domain: BlueprintDomain; domain: BlueprintDomain;
name: string; name: string;
input?: Record<string, BlueprintInput | null>; input?: Record<string, BlueprintInput | BlueprintInputSection | null>;
description?: string; description?: string;
source_url?: string; source_url?: string;
author?: string; author?: string;
@ -26,6 +26,14 @@ export interface BlueprintInput {
default?: any; default?: any;
} }
export interface BlueprintInputSection {
name?: string;
icon?: string;
description?: string;
collapsed?: boolean;
input: Record<string, BlueprintInput | null>;
}
export interface BlueprintImportResult { export interface BlueprintImportResult {
suggested_filename: string; suggested_filename: string;
raw_data: string; raw_data: string;

View File

@ -1,5 +1,5 @@
import "@material/mwc-button/mwc-button"; import "@material/mwc-button/mwc-button";
import { css, CSSResultGroup, html, LitElement } from "lit"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import { nestedArrayMove } from "../../../common/util/array-move"; import { nestedArrayMove } from "../../../common/util/array-move";
@ -11,7 +11,12 @@ import "../../../components/ha-markdown";
import "../../../components/ha-selector/ha-selector"; import "../../../components/ha-selector/ha-selector";
import "../../../components/ha-settings-row"; import "../../../components/ha-settings-row";
import { BlueprintAutomationConfig } from "../../../data/automation"; import { BlueprintAutomationConfig } from "../../../data/automation";
import { BlueprintOrError, Blueprints } from "../../../data/blueprint"; import {
BlueprintInput,
BlueprintInputSection,
BlueprintOrError,
Blueprints,
} from "../../../data/blueprint";
import { BlueprintScriptConfig } from "../../../data/script"; import { BlueprintScriptConfig } from "../../../data/script";
import { haStyle } from "../../../resources/styles"; import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
@ -46,6 +51,7 @@ export abstract class HaBlueprintGenericEditor extends LitElement {
protected renderCard() { protected renderCard() {
const blueprint = this._blueprint; const blueprint = this._blueprint;
let border = true;
return html` return html`
<ha-card <ha-card
outlined outlined
@ -91,44 +97,14 @@ export abstract class HaBlueprintGenericEditor extends LitElement {
Object.keys(blueprint.metadata.input).length Object.keys(blueprint.metadata.input).length
? Object.entries(blueprint.metadata.input).map( ? Object.entries(blueprint.metadata.input).map(
([key, value]) => { ([key, value]) => {
const selector = value?.selector ?? { text: undefined }; if (value && "input" in value) {
const type = Object.keys(selector)[0]; const section = this.renderSection(key, value);
const enhancedSelector = [ border = false;
"action", return section;
"condition", }
"trigger", const row = this.renderSettingRow(key, value, border);
].includes(type) border = true;
? { return row;
[type]: {
...selector[type],
path: [key],
},
}
: selector;
return html`<ha-settings-row .narrow=${this.narrow}>
<span slot="heading">${value?.name || key}</span>
<ha-markdown
slot="description"
class="card-content"
breaks
.content=${value?.description}
></ha-markdown>
${html`<ha-selector
.hass=${this.hass}
.selector=${enhancedSelector}
.key=${key}
.disabled=${this.disabled}
.required=${value?.default === undefined}
.placeholder=${value?.default}
.value=${this._config.use_blueprint.input &&
key in this._config.use_blueprint.input
? this._config.use_blueprint.input[key]
: value?.default}
@value-changed=${this._inputChanged}
@item-moved=${this._itemMoved}
></ha-selector>`}
</ha-settings-row>`;
} }
) )
: html`<p class="padding"> : html`<p class="padding">
@ -141,6 +117,85 @@ export abstract class HaBlueprintGenericEditor extends LitElement {
`; `;
} }
private renderSection(sectionKey: string, section: BlueprintInputSection) {
const title = section?.name || sectionKey;
const anyRequired =
section.input &&
Object.values(section.input).some(
(item) => item === null || item.default === undefined
);
const expanded = !section.collapsed || anyRequired;
return html` <ha-expansion-panel
outlined
.expanded=${expanded}
.noCollapse=${anyRequired}
>
<div slot="header" role="heading" aria-level="3" class="section-header">
${section?.icon
? html` <ha-icon
class="section-header"
.icon=${section.icon}
></ha-icon>`
: nothing}
<ha-markdown .content=${title}></ha-markdown>
</div>
<div class="content">
${section?.description
? html`<ha-markdown .content=${section.description}></ha-markdown>`
: nothing}
${section.input
? Object.entries(section.input).map(([key, value]) =>
this.renderSettingRow(key, value, true)
)
: nothing}
</div>
</ha-expansion-panel>`;
}
private renderSettingRow(
key: string,
value: BlueprintInput | null,
border: boolean
) {
const selector = value?.selector ?? { text: undefined };
const type = Object.keys(selector)[0];
const enhancedSelector = ["action", "condition", "trigger"].includes(type)
? {
[type]: {
...selector[type],
path: [key],
},
}
: selector;
return html`<ha-settings-row
.narrow=${this.narrow}
class=${border ? "border" : ""}
>
<span slot="heading">${value?.name || key}</span>
<ha-markdown
slot="description"
class="card-content"
breaks
.content=${value?.description}
></ha-markdown>
${html`<ha-selector
.hass=${this.hass}
.selector=${enhancedSelector}
.key=${key}
.disabled=${this.disabled}
.required=${value?.default === undefined}
.placeholder=${value?.default}
.value=${this._config.use_blueprint.input &&
key in this._config.use_blueprint.input
? this._config.use_blueprint.input[key]
: value?.default}
@value-changed=${this._inputChanged}
@item-moved=${this._itemMoved}
></ha-selector>`}
</ha-settings-row>`;
}
protected abstract _getBlueprints(); protected abstract _getBlueprints();
private _blueprintChanged(ev) { private _blueprintChanged(ev) {
@ -219,6 +274,7 @@ export abstract class HaBlueprintGenericEditor extends LitElement {
} }
ha-card.blueprint { ha-card.blueprint {
margin: 0 auto; margin: 0 auto;
margin-bottom: 64px;
} }
.padding { .padding {
padding: 16px; padding: 16px;
@ -253,8 +309,15 @@ export abstract class HaBlueprintGenericEditor extends LitElement {
--paper-time-input-justify-content: flex-end; --paper-time-input-justify-content: flex-end;
--settings-row-content-width: 100%; --settings-row-content-width: 100%;
--settings-row-prefix-display: contents; --settings-row-prefix-display: contents;
}
ha-settings-row.border {
border-top: 1px solid var(--divider-color); border-top: 1px solid var(--divider-color);
} }
ha-expansion-panel {
margin: 8px;
margin-left: 8px;
margin-right: 8px;
}
ha-alert { ha-alert {
margin-bottom: 16px; margin-bottom: 16px;
display: block; display: block;
@ -263,6 +326,13 @@ export abstract class HaBlueprintGenericEditor extends LitElement {
border-radius: var(--ha-card-border-radius, 12px); border-radius: var(--ha-card-border-radius, 12px);
overflow: hidden; overflow: hidden;
} }
div.section-header {
display: flex;
vertical-align: middle;
}
ha-icon.section-header {
padding-right: 10px;
}
`, `,
]; ];
} }