diff --git a/src/data/automation.ts b/src/data/automation.ts index 725e549160..0e3b2fb6df 100644 --- a/src/data/automation.ts +++ b/src/data/automation.ts @@ -8,6 +8,7 @@ import { Context, HomeAssistant } from "../types"; import { BlueprintInput } from "./blueprint"; import { DeviceCondition, DeviceTrigger } from "./device_automation"; import { Action, MODES, migrateAutomationAction } from "./script"; +import { createSearchParam } from "../common/url/search-params"; export const AUTOMATION_DEFAULT_MODE: (typeof MODES)[number] = "single"; export const AUTOMATION_DEFAULT_MAX = 10; @@ -462,9 +463,13 @@ export const flattenTriggers = ( return flatTriggers; }; -export const showAutomationEditor = (data?: Partial) => { +export const showAutomationEditor = ( + data?: Partial, + expanded?: boolean +) => { initialAutomationEditorData = data; - navigate("/config/automation/edit/new"); + const params = expanded ? `?${createSearchParam({ expanded: "1" })}` : ""; + navigate(`/config/automation/edit/new${params}`); }; export const duplicateAutomation = (config: AutomationConfig) => { diff --git a/src/data/script.ts b/src/data/script.ts index 631882a7cb..c2273d18d5 100644 --- a/src/data/script.ts +++ b/src/data/script.ts @@ -28,6 +28,7 @@ import { } from "./automation"; import { BlueprintInput } from "./blueprint"; import { computeObjectId } from "../common/entity/compute_object_id"; +import { createSearchParam } from "../common/url/search-params"; export const MODES = ["single", "restart", "queued", "parallel"] as const; export const MODES_MAX = ["queued", "parallel"] as const; @@ -347,9 +348,13 @@ export const getScriptStateConfig = (hass: HomeAssistant, entity_id: string) => entity_id, }); -export const showScriptEditor = (data?: Partial) => { +export const showScriptEditor = ( + data?: Partial, + expanded?: boolean +) => { inititialScriptEditorData = data; - navigate("/config/script/edit/new"); + const params = expanded ? `?${createSearchParam({ expanded: "1" })}` : ""; + navigate(`/config/script/edit/new${params}`); }; export const getScriptEditorInitData = () => { diff --git a/src/panels/config/automation/action/ha-automation-action.ts b/src/panels/config/automation/action/ha-automation-action.ts index f3dc0257ce..301dc97411 100644 --- a/src/panels/config/automation/action/ha-automation-action.ts +++ b/src/panels/config/automation/action/ha-automation-action.ts @@ -156,6 +156,15 @@ export default class HaAutomationAction extends LitElement { } } + public expandAll() { + const rows = this.shadowRoot!.querySelectorAll( + "ha-automation-action-row" + )!; + rows.forEach((row) => { + row.expand(); + }); + } + private _addActionDialog() { showAddAutomationElementDialog(this, { type: "action", diff --git a/src/panels/config/automation/condition/ha-automation-condition.ts b/src/panels/config/automation/condition/ha-automation-condition.ts index 4ee3e674b4..481a34eb6e 100644 --- a/src/panels/config/automation/condition/ha-automation-condition.ts +++ b/src/panels/config/automation/condition/ha-automation-condition.ts @@ -106,6 +106,15 @@ export default class HaAutomationCondition extends LitElement { } } + public expandAll() { + const rows = this.shadowRoot!.querySelectorAll( + "ha-automation-condition-row" + )!; + rows.forEach((row) => { + row.expand(); + }); + } + private get nested() { return this.path !== undefined; } diff --git a/src/panels/config/automation/manual-automation-editor.ts b/src/panels/config/automation/manual-automation-editor.ts index 741ecb99d2..ca4f8179aa 100644 --- a/src/panels/config/automation/manual-automation-editor.ts +++ b/src/panels/config/automation/manual-automation-editor.ts @@ -1,7 +1,14 @@ import "@material/mwc-button/mwc-button"; import { mdiHelpCircle } from "@mdi/js"; import { HassEntity } from "home-assistant-js-websocket"; -import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; +import { + css, + CSSResultGroup, + html, + LitElement, + nothing, + PropertyValues, +} from "lit"; import { customElement, property } from "lit/decorators"; import { ensureArray } from "../../../common/array/ensure-array"; import { fireEvent } from "../../../common/dom/fire_event"; @@ -21,6 +28,14 @@ import { documentationUrl } from "../../../util/documentation-url"; import "./action/ha-automation-action"; import "./condition/ha-automation-condition"; import "./trigger/ha-automation-trigger"; +import type HaAutomationTrigger from "./trigger/ha-automation-trigger"; +import type HaAutomationAction from "./action/ha-automation-action"; +import type HaAutomationCondition from "./condition/ha-automation-condition"; +import { + extractSearchParam, + removeSearchParam, +} from "../../../common/url/search-params"; +import { constructUrlCurrentPath } from "../../../common/url/construct-url"; @customElement("manual-automation-editor") export class HaManualAutomationEditor extends LitElement { @@ -36,6 +51,31 @@ export class HaManualAutomationEditor extends LitElement { @property({ attribute: false }) public stateObj?: HassEntity; + protected firstUpdated(changedProps: PropertyValues): void { + super.firstUpdated(changedProps); + const expanded = extractSearchParam("expanded"); + if (expanded === "1") { + this._clearParam("expanded"); + const items = this.shadowRoot!.querySelectorAll< + HaAutomationTrigger | HaAutomationCondition | HaAutomationAction + >("ha-automation-trigger, ha-automation-condition, ha-automation-action"); + + items.forEach((el) => { + el.updateComplete.then(() => { + el.expandAll(); + }); + }); + } + } + + private _clearParam(param: string) { + window.history.replaceState( + null, + "", + constructUrlCurrentPath(removeSearchParam(param)) + ); + } + protected render() { return html` ${this.stateObj?.state === "off" diff --git a/src/panels/config/automation/trigger/ha-automation-trigger.ts b/src/panels/config/automation/trigger/ha-automation-trigger.ts index 5fe2a100a1..2a1d254799 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger.ts @@ -179,6 +179,15 @@ export default class HaAutomationTrigger extends LitElement { } } + public expandAll() { + const rows = this.shadowRoot!.querySelectorAll( + "ha-automation-trigger-row" + )!; + rows.forEach((row) => { + row.expand(); + }); + } + private _getKey(action: Trigger) { if (!this._triggerKeys.has(action)) { this._triggerKeys.set(action, Math.random().toString()); diff --git a/src/panels/config/devices/device-detail/ha-device-actions-card.ts b/src/panels/config/devices/device-detail/ha-device-actions-card.ts deleted file mode 100644 index 6498b7df9a..0000000000 --- a/src/panels/config/devices/device-detail/ha-device-actions-card.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { customElement } from "lit/decorators"; -import { - DeviceAction, - localizeDeviceAutomationAction, -} from "../../../../data/device_automation"; -import { HaDeviceAutomationCard } from "./ha-device-automation-card"; - -@customElement("ha-device-actions-card") -export class HaDeviceActionsCard extends HaDeviceAutomationCard { - readonly type = "action"; - - readonly headerKey = "ui.panel.config.devices.automation.actions.caption"; - - constructor() { - super(localizeDeviceAutomationAction); - } -} - -declare global { - interface HTMLElementTagNameMap { - "ha-device-actions-card": HaDeviceActionsCard; - } -} diff --git a/src/panels/config/devices/device-detail/ha-device-automation-card.ts b/src/panels/config/devices/device-detail/ha-device-automation-card.ts deleted file mode 100644 index 26ff027d77..0000000000 --- a/src/panels/config/devices/device-detail/ha-device-automation-card.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { css, html, LitElement, nothing } from "lit"; -import { property, state } from "lit/decorators"; -import { fireEvent } from "../../../../common/dom/fire_event"; -import "../../../../components/chips/ha-assist-chip"; -import "../../../../components/chips/ha-chip-set"; -import { showAutomationEditor } from "../../../../data/automation"; -import { - DeviceAction, - DeviceAutomation, -} from "../../../../data/device_automation"; -import { EntityRegistryEntry } from "../../../../data/entity_registry"; -import { showScriptEditor } from "../../../../data/script"; -import { buttonLinkStyle } from "../../../../resources/styles"; -import { HomeAssistant } from "../../../../types"; - -declare global { - interface HASSDomEvents { - "entry-selected": undefined; - } -} - -export abstract class HaDeviceAutomationCard< - T extends DeviceAutomation, -> extends LitElement { - @property({ attribute: false }) public hass!: HomeAssistant; - - @property() public deviceId?: string; - - @property({ type: Boolean }) public script = false; - - @property({ attribute: false }) public automations: T[] = []; - - @property({ attribute: false }) entityReg?: EntityRegistryEntry[]; - - @state() public _showSecondary = false; - - abstract headerKey: Parameters[0]; - - abstract type: "action" | "condition" | "trigger"; - - private _localizeDeviceAutomation: ( - hass: HomeAssistant, - entityRegistry: EntityRegistryEntry[], - automation: T - ) => string; - - constructor( - localizeDeviceAutomation: HaDeviceAutomationCard["_localizeDeviceAutomation"] - ) { - super(); - this._localizeDeviceAutomation = localizeDeviceAutomation; - } - - protected shouldUpdate(changedProps): boolean { - if (changedProps.has("deviceId") || changedProps.has("automations")) { - return true; - } - const oldHass = changedProps.get("hass") as HomeAssistant | undefined; - if (!oldHass || oldHass.language !== this.hass.language) { - return true; - } - return false; - } - - protected render() { - if (this.automations.length === 0 || !this.entityReg) { - return nothing; - } - const automations = this._showSecondary - ? this.automations - : this.automations.filter( - (automation) => automation.metadata?.secondary === false - ); - return html` -

${this.hass.localize(this.headerKey)}

-
- - ${automations.map( - (automation, idx) => html` - - - ` - )} - - ${!this._showSecondary && automations.length < this.automations.length - ? html`` - : ""} -
- `; - } - - private _toggleSecondary() { - this._showSecondary = !this._showSecondary; - } - - private _handleAutomationClicked(ev: CustomEvent) { - const automation = { ...this.automations[(ev.currentTarget as any).index] }; - if (!automation) { - return; - } - delete automation.metadata; - if (this.script) { - showScriptEditor({ sequence: [automation as DeviceAction] }); - fireEvent(this, "entry-selected"); - return; - } - const data = {}; - data[this.type] = [automation]; - showAutomationEditor(data); - fireEvent(this, "entry-selected"); - } - - static styles = [ - buttonLinkStyle, - css` - h3 { - color: var(--primary-text-color); - } - .secondary { - --ha-assist-chip-filled-container-color: rgba( - var(--rgb-primary-text-color), - 0.07 - ); - } - button.link { - color: var(--primary-color); - } - `, - ]; -} diff --git a/src/panels/config/devices/device-detail/ha-device-automation-dialog.ts b/src/panels/config/devices/device-detail/ha-device-automation-dialog.ts index f3d1835b1c..c79347c67e 100644 --- a/src/panels/config/devices/device-detail/ha-device-automation-dialog.ts +++ b/src/panels/config/devices/device-detail/ha-device-automation-dialog.ts @@ -1,8 +1,18 @@ -import "@material/mwc-button/mwc-button"; -import { CSSResultGroup, html, LitElement, nothing } from "lit"; +import { + mdiAbTesting, + mdiGestureTap, + mdiPencilOutline, + mdiRoomService, +} from "@mdi/js"; +import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../../../common/dom/fire_event"; -import "../../../../components/ha-dialog"; +import { shouldHandleRequestSelectedEvent } from "../../../../common/mwc/handle-request-selected-event"; +import { createCloseHeading } from "../../../../components/ha-dialog"; +import { + AutomationConfig, + showAutomationEditor, +} from "../../../../data/automation"; import { DeviceAction, DeviceCondition, @@ -12,11 +22,9 @@ import { fetchDeviceTriggers, sortDeviceAutomations, } from "../../../../data/device_automation"; -import { haStyleDialog } from "../../../../resources/styles"; +import { ScriptConfig, showScriptEditor } from "../../../../data/script"; +import { haStyle, haStyleDialog } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; -import "./ha-device-actions-card"; -import "./ha-device-conditions-card"; -import "./ha-device-triggers-card"; import { DeviceAutomationDialogParams } from "./show-dialog-device-automation"; @customElement("dialog-device-automation") @@ -77,75 +85,184 @@ export class DialogDeviceAutomation extends LitElement { }); } + private _handleRowClick = (ev) => { + if (!shouldHandleRequestSelectedEvent(ev) || !this._params) { + return; + } + const type = (ev.currentTarget as any).type; + const isScript = this._params.script; + + this.closeDialog(); + + if (isScript) { + const newScript = {} as ScriptConfig; + if (type === "action") { + newScript.sequence = [this._actions[0]]; + } + showScriptEditor(newScript, true); + } else { + const newAutomation = {} as AutomationConfig; + if (type === "trigger") { + newAutomation.triggers = [this._triggers[0]]; + } + if (type === "condition") { + newAutomation.conditions = [this._conditions[0]]; + } + if (type === "action") { + newAutomation.actions = [this._actions[0]]; + } + showAutomationEditor(newAutomation, true); + } + }; + protected render() { if (!this._params) { return nothing; } + const mode = this._params.script ? "script" : "automation"; + + const title = this.hass.localize(`ui.panel.config.devices.${mode}.create`, { + type: this.hass.localize( + `ui.panel.config.devices.type.${ + this._params.device.entry_type || "device" + }` + ), + }); + return html` -
+ + ${this._triggers.length + ? html` + + + ${this.hass.localize( + `ui.panel.config.devices.automation.triggers.title` + )} + + ${this.hass.localize( + `ui.panel.config.devices.automation.triggers.description` + )} + + + + ` + : nothing} + ${this._conditions.length + ? html` + + + ${this.hass.localize( + `ui.panel.config.devices.automation.conditions.title` + )} + + ${this.hass.localize( + `ui.panel.config.devices.automation.conditions.description` + )} + + + + ` + : nothing} + ${this._actions.length + ? html` + + + ${this.hass.localize( + `ui.panel.config.devices.${mode}.actions.title` + )} + + ${this.hass.localize( + `ui.panel.config.devices.${mode}.actions.description` + )} + + + + ` + : nothing} ${this._triggers.length || this._conditions.length || this._actions.length - ? html` - ${this._triggers.length - ? html` - - ` - : ""} - ${this._conditions.length - ? html` - - ` - : ""} - ${this._actions.length - ? html` - - ` - : ""} - ` - : this.hass.localize( - "ui.panel.config.devices.automation.no_device_automations" + ? html`
  • ` + : nothing} + + + ${this.hass.localize(`ui.panel.config.devices.${mode}.new.title`)} + + ${this.hass.localize( + `ui.panel.config.devices.${mode}.new.description` )} -
    - - ${this.hass.localize("ui.common.close")} - + + + +
    `; } static get styles(): CSSResultGroup { - return haStyleDialog; + return [ + haStyle, + haStyleDialog, + css` + ha-dialog { + --dialog-content-padding: 0; + --mdc-dialog-max-height: 60vh; + } + @media all and (min-width: 550px) { + ha-dialog { + --mdc-dialog-min-width: 500px; + } + } + ha-icon-next { + width: 24px; + } + `, + ]; } } diff --git a/src/panels/config/devices/device-detail/ha-device-conditions-card.ts b/src/panels/config/devices/device-detail/ha-device-conditions-card.ts deleted file mode 100644 index 3fb7e150d3..0000000000 --- a/src/panels/config/devices/device-detail/ha-device-conditions-card.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { customElement } from "lit/decorators"; -import { - DeviceCondition, - localizeDeviceAutomationCondition, -} from "../../../../data/device_automation"; -import { HaDeviceAutomationCard } from "./ha-device-automation-card"; - -@customElement("ha-device-conditions-card") -export class HaDeviceConditionsCard extends HaDeviceAutomationCard { - readonly type = "condition"; - - readonly headerKey = "ui.panel.config.devices.automation.conditions.caption"; - - constructor() { - super(localizeDeviceAutomationCondition); - } -} - -declare global { - interface HTMLElementTagNameMap { - "ha-device-conditions-card": HaDeviceConditionsCard; - } -} diff --git a/src/panels/config/devices/device-detail/ha-device-triggers-card.ts b/src/panels/config/devices/device-detail/ha-device-triggers-card.ts deleted file mode 100644 index c65acc738a..0000000000 --- a/src/panels/config/devices/device-detail/ha-device-triggers-card.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { customElement } from "lit/decorators"; -import { - DeviceTrigger, - localizeDeviceAutomationTrigger, -} from "../../../../data/device_automation"; -import { HaDeviceAutomationCard } from "./ha-device-automation-card"; - -@customElement("ha-device-triggers-card") -export class HaDeviceTriggersCard extends HaDeviceAutomationCard { - readonly type = "trigger"; - - readonly headerKey = "ui.panel.config.devices.automation.triggers.caption"; - - constructor() { - super(localizeDeviceAutomationTrigger); - } -} - -declare global { - interface HTMLElementTagNameMap { - "ha-device-triggers-card": HaDeviceTriggersCard; - } -} diff --git a/src/panels/config/script/manual-script-editor.ts b/src/panels/config/script/manual-script-editor.ts index 63f0b555e4..240dc31437 100644 --- a/src/panels/config/script/manual-script-editor.ts +++ b/src/panels/config/script/manual-script-editor.ts @@ -1,8 +1,20 @@ import "@material/mwc-button/mwc-button"; import { mdiHelpCircle } from "@mdi/js"; -import { CSSResultGroup, LitElement, css, html, nothing } from "lit"; +import { + CSSResultGroup, + LitElement, + PropertyValues, + css, + html, + nothing, +} from "lit"; import { customElement, property, query } from "lit/decorators"; import { fireEvent } from "../../../common/dom/fire_event"; +import { constructUrlCurrentPath } from "../../../common/url/construct-url"; +import { + extractSearchParam, + removeSearchParam, +} from "../../../common/url/search-params"; import { nestedArrayMove } from "../../../common/util/array-move"; import "../../../components/ha-card"; import "../../../components/ha-icon-button"; @@ -12,6 +24,7 @@ import { haStyle } from "../../../resources/styles"; import type { HomeAssistant } from "../../../types"; import { documentationUrl } from "../../../util/documentation-url"; import "../automation/action/ha-automation-action"; +import type HaAutomationAction from "../automation/action/ha-automation-action"; import "./ha-script-fields"; import type HaScriptFields from "./ha-script-fields"; @@ -58,6 +71,31 @@ export class HaManualScriptEditor extends LitElement { } } + protected firstUpdated(changedProps: PropertyValues): void { + super.firstUpdated(changedProps); + const expanded = extractSearchParam("expanded"); + if (expanded === "1") { + this._clearParam("expanded"); + const items = this.shadowRoot!.querySelectorAll( + "ha-automation-action" + ); + + items.forEach((el) => { + el.updateComplete.then(() => { + el.expandAll(); + }); + }); + } + } + + private _clearParam(param: string) { + window.history.replaceState( + null, + "", + constructUrlCurrentPath(removeSearchParam(param)) + ); + } + protected render() { return html` ${this.config.description diff --git a/src/translations/en.json b/src/translations/en.json index 92d5b38048..ecb40e647a 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -4039,18 +4039,25 @@ "unknown_automation": "Unknown automation", "create": "Create automation with {type}", "create_disable": "Can't create automation with disabled {type}", + "new": { + "title": "Create new automation", + "description": "Start with an empty automation from scratch" + }, "triggers": { - "caption": "Do something when…", + "title": "Use device as trigger", + "description": "When something happens to the device", "no_triggers": "No triggers", "unknown_trigger": "Unknown trigger" }, "conditions": { - "caption": "Only do something if…", + "title": "Use device as condition", + "description": "Only if a condition is met for the device", "no_conditions": "No conditions", "unknown_condition": "Unknown condition" }, "actions": { - "caption": "When something is triggered…", + "title": "Use device as action", + "description": "Do something on the device", "no_actions": "No actions", "unknown_action": "Unknown action" }, @@ -4061,7 +4068,15 @@ "scripts": "scripts", "no_scripts": "No scripts", "create": "Create script with {type}", - "create_disable": "Can't create script with disabled {type}" + "create_disable": "Can't create script with disabled {type}", + "new": { + "title": "Create new script", + "description": "Start with an empty script from scratch" + }, + "actions": { + "title": "Use device as action", + "description": "Do something on this device." + } }, "scene": { "scenes_heading": "Scenes",