diff --git a/src/components/ha-selector/ha-selector-action.ts b/src/components/ha-selector/ha-selector-action.ts index ce1ed97a9b..29c6b92804 100644 --- a/src/components/ha-selector/ha-selector-action.ts +++ b/src/components/ha-selector/ha-selector-action.ts @@ -1,7 +1,7 @@ import { consume, ContextProvider } from "@lit/context"; import type { UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, html, LitElement, nothing } from "lit"; -import { customElement, property, state } from "lit/decorators"; +import { customElement, property, query, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { fullEntitiesContext } from "../../data/context"; import { @@ -13,6 +13,7 @@ import { migrateAutomationAction } from "../../data/script"; import type { ActionSelector } from "../../data/selector"; import { SubscribeMixin } from "../../mixins/subscribe-mixin"; import "../../panels/config/automation/action/ha-automation-action"; +import type HaAutomationAction from "../../panels/config/automation/action/ha-automation-action"; import type { HomeAssistant } from "../../types"; @customElement("ha-selector-action") @@ -35,6 +36,9 @@ export class HaActionSelector extends SubscribeMixin(LitElement) { @state() private _entitiesContext; + @query("ha-automation-action") + private _actionElement?: HaAutomationAction; + protected hassSubscribeRequiredHostProps = ["_entitiesContext"]; private _actions = memoizeOne((action: Action | undefined) => { @@ -61,6 +65,14 @@ export class HaActionSelector extends SubscribeMixin(LitElement) { ]; } + public expandAll() { + this._actionElement?.expandAll(); + } + + public collapseAll() { + this._actionElement?.collapseAll(); + } + protected render() { return html` ${this.label ? html`` : nothing} diff --git a/src/components/ha-selector/ha-selector-condition.ts b/src/components/ha-selector/ha-selector-condition.ts index 5096be6144..9b0814114c 100644 --- a/src/components/ha-selector/ha-selector-condition.ts +++ b/src/components/ha-selector/ha-selector-condition.ts @@ -1,8 +1,9 @@ import { css, html, LitElement, nothing } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, query } from "lit/decorators"; import type { Condition } from "../../data/automation"; import type { ConditionSelector } from "../../data/selector"; import "../../panels/config/automation/condition/ha-automation-condition"; +import type HaAutomationCondition from "../../panels/config/automation/condition/ha-automation-condition"; import type { HomeAssistant } from "../../types"; @customElement("ha-selector-condition") @@ -19,6 +20,9 @@ export class HaConditionSelector extends LitElement { @property({ type: Boolean, reflect: true }) public disabled = false; + @query("ha-automation-condition") + private _conditionElement?: HaAutomationCondition; + protected render() { return html` ${this.label ? html`` : nothing} @@ -32,6 +36,14 @@ export class HaConditionSelector extends LitElement { `; } + public expandAll() { + this._conditionElement?.expandAll(); + } + + public collapseAll() { + this._conditionElement?.collapseAll(); + } + static styles = css` ha-automation-condition { display: block; diff --git a/src/data/action.ts b/src/data/action.ts index 04a6e2e7e2..f8fe4aeb75 100644 --- a/src/data/action.ts +++ b/src/data/action.ts @@ -91,6 +91,15 @@ export const isService = (key: string | undefined): boolean | undefined => export const getService = (key: string): string => key.substring(SERVICE_PREFIX.length); +export const COLLAPSIBLE_ACTION_ELEMENTS = [ + "ha-automation-action-choose", + "ha-automation-action-condition", + "ha-automation-action-if", + "ha-automation-action-parallel", + "ha-automation-action-repeat", + "ha-automation-action-sequence", +]; + export const ACTION_BUILDING_BLOCKS = [ "choose", "if", diff --git a/src/data/condition.ts b/src/data/condition.ts index 59e7828c00..8c44a3d001 100644 --- a/src/data/condition.ts +++ b/src/data/condition.ts @@ -52,3 +52,9 @@ export const CONDITION_GROUPS: AutomationElementGroup = { } as const; export const CONDITION_BUILDING_BLOCKS = ["and", "or", "not"]; + +export const COLLAPSIBLE_CONDITION_ELEMENTS = [ + "ha-automation-condition-and", + "ha-automation-condition-not", + "ha-automation-condition-or", +]; diff --git a/src/panels/config/automation/action/ha-automation-action-editor.ts b/src/panels/config/automation/action/ha-automation-action-editor.ts index e0aabf0e02..ee352f80a8 100644 --- a/src/panels/config/automation/action/ha-automation-action-editor.ts +++ b/src/panels/config/automation/action/ha-automation-action-editor.ts @@ -5,11 +5,15 @@ import { dynamicElement } from "../../../../common/dom/dynamic-element-directive import { fireEvent } from "../../../../common/dom/fire_event"; import "../../../../components/ha-yaml-editor"; import type { HaYamlEditor } from "../../../../components/ha-yaml-editor"; +import { COLLAPSIBLE_ACTION_ELEMENTS } from "../../../../data/action"; import { migrateAutomationAction, type Action } from "../../../../data/script"; import type { HomeAssistant } from "../../../../types"; import "../ha-automation-editor-warning"; import { editorStyles } from "../styles"; -import { getAutomationActionType } from "./ha-automation-action-row"; +import { + getAutomationActionType, + type ActionElement, +} from "./ha-automation-action-row"; @customElement("ha-automation-action-editor") export default class HaAutomationActionEditor extends LitElement { @@ -34,6 +38,9 @@ export default class HaAutomationActionEditor extends LitElement { @query("ha-yaml-editor") public yamlEditor?: HaYamlEditor; + @query(COLLAPSIBLE_ACTION_ELEMENTS.join(", ")) + private _collapsibleElement?: ActionElement; + protected render() { const yamlMode = this.yamlMode || !this.uiSupported; const type = getAutomationActionType(this.action); @@ -103,6 +110,14 @@ export default class HaAutomationActionEditor extends LitElement { fireEvent(this, "value-changed", { value }); } + public expandAll() { + this._collapsibleElement?.expandAll?.(); + } + + public collapseAll() { + this._collapsibleElement?.collapseAll?.(); + } + static styles = editorStyles; } diff --git a/src/panels/config/automation/action/ha-automation-action-row.ts b/src/panels/config/automation/action/ha-automation-action-row.ts index 48d9a7dc4d..abb3a541c3 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -109,6 +109,8 @@ export const getAutomationActionType = memoizeOne( export interface ActionElement extends LitElement { action: Action; + expandAll?: () => void; + collapseAll?: () => void; } export const handleChangeEvent = (element: ActionElement, ev: CustomEvent) => { @@ -181,7 +183,7 @@ export default class HaAutomationActionRow extends LitElement { @state() private _warnings?: string[]; @query("ha-automation-action-editor") - private actionEditor?: HaAutomationActionEditor; + private _actionEditor?: HaAutomationActionEditor; protected willUpdate(changedProperties: PropertyValues) { if (changedProperties.has("yamlMode")) { @@ -479,7 +481,7 @@ export default class HaAutomationActionRow extends LitElement { this.openSidebar(value); // refresh sidebar if (this._yamlMode && !this.optionsInSidebar) { - this.actionEditor?.yamlEditor?.setValue(value); + this._actionEditor?.yamlEditor?.setValue(value); } }; @@ -582,7 +584,7 @@ export default class HaAutomationActionRow extends LitElement { if (this._selected && this.optionsInSidebar) { this.openSidebar(value); // refresh sidebar } else if (this._yamlMode) { - this.actionEditor?.yamlEditor?.setValue(value); + this._actionEditor?.yamlEditor?.setValue(value); } } }; @@ -669,6 +671,7 @@ export default class HaAutomationActionRow extends LitElement { yamlMode: this._yamlMode, } satisfies ActionSidebarConfig); this._selected = true; + this._collapsed = false; if (this.narrow) { this.scrollIntoView({ @@ -679,11 +682,39 @@ export default class HaAutomationActionRow extends LitElement { } public expand() { + if (this.optionsInSidebar) { + this._collapsed = false; + return; + } + this.updateComplete.then(() => { this.shadowRoot!.querySelector("ha-expansion-panel")!.expanded = true; }); } + public collapse() { + if (this.optionsInSidebar) { + this._collapsed = true; + return; + } + + this.updateComplete.then(() => { + this.shadowRoot!.querySelector("ha-expansion-panel")!.expanded = false; + }); + } + + public expandAll() { + this.expand(); + + this._actionEditor?.expandAll(); + } + + public collapseAll() { + this.collapse(); + + this._actionEditor?.collapseAll(); + } + private _uiSupported = memoizeOne( (type: string) => customElements.get(`ha-automation-action-${type}`) !== undefined diff --git a/src/panels/config/automation/action/ha-automation-action.ts b/src/panels/config/automation/action/ha-automation-action.ts index b024e3b47f..0d7116ce93 100644 --- a/src/panels/config/automation/action/ha-automation-action.ts +++ b/src/panels/config/automation/action/ha-automation-action.ts @@ -2,7 +2,7 @@ import { mdiDrag, mdiPlus } from "@mdi/js"; import deepClone from "deep-clone-simple"; import type { PropertyValues } from "lit"; import { LitElement, css, html, nothing } from "lit"; -import { customElement, property, state } from "lit/decorators"; +import { customElement, property, queryAll, state } from "lit/decorators"; import { repeat } from "lit/directives/repeat"; import { storage } from "../../../../common/decorators/storage"; import { fireEvent } from "../../../../common/dom/fire_event"; @@ -55,6 +55,9 @@ export default class HaAutomationAction extends LitElement { }) public _clipboard?: AutomationClipboard; + @queryAll("ha-automation-action-row") + private _actionRowElements?: HaAutomationActionRow[]; + private _focusLastActionOnChange = false; private _actionKeys = new WeakMap(); @@ -179,11 +182,14 @@ export default class HaAutomationAction extends LitElement { } public expandAll() { - const rows = this.shadowRoot!.querySelectorAll( - "ha-automation-action-row" - )!; - rows.forEach((row) => { - row.expand(); + this._actionRowElements?.forEach((row) => { + row.expandAll(); + }); + } + + public collapseAll() { + this._actionRowElements?.forEach((row) => { + row.collapseAll(); }); } diff --git a/src/panels/config/automation/action/types/ha-automation-action-choose.ts b/src/panels/config/automation/action/types/ha-automation-action-choose.ts index e5cf642a84..1fb4dae95e 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-choose.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-choose.ts @@ -1,5 +1,5 @@ import { type CSSResultGroup, LitElement, css, html } from "lit"; -import { customElement, property, state } from "lit/decorators"; +import { customElement, property, query, state } from "lit/decorators"; import { ensureArray } from "../../../../../common/array/ensure-array"; import { fireEvent } from "../../../../../common/dom/fire_event"; import "../../../../../components/ha-button"; @@ -7,7 +7,9 @@ import type { Action, ChooseAction, Option } from "../../../../../data/script"; import { haStyle } from "../../../../../resources/styles"; import type { HomeAssistant } from "../../../../../types"; import "../../option/ha-automation-option"; +import type HaAutomationOption from "../../option/ha-automation-option"; import "../ha-automation-action"; +import type HaAutomationAction from "../ha-automation-action"; import type { ActionElement } from "../ha-automation-action-row"; @customElement("ha-automation-action-choose") @@ -24,6 +26,10 @@ export class HaChooseAction extends LitElement implements ActionElement { @state() private _showDefault = false; + @query("ha-automation-option") private _optionElement?: HaAutomationOption; + + @query("ha-automation-action") private _actionElement?: HaAutomationAction; + public static get defaultConfig(): ChooseAction { return { choose: [{ conditions: [], sequence: [] }] }; } @@ -104,6 +110,16 @@ export class HaChooseAction extends LitElement implements ActionElement { fireEvent(this, "value-changed", { value: newValue }); } + public expandAll() { + this._optionElement?.expandAll(); + this._actionElement?.expandAll(); + } + + public collapseAll() { + this._optionElement?.collapseAll(); + this._actionElement?.collapseAll(); + } + static get styles(): CSSResultGroup { return [ haStyle, diff --git a/src/panels/config/automation/action/types/ha-automation-action-condition.ts b/src/panels/config/automation/action/types/ha-automation-action-condition.ts index 70525c8102..5e2946f5fb 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-condition.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-condition.ts @@ -1,5 +1,5 @@ import { css, html, LitElement, nothing } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, query } from "lit/decorators"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../../common/dom/fire_event"; import { stringCompare } from "../../../../../common/string/compare"; @@ -14,6 +14,7 @@ import { } from "../../../../../data/condition"; import type { Entries, HomeAssistant } from "../../../../../types"; import "../../condition/ha-automation-condition-editor"; +import type HaAutomationConditionEditor from "../../condition/ha-automation-condition-editor"; import "../../condition/types/ha-automation-condition-and"; import "../../condition/types/ha-automation-condition-device"; import "../../condition/types/ha-automation-condition-not"; @@ -41,6 +42,9 @@ export class HaConditionAction extends LitElement implements ActionElement { @property({ type: Boolean, attribute: "indent" }) public indent = false; + @query("ha-automation-condition-editor") + private _conditionEditor?: HaAutomationConditionEditor; + public static get defaultConfig(): Omit { return { condition: "state" }; } @@ -146,6 +150,14 @@ export class HaConditionAction extends LitElement implements ActionElement { customElements.get(`ha-automation-condition-${type}`) !== undefined ); + public expandAll() { + this._conditionEditor?.expandAll(); + } + + public collapseAll() { + this._conditionEditor?.collapseAll(); + } + static styles = css` ha-select { margin-bottom: 24px; diff --git a/src/panels/config/automation/action/types/ha-automation-action-if.ts b/src/panels/config/automation/action/types/ha-automation-action-if.ts index 666c50a898..0443540b0f 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-if.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-if.ts @@ -1,13 +1,21 @@ import type { CSSResultGroup } from "lit"; import { css, html, LitElement } from "lit"; -import { customElement, property, state } from "lit/decorators"; +import { + customElement, + property, + query, + queryAll, + state, +} from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; import "../../../../../components/ha-textfield"; import type { Action, IfAction } from "../../../../../data/script"; import { haStyle } from "../../../../../resources/styles"; import type { HomeAssistant } from "../../../../../types"; import type { Condition } from "../../../../lovelace/common/validate-condition"; +import type HaAutomationCondition from "../../condition/ha-automation-condition"; import "../ha-automation-action"; +import type HaAutomationAction from "../ha-automation-action"; import type { ActionElement } from "../ha-automation-action-row"; @customElement("ha-automation-action-if") @@ -24,6 +32,12 @@ export class HaIfAction extends LitElement implements ActionElement { @state() private _showElse = false; + @query("ha-automation-condition") + private _conditionElement?: HaAutomationCondition; + + @queryAll("ha-automation-action") + private _actionElements?: HaAutomationAction[]; + public static get defaultConfig(): IfAction { return { if: [], @@ -132,6 +146,16 @@ export class HaIfAction extends LitElement implements ActionElement { fireEvent(this, "value-changed", { value: newValue }); } + public expandAll() { + this._conditionElement?.expandAll(); + this._actionElements?.forEach((element) => element.expandAll?.()); + } + + public collapseAll() { + this._conditionElement?.collapseAll(); + this._actionElements?.forEach((element) => element.collapseAll?.()); + } + static get styles(): CSSResultGroup { return [ haStyle, diff --git a/src/panels/config/automation/action/types/ha-automation-action-parallel.ts b/src/panels/config/automation/action/types/ha-automation-action-parallel.ts index 4b36e1d2cf..88a1fa15a1 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-parallel.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-parallel.ts @@ -1,12 +1,13 @@ import type { CSSResultGroup } from "lit"; import { html, LitElement } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, query } from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; import "../../../../../components/ha-textfield"; import type { Action, ParallelAction } from "../../../../../data/script"; import { haStyle } from "../../../../../resources/styles"; import type { HomeAssistant } from "../../../../../types"; import "../ha-automation-action"; +import type HaAutomationAction from "../ha-automation-action"; import type { ActionElement } from "../ha-automation-action-row"; @customElement("ha-automation-action-parallel") @@ -21,6 +22,9 @@ export class HaParallelAction extends LitElement implements ActionElement { @property({ type: Boolean }) public indent = false; + @query("ha-automation-action") + private _actionElement?: HaAutomationAction; + public static get defaultConfig(): ParallelAction { return { parallel: [], @@ -53,6 +57,14 @@ export class HaParallelAction extends LitElement implements ActionElement { }); } + public expandAll() { + this._actionElement?.expandAll(); + } + + public collapseAll() { + this._actionElement?.collapseAll(); + } + static get styles(): CSSResultGroup { return haStyle; } diff --git a/src/panels/config/automation/action/types/ha-automation-action-repeat.ts b/src/panels/config/automation/action/types/ha-automation-action-repeat.ts index 15f492b920..0322ed6e88 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-repeat.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-repeat.ts @@ -1,6 +1,6 @@ import type { CSSResultGroup } from "lit"; import { css, html, LitElement } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, query } from "lit/decorators"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../../common/dom/fire_event"; import "../../../../../components/ha-textfield"; @@ -12,10 +12,14 @@ import type { ActionElement } from "../ha-automation-action-row"; import { isTemplate } from "../../../../../common/string/has-template"; import "../../../../../components/ha-form/ha-form"; +import type { HaForm } from "../../../../../components/ha-form/ha-form"; import type { HaFormSchema, SchemaUnion, } from "../../../../../components/ha-form/types"; +import type { HaSelector } from "../../../../../components/ha-selector/ha-selector"; +import type { HaActionSelector } from "../../../../../components/ha-selector/ha-selector-action"; +import type { HaConditionSelector } from "../../../../../components/ha-selector/ha-selector-condition"; const OPTIONS = ["count", "while", "until", "for_each"] as const; type RepeatType = (typeof OPTIONS)[number]; @@ -37,6 +41,9 @@ export class HaRepeatAction extends LitElement implements ActionElement { @property({ type: Boolean, attribute: "indent" }) public indent = false; + @query("ha-form") + private _formElement?: HaForm; + public static get defaultConfig(): RepeatAction { return { repeat: { count: 2, sequence: [] } }; } @@ -175,6 +182,41 @@ export class HaRepeatAction extends LitElement implements ActionElement { ]; } + private _getSelectorElements() { + if (this._formElement) { + const selectors = + this._formElement.shadowRoot?.querySelectorAll( + "ha-selector" + ); + + const selectorElements: (HaConditionSelector | HaActionSelector)[] = []; + + selectors?.forEach((selector) => { + selectorElements.push( + ...Array.from( + selector.shadowRoot?.querySelectorAll< + HaConditionSelector | HaActionSelector + >("ha-selector-condition, ha-selector-action") || [] + ) + ); + }); + return selectorElements; + } + return []; + } + + public expandAll() { + this._getSelectorElements().forEach((element) => { + element.expandAll?.(); + }); + } + + public collapseAll() { + this._getSelectorElements().forEach((element) => { + element.collapseAll?.(); + }); + } + private _computeLabelCallback = ( schema: SchemaUnion> ): string => { diff --git a/src/panels/config/automation/action/types/ha-automation-action-sequence.ts b/src/panels/config/automation/action/types/ha-automation-action-sequence.ts index 50f126bd2f..9a8b93522d 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-sequence.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-sequence.ts @@ -1,6 +1,6 @@ import type { CSSResultGroup } from "lit"; import { html, LitElement } from "lit"; -import { customElement, property } from "lit/decorators"; +import { query, customElement, property } from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; import "../../../../../components/ha-textfield"; import type { Action, SequenceAction } from "../../../../../data/script"; @@ -8,6 +8,7 @@ import { haStyle } from "../../../../../resources/styles"; import type { HomeAssistant } from "../../../../../types"; import "../ha-automation-action"; import type { ActionElement } from "../ha-automation-action-row"; +import type HaAutomationAction from "../ha-automation-action"; @customElement("ha-automation-action-sequence") export class HaSequenceAction extends LitElement implements ActionElement { @@ -21,6 +22,9 @@ export class HaSequenceAction extends LitElement implements ActionElement { @property({ type: Boolean }) public indent = false; + @query("ha-automation-action") + private _actionElement?: HaAutomationAction; + public static get defaultConfig(): SequenceAction { return { sequence: [], @@ -53,6 +57,14 @@ export class HaSequenceAction extends LitElement implements ActionElement { }); } + public expandAll() { + this._actionElement?.expandAll(); + } + + public collapseAll() { + this._actionElement?.collapseAll(); + } + static get styles(): CSSResultGroup { return haStyle; } diff --git a/src/panels/config/automation/condition/ha-automation-condition-editor.ts b/src/panels/config/automation/condition/ha-automation-condition-editor.ts index da2660826d..88d89b8acc 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-editor.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-editor.ts @@ -8,9 +8,11 @@ import "../../../../components/ha-yaml-editor"; import type { HaYamlEditor } from "../../../../components/ha-yaml-editor"; import type { Condition } from "../../../../data/automation"; import { expandConditionWithShorthand } from "../../../../data/automation"; +import { COLLAPSIBLE_CONDITION_ELEMENTS } from "../../../../data/condition"; import type { HomeAssistant } from "../../../../types"; import "../ha-automation-editor-warning"; import { editorStyles } from "../styles"; +import type { ConditionElement } from "./ha-automation-condition-row"; @customElement("ha-automation-condition-editor") export default class HaAutomationConditionEditor extends LitElement { @@ -33,6 +35,9 @@ export default class HaAutomationConditionEditor extends LitElement { @query("ha-yaml-editor") public yamlEditor?: HaYamlEditor; + @query(COLLAPSIBLE_CONDITION_ELEMENTS.join(", ")) + private _collapsibleElement?: ConditionElement; + private _processedCondition = memoizeOne((condition) => expandConditionWithShorthand(condition) ); @@ -108,6 +113,14 @@ export default class HaAutomationConditionEditor extends LitElement { fireEvent(this, "value-changed", { value }); } + public expandAll() { + this._collapsibleElement?.expandAll?.(); + } + + public collapseAll() { + this._collapsibleElement?.collapseAll?.(); + } + static styles = [ editorStyles, css` diff --git a/src/panels/config/automation/condition/ha-automation-condition-row.ts b/src/panels/config/automation/condition/ha-automation-condition-row.ts index 1156d3bbff..9560b284a3 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-row.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-row.ts @@ -70,6 +70,8 @@ import "./types/ha-automation-condition-zone"; export interface ConditionElement extends LitElement { condition: Condition; + expandAll?: () => void; + collapseAll?: () => void; } export const handleChangeEvent = ( @@ -575,11 +577,32 @@ export default class HaAutomationConditionRow extends LitElement { }; public expand() { + if (this.optionsInSidebar) { + this._collapsed = false; + return; + } + this.updateComplete.then(() => { this.shadowRoot!.querySelector("ha-expansion-panel")!.expanded = true; }); } + public collapse() { + this._collapsed = true; + } + + public expandAll() { + this.expand(); + + this.conditionEditor?.expandAll(); + } + + public collapseAll() { + this.collapse(); + + this.conditionEditor?.collapseAll(); + } + private _handleUiModeNotAvailable(ev: CustomEvent) { this._warnings = handleStructError(this.hass, ev.detail).warnings; if (!this._yamlMode) { @@ -622,6 +645,7 @@ export default class HaAutomationConditionRow extends LitElement { yamlMode: this._yamlMode, } satisfies ConditionSidebarConfig); this._selected = true; + this._collapsed = false; if (this.narrow) { this.scrollIntoView({ diff --git a/src/panels/config/automation/condition/ha-automation-condition.ts b/src/panels/config/automation/condition/ha-automation-condition.ts index 3604306e94..2ed3cdf401 100644 --- a/src/panels/config/automation/condition/ha-automation-condition.ts +++ b/src/panels/config/automation/condition/ha-automation-condition.ts @@ -2,7 +2,7 @@ import { mdiDrag, mdiPlus } from "@mdi/js"; import deepClone from "deep-clone-simple"; import type { PropertyValues } from "lit"; import { LitElement, css, html, nothing } from "lit"; -import { customElement, property, state } from "lit/decorators"; +import { customElement, property, queryAll, state } from "lit/decorators"; import { repeat } from "lit/directives/repeat"; import { storage } from "../../../../common/decorators/storage"; import { fireEvent } from "../../../../common/dom/fire_event"; @@ -53,6 +53,9 @@ export default class HaAutomationCondition extends LitElement { }) public _clipboard?: AutomationClipboard; + @queryAll("ha-automation-condition-row") + private _conditionRowElements?: HaAutomationConditionRow[]; + private _focusLastConditionOnChange = false; private _conditionKeys = new WeakMap(); @@ -123,11 +126,14 @@ export default class HaAutomationCondition extends LitElement { } public expandAll() { - const rows = this.shadowRoot!.querySelectorAll( - "ha-automation-condition-row" - )!; - rows.forEach((row) => { - row.expand(); + this._conditionRowElements?.forEach((row) => { + row.expandAll(); + }); + } + + public collapseAll() { + this._conditionRowElements?.forEach((row) => { + row.collapseAll(); }); } diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-logical.ts b/src/panels/config/automation/condition/types/ha-automation-condition-logical.ts index 7cea32e31a..5b97eed512 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-logical.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-logical.ts @@ -1,9 +1,10 @@ import { html, LitElement } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, query } from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; import type { LogicalCondition } from "../../../../../data/automation"; import type { HomeAssistant } from "../../../../../types"; import "../ha-automation-condition"; +import type HaAutomationCondition from "../ha-automation-condition"; import type { ConditionElement } from "../ha-automation-condition-row"; @customElement("ha-automation-condition-logical") @@ -22,6 +23,9 @@ export abstract class HaLogicalCondition @property({ type: Boolean, attribute: "sidebar" }) public optionsInSidebar = false; + @query("ha-automation-condition") + private _conditionElement?: HaAutomationCondition; + protected render() { return html` + ${!useBlueprint + ? html` + + + ${this.hass.localize( + "ui.panel.config.automation.editor.collapse_all" + )} + + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.expand_all" + )} + + ` + : nothing} +
  • ("ha-automation-action, ha-automation-condition"); + } + + public expandAll() { + this._getCollapsableElements().forEach((element) => { + element.expandAll(); + }); + } + + public collapseAll() { + this._getCollapsableElements().forEach((element) => { + element.collapseAll(); + }); + } + static get styles(): CSSResultGroup { return [ saveFabStyles, diff --git a/src/panels/config/automation/option/ha-automation-option-row.ts b/src/panels/config/automation/option/ha-automation-option-row.ts index 050e4a8654..98e7e1017f 100644 --- a/src/panels/config/automation/option/ha-automation-option-row.ts +++ b/src/panels/config/automation/option/ha-automation-option-row.ts @@ -9,7 +9,7 @@ import { } from "@mdi/js"; import type { CSSResultGroup } from "lit"; import { LitElement, css, html, nothing } from "lit"; -import { customElement, property, state } from "lit/decorators"; +import { customElement, property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { ensureArray } from "../../../../common/array/ensure-array"; import { fireEvent } from "../../../../common/dom/fire_event"; @@ -37,7 +37,9 @@ import { } from "../../../../dialogs/generic/show-dialog-box"; import type { HomeAssistant } from "../../../../types"; import "../action/ha-automation-action"; +import type HaAutomationAction from "../action/ha-automation-action"; import "../condition/ha-automation-condition"; +import type HaAutomationCondition from "../condition/ha-automation-condition"; import { editorStyles, rowStyles } from "../styles"; @customElement("ha-automation-option-row") @@ -69,6 +71,12 @@ export default class HaAutomationOptionRow extends LitElement { @consume({ context: fullEntitiesContext, subscribe: true }) _entityReg!: EntityRegistryEntry[]; + @query("ha-automation-condition") + private _conditionElement?: HaAutomationCondition; + + @query("ha-automation-action") + private _actionElement?: HaAutomationAction; + private _expandedChanged(ev) { if (ev.currentTarget.id !== "option") { return; @@ -355,6 +363,7 @@ export default class HaAutomationOptionRow extends LitElement { delete: this._removeOption, } satisfies OptionSidebarConfig); this._selected = true; + this._collapsed = false; if (this.narrow) { this.scrollIntoView({ @@ -365,11 +374,34 @@ export default class HaAutomationOptionRow extends LitElement { } public expand() { + if (this.optionsInSidebar) { + this._collapsed = false; + return; + } + this.updateComplete.then(() => { this.shadowRoot!.querySelector("ha-expansion-panel")!.expanded = true; }); } + public collapse() { + this._collapsed = true; + } + + public expandAll() { + this.expand(); + + this._conditionElement?.expandAll(); + this._actionElement?.expandAll(); + } + + public collapseAll() { + this.collapse(); + + this._conditionElement?.collapseAll(); + this._actionElement?.collapseAll(); + } + private _toggleCollapse() { this._collapsed = !this._collapsed; } diff --git a/src/panels/config/automation/option/ha-automation-option.ts b/src/panels/config/automation/option/ha-automation-option.ts index 4d74ce7af7..38e7a96ac1 100644 --- a/src/panels/config/automation/option/ha-automation-option.ts +++ b/src/panels/config/automation/option/ha-automation-option.ts @@ -2,7 +2,7 @@ import { mdiDrag, mdiPlus } from "@mdi/js"; import deepClone from "deep-clone-simple"; import type { PropertyValues } from "lit"; import { LitElement, css, html, nothing } from "lit"; -import { customElement, property, state } from "lit/decorators"; +import { customElement, property, queryAll, state } from "lit/decorators"; import { repeat } from "lit/directives/repeat"; import { storage } from "../../../../common/decorators/storage"; import { fireEvent } from "../../../../common/dom/fire_event"; @@ -41,6 +41,9 @@ export default class HaAutomationOption extends LitElement { }) public _clipboard?: AutomationClipboard; + @queryAll("ha-automation-option-row") + private _optionRowElements?: HaAutomationOptionRow[]; + private _focusLastOptionOnChange = false; private _optionsKeys = new WeakMap(); @@ -144,12 +147,11 @@ export default class HaAutomationOption extends LitElement { } public expandAll() { - const rows = this.shadowRoot!.querySelectorAll( - "ha-automation-option-row" - )!; - rows.forEach((row) => { - row.expand(); - }); + this._optionRowElements?.forEach((row) => row.expandAll()); + } + + public collapseAll() { + this._optionRowElements?.forEach((row) => row.collapseAll()); } private _addOption = () => { diff --git a/src/panels/config/script/ha-script-editor.ts b/src/panels/config/script/ha-script-editor.ts index a7224e7406..584a999fd8 100644 --- a/src/panels/config/script/ha-script-editor.ts +++ b/src/panels/config/script/ha-script-editor.ts @@ -15,6 +15,8 @@ import { mdiRobotConfused, mdiTag, mdiTransitConnection, + mdiUnfoldLessHorizontal, + mdiUnfoldMoreHorizontal, } from "@mdi/js"; import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit"; import { LitElement, css, html, nothing } from "lit"; @@ -340,6 +342,30 @@ export class HaScriptEditor extends SubscribeMixin( + ${!useBlueprint + ? html` + + + ${this.hass.localize( + "ui.panel.config.automation.editor.collapse_all" + )} + + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.expand_all" + )} + + ` + : nothing} +
  • @@ -141,10 +145,40 @@ export default class HaScriptFieldRow extends LitElement { this._collapsed = !this._collapsed; } + public expand() { + this._collapsed = false; + } + + public collapse() { + this._collapsed = true; + } + + public expandSelectorRow() { + this._selectorRowCollapsed = false; + } + + public collapseSelectorRow() { + this._selectorRowCollapsed = true; + } + private _toggleSelectorRowCollapse() { this._selectorRowCollapsed = !this._selectorRowCollapsed; } + public expandAll() { + this.expand(); + this.expandSelectorRow(); + + this._selectorEditor?.expandAll(); + } + + public collapseAll() { + this.collapse(); + this.collapseSelectorRow(); + + this._selectorEditor?.collapseAll(); + } + private _toggleSidebar(ev: Event) { ev?.stopPropagation(); @@ -155,6 +189,7 @@ export default class HaScriptFieldRow extends LitElement { } this._selected = true; + this._collapsed = false; this.openSidebar(); } @@ -168,6 +203,7 @@ export default class HaScriptFieldRow extends LitElement { } this._selectorRowSelected = true; + this._selectorRowCollapsed = false; this.openSidebar(true); } diff --git a/src/panels/config/script/ha-script-field-selector-editor.ts b/src/panels/config/script/ha-script-field-selector-editor.ts index c391de7372..d29ee9829a 100644 --- a/src/panels/config/script/ha-script-field-selector-editor.ts +++ b/src/panels/config/script/ha-script-field-selector-editor.ts @@ -1,12 +1,16 @@ import type { CSSResultGroup } from "lit"; import { LitElement, css, html, nothing } from "lit"; -import { customElement, property, state } from "lit/decorators"; +import { customElement, property, query, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../../common/dom/fire_event"; import type { LocalizeKeys } from "../../../common/translations/localize"; import "../../../components/ha-alert"; import "../../../components/ha-form/ha-form"; +import type { HaForm } from "../../../components/ha-form/ha-form"; import type { SchemaUnion } from "../../../components/ha-form/types"; +import type { HaSelector } from "../../../components/ha-selector/ha-selector"; +import type { HaActionSelector } from "../../../components/ha-selector/ha-selector-action"; +import type { HaConditionSelector } from "../../../components/ha-selector/ha-selector-condition"; import "../../../components/ha-yaml-editor"; import type { Field } from "../../../data/script"; import { SELECTOR_SELECTOR_BUILDING_BLOCKS } from "../../../data/selector/selector_selector"; @@ -33,6 +37,9 @@ export default class HaScriptFieldSelectorEditor extends LitElement { @state() private _yamlError?: undefined | "yaml_error" | "key_not_unique"; + @query("ha-form") + private _formElement?: HaForm; + private _schema = memoizeOne( (selector: any) => [ @@ -138,6 +145,41 @@ export default class HaScriptFieldSelectorEditor extends LitElement { this.hass.localize(`ui.panel.config.script.editor.field.${error}` as any) || error; + private _getSelectorElements() { + if (this._formElement) { + const selectors = + this._formElement.shadowRoot?.querySelectorAll( + "ha-selector" + ); + + const selectorElements: (HaConditionSelector | HaActionSelector)[] = []; + + selectors?.forEach((selector) => { + selectorElements.push( + ...Array.from( + selector.shadowRoot?.querySelectorAll< + HaConditionSelector | HaActionSelector + >("ha-selector-condition, ha-selector-action") || [] + ) + ); + }); + return selectorElements; + } + return []; + } + + public expandAll() { + this._getSelectorElements().forEach((element) => { + element.expandAll?.(); + }); + } + + public collapseAll() { + this._getSelectorElements().forEach((element) => { + element.collapseAll?.(); + }); + } + static get styles(): CSSResultGroup { return [ haStyle, diff --git a/src/panels/config/script/ha-script-fields.ts b/src/panels/config/script/ha-script-fields.ts index 0f3a94ddf6..3ace088fa6 100644 --- a/src/panels/config/script/ha-script-fields.ts +++ b/src/panels/config/script/ha-script-fields.ts @@ -1,7 +1,7 @@ import { mdiPlus } from "@mdi/js"; import type { PropertyValues } from "lit"; import { LitElement, css, html, nothing } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, queryAll } from "lit/decorators"; import { fireEvent } from "../../../common/dom/fire_event"; import "../../../components/ha-button"; import "../../../components/ha-button-menu"; @@ -23,6 +23,9 @@ export default class HaScriptFields extends LitElement { @property({ type: Boolean }) public narrow = false; + @queryAll("ha-script-field-row") + private _fieldRowElements?: HaScriptFieldRow[]; + private _focusLastActionOnChange = false; protected render() { @@ -48,12 +51,7 @@ export default class HaScriptFields extends LitElement { )} ` : nothing} - + ${this.hass.localize("ui.panel.config.script.editor.field.add_field")} @@ -144,6 +142,18 @@ export default class HaScriptFields extends LitElement { return key; } + public expandAll() { + this._fieldRowElements?.forEach((row) => { + row.expandAll(); + }); + } + + public collapseAll() { + this._fieldRowElements?.forEach((row) => { + row.collapseAll(); + }); + } + static styles = css` ha-script-field-row { display: block; diff --git a/src/panels/config/script/manual-script-editor.ts b/src/panels/config/script/manual-script-editor.ts index 018c695465..52df2fd0c8 100644 --- a/src/panels/config/script/manual-script-editor.ts +++ b/src/panels/config/script/manual-script-editor.ts @@ -31,6 +31,7 @@ import type { HomeAssistant } from "../../../types"; import { documentationUrl } from "../../../util/documentation-url"; import { showToast } from "../../../util/toast"; import "../automation/action/ha-automation-action"; +import type HaAutomationAction from "../automation/action/ha-automation-action"; import "../automation/ha-automation-sidebar"; import { showPasteReplaceDialog } from "../automation/paste-replace-dialog/show-dialog-paste-replace"; import { saveFabStyles } from "../automation/styles"; @@ -460,6 +461,24 @@ export class HaManualScriptEditor extends LitElement { fireEvent(this, "save-script"); } + private _getCollapsableElements() { + return this.shadowRoot!.querySelectorAll< + HaAutomationAction | HaScriptFields + >("ha-automation-action, ha-script-fields"); + } + + public expandAll() { + this._getCollapsableElements().forEach((element) => { + element.expandAll(); + }); + } + + public collapseAll() { + this._getCollapsableElements().forEach((element) => { + element.collapseAll(); + }); + } + static get styles(): CSSResultGroup { return [ saveFabStyles, diff --git a/src/translations/en.json b/src/translations/en.json index c37af3ae7d..1517434c3b 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3792,6 +3792,8 @@ "automation_settings": "Automation settings", "move_up": "Move up", "move_down": "Move down", + "collapse_all": "Collapse all", + "expand_all": "Expand all", "description": { "label": "Description", "placeholder": "Optional description",