mirror of
https://github.com/home-assistant/frontend.git
synced 2025-11-08 18:39:40 +00:00
Compare commits
12 Commits
automation
...
feature/sa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f29fbb6dbc | ||
|
|
1305689875 | ||
|
|
98cecc1f08 | ||
|
|
b4223e9e92 | ||
|
|
99955d7818 | ||
|
|
f66768726c | ||
|
|
0e4be02b2c | ||
|
|
6daea23b3c | ||
|
|
e21ddcb1e5 | ||
|
|
ded85d9f27 | ||
|
|
eea43494da | ||
|
|
9cf9ef927d |
@@ -1106,7 +1106,7 @@ export default {
|
||||
friendly_name: "Philips Hue",
|
||||
entity_picture: null,
|
||||
description:
|
||||
"Press the button on the bridge to register Philips Hue with Home Assistant.\n\n",
|
||||
"Press the button on the bridge to register Philips Hue with Home Assistant.",
|
||||
submit_caption: "I have pressed the button",
|
||||
},
|
||||
last_changed: "2018-07-19T10:44:46.515160+00:00",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.4 KiB |
@@ -533,7 +533,11 @@ export class HaDataTable extends LitElement {
|
||||
return nothing;
|
||||
}
|
||||
if (row.append) {
|
||||
return html`<div class="mdc-data-table__row">${row.content}</div>`;
|
||||
return html`<div
|
||||
class=${`mdc-data-table__row append${row.groupHeader ? " group-header" : ""}`}
|
||||
>
|
||||
${row.content}
|
||||
</div>`;
|
||||
}
|
||||
if (row.empty) {
|
||||
return html`<div class="mdc-data-table__row empty-row"></div>`;
|
||||
@@ -768,8 +772,9 @@ export class HaDataTable extends LitElement {
|
||||
groupedItems.push({
|
||||
append: true,
|
||||
selectable: false,
|
||||
groupHeader: true,
|
||||
content: html`<div
|
||||
class="mdc-data-table__cell group-header"
|
||||
class="mdc-data-table__cell"
|
||||
role="cell"
|
||||
.group=${groupName}
|
||||
@click=${this._collapseGroup}
|
||||
@@ -1071,12 +1076,18 @@ export class HaDataTable extends LitElement {
|
||||
display: flex;
|
||||
height: var(--data-table-row-height, 52px);
|
||||
width: var(--table-row-width, 100%);
|
||||
box-sizing: border-box;
|
||||
padding-left: var(--data-row-padding-left);
|
||||
padding-right: var(--data-row-padding-right);
|
||||
}
|
||||
|
||||
.mdc-data-table__row.empty-row {
|
||||
height: var(
|
||||
--data-table-empty-row-height,
|
||||
var(--data-table-row-height, 52px)
|
||||
height: calc(
|
||||
var(
|
||||
--data-table-empty-row-height,
|
||||
var(--data-table-row-height, 52px)
|
||||
) +
|
||||
var(--safe-area-inset-bottom)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1103,6 +1114,9 @@ export class HaDataTable extends LitElement {
|
||||
display: flex;
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
overflow: auto;
|
||||
box-sizing: border-box;
|
||||
padding-left: var(--data-row-padding-left);
|
||||
padding-right: var(--data-row-padding-right);
|
||||
}
|
||||
|
||||
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||
@@ -1305,6 +1319,10 @@ export class HaDataTable extends LitElement {
|
||||
/* custom from here */
|
||||
|
||||
.group-header {
|
||||
background-color: var(--primary-background-color);
|
||||
}
|
||||
|
||||
.group-header .mdc-data-table__cell {
|
||||
padding-top: 12px;
|
||||
height: var(--data-table-row-height, 52px);
|
||||
padding-left: 12px;
|
||||
@@ -1315,7 +1333,6 @@ export class HaDataTable extends LitElement {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
background-color: var(--primary-background-color);
|
||||
}
|
||||
|
||||
.group-header ha-icon-button {
|
||||
|
||||
@@ -707,7 +707,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.menu {
|
||||
height: var(--header-height);
|
||||
height: calc(var(--header-height) + var(--safe-area-inset-top));
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
padding: 0 4px;
|
||||
@@ -728,6 +728,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
padding-left: calc(4px + var(--safe-area-inset-left));
|
||||
padding-inline-start: calc(4px + var(--safe-area-inset-left));
|
||||
padding-inline-end: initial;
|
||||
padding-top: var(--safe-area-inset-top);
|
||||
}
|
||||
:host([expanded]) .menu {
|
||||
width: calc(256px + var(--safe-area-inset-left));
|
||||
@@ -755,8 +756,11 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
|
||||
ha-fade-in,
|
||||
ha-md-list {
|
||||
padding: 4px 0;
|
||||
box-sizing: border-box;
|
||||
height: calc(
|
||||
100% - var(--header-height) - 132px - var(--safe-area-inset-bottom)
|
||||
100% - var(--header-height) - var(--safe-area-inset-top) -
|
||||
132px - var(--safe-area-inset-bottom)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -767,8 +771,6 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
ha-md-list {
|
||||
padding: 4px 0;
|
||||
box-sizing: border-box;
|
||||
overflow-x: hidden;
|
||||
background: none;
|
||||
margin-left: var(--safe-area-inset-left);
|
||||
|
||||
@@ -387,18 +387,6 @@ export const normalizeAutomationConfig = <
|
||||
}
|
||||
}
|
||||
|
||||
// We move all conditions into the action for display
|
||||
if (config.conditions) {
|
||||
if (config.actions) {
|
||||
(config.actions as Action[]).unshift(
|
||||
...(config.conditions as Condition[])
|
||||
);
|
||||
} else {
|
||||
config.actions = config.conditions;
|
||||
}
|
||||
delete config.conditions;
|
||||
}
|
||||
|
||||
return config;
|
||||
};
|
||||
|
||||
@@ -568,7 +556,6 @@ export interface AutomationClipboard {
|
||||
}
|
||||
|
||||
export interface BaseSidebarConfig {
|
||||
toggleYamlMode: () => boolean;
|
||||
delete: () => void;
|
||||
close: (focus?: boolean) => void;
|
||||
}
|
||||
@@ -580,6 +567,7 @@ export interface TriggerSidebarConfig extends BaseSidebarConfig {
|
||||
duplicate: () => void;
|
||||
cut: () => void;
|
||||
copy: () => void;
|
||||
toggleYamlMode: () => void;
|
||||
config: Trigger;
|
||||
yamlMode: boolean;
|
||||
uiSupported: boolean;
|
||||
@@ -593,6 +581,7 @@ export interface ConditionSidebarConfig extends BaseSidebarConfig {
|
||||
duplicate: () => void;
|
||||
cut: () => void;
|
||||
copy: () => void;
|
||||
toggleYamlMode: () => void;
|
||||
config: Condition;
|
||||
yamlMode: boolean;
|
||||
uiSupported: boolean;
|
||||
@@ -606,6 +595,7 @@ export interface ActionSidebarConfig extends BaseSidebarConfig {
|
||||
cut: () => void;
|
||||
copy: () => void;
|
||||
run: () => void;
|
||||
toggleYamlMode: () => void;
|
||||
config: {
|
||||
action: Action;
|
||||
};
|
||||
@@ -627,6 +617,7 @@ export interface ScriptFieldSidebarConfig extends BaseSidebarConfig {
|
||||
key: string;
|
||||
excludeKeys: string[];
|
||||
};
|
||||
toggleYamlMode: () => void;
|
||||
yamlMode: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -98,8 +98,11 @@ class HassSubpage extends LitElement {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: var(--ha-font-size-xl);
|
||||
height: var(--header-height);
|
||||
height: calc(var(--header-height) + var(--safe-area-inset-top));
|
||||
padding: 8px 12px;
|
||||
padding-top: max(8px, var(--safe-area-inset-top));
|
||||
padding-right: max(12px, var(--safe-area-content-inset-right));
|
||||
padding-left: max(12px, var(--safe-area-content-inset-left));
|
||||
background-color: var(--app-header-background-color);
|
||||
font-weight: var(--ha-font-weight-normal);
|
||||
color: var(--app-header-text-color, white);
|
||||
@@ -109,6 +112,9 @@ class HassSubpage extends LitElement {
|
||||
@media (max-width: 599px) {
|
||||
.toolbar {
|
||||
padding: 4px;
|
||||
padding-top: max(4px, var(--safe-area-inset-top));
|
||||
padding-right: max(4px, var(--safe-area-inset-right));
|
||||
padding-left: max(4px, var(--safe-area-inset-left));
|
||||
}
|
||||
}
|
||||
.toolbar a {
|
||||
@@ -140,7 +146,9 @@ class HassSubpage extends LitElement {
|
||||
.content {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: calc(100% - 1px - var(--header-height));
|
||||
height: calc(
|
||||
100% - 1px - var(--header-height) - var(--safe-area-inset-top)
|
||||
);
|
||||
overflow-y: auto;
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
|
||||
@@ -697,14 +697,24 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
hass-tabs-subpage {
|
||||
background: var(--data-table-background-color);
|
||||
--tabs-subpage-content-padding-left: 0;
|
||||
--tabs-subpage-content-padding-right: 0;
|
||||
}
|
||||
|
||||
ha-data-table {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
--data-table-border-width: 0;
|
||||
--data-row-padding-left: var(--safe-area-inset-left);
|
||||
--data-row-padding-right: var(--safe-area-inset-right);
|
||||
}
|
||||
:host(:not([narrow])) ha-data-table,
|
||||
.pane {
|
||||
height: calc(100vh - 1px - var(--header-height));
|
||||
height: calc(
|
||||
100vh - 1px - var(--header-height) - var(--safe-area-inset-top)
|
||||
);
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -727,7 +737,8 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
|
||||
height: 56px;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
padding: 0 16px;
|
||||
padding-left: max(16px, var(--safe-area-content-inset-left));
|
||||
padding-right: max(16px, var(--safe-area-content-inset-right));
|
||||
gap: 16px;
|
||||
box-sizing: border-box;
|
||||
background: var(--primary-background-color);
|
||||
|
||||
@@ -225,7 +225,9 @@ class HassTabsSubpage extends LitElement {
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
height: calc(100% - var(--header-height));
|
||||
height: calc(
|
||||
100% - var(--header-height) - var(--safe-area-inset-top)
|
||||
);
|
||||
}
|
||||
|
||||
:host([narrow]) .container {
|
||||
@@ -240,7 +242,10 @@ class HassTabsSubpage extends LitElement {
|
||||
|
||||
.toolbar {
|
||||
font-size: var(--ha-font-size-xl);
|
||||
height: var(--header-height);
|
||||
height: calc(var(--header-height) + var(--safe-area-inset-top));
|
||||
padding-top: var(--safe-area-inset-top);
|
||||
padding-right: var(--safe-area-inset-right);
|
||||
padding-left: var(--safe-area-content-inset-left);
|
||||
background-color: var(--sidebar-background-color);
|
||||
font-weight: var(--ha-font-weight-normal);
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
@@ -320,13 +325,16 @@ class HassTabsSubpage extends LitElement {
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
width: calc(
|
||||
100% - var(--safe-area-inset-left) - var(--safe-area-inset-right)
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding-left: var(
|
||||
--tabs-subpage-content-padding-left,
|
||||
var(--safe-area-content-inset-left)
|
||||
);
|
||||
padding-right: var(
|
||||
--tabs-subpage-content-padding-right,
|
||||
var(--safe-area-content-inset-right)
|
||||
);
|
||||
margin-left: var(--safe-area-inset-left);
|
||||
margin-right: var(--safe-area-inset-right);
|
||||
margin-inline-start: var(--safe-area-inset-left);
|
||||
margin-inline-end: var(--safe-area-inset-right);
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ export default class HaAutomationActionEditor extends LitElement {
|
||||
if (!ev.detail.isValid) {
|
||||
return;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
fireEvent(this, "yaml-changed", {
|
||||
value: migrateAutomationAction(ev.detail.value),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -688,7 +688,7 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
},
|
||||
toggleYamlMode: () => {
|
||||
this._toggleYamlMode();
|
||||
return this._yamlMode;
|
||||
this.openSidebar();
|
||||
},
|
||||
disable: this._onDisable,
|
||||
delete: this._onDelete,
|
||||
|
||||
@@ -6,7 +6,6 @@ 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";
|
||||
import { listenMediaQuery } from "../../../../common/dom/media_query";
|
||||
import { nextRender } from "../../../../common/util/render-status";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-sortable";
|
||||
@@ -46,8 +45,6 @@ export default class HaAutomationAction extends LitElement {
|
||||
@property({ type: Boolean, attribute: "sidebar" }) public optionsInSidebar =
|
||||
false;
|
||||
|
||||
@state() private _showReorder = false;
|
||||
|
||||
@state() private _rowSortSelected?: number;
|
||||
|
||||
@state()
|
||||
@@ -68,27 +65,12 @@ export default class HaAutomationAction extends LitElement {
|
||||
|
||||
private _actionKeys = new WeakMap<Action, string>();
|
||||
|
||||
private _unsubMql?: () => void;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._unsubMql = listenMediaQuery("(min-width: 600px)", (matches) => {
|
||||
this._showReorder = matches;
|
||||
});
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._unsubMql?.();
|
||||
this._unsubMql = undefined;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-sortable
|
||||
handle-selector=".handle"
|
||||
draggable-selector="ha-automation-action-row"
|
||||
.disabled=${!this._showReorder || this.disabled}
|
||||
.disabled=${this.disabled}
|
||||
group="actions"
|
||||
invert-swap
|
||||
@item-moved=${this._actionMoved}
|
||||
@@ -120,7 +102,7 @@ export default class HaAutomationAction extends LitElement {
|
||||
.sortSelected=${this._rowSortSelected === idx}
|
||||
@stop-sort-selection=${this._stopSortSelection}
|
||||
>
|
||||
${this._showReorder && !this.disabled
|
||||
${!this.disabled
|
||||
? html`
|
||||
<div
|
||||
tabindex="0"
|
||||
|
||||
@@ -103,8 +103,7 @@ export default class HaAutomationConditionEditor extends LitElement {
|
||||
if (!ev.detail.isValid) {
|
||||
return;
|
||||
}
|
||||
// @ts-ignore
|
||||
fireEvent(this, "value-changed", { value: ev.detail.value, yaml: true });
|
||||
fireEvent(this, "yaml-changed", { value: ev.detail.value });
|
||||
}
|
||||
|
||||
private _onUiChanged(ev: CustomEvent) {
|
||||
|
||||
@@ -660,7 +660,7 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
},
|
||||
toggleYamlMode: () => {
|
||||
this._toggleYamlMode();
|
||||
return this._yamlMode;
|
||||
this.openSidebar();
|
||||
},
|
||||
disable: this._onDisable,
|
||||
delete: this._onDelete,
|
||||
|
||||
@@ -6,7 +6,6 @@ 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";
|
||||
import { listenMediaQuery } from "../../../../common/dom/media_query";
|
||||
import { nextRender } from "../../../../common/util/render-status";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-button-menu";
|
||||
@@ -44,8 +43,6 @@ export default class HaAutomationCondition extends LitElement {
|
||||
@property({ type: Boolean, attribute: "sidebar" }) public optionsInSidebar =
|
||||
false;
|
||||
|
||||
@state() private _showReorder = false;
|
||||
|
||||
@state() private _rowSortSelected?: number;
|
||||
|
||||
@state()
|
||||
@@ -66,21 +63,6 @@ export default class HaAutomationCondition extends LitElement {
|
||||
|
||||
private _conditionKeys = new WeakMap<Condition, string>();
|
||||
|
||||
private _unsubMql?: () => void;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._unsubMql = listenMediaQuery("(min-width: 600px)", (matches) => {
|
||||
this._showReorder = matches;
|
||||
});
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._unsubMql?.();
|
||||
this._unsubMql = undefined;
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
if (!changedProperties.has("conditions")) {
|
||||
return;
|
||||
@@ -165,7 +147,7 @@ export default class HaAutomationCondition extends LitElement {
|
||||
<ha-sortable
|
||||
handle-selector=".handle"
|
||||
draggable-selector="ha-automation-condition-row"
|
||||
.disabled=${!this._showReorder || this.disabled}
|
||||
.disabled=${this.disabled}
|
||||
group="conditions"
|
||||
invert-swap
|
||||
@item-moved=${this._conditionMoved}
|
||||
@@ -198,7 +180,7 @@ export default class HaAutomationCondition extends LitElement {
|
||||
.sortSelected=${this._rowSortSelected === idx}
|
||||
@stop-sort-selection=${this._stopSortSelection}
|
||||
>
|
||||
${this._showReorder && !this.disabled
|
||||
${!this.disabled
|
||||
? html`
|
||||
<div
|
||||
tabindex="0"
|
||||
|
||||
@@ -24,7 +24,6 @@ import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { isArray } from "@tsparticles/engine";
|
||||
import { transform } from "../../../common/decorators/transform";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
@@ -44,7 +43,6 @@ import type {
|
||||
AutomationConfig,
|
||||
AutomationEntity,
|
||||
BlueprintAutomationConfig,
|
||||
Condition,
|
||||
} from "../../../data/automation";
|
||||
import {
|
||||
deleteAutomation,
|
||||
@@ -1064,28 +1062,8 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
});
|
||||
}
|
||||
|
||||
// Move conditions at top of action to automation condition key
|
||||
const configToSave = { ...this._config! };
|
||||
configToSave.conditions = !configToSave.conditions
|
||||
? []
|
||||
: isArray(configToSave.conditions)
|
||||
? [...configToSave.conditions]
|
||||
: [configToSave.conditions];
|
||||
configToSave.actions = !configToSave.actions
|
||||
? []
|
||||
: isArray(configToSave.actions)
|
||||
? [...configToSave.actions]
|
||||
: [configToSave.actions];
|
||||
|
||||
while (
|
||||
configToSave.actions.length > 0 &&
|
||||
"condition" in configToSave.actions[0]
|
||||
) {
|
||||
configToSave.conditions.push(configToSave.actions.shift() as Condition);
|
||||
}
|
||||
|
||||
try {
|
||||
await saveAutomationConfig(this.hass, id, configToSave);
|
||||
await saveAutomationConfig(this.hass, id, this._config!);
|
||||
|
||||
if (this._entityRegistryUpdate !== undefined) {
|
||||
let entityId = this._entityId;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/ha-bottom-sheet";
|
||||
import type { HaBottomSheet } from "../../../components/ha-bottom-sheet";
|
||||
import {
|
||||
@@ -34,6 +33,8 @@ export default class HaAutomationSidebar extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: "sidebar-key" }) public sidebarKey?: string;
|
||||
|
||||
@state() private _yamlMode = false;
|
||||
|
||||
@query("ha-bottom-sheet") private _bottomSheetElement?: HaBottomSheet;
|
||||
@@ -52,6 +53,7 @@ export default class HaAutomationSidebar extends LitElement {
|
||||
.narrow=${this.narrow}
|
||||
.disabled=${this.disabled}
|
||||
.yamlMode=${this._yamlMode}
|
||||
.sidebarKey=${this.sidebarKey}
|
||||
@toggle-yaml-mode=${this._toggleYamlMode}
|
||||
@close-sidebar=${this._handleCloseSidebar}
|
||||
></ha-automation-sidebar-trigger>
|
||||
@@ -67,6 +69,7 @@ export default class HaAutomationSidebar extends LitElement {
|
||||
.narrow=${this.narrow}
|
||||
.disabled=${this.disabled}
|
||||
.yamlMode=${this._yamlMode}
|
||||
.sidebarKey=${this.sidebarKey}
|
||||
@toggle-yaml-mode=${this._toggleYamlMode}
|
||||
@close-sidebar=${this._handleCloseSidebar}
|
||||
></ha-automation-sidebar-condition>
|
||||
@@ -82,6 +85,7 @@ export default class HaAutomationSidebar extends LitElement {
|
||||
.narrow=${this.narrow}
|
||||
.disabled=${this.disabled}
|
||||
.yamlMode=${this._yamlMode}
|
||||
.sidebarKey=${this.sidebarKey}
|
||||
@toggle-yaml-mode=${this._toggleYamlMode}
|
||||
@close-sidebar=${this._handleCloseSidebar}
|
||||
></ha-automation-sidebar-action>
|
||||
@@ -110,6 +114,7 @@ export default class HaAutomationSidebar extends LitElement {
|
||||
.narrow=${this.narrow}
|
||||
.disabled=${this.disabled}
|
||||
.yamlMode=${this._yamlMode}
|
||||
.sidebarKey=${this.sidebarKey}
|
||||
@toggle-yaml-mode=${this._toggleYamlMode}
|
||||
@close-sidebar=${this._handleCloseSidebar}
|
||||
></ha-automation-sidebar-script-field-selector>
|
||||
@@ -125,6 +130,7 @@ export default class HaAutomationSidebar extends LitElement {
|
||||
.narrow=${this.narrow}
|
||||
.disabled=${this.disabled}
|
||||
.yamlMode=${this._yamlMode}
|
||||
.sidebarKey=${this.sidebarKey}
|
||||
@toggle-yaml-mode=${this._toggleYamlMode}
|
||||
@close-sidebar=${this._handleCloseSidebar}
|
||||
></ha-automation-sidebar-script-field>
|
||||
@@ -197,13 +203,7 @@ export default class HaAutomationSidebar extends LitElement {
|
||||
}
|
||||
|
||||
private _toggleYamlMode = () => {
|
||||
this._yamlMode = this.config!.toggleYamlMode();
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
...this.config,
|
||||
yamlMode: this._yamlMode,
|
||||
},
|
||||
});
|
||||
(this.config as ActionSidebarConfig)?.toggleYamlMode();
|
||||
};
|
||||
|
||||
static styles = css`
|
||||
@@ -235,5 +235,8 @@ declare global {
|
||||
|
||||
interface HASSDomEvents {
|
||||
"toggle-yaml-mode": undefined;
|
||||
"yaml-changed": {
|
||||
value: unknown;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@ import { documentationUrl } from "../../../util/documentation-url";
|
||||
import { showToast } from "../../../util/toast";
|
||||
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 "./ha-automation-sidebar";
|
||||
import type HaAutomationSidebar from "./ha-automation-sidebar";
|
||||
import { showPasteReplaceDialog } from "./paste-replace-dialog/show-dialog-paste-replace";
|
||||
@@ -90,6 +92,8 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
|
||||
@state() private _sidebarConfig?: SidebarConfig;
|
||||
|
||||
@state() private _sidebarKey?: string;
|
||||
|
||||
@query("ha-automation-sidebar") private _sidebarElement?: HaAutomationSidebar;
|
||||
|
||||
private _previousConfig?: ManualAutomationConfig;
|
||||
@@ -156,6 +160,53 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
sidebar
|
||||
></ha-automation-trigger>
|
||||
|
||||
<div class="header">
|
||||
<h2 id="conditions-heading" class="name">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.header"
|
||||
)}
|
||||
<span class="small"
|
||||
>(${this.hass.localize("ui.common.optional")})</span
|
||||
>
|
||||
</h2>
|
||||
<a
|
||||
href=${documentationUrl(this.hass, "/docs/automation/condition/")}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<ha-icon-button
|
||||
.path=${mdiHelpCircle}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.learn_more"
|
||||
)}
|
||||
></ha-icon-button>
|
||||
</a>
|
||||
</div>
|
||||
${!ensureArray(this.config.conditions)?.length
|
||||
? html`<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.description",
|
||||
{ user: this.hass.user?.name || "Alice" }
|
||||
)}
|
||||
</p>`
|
||||
: nothing}
|
||||
|
||||
<ha-automation-condition
|
||||
role="region"
|
||||
aria-labelledby="conditions-heading"
|
||||
.conditions=${this.config.conditions || []}
|
||||
.highlightedConditions=${this._pastedConfig?.conditions || []}
|
||||
@value-changed=${this._conditionChanged}
|
||||
.hass=${this.hass}
|
||||
.disabled=${this.disabled || this.saving}
|
||||
.narrow=${this.narrow}
|
||||
@open-sidebar=${this._openSidebar}
|
||||
@request-close-sidebar=${this._closeSidebar}
|
||||
@close-sidebar=${this._handleCloseSidebar}
|
||||
root
|
||||
sidebar
|
||||
></ha-automation-condition>
|
||||
|
||||
<div class="header">
|
||||
<h2 id="actions-heading" class="name">
|
||||
${this.hass.localize(
|
||||
@@ -238,6 +289,7 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
.config=${this._sidebarConfig}
|
||||
@value-changed=${this._sidebarConfigChanged}
|
||||
.disabled=${this.disabled}
|
||||
.sidebarKey=${this._sidebarKey}
|
||||
></ha-automation-sidebar>
|
||||
</div>
|
||||
</div>
|
||||
@@ -265,6 +317,7 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
// deselect previous selected row
|
||||
this._sidebarConfig?.close?.();
|
||||
this._sidebarConfig = ev.detail;
|
||||
this._sidebarKey = JSON.stringify(this._sidebarConfig);
|
||||
|
||||
await this._sidebarElement?.updateComplete;
|
||||
this._sidebarElement?.focus();
|
||||
@@ -535,9 +588,9 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
}
|
||||
|
||||
private _getCollapsableElements() {
|
||||
return this.shadowRoot!.querySelectorAll<HaAutomationAction>(
|
||||
"ha-automation-action"
|
||||
);
|
||||
return this.shadowRoot!.querySelectorAll<
|
||||
HaAutomationAction | HaAutomationCondition
|
||||
>("ha-automation-action, ha-automation-condition");
|
||||
}
|
||||
|
||||
public expandAll() {
|
||||
|
||||
@@ -395,7 +395,6 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
rename: () => {
|
||||
this._renameOption();
|
||||
},
|
||||
toggleYamlMode: () => false, // no yaml mode for options
|
||||
delete: this._removeOption,
|
||||
duplicate: this._duplicateOption,
|
||||
defaultOption: !!this.defaultActions,
|
||||
|
||||
@@ -6,7 +6,6 @@ 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";
|
||||
import { listenMediaQuery } from "../../../../common/dom/media_query";
|
||||
import { nextRender } from "../../../../common/util/render-status";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-sortable";
|
||||
@@ -35,8 +34,6 @@ export default class HaAutomationOption extends LitElement {
|
||||
@property({ type: Boolean, attribute: "show-default" })
|
||||
public showDefaultActions = false;
|
||||
|
||||
@state() private _showReorder = false;
|
||||
|
||||
@state() private _rowSortSelected?: number;
|
||||
|
||||
@state()
|
||||
@@ -57,27 +54,12 @@ export default class HaAutomationOption extends LitElement {
|
||||
|
||||
private _optionsKeys = new WeakMap<Option, string>();
|
||||
|
||||
private _unsubMql?: () => void;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._unsubMql = listenMediaQuery("(min-width: 600px)", (matches) => {
|
||||
this._showReorder = matches;
|
||||
});
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._unsubMql?.();
|
||||
this._unsubMql = undefined;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-sortable
|
||||
handle-selector=".handle"
|
||||
draggable-selector="ha-automation-option-row"
|
||||
.disabled=${!this._showReorder || this.disabled}
|
||||
.disabled=${this.disabled}
|
||||
group="options"
|
||||
invert-swap
|
||||
@item-moved=${this._optionMoved}
|
||||
@@ -107,7 +89,7 @@ export default class HaAutomationOption extends LitElement {
|
||||
.sortSelected=${this._rowSortSelected === idx}
|
||||
@stop-sort-selection=${this._stopSortSelection}
|
||||
>
|
||||
${this._showReorder && !this.disabled
|
||||
${!this.disabled
|
||||
? html`
|
||||
<div
|
||||
tabindex="0"
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { css, type CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
||||
import "../../../../components/ha-button";
|
||||
import { createCloseHeading } from "../../../../components/ha-dialog";
|
||||
import "../trigger/ha-automation-trigger-row";
|
||||
import "../../../../components/ha-yaml-editor";
|
||||
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
||||
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { PasteReplaceDialogParams } from "./show-dialog-paste-replace";
|
||||
|
||||
@customElement("ha-dialog-paste-replace")
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "@mdi/js";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { keyed } from "lit/directives/keyed";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { handleStructError } from "../../../../common/structs/handle-errors";
|
||||
import type { LocalizeKeys } from "../../../../common/translations/localize";
|
||||
@@ -41,6 +42,8 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: "sidebar-key" }) public sidebarKey?: string;
|
||||
|
||||
@state() private _warnings?: string[];
|
||||
|
||||
@query(".sidebar-editor")
|
||||
@@ -181,18 +184,22 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
</ha-md-menu-item>
|
||||
${description && !this.yamlMode
|
||||
? html`<div class="description">${description}</div>`
|
||||
: html`<ha-automation-action-editor
|
||||
class="sidebar-editor"
|
||||
.hass=${this.hass}
|
||||
.action=${actionConfig}
|
||||
.yamlMode=${this.yamlMode}
|
||||
.uiSupported=${this.config.uiSupported}
|
||||
@value-changed=${this._valueChangedSidebar}
|
||||
sidebar
|
||||
narrow
|
||||
.disabled=${this.disabled}
|
||||
@ui-mode-not-available=${this._handleUiModeNotAvailable}
|
||||
></ha-automation-action-editor>`}
|
||||
: keyed(
|
||||
this.sidebarKey,
|
||||
html`<ha-automation-action-editor
|
||||
class="sidebar-editor"
|
||||
.hass=${this.hass}
|
||||
.action=${actionConfig}
|
||||
.yamlMode=${this.yamlMode}
|
||||
.uiSupported=${this.config.uiSupported}
|
||||
@value-changed=${this._valueChangedSidebar}
|
||||
@yaml-changed=${this._yamlChangedSidebar}
|
||||
sidebar
|
||||
narrow
|
||||
.disabled=${this.disabled}
|
||||
@ui-mode-not-available=${this._handleUiModeNotAvailable}
|
||||
></ha-automation-action-editor>`
|
||||
)}
|
||||
</ha-automation-sidebar-card>`;
|
||||
}
|
||||
|
||||
@@ -220,6 +227,12 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _yamlChangedSidebar(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
|
||||
this.config?.save?.(ev.detail.value);
|
||||
}
|
||||
|
||||
private _toggleYamlMode = () => {
|
||||
fireEvent(this, "toggle-yaml-mode");
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "@mdi/js";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { keyed } from "lit/directives/keyed";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { handleStructError } from "../../../../common/structs/handle-errors";
|
||||
import type { ConditionSidebarConfig } from "../../../../data/automation";
|
||||
@@ -35,6 +36,8 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: "sidebar-key" }) public sidebarKey?: string;
|
||||
|
||||
@state() private _warnings?: string[];
|
||||
|
||||
@query(".sidebar-editor")
|
||||
@@ -173,17 +176,21 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
</ha-md-menu-item>
|
||||
${description && !this.yamlMode
|
||||
? html`<div class="description">${description}</div>`
|
||||
: html`<ha-automation-condition-editor
|
||||
class="sidebar-editor"
|
||||
.hass=${this.hass}
|
||||
.condition=${this.config.config}
|
||||
.yamlMode=${this.yamlMode}
|
||||
.uiSupported=${this.config.uiSupported}
|
||||
@value-changed=${this._valueChangedSidebar}
|
||||
.disabled=${this.disabled}
|
||||
@ui-mode-not-available=${this._handleUiModeNotAvailable}
|
||||
sidebar
|
||||
></ha-automation-condition-editor> `}
|
||||
: keyed(
|
||||
this.sidebarKey,
|
||||
html`<ha-automation-condition-editor
|
||||
class="sidebar-editor"
|
||||
.hass=${this.hass}
|
||||
.condition=${this.config.config}
|
||||
.yamlMode=${this.yamlMode}
|
||||
.uiSupported=${this.config.uiSupported}
|
||||
@value-changed=${this._valueChangedSidebar}
|
||||
@yaml-changed=${this._yamlChangedSidebar}
|
||||
.disabled=${this.disabled}
|
||||
@ui-mode-not-available=${this._handleUiModeNotAvailable}
|
||||
sidebar
|
||||
></ha-automation-condition-editor>`
|
||||
)}
|
||||
</ha-automation-sidebar-card>`;
|
||||
}
|
||||
|
||||
@@ -209,6 +216,12 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _yamlChangedSidebar(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
|
||||
this.config?.save?.(ev.detail.value);
|
||||
}
|
||||
|
||||
private _toggleYamlMode = () => {
|
||||
fireEvent(this, "toggle-yaml-mode");
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { mdiDelete, mdiPlaylistEdit } from "@mdi/js";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { keyed } from "lit/directives/keyed";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import type { LocalizeKeys } from "../../../../common/translations/localize";
|
||||
import type { ScriptFieldSidebarConfig } from "../../../../data/automation";
|
||||
@@ -24,6 +25,8 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: "sidebar-key" }) public sidebarKey?: string;
|
||||
|
||||
@state() private _warnings?: string[];
|
||||
|
||||
@query(".sidebar-editor")
|
||||
@@ -81,14 +84,18 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-script-field-selector-editor
|
||||
class="sidebar-editor"
|
||||
.hass=${this.hass}
|
||||
.field=${this.config.config.field}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._valueChangedSidebar}
|
||||
.yamlMode=${this.yamlMode}
|
||||
></ha-script-field-selector-editor>
|
||||
${keyed(
|
||||
this.sidebarKey,
|
||||
html`<ha-script-field-selector-editor
|
||||
class="sidebar-editor"
|
||||
.hass=${this.hass}
|
||||
.field=${this.config.config.field}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._valueChangedSidebar}
|
||||
@yaml-changed=${this._yamlChangedSidebar}
|
||||
.yamlMode=${this.yamlMode}
|
||||
></ha-script-field-selector-editor>`
|
||||
)}
|
||||
</ha-automation-sidebar-card>`;
|
||||
}
|
||||
|
||||
@@ -116,6 +123,12 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _yamlChangedSidebar(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
|
||||
this.config?.save?.(ev.detail.value);
|
||||
}
|
||||
|
||||
private _toggleYamlMode = () => {
|
||||
fireEvent(this, "toggle-yaml-mode");
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { mdiDelete, mdiPlaylistEdit } from "@mdi/js";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { keyed } from "lit/directives/keyed";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import type { ScriptFieldSidebarConfig } from "../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
@@ -23,6 +24,8 @@ export default class HaAutomationSidebarScriptField extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: "sidebar-key" }) public sidebarKey?: string;
|
||||
|
||||
@state() private _warnings?: string[];
|
||||
|
||||
@query(".sidebar-editor")
|
||||
@@ -74,16 +77,20 @@ export default class HaAutomationSidebarScriptField extends LitElement {
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-script-field-editor
|
||||
class="sidebar-editor"
|
||||
.hass=${this.hass}
|
||||
.field=${this.config.config.field}
|
||||
.key=${this.config.config.key}
|
||||
.excludeKeys=${this.config.config.excludeKeys}
|
||||
.disabled=${this.disabled}
|
||||
.yamlMode=${this.yamlMode}
|
||||
@value-changed=${this._valueChangedSidebar}
|
||||
></ha-script-field-editor>
|
||||
${keyed(
|
||||
this.sidebarKey,
|
||||
html`<ha-script-field-editor
|
||||
class="sidebar-editor"
|
||||
.hass=${this.hass}
|
||||
.field=${this.config.config.field}
|
||||
.key=${this.config.config.key}
|
||||
.excludeKeys=${this.config.config.excludeKeys}
|
||||
.disabled=${this.disabled}
|
||||
.yamlMode=${this.yamlMode}
|
||||
@value-changed=${this._valueChangedSidebar}
|
||||
@yaml-changed=${this._yamlChangedSidebar}
|
||||
></ha-script-field-editor>`
|
||||
)}
|
||||
</ha-automation-sidebar-card>`;
|
||||
}
|
||||
|
||||
@@ -110,6 +117,12 @@ export default class HaAutomationSidebarScriptField extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _yamlChangedSidebar(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
|
||||
this.config?.save?.(ev.detail.value);
|
||||
}
|
||||
|
||||
private _toggleYamlMode = () => {
|
||||
fireEvent(this, "toggle-yaml-mode");
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "@mdi/js";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { keyed } from "lit/directives/keyed";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { handleStructError } from "../../../../common/structs/handle-errors";
|
||||
import type { TriggerSidebarConfig } from "../../../../data/automation";
|
||||
@@ -35,6 +36,8 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: "sidebar-key" }) public sidebarKey?: string;
|
||||
|
||||
@state() private _requestShowId = false;
|
||||
|
||||
@state() private _warnings?: string[];
|
||||
@@ -183,18 +186,22 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-automation-trigger-editor
|
||||
class="sidebar-editor"
|
||||
.hass=${this.hass}
|
||||
.trigger=${this.config.config}
|
||||
@value-changed=${this._valueChangedSidebar}
|
||||
.uiSupported=${this.config.uiSupported}
|
||||
.showId=${this._requestShowId}
|
||||
.yamlMode=${this.yamlMode}
|
||||
.disabled=${this.disabled}
|
||||
@ui-mode-not-available=${this._handleUiModeNotAvailable}
|
||||
sidebar
|
||||
></ha-automation-trigger-editor>
|
||||
${keyed(
|
||||
this.sidebarKey,
|
||||
html`<ha-automation-trigger-editor
|
||||
class="sidebar-editor"
|
||||
.hass=${this.hass}
|
||||
.trigger=${this.config.config}
|
||||
@value-changed=${this._valueChangedSidebar}
|
||||
@yaml-changed=${this._yamlChangedSidebar}
|
||||
.uiSupported=${this.config.uiSupported}
|
||||
.showId=${this._requestShowId}
|
||||
.yamlMode=${this.yamlMode}
|
||||
.disabled=${this.disabled}
|
||||
@ui-mode-not-available=${this._handleUiModeNotAvailable}
|
||||
sidebar
|
||||
></ha-automation-trigger-editor>`
|
||||
)}
|
||||
</ha-automation-sidebar-card>
|
||||
`;
|
||||
}
|
||||
@@ -221,6 +228,12 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _yamlChangedSidebar(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
|
||||
this.config?.save?.(ev.detail.value);
|
||||
}
|
||||
|
||||
private _toggleYamlMode = () => {
|
||||
fireEvent(this, "toggle-yaml-mode");
|
||||
};
|
||||
|
||||
@@ -121,7 +121,7 @@ export default class HaAutomationTriggerEditor extends LitElement {
|
||||
if (!ev.detail.isValid) {
|
||||
return;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
fireEvent(this, "yaml-changed", {
|
||||
value: migrateAutomationTrigger(ev.detail.value),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -494,7 +494,7 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
},
|
||||
toggleYamlMode: () => {
|
||||
this._toggleYamlMode();
|
||||
return this._yamlMode;
|
||||
this.openSidebar();
|
||||
},
|
||||
disable: this._onDisable,
|
||||
delete: this._onDelete,
|
||||
|
||||
@@ -6,7 +6,6 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { storage } from "../../../../common/decorators/storage";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { listenMediaQuery } from "../../../../common/dom/media_query";
|
||||
import { nextRender } from "../../../../common/util/render-status";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-button-menu";
|
||||
@@ -45,8 +44,6 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public root = false;
|
||||
|
||||
@state() private _showReorder = false;
|
||||
|
||||
@state() private _rowSortSelected?: number;
|
||||
|
||||
@state()
|
||||
@@ -64,27 +61,12 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
|
||||
private _triggerKeys = new WeakMap<Trigger, string>();
|
||||
|
||||
private _unsubMql?: () => void;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._unsubMql = listenMediaQuery("(min-width: 600px)", (matches) => {
|
||||
this._showReorder = matches;
|
||||
});
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._unsubMql?.();
|
||||
this._unsubMql = undefined;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-sortable
|
||||
handle-selector=".handle"
|
||||
draggable-selector="ha-automation-trigger-row"
|
||||
.disabled=${!this._showReorder || this.disabled}
|
||||
.disabled=${this.disabled}
|
||||
group="triggers"
|
||||
invert-swap
|
||||
@item-moved=${this._triggerMoved}
|
||||
@@ -115,7 +97,7 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
.sortSelected=${this._rowSortSelected === idx}
|
||||
@stop-sort-selection=${this._stopSortSelection}
|
||||
>
|
||||
${this._showReorder && !this.disabled
|
||||
${!this.disabled
|
||||
? html`
|
||||
<div
|
||||
tabindex="0"
|
||||
|
||||
@@ -152,7 +152,12 @@ export default class HaScriptFieldEditor extends LitElement {
|
||||
ev.stopPropagation();
|
||||
const value = { ...ev.detail.value };
|
||||
|
||||
if (typeof value !== "object" || Object.keys(value).length !== 1) {
|
||||
if (
|
||||
typeof value !== "object" ||
|
||||
Object.keys(value).length !== 1 ||
|
||||
!value[Object.keys(value)[0]] ||
|
||||
!value[Object.keys(value)[0]].selector
|
||||
) {
|
||||
this._yamlError = "yaml_error";
|
||||
return;
|
||||
}
|
||||
@@ -165,7 +170,7 @@ export default class HaScriptFieldEditor extends LitElement {
|
||||
|
||||
const newValue = { ...value[key], key };
|
||||
|
||||
fireEvent(this, "value-changed", { value: newValue });
|
||||
fireEvent(this, "yaml-changed", { value: newValue });
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (
|
||||
|
||||
@@ -218,7 +218,7 @@ export default class HaScriptFieldRow extends LitElement {
|
||||
},
|
||||
toggleYamlMode: () => {
|
||||
this._toggleYamlMode();
|
||||
return this._yamlMode;
|
||||
this.openSidebar();
|
||||
},
|
||||
delete: this._onDelete,
|
||||
config: {
|
||||
|
||||
@@ -132,7 +132,7 @@ export default class HaScriptFieldSelectorEditor extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
fireEvent(this, "value-changed", { value });
|
||||
fireEvent(this, "yaml-changed", { value });
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (
|
||||
|
||||
@@ -73,6 +73,8 @@ export class HaManualScriptEditor extends LitElement {
|
||||
|
||||
@state() private _sidebarConfig?: SidebarConfig;
|
||||
|
||||
@state() private _sidebarKey?: string;
|
||||
|
||||
@query("ha-script-fields")
|
||||
private _scriptFields?: HaScriptFields;
|
||||
|
||||
@@ -223,6 +225,7 @@ export class HaManualScriptEditor extends LitElement {
|
||||
</div>
|
||||
<div class="sidebar-positioner">
|
||||
<ha-automation-sidebar
|
||||
.sidebarKey=${this._sidebarKey}
|
||||
tabindex="-1"
|
||||
class=${classMap({ hidden: !this._sidebarConfig })}
|
||||
.narrow=${this.narrow}
|
||||
@@ -463,6 +466,7 @@ export class HaManualScriptEditor extends LitElement {
|
||||
// deselect previous selected row
|
||||
this._sidebarConfig?.close?.();
|
||||
this._sidebarConfig = ev.detail;
|
||||
this._sidebarKey = JSON.stringify(this._sidebarConfig);
|
||||
|
||||
await this._sidebarElement?.updateComplete;
|
||||
this._sidebarElement?.focus();
|
||||
|
||||
@@ -19,7 +19,6 @@ import type {
|
||||
import { getSensorNumericDeviceClasses } from "../../../data/sensor";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { getGraphColorByIndex } from "../../../common/color/colors";
|
||||
import { computeTimelineColor } from "../../../components/chart/timeline-color";
|
||||
import { downSampleLineData } from "../../../components/chart/down-sample";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
@@ -93,20 +92,19 @@ class HuiHistoryChartCardFeature
|
||||
const width = this.clientWidth;
|
||||
const height = this.clientHeight;
|
||||
if (line) {
|
||||
const points = this._generateLinePoints(line);
|
||||
const { paths, filledPaths } = this._getLinePaths(points);
|
||||
const color = getGraphColorByIndex(0, this.style);
|
||||
const { points, yAxisOrigin } = this._generateLinePoints(line);
|
||||
const { paths, filledPaths } = this._getLinePaths(points, yAxisOrigin);
|
||||
|
||||
return html`
|
||||
<div class="line" @click=${this._handleClick}>
|
||||
${svg`<svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
|
||||
${paths.map(
|
||||
(path) =>
|
||||
svg`<path d="${path}" stroke="${color}" stroke-width="1" stroke-linecap="round" fill="none" />`
|
||||
svg`<path d="${path}" stroke="var(--feature-color)" stroke-width="1" stroke-linecap="round" fill="none" />`
|
||||
)}
|
||||
${filledPaths.map(
|
||||
(path) =>
|
||||
svg`<path d="${path}" stroke="none" stroke-linecap="round" fill="${color}" fill-opacity="0.2" />`
|
||||
svg`<path d="${path}" stroke="none" stroke-linecap="round" fill="var(--feature-color)" fill-opacity="0.2" />`
|
||||
)}
|
||||
</svg>`}
|
||||
</div>
|
||||
@@ -161,9 +159,13 @@ class HuiHistoryChartCardFeature
|
||||
);
|
||||
}
|
||||
|
||||
private _generateLinePoints(line: LineChartUnit): { x: number; y: number }[] {
|
||||
private _generateLinePoints(line: LineChartUnit): {
|
||||
points: { x: number; y: number }[];
|
||||
yAxisOrigin: number;
|
||||
} {
|
||||
const width = this.clientWidth;
|
||||
const height = this.clientHeight;
|
||||
let yAxisOrigin = height;
|
||||
let minY = Number(line.data[0].states[0].state);
|
||||
let maxY = Number(line.data[0].states[0].state);
|
||||
const minX = line.data[0].states[0].last_changed;
|
||||
@@ -172,8 +174,7 @@ class HuiHistoryChartCardFeature
|
||||
const stateValue = Number(stateData.state);
|
||||
if (stateValue < minY) {
|
||||
minY = stateValue;
|
||||
}
|
||||
if (stateValue > maxY) {
|
||||
} else if (stateValue > maxY) {
|
||||
maxY = stateValue;
|
||||
}
|
||||
});
|
||||
@@ -187,9 +188,21 @@ class HuiHistoryChartCardFeature
|
||||
minX,
|
||||
maxX
|
||||
);
|
||||
// add margin to the min and max
|
||||
minY -= rangeY * 0.1;
|
||||
maxY += rangeY * 0.1;
|
||||
if (maxY < 0) {
|
||||
// all values are negative
|
||||
// add margin
|
||||
maxY += rangeY * 0.1;
|
||||
maxY = Math.min(0, maxY);
|
||||
yAxisOrigin = 0;
|
||||
} else if (minY < 0) {
|
||||
// some values are negative
|
||||
yAxisOrigin = (maxY / (maxY - minY || 1)) * height;
|
||||
} else {
|
||||
// all values are positive
|
||||
// add margin
|
||||
minY -= rangeY * 0.1;
|
||||
minY = Math.max(0, minY);
|
||||
}
|
||||
const yDenom = maxY - minY || 1;
|
||||
const xDenom = maxX - minX || 1;
|
||||
const points = sampledData!.map((point) => {
|
||||
@@ -198,7 +211,7 @@ class HuiHistoryChartCardFeature
|
||||
return { x, y };
|
||||
});
|
||||
points.push({ x: width, y: points[points.length - 1].y });
|
||||
return points;
|
||||
return { points, yAxisOrigin };
|
||||
}
|
||||
|
||||
private _generateTimelineRanges(timeline: TimelineEntity) {
|
||||
@@ -234,7 +247,10 @@ class HuiHistoryChartCardFeature
|
||||
return ranges;
|
||||
}
|
||||
|
||||
private _getLinePaths(points: { x: number; y: number }[]) {
|
||||
private _getLinePaths(
|
||||
points: { x: number; y: number }[],
|
||||
yAxisOrigin: number
|
||||
) {
|
||||
const paths: string[] = [];
|
||||
const filledPaths: string[] = [];
|
||||
if (!points.length) {
|
||||
@@ -269,7 +285,7 @@ class HuiHistoryChartCardFeature
|
||||
paths.push(path);
|
||||
filledPaths.push(
|
||||
path +
|
||||
` L ${next!.x},${this.clientHeight} L ${pathPoints[0].x},${this.clientHeight} Z`
|
||||
` L ${next!.x},${yAxisOrigin} L ${pathPoints[0].x},${yAxisOrigin} Z`
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -138,6 +138,7 @@ export class HuiHeadingCard extends LitElement implements LovelaceCard {
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
height: 100%;
|
||||
min-height: 24px;
|
||||
}
|
||||
[role="button"] {
|
||||
cursor: pointer;
|
||||
@@ -147,7 +148,7 @@ export class HuiHeadingCard extends LitElement implements LovelaceCard {
|
||||
transition: transform 180ms ease-in-out;
|
||||
}
|
||||
.container {
|
||||
padding: 2px 4px;
|
||||
padding: 0 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
372
src/panels/lovelace/cards/hui-home-summary-card.ts
Normal file
372
src/panels/lovelace/cards/hui-home-summary-card.ts
Normal file
@@ -0,0 +1,372 @@
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { computeCssColor } from "../../../common/color/compute-color";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { generateEntityFilter } from "../../../common/entity/entity_filter";
|
||||
import { formatNumber } from "../../../common/number/format_number";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon";
|
||||
import "../../../components/ha-ripple";
|
||||
import "../../../components/tile/ha-tile-icon";
|
||||
import "../../../components/tile/ha-tile-info";
|
||||
import type { ActionHandlerEvent } from "../../../data/lovelace/action_handler";
|
||||
import "../../../state-display/state-display";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import {
|
||||
findEntities,
|
||||
getSummaryLabel,
|
||||
HOME_SUMMARIES_FILTERS,
|
||||
HOME_SUMMARIES_ICONS,
|
||||
type HomeSummary,
|
||||
} from "../strategies/home/helpers/home-summaries";
|
||||
import type { LovelaceCard, LovelaceGridOptions } from "../types";
|
||||
import type { HomeSummaryCard } from "./types";
|
||||
|
||||
const COLORS: Record<HomeSummary, string> = {
|
||||
lights: "amber",
|
||||
climate: "deep-orange",
|
||||
security: "blue",
|
||||
media_players: "purple",
|
||||
};
|
||||
|
||||
@customElement("hui-home-summary-card")
|
||||
export class HuiHomeSummaryCard extends LitElement implements LovelaceCard {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@state() private _config?: HomeSummaryCard;
|
||||
|
||||
public setConfig(config: HomeSummaryCard): void {
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
public getCardSize(): number {
|
||||
return this._config?.vertical ? 2 : 1;
|
||||
}
|
||||
|
||||
public getGridOptions(): LovelaceGridOptions {
|
||||
const columns = 6;
|
||||
let min_columns = 6;
|
||||
let rows = 1;
|
||||
|
||||
if (this._config?.vertical) {
|
||||
rows++;
|
||||
min_columns = 3;
|
||||
}
|
||||
return {
|
||||
columns,
|
||||
rows,
|
||||
min_columns,
|
||||
min_rows: rows,
|
||||
};
|
||||
}
|
||||
|
||||
private _handleAction(ev: ActionHandlerEvent) {
|
||||
handleAction(this, this.hass!, this._config!, ev.detail.action!);
|
||||
}
|
||||
|
||||
private get _hasCardAction() {
|
||||
return (
|
||||
hasAction(this._config?.tap_action) ||
|
||||
hasAction(this._config?.hold_action) ||
|
||||
hasAction(this._config?.double_tap_action)
|
||||
);
|
||||
}
|
||||
|
||||
private _computeSummaryState(): string {
|
||||
if (!this._config || !this.hass) {
|
||||
return "";
|
||||
}
|
||||
const allEntities = Object.keys(this.hass!.states);
|
||||
|
||||
const areas = Object.values(this.hass.areas);
|
||||
const areasFilter = generateEntityFilter(this.hass, {
|
||||
area: areas.map((area) => area.area_id),
|
||||
});
|
||||
|
||||
const entitiesInsideArea = allEntities.filter(areasFilter);
|
||||
|
||||
switch (this._config.summary) {
|
||||
case "lights": {
|
||||
// Number of lights on
|
||||
const lightsFilters = HOME_SUMMARIES_FILTERS.lights.map((filter) =>
|
||||
generateEntityFilter(this.hass!, filter)
|
||||
);
|
||||
|
||||
const lightEntities = findEntities(entitiesInsideArea, lightsFilters);
|
||||
|
||||
const onLights = lightEntities.filter((entityId) => {
|
||||
const s = this.hass!.states[entityId]?.state;
|
||||
return s === "on";
|
||||
});
|
||||
|
||||
return onLights.length
|
||||
? this.hass.localize("ui.card.home-summary.count_lights_on", {
|
||||
count: onLights.length,
|
||||
})
|
||||
: this.hass.localize("ui.card.home-summary.all_lights_off");
|
||||
}
|
||||
case "climate": {
|
||||
// Min/Max temperature of the areas
|
||||
const areaSensors = areas
|
||||
.map((area) => area.temperature_entity_id)
|
||||
.filter(Boolean);
|
||||
|
||||
const sensorsValues = areaSensors
|
||||
.map(
|
||||
(entityId) => parseFloat(this.hass!.states[entityId!].state) || NaN
|
||||
)
|
||||
.filter((value) => !isNaN(value));
|
||||
|
||||
if (sensorsValues.length === 0) {
|
||||
return "";
|
||||
}
|
||||
const minTemp = Math.min(...sensorsValues);
|
||||
const maxTemp = Math.max(...sensorsValues);
|
||||
|
||||
if (isNaN(minTemp) || isNaN(maxTemp)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const formattedMinTemp = formatNumber(minTemp, this.hass?.locale, {
|
||||
minimumFractionDigits: 1,
|
||||
maximumFractionDigits: 1,
|
||||
});
|
||||
const formattedMaxTemp = formatNumber(maxTemp, this.hass?.locale, {
|
||||
minimumFractionDigits: 1,
|
||||
maximumFractionDigits: 1,
|
||||
});
|
||||
return formattedMinTemp === formattedMaxTemp
|
||||
? `${formattedMinTemp}°`
|
||||
: `${formattedMinTemp} - ${formattedMaxTemp}°`;
|
||||
}
|
||||
case "security": {
|
||||
// Alarm and lock status
|
||||
const securityFilters = HOME_SUMMARIES_FILTERS.security.map((filter) =>
|
||||
generateEntityFilter(this.hass!, filter)
|
||||
);
|
||||
|
||||
const securityEntities = findEntities(
|
||||
entitiesInsideArea,
|
||||
securityFilters
|
||||
);
|
||||
|
||||
const locks = securityEntities.filter((entityId) => {
|
||||
const domain = computeDomain(entityId);
|
||||
return domain === "lock";
|
||||
});
|
||||
|
||||
const alarms = securityEntities.filter((entityId) => {
|
||||
const domain = computeDomain(entityId);
|
||||
return domain === "alarm_control_panel";
|
||||
});
|
||||
|
||||
const disarmedAlarms = alarms.filter((entityId) => {
|
||||
const s = this.hass!.states[entityId]?.state;
|
||||
return s === "disarmed";
|
||||
});
|
||||
|
||||
if (!locks.length && !alarms.length) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const unlockedLocks = locks.filter((entityId) => {
|
||||
const s = this.hass!.states[entityId]?.state;
|
||||
return s === "unlocked" || s === "jammed" || s === "open";
|
||||
});
|
||||
|
||||
if (unlockedLocks.length) {
|
||||
return this.hass.localize(
|
||||
"ui.card.home-summary.count_locks_unlocked",
|
||||
{
|
||||
count: unlockedLocks.length,
|
||||
}
|
||||
);
|
||||
}
|
||||
if (disarmedAlarms.length) {
|
||||
return this.hass.localize(
|
||||
"ui.card.home-summary.count_alarms_disarmed",
|
||||
{
|
||||
count: disarmedAlarms.length,
|
||||
}
|
||||
);
|
||||
}
|
||||
return this.hass.localize("ui.card.home-summary.all_secure");
|
||||
}
|
||||
case "media_players": {
|
||||
// Playing media
|
||||
const mediaPlayerFilters = HOME_SUMMARIES_FILTERS.media_players.map(
|
||||
(filter) => generateEntityFilter(this.hass!, filter)
|
||||
);
|
||||
|
||||
const mediaPlayerEntities = findEntities(
|
||||
entitiesInsideArea,
|
||||
mediaPlayerFilters
|
||||
);
|
||||
|
||||
const playingMedia = mediaPlayerEntities.filter((entityId) => {
|
||||
const s = this.hass!.states[entityId]?.state;
|
||||
return s === "playing";
|
||||
});
|
||||
|
||||
return playingMedia.length
|
||||
? this.hass.localize("ui.card.home-summary.count_media_playing", {
|
||||
count: playingMedia.length,
|
||||
})
|
||||
: this.hass.localize("ui.card.home-summary.no_media_playing");
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._config || !this.hass) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const contentClasses = { vertical: Boolean(this._config.vertical) };
|
||||
|
||||
const color = computeCssColor(COLORS[this._config.summary]);
|
||||
|
||||
const style = {
|
||||
"--tile-color": color,
|
||||
};
|
||||
|
||||
const secondary = this._computeSummaryState();
|
||||
|
||||
const label = getSummaryLabel(this.hass.localize, this._config.summary);
|
||||
const icon = HOME_SUMMARIES_ICONS[this._config.summary];
|
||||
|
||||
return html`
|
||||
<ha-card style=${styleMap(style)}>
|
||||
<div
|
||||
class="background"
|
||||
@action=${this._handleAction}
|
||||
.actionHandler=${actionHandler({
|
||||
hasHold: hasAction(this._config!.hold_action),
|
||||
hasDoubleClick: hasAction(this._config!.double_tap_action),
|
||||
})}
|
||||
role=${ifDefined(this._hasCardAction ? "button" : undefined)}
|
||||
tabindex=${ifDefined(this._hasCardAction ? "0" : undefined)}
|
||||
aria-labelledby="info"
|
||||
>
|
||||
<ha-ripple .disabled=${!this._hasCardAction}></ha-ripple>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="content ${classMap(contentClasses)}">
|
||||
<ha-tile-icon>
|
||||
<ha-icon slot="icon" .icon=${icon}></ha-icon>
|
||||
</ha-tile-icon>
|
||||
<ha-tile-info
|
||||
id="info"
|
||||
.primary=${label}
|
||||
.secondary=${secondary}
|
||||
></ha-tile-info>
|
||||
</div>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
--tile-color: var(--state-inactive-color);
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
ha-card:has(.background:focus-visible) {
|
||||
--shadow-default: var(--ha-card-box-shadow, 0 0 0 0 transparent);
|
||||
--shadow-focus: 0 0 0 1px var(--tile-color);
|
||||
border-color: var(--tile-color);
|
||||
box-shadow: var(--shadow-default), var(--shadow-focus);
|
||||
}
|
||||
ha-card {
|
||||
--ha-ripple-color: var(--tile-color);
|
||||
--ha-ripple-hover-opacity: 0.04;
|
||||
--ha-ripple-pressed-opacity: 0.12;
|
||||
height: 100%;
|
||||
transition:
|
||||
box-shadow 180ms ease-in-out,
|
||||
border-color 180ms ease-in-out;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
ha-card.active {
|
||||
--tile-color: var(--state-icon-color);
|
||||
}
|
||||
[role="button"] {
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
}
|
||||
[role="button"]:focus {
|
||||
outline: none;
|
||||
}
|
||||
.background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
border-radius: var(--ha-card-border-radius, 12px);
|
||||
margin: calc(-1 * var(--ha-card-border-width, 1px));
|
||||
overflow: hidden;
|
||||
}
|
||||
.container {
|
||||
margin: calc(-1 * var(--ha-card-border-width, 1px));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
.container.horizontal {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
pointer-events: none;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.vertical {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.vertical ha-tile-info {
|
||||
width: 100%;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
ha-tile-icon {
|
||||
--tile-icon-color: var(--tile-color);
|
||||
position: relative;
|
||||
padding: 6px;
|
||||
margin: -6px;
|
||||
}
|
||||
|
||||
ha-tile-info {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
transition: background-color 180ms ease-in-out;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-home-summary-card": HuiHomeSummaryCard;
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import type {
|
||||
import type { LovelaceHeaderFooterConfig } from "../header-footer/types";
|
||||
import type { LovelaceHeadingBadgeConfig } from "../heading-badges/types";
|
||||
import type { TimeFormat } from "../../../data/translation";
|
||||
import type { HomeSummary } from "../strategies/home/helpers/home-summaries";
|
||||
|
||||
export type AlarmPanelCardConfigState =
|
||||
| "arm_away"
|
||||
@@ -588,3 +589,11 @@ export interface HeadingCardConfig extends LovelaceCardConfig {
|
||||
/** @deprecated Use `badges` instead */
|
||||
entities?: LovelaceHeadingBadgeConfig[];
|
||||
}
|
||||
|
||||
export interface HomeSummaryCard extends LovelaceCardConfig {
|
||||
summary: HomeSummary;
|
||||
vertical?: boolean;
|
||||
tap_action?: ActionConfig;
|
||||
hold_action?: ActionConfig;
|
||||
double_tap_action?: ActionConfig;
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ const LAZY_LOAD_TYPES = {
|
||||
"energy-sankey": () => import("../cards/energy/hui-energy-sankey-card"),
|
||||
"entity-filter": () => import("../cards/hui-entity-filter-card"),
|
||||
error: () => import("../cards/hui-error-card"),
|
||||
"home-summary": () => import("../cards/hui-home-summary-card"),
|
||||
gauge: () => import("../cards/hui-gauge-card"),
|
||||
"history-graph": () => import("../cards/hui-history-graph-card"),
|
||||
"horizontal-stack": () => import("../cards/hui-horizontal-stack-card"),
|
||||
|
||||
@@ -355,7 +355,10 @@ class HUIRoot extends LitElement {
|
||||
overflowItems.forEach((i) => {
|
||||
const title = [this.hass!.localize(i.key), i.suffix].join(" ");
|
||||
const action = i.subItems
|
||||
? () => {
|
||||
? (e) => {
|
||||
if (!shouldHandleRequestSelectedEvent(e)) {
|
||||
return;
|
||||
}
|
||||
showListItemsDialog(this, {
|
||||
title: title,
|
||||
items: i.subItems!.map((si) => ({
|
||||
|
||||
@@ -9,16 +9,12 @@ import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type {
|
||||
AreaCardConfig,
|
||||
ButtonCardConfig,
|
||||
HomeSummaryCard,
|
||||
MarkdownCardConfig,
|
||||
TileCardConfig,
|
||||
WeatherForecastCardConfig,
|
||||
} from "../../cards/types";
|
||||
import { getAreas } from "../areas/helpers/areas-strategy-helper";
|
||||
import {
|
||||
getSummaryLabel,
|
||||
HOME_SUMMARIES_ICONS,
|
||||
} from "./helpers/home-summaries";
|
||||
|
||||
export interface HomeMainViewStrategyConfig {
|
||||
type: "home-main";
|
||||
@@ -114,61 +110,57 @@ export class HomeMainViewStrategy extends ReactiveElement {
|
||||
heading: hass.localize("ui.panel.lovelace.strategy.home.summaries"),
|
||||
},
|
||||
{
|
||||
type: "button",
|
||||
icon: HOME_SUMMARIES_ICONS.lights,
|
||||
name: getSummaryLabel(hass.localize, "lights"),
|
||||
icon_height: "24px",
|
||||
grid_options: {
|
||||
rows: 2,
|
||||
columns: 4,
|
||||
},
|
||||
type: "home-summary",
|
||||
summary: "lights",
|
||||
vertical: true,
|
||||
tap_action: {
|
||||
action: "navigate",
|
||||
navigation_path: "lights",
|
||||
},
|
||||
} satisfies ButtonCardConfig,
|
||||
{
|
||||
type: "button",
|
||||
icon: HOME_SUMMARIES_ICONS.climate,
|
||||
name: getSummaryLabel(hass.localize, "climate"),
|
||||
icon_height: "30px",
|
||||
grid_options: {
|
||||
rows: 2,
|
||||
columns: 4,
|
||||
},
|
||||
} satisfies HomeSummaryCard,
|
||||
{
|
||||
type: "home-summary",
|
||||
summary: "climate",
|
||||
vertical: true,
|
||||
tap_action: {
|
||||
action: "navigate",
|
||||
navigation_path: "climate",
|
||||
},
|
||||
} satisfies ButtonCardConfig,
|
||||
{
|
||||
type: "button",
|
||||
icon: HOME_SUMMARIES_ICONS.security,
|
||||
name: getSummaryLabel(hass.localize, "security"),
|
||||
icon_height: "30px",
|
||||
grid_options: {
|
||||
rows: 2,
|
||||
columns: 4,
|
||||
},
|
||||
} satisfies HomeSummaryCard,
|
||||
{
|
||||
type: "home-summary",
|
||||
summary: "security",
|
||||
vertical: true,
|
||||
tap_action: {
|
||||
action: "navigate",
|
||||
navigation_path: "security",
|
||||
},
|
||||
} satisfies ButtonCardConfig,
|
||||
{
|
||||
type: "button",
|
||||
icon: HOME_SUMMARIES_ICONS.media_players,
|
||||
name: getSummaryLabel(hass.localize, "media_players"),
|
||||
icon_height: "30px",
|
||||
grid_options: {
|
||||
rows: 2,
|
||||
columns: 4,
|
||||
},
|
||||
} satisfies HomeSummaryCard,
|
||||
{
|
||||
type: "home-summary",
|
||||
summary: "media_players",
|
||||
vertical: true,
|
||||
tap_action: {
|
||||
action: "navigate",
|
||||
navigation_path: "media-players",
|
||||
},
|
||||
} satisfies ButtonCardConfig,
|
||||
grid_options: {
|
||||
rows: 2,
|
||||
columns: 4,
|
||||
},
|
||||
} satisfies HomeSummaryCard,
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@@ -201,6 +201,15 @@
|
||||
"open_door_confirm": "Really open?",
|
||||
"open_door_done": "Done"
|
||||
},
|
||||
"home-summary": {
|
||||
"all_lights_off": "All off",
|
||||
"count_lights_on": "{count} {count, plural,\n one {on}\n other {on}\n}",
|
||||
"count_locks_unlocked": "{count} {count, plural,\n one {unlocked}\n other {unlocked}\n}",
|
||||
"count_alarms_disarmed": "{count} {count, plural,\n one {disarmed}\n other {disarmed}\n}",
|
||||
"all_secure": "All secure",
|
||||
"no_media_playing": "No media playing",
|
||||
"count_media_playing": "{count} {count, plural,\n one {playing}\n other {playing}\n}"
|
||||
},
|
||||
"media_player": {
|
||||
"source": "Source",
|
||||
"sound_mode": "Sound mode",
|
||||
@@ -4109,6 +4118,9 @@
|
||||
},
|
||||
"conditions": {
|
||||
"name": "Conditions",
|
||||
"header": "And if",
|
||||
"description": "All conditions added here need to be satisfied for the automation to run. A condition can be satisfied or not at any given time, for example: ''If {user} is home''. You can use building blocks to create more complex conditions.",
|
||||
"learn_more": "Learn more about conditions",
|
||||
"add": "Add condition",
|
||||
"search": "Search condition",
|
||||
"add_building_block": "Add building block",
|
||||
@@ -5115,7 +5127,7 @@
|
||||
"integration": "Integration",
|
||||
"config_entry": "Config entry"
|
||||
},
|
||||
"enabled_description": "Disabled devices will not be shown and entities belonging to the device will be disabled and not added to Home Assistant.",
|
||||
"enabled_description": "Disabled devices and services will not be shown and entities belonging to them will be disabled, too.",
|
||||
"open_configuration_url": "Visit",
|
||||
"set_up_voice_assistant": "Set up voice assistant",
|
||||
"download_diagnostics": "Download diagnostics",
|
||||
|
||||
Reference in New Issue
Block a user