Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Collapsible blueprint input sections #19946

Merged
18 changes: 15 additions & 3 deletions src/components/ha-expansion-panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export class HaExpansionPanel extends LitElement {

@property({ type: Boolean, reflect: true }) leftChevron = false;

@property({ type: Boolean, reflect: true }) noCollapse = false;

@property() header?: string;

@property() secondary?: string;
Expand All @@ -34,16 +36,17 @@ export class HaExpansionPanel extends LitElement {
<div class="top ${classMap({ expanded: this.expanded })}">
<div
id="summary"
class=${classMap({ noCollapse: this.noCollapse })}
@click=${this._toggleContainer}
@keydown=${this._toggleContainer}
@focus=${this._focusChanged}
@blur=${this._focusChanged}
role="button"
tabindex="0"
tabindex=${this.noCollapse ? -1 : 0}
aria-expanded=${this.expanded}
aria-controls="sect1"
>
${this.leftChevron
${this.leftChevron && !this.noCollapse
? html`
<ha-svg-icon
.path=${mdiChevronDown}
Expand All @@ -57,7 +60,7 @@ export class HaExpansionPanel extends LitElement {
<slot class="secondary" name="secondary">${this.secondary}</slot>
</div>
</slot>
${!this.leftChevron
${!this.leftChevron && !this.noCollapse
? html`
<ha-svg-icon
.path=${mdiChevronDown}
Expand Down Expand Up @@ -106,6 +109,9 @@ export class HaExpansionPanel extends LitElement {
return;
}
ev.preventDefault();
if (this.noCollapse) {
return;
}
const newExpanded = !this.expanded;
fireEvent(this, "expanded-will-change", { expanded: newExpanded });
this._container.style.overflow = "hidden";
Expand All @@ -130,6 +136,9 @@ export class HaExpansionPanel extends LitElement {
}

private _focusChanged(ev) {
if (this.noCollapse) {
return;
}
this.shadowRoot!.querySelector(".top")!.classList.toggle(
"focused",
ev.type === "focus"
Expand Down Expand Up @@ -191,6 +200,9 @@ export class HaExpansionPanel extends LitElement {
font-weight: 500;
outline: none;
}
#summary.noCollapse {
cursor: default;
}

.summary-icon.expanded {
transform: rotate(180deg);
Expand Down
10 changes: 9 additions & 1 deletion src/data/blueprint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export interface Blueprint {
export interface BlueprintMetaData {
domain: BlueprintDomain;
name: string;
input?: Record<string, BlueprintInput | null>;
input?: Record<string, BlueprintInput | BlueprintInputSection | null>;
description?: string;
source_url?: string;
author?: string;
Expand All @@ -26,6 +26,14 @@ export interface BlueprintInput {
default?: any;
}

export interface BlueprintInputSection {
name?: string;
icon?: string;
description?: string;
collapsed?: boolean;
input: Record<string, BlueprintInput | null>;
}

export interface BlueprintImportResult {
suggested_filename: string;
raw_data: string;
Expand Down
150 changes: 110 additions & 40 deletions src/panels/config/blueprint/blueprint-generic-editor.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
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 { fireEvent } from "../../../common/dom/fire_event";
import { nestedArrayMove } from "../../../common/util/array-move";
Expand All @@ -11,7 +11,12 @@ import "../../../components/ha-markdown";
import "../../../components/ha-selector/ha-selector";
import "../../../components/ha-settings-row";
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 { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
Expand Down Expand Up @@ -46,6 +51,7 @@ export abstract class HaBlueprintGenericEditor extends LitElement {

protected renderCard() {
const blueprint = this._blueprint;
let border = true;
return html`
<ha-card
outlined
Expand Down Expand Up @@ -91,44 +97,14 @@ export abstract class HaBlueprintGenericEditor extends LitElement {
Object.keys(blueprint.metadata.input).length
? Object.entries(blueprint.metadata.input).map(
([key, value]) => {
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}>
<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>`;
if (value && "input" in value) {
const section = this.renderSection(key, value);
border = false;
return section;
}
const row = this.renderSettingRow(key, value, border);
border = true;
return row;
}
)
: html`<p class="padding">
Expand All @@ -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}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use an expansion panel if it can not be collapsed anyway? Shouldn't we just use a ha-card in that case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially when I wrote this they were all collapsible, so the removal of collapsing was a last minute add on.

I think I was concerned that the CSS styling might not look the same if some sections were expPanels and some were ha-cards. Just looking at it it seems like the font-size for the headers might be different, and possibly other paddings and margins as well. It could maybe be fixed but I'm not convinced the styling wouldn't further diverge in the future.

Keeping the same class I think guarantees that collapsible/noncollapsible sections always have a similar look and feel.

>
<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();

private _blueprintChanged(ev) {
Expand Down Expand Up @@ -219,6 +274,7 @@ export abstract class HaBlueprintGenericEditor extends LitElement {
}
ha-card.blueprint {
margin: 0 auto;
margin-bottom: 64px;
}
.padding {
padding: 16px;
Expand Down Expand Up @@ -253,8 +309,15 @@ export abstract class HaBlueprintGenericEditor extends LitElement {
--paper-time-input-justify-content: flex-end;
--settings-row-content-width: 100%;
--settings-row-prefix-display: contents;
}
ha-settings-row.border {
border-top: 1px solid var(--divider-color);
}
ha-expansion-panel {
margin: 8px;
margin-left: 8px;
margin-right: 8px;
}
ha-alert {
margin-bottom: 16px;
display: block;
Expand All @@ -263,6 +326,13 @@ export abstract class HaBlueprintGenericEditor extends LitElement {
border-radius: var(--ha-card-border-radius, 12px);
overflow: hidden;
}
div.section-header {
display: flex;
vertical-align: middle;
}
ha-icon.section-header {
padding-right: 10px;
}
`,
];
}
Expand Down
Loading