mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-28 03:36:44 +00:00
Add screen condition to conditional card. (#18041)
This commit is contained in:
parent
5a6d6dc7d3
commit
86c014b677
9
src/common/array/combinations.ts
Normal file
9
src/common/array/combinations.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export function getAllCombinations<T>(arr: T[]) {
|
||||||
|
return arr.reduce<T[][]>(
|
||||||
|
(combinations, element) =>
|
||||||
|
combinations.concat(
|
||||||
|
combinations.map((combination) => [...combination, element])
|
||||||
|
),
|
||||||
|
[[]]
|
||||||
|
);
|
||||||
|
}
|
7
src/panels/lovelace/common/icon-condition.ts
Normal file
7
src/panels/lovelace/common/icon-condition.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { mdiResponsive, mdiStateMachine } from "@mdi/js";
|
||||||
|
import { Condition } from "./validate-condition";
|
||||||
|
|
||||||
|
export const ICON_CONDITION: Record<Condition["condition"], string> = {
|
||||||
|
state: mdiStateMachine,
|
||||||
|
screen: mdiResponsive,
|
||||||
|
};
|
@ -1,10 +1,44 @@
|
|||||||
import { UNAVAILABLE } from "../../../data/entity";
|
import { UNAVAILABLE } from "../../../data/entity";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
|
|
||||||
export interface Condition {
|
export type Condition = StateCondition | ScreenCondition;
|
||||||
entity: string;
|
|
||||||
|
export type LegacyCondition = {
|
||||||
|
entity?: string;
|
||||||
state?: string;
|
state?: string;
|
||||||
state_not?: string;
|
state_not?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type StateCondition = {
|
||||||
|
condition: "state";
|
||||||
|
entity?: string;
|
||||||
|
state?: string;
|
||||||
|
state_not?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ScreenCondition = {
|
||||||
|
condition: "screen";
|
||||||
|
media_query?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function checkStateCondition(condition: StateCondition, hass: HomeAssistant) {
|
||||||
|
const state =
|
||||||
|
condition.entity && hass.states[condition.entity]
|
||||||
|
? hass.states[condition.entity].state
|
||||||
|
: UNAVAILABLE;
|
||||||
|
|
||||||
|
return condition.state != null
|
||||||
|
? state === condition.state
|
||||||
|
: state !== condition.state_not;
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkScreenCondition(
|
||||||
|
condition: ScreenCondition,
|
||||||
|
_hass: HomeAssistant
|
||||||
|
) {
|
||||||
|
return condition.media_query
|
||||||
|
? matchMedia(condition.media_query).matches
|
||||||
|
: false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function checkConditionsMet(
|
export function checkConditionsMet(
|
||||||
@ -12,18 +46,30 @@ export function checkConditionsMet(
|
|||||||
hass: HomeAssistant
|
hass: HomeAssistant
|
||||||
): boolean {
|
): boolean {
|
||||||
return conditions.every((c) => {
|
return conditions.every((c) => {
|
||||||
const state = hass.states[c.entity]
|
if (c.condition === "screen") {
|
||||||
? hass!.states[c.entity].state
|
return checkScreenCondition(c, hass);
|
||||||
: UNAVAILABLE;
|
}
|
||||||
|
|
||||||
return c.state != null ? state === c.state : state !== c.state_not;
|
return checkStateCondition(c, hass);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validateConditionalConfig(conditions: Condition[]): boolean {
|
function valideStateCondition(condition: StateCondition) {
|
||||||
return conditions.every(
|
return (
|
||||||
(c) =>
|
condition.entity != null &&
|
||||||
(c.entity &&
|
(condition.state != null || condition.state_not != null)
|
||||||
(c.state != null || c.state_not != null)) as unknown as boolean
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function validateScreenCondition(condition: ScreenCondition) {
|
||||||
|
return condition.media_query != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateConditionalConfig(conditions: Condition[]): boolean {
|
||||||
|
return conditions.every((c) => {
|
||||||
|
if (c.condition === "screen") {
|
||||||
|
return validateScreenCondition(c);
|
||||||
|
}
|
||||||
|
return valideStateCondition(c);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -3,11 +3,14 @@ import { customElement, property } from "lit/decorators";
|
|||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { ConditionalCardConfig } from "../cards/types";
|
import { ConditionalCardConfig } from "../cards/types";
|
||||||
import {
|
import {
|
||||||
|
ScreenCondition,
|
||||||
checkConditionsMet,
|
checkConditionsMet,
|
||||||
validateConditionalConfig,
|
validateConditionalConfig,
|
||||||
} from "../common/validate-condition";
|
} from "../common/validate-condition";
|
||||||
import { ConditionalRowConfig, LovelaceRow } from "../entity-rows/types";
|
import { ConditionalRowConfig, LovelaceRow } from "../entity-rows/types";
|
||||||
import { LovelaceCard } from "../types";
|
import { LovelaceCard } from "../types";
|
||||||
|
import { listenMediaQuery } from "../../../common/dom/media_query";
|
||||||
|
import { deepEqual } from "../../../common/util/deep-equal";
|
||||||
|
|
||||||
@customElement("hui-conditional-base")
|
@customElement("hui-conditional-base")
|
||||||
export class HuiConditionalBase extends ReactiveElement {
|
export class HuiConditionalBase extends ReactiveElement {
|
||||||
@ -21,6 +24,10 @@ export class HuiConditionalBase extends ReactiveElement {
|
|||||||
|
|
||||||
protected _element?: LovelaceCard | LovelaceRow;
|
protected _element?: LovelaceCard | LovelaceRow;
|
||||||
|
|
||||||
|
private _mediaQueriesListeners: Array<() => void> = [];
|
||||||
|
|
||||||
|
private _mediaQueries: string[] = [];
|
||||||
|
|
||||||
protected createRenderRoot() {
|
protected createRenderRoot() {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -47,27 +54,98 @@ export class HuiConditionalBase extends ReactiveElement {
|
|||||||
this._config = config;
|
this._config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
this._clearMediaQueries();
|
||||||
|
}
|
||||||
|
|
||||||
|
public connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
this._listenMediaQueries();
|
||||||
|
this._updateVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _clearMediaQueries() {
|
||||||
|
this._mediaQueries = [];
|
||||||
|
while (this._mediaQueriesListeners.length) {
|
||||||
|
this._mediaQueriesListeners.pop()!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _listenMediaQueries() {
|
||||||
|
if (!this._config) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const conditions = this._config.conditions.filter(
|
||||||
|
(c) => c.condition === "screen"
|
||||||
|
) as ScreenCondition[];
|
||||||
|
|
||||||
|
const mediaQueries = conditions
|
||||||
|
.filter((c) => c.media_query)
|
||||||
|
.map((c) => c.media_query as string);
|
||||||
|
|
||||||
|
if (deepEqual(mediaQueries, this._mediaQueries)) return;
|
||||||
|
|
||||||
|
this._mediaQueries = mediaQueries;
|
||||||
|
while (this._mediaQueriesListeners.length) {
|
||||||
|
this._mediaQueriesListeners.pop()!();
|
||||||
|
}
|
||||||
|
mediaQueries.forEach((query) => {
|
||||||
|
const listener = listenMediaQuery(query, (matches) => {
|
||||||
|
// For performance, if there is only one condition, set the visibility directly
|
||||||
|
if (this._config!.conditions.length === 1) {
|
||||||
|
this._setVisibility(matches);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._updateVisibility();
|
||||||
|
});
|
||||||
|
this._mediaQueriesListeners.push(listener);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
protected update(changed: PropertyValues): void {
|
protected update(changed: PropertyValues): void {
|
||||||
super.update(changed);
|
super.update(changed);
|
||||||
|
|
||||||
|
if (
|
||||||
|
changed.has("_element") ||
|
||||||
|
changed.has("_config") ||
|
||||||
|
changed.has("hass")
|
||||||
|
) {
|
||||||
|
this._listenMediaQueries();
|
||||||
|
this._updateVisibility();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _updateVisibility() {
|
||||||
if (!this._element || !this.hass || !this._config) {
|
if (!this._element || !this.hass || !this._config) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._element.editMode = this.editMode;
|
this._element.editMode = this.editMode;
|
||||||
|
|
||||||
const visible =
|
const conditionMet = checkConditionsMet(
|
||||||
this.editMode || checkConditionsMet(this._config.conditions, this.hass);
|
this._config!.conditions,
|
||||||
this.hidden = !visible;
|
this.hass!
|
||||||
|
);
|
||||||
|
this._setVisibility(conditionMet);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _setVisibility(conditionMet: boolean) {
|
||||||
|
if (!this._element || !this.hass) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const visible = this.editMode || conditionMet;
|
||||||
|
this.hidden = !visible;
|
||||||
this.style.setProperty("display", visible ? "" : "none");
|
this.style.setProperty("display", visible ? "" : "none");
|
||||||
|
|
||||||
if (visible) {
|
if (visible) {
|
||||||
this._element.hass = this.hass;
|
this._element.hass = this.hass;
|
||||||
if (!this._element.parentElement) {
|
if (!this._element!.parentElement) {
|
||||||
this.appendChild(this._element);
|
this.appendChild(this._element!);
|
||||||
}
|
}
|
||||||
} else if (this._element.parentElement) {
|
} else if (this._element.parentElement) {
|
||||||
this.removeChild(this._element);
|
this.removeChild(this._element!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,197 @@
|
|||||||
|
import { preventDefault } from "@fullcalendar/core/internal";
|
||||||
|
import { ActionDetail } from "@material/mwc-list";
|
||||||
|
import { mdiCheck, mdiDelete, mdiDotsVertical } from "@mdi/js";
|
||||||
|
import { LitElement, css, html, nothing } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
|
||||||
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||||
|
import "../../../../components/ha-button-menu";
|
||||||
|
import "../../../../components/ha-icon-button";
|
||||||
|
import "../../../../components/ha-list-item";
|
||||||
|
import "../../../../components/ha-svg-icon";
|
||||||
|
import "../../../../components/ha-yaml-editor";
|
||||||
|
import { haStyle } from "../../../../resources/styles";
|
||||||
|
import type { HomeAssistant } from "../../../../types";
|
||||||
|
import { Condition, LegacyCondition } from "../../common/validate-condition";
|
||||||
|
import type { LovelaceConditionEditorConstructor } from "./types";
|
||||||
|
import { ICON_CONDITION } from "../../common/icon-condition";
|
||||||
|
|
||||||
|
@customElement("ha-card-condition-editor")
|
||||||
|
export default class HaCardConditionEditor extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) condition!: Condition | LegacyCondition;
|
||||||
|
|
||||||
|
@state() public _yamlMode = false;
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
const condition: Condition = {
|
||||||
|
condition: "state",
|
||||||
|
...this.condition,
|
||||||
|
};
|
||||||
|
const element = customElements.get(
|
||||||
|
`ha-card-condition-${condition.condition}`
|
||||||
|
) as LovelaceConditionEditorConstructor | undefined;
|
||||||
|
const supported = element !== undefined;
|
||||||
|
|
||||||
|
const valid =
|
||||||
|
element &&
|
||||||
|
(!element.validateUIConfig || element.validateUIConfig(condition));
|
||||||
|
|
||||||
|
const yamlMode = this._yamlMode || !supported || !valid;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="header">
|
||||||
|
<ha-svg-icon
|
||||||
|
class="icon"
|
||||||
|
.path=${ICON_CONDITION[condition.condition]}
|
||||||
|
></ha-svg-icon>
|
||||||
|
<span class="title">
|
||||||
|
${this.hass.localize(
|
||||||
|
`ui.panel.lovelace.editor.card.conditional.condition.${condition.condition}.label`
|
||||||
|
) || condition.condition}
|
||||||
|
</span>
|
||||||
|
<ha-button-menu
|
||||||
|
slot="icons"
|
||||||
|
@action=${this._handleAction}
|
||||||
|
@click=${preventDefault}
|
||||||
|
@closed=${stopPropagation}
|
||||||
|
fixed
|
||||||
|
.corner=${"BOTTOM_END"}
|
||||||
|
.menuCorner=${"END"}
|
||||||
|
>
|
||||||
|
<ha-icon-button
|
||||||
|
slot="trigger"
|
||||||
|
.label=${this.hass.localize("ui.common.menu")}
|
||||||
|
.path=${mdiDotsVertical}
|
||||||
|
>
|
||||||
|
</ha-icon-button>
|
||||||
|
|
||||||
|
<ha-list-item graphic="icon" .disabled=${!supported || !valid}>
|
||||||
|
${this.hass.localize("ui.panel.lovelace.editor.edit_card.edit_ui")}
|
||||||
|
${!yamlMode
|
||||||
|
? html`
|
||||||
|
<ha-svg-icon
|
||||||
|
class="selected_menu_item"
|
||||||
|
slot="graphic"
|
||||||
|
.path=${mdiCheck}
|
||||||
|
></ha-svg-icon>
|
||||||
|
`
|
||||||
|
: ``}
|
||||||
|
</ha-list-item>
|
||||||
|
|
||||||
|
<ha-list-item graphic="icon">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.lovelace.editor.edit_card.edit_yaml"
|
||||||
|
)}
|
||||||
|
${yamlMode
|
||||||
|
? html`
|
||||||
|
<ha-svg-icon
|
||||||
|
class="selected_menu_item"
|
||||||
|
slot="graphic"
|
||||||
|
.path=${mdiCheck}
|
||||||
|
></ha-svg-icon>
|
||||||
|
`
|
||||||
|
: ``}
|
||||||
|
</ha-list-item>
|
||||||
|
|
||||||
|
<li divider role="separator"></li>
|
||||||
|
|
||||||
|
<ha-list-item class="warning" graphic="icon">
|
||||||
|
${this.hass!.localize("ui.common.delete")}
|
||||||
|
<ha-svg-icon
|
||||||
|
class="warning"
|
||||||
|
slot="graphic"
|
||||||
|
.path=${mdiDelete}
|
||||||
|
></ha-svg-icon>
|
||||||
|
</ha-list-item>
|
||||||
|
</ha-button-menu>
|
||||||
|
</div>
|
||||||
|
${!valid
|
||||||
|
? html`
|
||||||
|
<ha-alert alert-type="warning">
|
||||||
|
${this.hass.localize("ui.errors.config.editor_not_supported")}
|
||||||
|
</ha-alert>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
<div class="content">
|
||||||
|
${yamlMode
|
||||||
|
? html`
|
||||||
|
<ha-yaml-editor
|
||||||
|
.hass=${this.hass}
|
||||||
|
.defaultValue=${this.condition}
|
||||||
|
@value-changed=${this._onYamlChange}
|
||||||
|
></ha-yaml-editor>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
${dynamicElement(`ha-card-condition-${condition.condition}`, {
|
||||||
|
hass: this.hass,
|
||||||
|
condition: condition,
|
||||||
|
})}
|
||||||
|
`}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleAction(ev: CustomEvent<ActionDetail>) {
|
||||||
|
switch (ev.detail.index) {
|
||||||
|
case 0:
|
||||||
|
this._yamlMode = false;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
this._yamlMode = true;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
this._delete();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _delete() {
|
||||||
|
fireEvent(this, "value-changed", { value: null });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onYamlChange(ev: CustomEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
if (!ev.detail.isValid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
fireEvent(this, "value-changed", { value: ev.detail.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = [
|
||||||
|
haStyle,
|
||||||
|
css`
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.header span {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
.header .icon {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
.selected_menu_item {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
li[role="separator"] {
|
||||||
|
border-bottom-color: var(--divider-color);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-card-condition-editor": HaCardConditionEditor;
|
||||||
|
}
|
||||||
|
}
|
6
src/panels/lovelace/editor/conditions/types.ts
Normal file
6
src/panels/lovelace/editor/conditions/types.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { Condition } from "../../common/validate-condition";
|
||||||
|
|
||||||
|
export interface LovelaceConditionEditorConstructor {
|
||||||
|
defaultConfig?: Condition;
|
||||||
|
validateUIConfig?: (condition: Condition) => boolean;
|
||||||
|
}
|
@ -0,0 +1,196 @@
|
|||||||
|
import { html, LitElement } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
import { getAllCombinations } from "../../../../../common/array/combinations";
|
||||||
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
|
import { LocalizeFunc } from "../../../../../common/translations/localize";
|
||||||
|
import "../../../../../components/ha-form/ha-form";
|
||||||
|
import type { SchemaUnion } from "../../../../../components/ha-form/types";
|
||||||
|
import { HaFormSchema } from "../../../../../components/ha-form/types";
|
||||||
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
|
import { ScreenCondition } from "../../../common/validate-condition";
|
||||||
|
|
||||||
|
const BREAKPOINT_VALUES = [0, 768, 1024, 1280, Infinity];
|
||||||
|
const BREAKPOINTS = ["mobile", "tablet", "desktop", "wide"] as const;
|
||||||
|
|
||||||
|
type BreakpointSize = [number, number];
|
||||||
|
type Breakpoint = (typeof BREAKPOINTS)[number];
|
||||||
|
|
||||||
|
function mergeConsecutiveRanges(arr: [number, number][]): [number, number][] {
|
||||||
|
if (arr.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
[...arr].sort((a, b) => a[0] - b[0]);
|
||||||
|
|
||||||
|
const mergedRanges = [arr[0]];
|
||||||
|
|
||||||
|
for (let i = 1; i < arr.length; i++) {
|
||||||
|
const currentRange = arr[i];
|
||||||
|
const previousRange = mergedRanges[mergedRanges.length - 1];
|
||||||
|
|
||||||
|
if (currentRange[0] <= previousRange[1] + 1) {
|
||||||
|
previousRange[1] = currentRange[1];
|
||||||
|
} else {
|
||||||
|
mergedRanges.push(currentRange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mergedRanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildMediaQuery(size: BreakpointSize) {
|
||||||
|
const [min, max] = size;
|
||||||
|
const query: string[] = [];
|
||||||
|
if (min != null) {
|
||||||
|
query.push(`(min-width: ${min}px)`);
|
||||||
|
}
|
||||||
|
if (max != null && max !== Infinity) {
|
||||||
|
query.push(`(max-width: ${max - 1}px)`);
|
||||||
|
}
|
||||||
|
return query.join(" and ");
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeBreakpointsSize(breakpoints: Breakpoint[]) {
|
||||||
|
const sizes = breakpoints.map<BreakpointSize>((breakpoint) => {
|
||||||
|
const index = BREAKPOINTS.indexOf(breakpoint);
|
||||||
|
return [BREAKPOINT_VALUES[index], BREAKPOINT_VALUES[index + 1] || Infinity];
|
||||||
|
});
|
||||||
|
|
||||||
|
const mergedSizes = mergeConsecutiveRanges(sizes);
|
||||||
|
|
||||||
|
const queries = mergedSizes
|
||||||
|
.map((size) => buildMediaQuery(size))
|
||||||
|
.filter((size) => size);
|
||||||
|
|
||||||
|
return queries.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeBreakpointsKey(breakpoints) {
|
||||||
|
return [...breakpoints].sort().join("_");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute all possible media queries from each breakpoints combination (2 ^ breakpoints = 16)
|
||||||
|
const queries = getAllCombinations(BREAKPOINTS as unknown as Breakpoint[])
|
||||||
|
.filter((arr) => arr.length !== 0)
|
||||||
|
.map(
|
||||||
|
(breakpoints) =>
|
||||||
|
[breakpoints, computeBreakpointsSize(breakpoints)] as [
|
||||||
|
Breakpoint[],
|
||||||
|
string,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Store them in maps to avoid recomputing them
|
||||||
|
const mediaQueryMap = new Map(
|
||||||
|
queries.map(([b, m]) => [computeBreakpointsKey(b), m])
|
||||||
|
);
|
||||||
|
const mediaQueryReverseMap = new Map(queries.map(([b, m]) => [m, b]));
|
||||||
|
|
||||||
|
type ScreenConditionData = {
|
||||||
|
breakpoints: Breakpoint[];
|
||||||
|
};
|
||||||
|
|
||||||
|
@customElement("ha-card-condition-screen")
|
||||||
|
export class HaCardConditionScreen extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public condition!: ScreenCondition;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
public static get defaultConfig(): ScreenCondition {
|
||||||
|
return { condition: "screen", media_query: "" };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static validateUIConfig(condition: ScreenCondition) {
|
||||||
|
return (
|
||||||
|
!condition.media_query || mediaQueryReverseMap.get(condition.media_query)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _schema = memoizeOne(
|
||||||
|
(localize: LocalizeFunc) =>
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: "breakpoints",
|
||||||
|
selector: {
|
||||||
|
select: {
|
||||||
|
mode: "list",
|
||||||
|
options: BREAKPOINTS.map((b) => {
|
||||||
|
const value = BREAKPOINT_VALUES[BREAKPOINTS.indexOf(b)];
|
||||||
|
return {
|
||||||
|
value: b,
|
||||||
|
label: `${localize(
|
||||||
|
`ui.panel.lovelace.editor.card.conditional.condition.screen.breakpoints_list.${b}`
|
||||||
|
)}${
|
||||||
|
value
|
||||||
|
? ` (${localize(
|
||||||
|
`ui.panel.lovelace.editor.card.conditional.condition.screen.min`,
|
||||||
|
{ size: value }
|
||||||
|
)})`
|
||||||
|
: ""
|
||||||
|
}`,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
multiple: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
] as const satisfies readonly HaFormSchema[]
|
||||||
|
);
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
const breakpoints = this.condition.media_query
|
||||||
|
? mediaQueryReverseMap.get(this.condition.media_query)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const data: ScreenConditionData = {
|
||||||
|
breakpoints: breakpoints ?? [],
|
||||||
|
};
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-form
|
||||||
|
.hass=${this.hass}
|
||||||
|
.data=${data}
|
||||||
|
.schema=${this._schema(this.hass.localize)}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
.computeLabel=${this._computeLabelCallback}
|
||||||
|
></ha-form>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged(ev: CustomEvent): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const data = ev.detail.value as ScreenConditionData;
|
||||||
|
|
||||||
|
const { breakpoints } = data;
|
||||||
|
|
||||||
|
const condition: ScreenCondition = {
|
||||||
|
condition: "screen",
|
||||||
|
media_query: mediaQueryMap.get(computeBreakpointsKey(breakpoints)) ?? "",
|
||||||
|
};
|
||||||
|
|
||||||
|
fireEvent(this, "value-changed", { value: condition });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computeLabelCallback = (
|
||||||
|
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||||
|
): string => {
|
||||||
|
switch (schema.name) {
|
||||||
|
case "breakpoints":
|
||||||
|
return this.hass.localize(
|
||||||
|
`ui.panel.lovelace.editor.card.conditional.condition.screen.${schema.name}`
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-card-condition-screen": HaCardConditionScreen;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,164 @@
|
|||||||
|
import { html, LitElement, PropertyValues } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
import { assert, literal, object, optional, string } from "superstruct";
|
||||||
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
|
import { LocalizeFunc } from "../../../../../common/translations/localize";
|
||||||
|
import "../../../../../components/ha-form/ha-form";
|
||||||
|
import type { SchemaUnion } from "../../../../../components/ha-form/types";
|
||||||
|
import { HaFormSchema } from "../../../../../components/ha-form/types";
|
||||||
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
|
import { StateCondition } from "../../../common/validate-condition";
|
||||||
|
|
||||||
|
const stateConditionStruct = object({
|
||||||
|
condition: literal("state"),
|
||||||
|
entity: string(),
|
||||||
|
state: optional(string()),
|
||||||
|
state_not: optional(string()),
|
||||||
|
});
|
||||||
|
|
||||||
|
type StateConditionData = {
|
||||||
|
condition: "state";
|
||||||
|
entity: string;
|
||||||
|
invert: "true" | "false";
|
||||||
|
state?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
@customElement("ha-card-condition-state")
|
||||||
|
export class HaCardConditionState extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public condition!: StateCondition;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
public static get defaultConfig(): StateCondition {
|
||||||
|
return { condition: "state", entity: "", state: "" };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected willUpdate(changedProperties: PropertyValues): void {
|
||||||
|
if (!changedProperties.has("condition")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
assert(this.condition, stateConditionStruct);
|
||||||
|
} catch (err: any) {
|
||||||
|
fireEvent(this, "ui-mode-not-available", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _schema = memoizeOne(
|
||||||
|
(localize: LocalizeFunc) =>
|
||||||
|
[
|
||||||
|
{ name: "entity", selector: { entity: {} } },
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
type: "grid",
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
name: "invert",
|
||||||
|
selector: {
|
||||||
|
select: {
|
||||||
|
mode: "dropdown",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: localize(
|
||||||
|
"ui.panel.lovelace.editor.card.conditional.state_equal"
|
||||||
|
),
|
||||||
|
value: "false",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: localize(
|
||||||
|
"ui.panel.lovelace.editor.card.conditional.state_not_equal"
|
||||||
|
),
|
||||||
|
value: "true",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "state",
|
||||||
|
selector: {
|
||||||
|
state: {},
|
||||||
|
},
|
||||||
|
context: {
|
||||||
|
filter_entity: "entity",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] as const satisfies readonly HaFormSchema[]
|
||||||
|
);
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
const { state, state_not, ...content } = this.condition;
|
||||||
|
|
||||||
|
const data: StateConditionData = {
|
||||||
|
...content,
|
||||||
|
entity: this.condition.entity ?? "",
|
||||||
|
invert: this.condition.state_not ? "true" : "false",
|
||||||
|
state: this.condition.state_not ?? this.condition.state ?? "",
|
||||||
|
};
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-form
|
||||||
|
.hass=${this.hass}
|
||||||
|
.data=${data}
|
||||||
|
.schema=${this._schema(this.hass.localize)}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
.computeLabel=${this._computeLabelCallback}
|
||||||
|
></ha-form>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged(ev: CustomEvent): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const data = ev.detail.value as StateConditionData;
|
||||||
|
|
||||||
|
const { invert, state, entity, condition: _, ...content } = data;
|
||||||
|
|
||||||
|
const condition: StateCondition = {
|
||||||
|
condition: "state",
|
||||||
|
...content,
|
||||||
|
entity: entity ?? "",
|
||||||
|
state: invert === "false" ? state ?? "" : undefined,
|
||||||
|
state_not: invert === "true" ? state ?? "" : undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
fireEvent(this, "value-changed", { value: condition });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computeLabelCallback = (
|
||||||
|
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||||
|
): string => {
|
||||||
|
const entity = this.condition.entity
|
||||||
|
? this.hass.states[this.condition.entity]
|
||||||
|
: undefined;
|
||||||
|
switch (schema.name) {
|
||||||
|
case "entity":
|
||||||
|
return this.hass.localize("ui.components.entity.entity-picker.entity");
|
||||||
|
case "state":
|
||||||
|
if (entity) {
|
||||||
|
return `${this.hass.localize(
|
||||||
|
"ui.components.entity.entity-state-picker.state"
|
||||||
|
)} (${this.hass.localize(
|
||||||
|
"ui.panel.lovelace.editor.card.conditional.current_state"
|
||||||
|
)}: ${this.hass.formatEntityState(entity)})`;
|
||||||
|
}
|
||||||
|
return `${this.hass.localize(
|
||||||
|
"ui.components.entity.entity-state-picker.state"
|
||||||
|
)}`;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-card-condition-state": HaCardConditionState;
|
||||||
|
}
|
||||||
|
}
|
@ -1,52 +1,56 @@
|
|||||||
import "@material/mwc-list/mwc-list-item";
|
|
||||||
import "@material/mwc-tab-bar/mwc-tab-bar";
|
import "@material/mwc-tab-bar/mwc-tab-bar";
|
||||||
import "@material/mwc-tab/mwc-tab";
|
import "@material/mwc-tab/mwc-tab";
|
||||||
import { mdiCodeBraces, mdiContentCopy, mdiListBoxOutline } from "@mdi/js";
|
|
||||||
import deepClone from "deep-clone-simple";
|
|
||||||
import type { MDCTabBarActivatedEvent } from "@material/tab-bar";
|
import type { MDCTabBarActivatedEvent } from "@material/tab-bar";
|
||||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
|
||||||
import {
|
import {
|
||||||
any,
|
mdiCodeBraces,
|
||||||
array,
|
mdiContentCopy,
|
||||||
assert,
|
mdiListBoxOutline,
|
||||||
assign,
|
mdiPlus,
|
||||||
object,
|
} from "@mdi/js";
|
||||||
optional,
|
import deepClone from "deep-clone-simple";
|
||||||
string,
|
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||||
} from "superstruct";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
|
import { any, array, assert, assign, object, optional } from "superstruct";
|
||||||
import { storage } from "../../../../common/decorators/storage";
|
import { storage } from "../../../../common/decorators/storage";
|
||||||
import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event";
|
import { HASSDomEvent, fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||||
import "../../../../components/entity/ha-entity-picker";
|
import "../../../../components/ha-button";
|
||||||
import "../../../../components/ha-select";
|
import "../../../../components/ha-list-item";
|
||||||
import "../../../../components/ha-textfield";
|
import "../../../../components/ha-menu-button";
|
||||||
|
import type { HaSelect } from "../../../../components/ha-select";
|
||||||
|
import "../../../../components/ha-svg-icon";
|
||||||
import type {
|
import type {
|
||||||
LovelaceCardConfig,
|
LovelaceCardConfig,
|
||||||
LovelaceConfig,
|
LovelaceConfig,
|
||||||
} from "../../../../data/lovelace";
|
} from "../../../../data/lovelace";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
import type { ConditionalCardConfig } from "../../cards/types";
|
import type { ConditionalCardConfig } from "../../cards/types";
|
||||||
|
import { ICON_CONDITION } from "../../common/icon-condition";
|
||||||
|
import { Condition } from "../../common/validate-condition";
|
||||||
import type { LovelaceCardEditor } from "../../types";
|
import type { LovelaceCardEditor } from "../../types";
|
||||||
import "../card-editor/hui-card-element-editor";
|
import "../card-editor/hui-card-element-editor";
|
||||||
import type { HuiCardElementEditor } from "../card-editor/hui-card-element-editor";
|
import type { HuiCardElementEditor } from "../card-editor/hui-card-element-editor";
|
||||||
import "../card-editor/hui-card-picker";
|
import "../card-editor/hui-card-picker";
|
||||||
|
import "../conditions/ha-card-condition-editor";
|
||||||
|
import { LovelaceConditionEditorConstructor } from "../conditions/types";
|
||||||
|
import "../conditions/types/ha-card-condition-screen";
|
||||||
|
import "../conditions/types/ha-card-condition-state";
|
||||||
import "../hui-element-editor";
|
import "../hui-element-editor";
|
||||||
import type { ConfigChangedEvent } from "../hui-element-editor";
|
import type { ConfigChangedEvent } from "../hui-element-editor";
|
||||||
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
|
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
|
||||||
import type { GUIModeChangedEvent } from "../types";
|
import type { GUIModeChangedEvent } from "../types";
|
||||||
import { configElementStyle } from "./config-elements-style";
|
import { configElementStyle } from "./config-elements-style";
|
||||||
|
|
||||||
const conditionStruct = object({
|
const UI_CONDITION = [
|
||||||
entity: string(),
|
"state",
|
||||||
state: optional(string()),
|
"screen",
|
||||||
state_not: optional(string()),
|
] as const satisfies readonly Condition["condition"][];
|
||||||
});
|
|
||||||
const cardConfigStruct = assign(
|
const cardConfigStruct = assign(
|
||||||
baseLovelaceCardConfig,
|
baseLovelaceCardConfig,
|
||||||
object({
|
object({
|
||||||
card: any(),
|
card: any(),
|
||||||
conditions: optional(array(conditionStruct)),
|
conditions: optional(array(any())),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -127,7 +131,6 @@ export class HuiConditionalCardEditor
|
|||||||
)}
|
)}
|
||||||
.path=${isGuiMode ? mdiCodeBraces : mdiListBoxOutline}
|
.path=${isGuiMode ? mdiCodeBraces : mdiListBoxOutline}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
|
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
.label=${this.hass!.localize(
|
.label=${this.hass!.localize(
|
||||||
"ui.panel.lovelace.editor.edit_card.copy"
|
"ui.panel.lovelace.editor.edit_card.copy"
|
||||||
@ -166,61 +169,44 @@ export class HuiConditionalCardEditor
|
|||||||
${this._config.conditions.map(
|
${this._config.conditions.map(
|
||||||
(cond, idx) => html`
|
(cond, idx) => html`
|
||||||
<div class="condition">
|
<div class="condition">
|
||||||
<div class="entity">
|
<ha-card-condition-editor
|
||||||
<ha-entity-picker
|
.index=${idx}
|
||||||
.hass=${this.hass}
|
@value-changed=${this._conditionChanged}
|
||||||
.value=${cond.entity}
|
.hass=${this.hass}
|
||||||
.idx=${idx}
|
.condition=${cond}
|
||||||
.configValue=${"entity"}
|
></ha-card-condition-editor>
|
||||||
@change=${this._changeCondition}
|
|
||||||
allow-custom-entity
|
|
||||||
></ha-entity-picker>
|
|
||||||
</div>
|
|
||||||
<div class="state">
|
|
||||||
<ha-select
|
|
||||||
.value=${cond.state_not !== undefined
|
|
||||||
? "true"
|
|
||||||
: "false"}
|
|
||||||
.idx=${idx}
|
|
||||||
.configValue=${"invert"}
|
|
||||||
@selected=${this._changeCondition}
|
|
||||||
@closed=${stopPropagation}
|
|
||||||
naturalMenuWidth
|
|
||||||
fixedMenuPosition
|
|
||||||
>
|
|
||||||
<mwc-list-item value="false">
|
|
||||||
${this.hass!.localize(
|
|
||||||
"ui.panel.lovelace.editor.card.conditional.state_equal"
|
|
||||||
)}
|
|
||||||
</mwc-list-item>
|
|
||||||
<mwc-list-item value="true">
|
|
||||||
${this.hass!.localize(
|
|
||||||
"ui.panel.lovelace.editor.card.conditional.state_not_equal"
|
|
||||||
)}
|
|
||||||
</mwc-list-item>
|
|
||||||
</ha-select>
|
|
||||||
<ha-textfield
|
|
||||||
.label="${this.hass!.localize(
|
|
||||||
"ui.panel.lovelace.editor.card.generic.state"
|
|
||||||
)} (${this.hass!.localize(
|
|
||||||
"ui.panel.lovelace.editor.card.conditional.current_state"
|
|
||||||
)}: ${this.hass?.states[cond.entity].state})"
|
|
||||||
.value=${cond.state_not !== undefined
|
|
||||||
? cond.state_not
|
|
||||||
: cond.state}
|
|
||||||
.idx=${idx}
|
|
||||||
.configValue=${"state"}
|
|
||||||
@input=${this._changeCondition}
|
|
||||||
></ha-textfield>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
<div class="condition">
|
<div>
|
||||||
<ha-entity-picker
|
<ha-button-menu
|
||||||
.hass=${this.hass}
|
@action=${this._addCondition}
|
||||||
@change=${this._addCondition}
|
fixed
|
||||||
></ha-entity-picker>
|
@closed=${stopPropagation}
|
||||||
|
>
|
||||||
|
<ha-button
|
||||||
|
slot="trigger"
|
||||||
|
outlined
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.lovelace.editor.card.conditional.add_condition"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
||||||
|
</ha-button>
|
||||||
|
${UI_CONDITION.map(
|
||||||
|
(condition) => html`
|
||||||
|
<ha-list-item .value=${condition} graphic="icon">
|
||||||
|
${this.hass!.localize(
|
||||||
|
`ui.panel.lovelace.editor.card.conditional.condition.${condition}.label`
|
||||||
|
) || condition}
|
||||||
|
<ha-svg-icon
|
||||||
|
slot="graphic"
|
||||||
|
.path=${ICON_CONDITION[condition]}
|
||||||
|
></ha-svg-icon>
|
||||||
|
</ha-list-item>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</ha-button-menu>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`}
|
`}
|
||||||
@ -289,53 +275,40 @@ export class HuiConditionalCardEditor
|
|||||||
fireEvent(this, "config-changed", { config: this._config });
|
fireEvent(this, "config-changed", { config: this._config });
|
||||||
}
|
}
|
||||||
|
|
||||||
private _addCondition(ev: Event): void {
|
private _addCondition(ev: CustomEvent): void {
|
||||||
const target = ev.target! as any;
|
const condition = (ev.currentTarget as HaSelect).items[ev.detail.index]
|
||||||
if (target.value === "" || !this._config) {
|
.value as Condition["condition"];
|
||||||
|
if (!this._config) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const conditions = [...this._config.conditions];
|
const conditions = [...this._config.conditions];
|
||||||
conditions.push({
|
|
||||||
entity: target.value,
|
const elClass = customElements.get(`ha-card-condition-${condition}`) as
|
||||||
state: "",
|
| LovelaceConditionEditorConstructor
|
||||||
});
|
| undefined;
|
||||||
|
|
||||||
|
conditions.push(
|
||||||
|
elClass?.defaultConfig
|
||||||
|
? { ...elClass.defaultConfig }
|
||||||
|
: { condition: condition }
|
||||||
|
);
|
||||||
this._config = { ...this._config, conditions };
|
this._config = { ...this._config, conditions };
|
||||||
target.value = "";
|
|
||||||
fireEvent(this, "config-changed", { config: this._config });
|
fireEvent(this, "config-changed", { config: this._config });
|
||||||
}
|
}
|
||||||
|
|
||||||
private _changeCondition(ev: Event): void {
|
private _conditionChanged(ev: CustomEvent) {
|
||||||
const target = ev.target as any;
|
ev.stopPropagation();
|
||||||
if (!this._config || !target) {
|
const conditions = [...this._config!.conditions];
|
||||||
return;
|
const newValue = ev.detail.value;
|
||||||
}
|
const index = (ev.target as any).index;
|
||||||
const conditions = [...this._config.conditions];
|
|
||||||
if (target.configValue === "entity" && target.value === "") {
|
if (newValue === null) {
|
||||||
conditions.splice(target.idx, 1);
|
conditions.splice(index, 1);
|
||||||
} else {
|
} else {
|
||||||
const condition = { ...conditions[target.idx] };
|
conditions[index] = newValue;
|
||||||
if (target.configValue === "entity") {
|
|
||||||
condition.entity = target.value;
|
|
||||||
} else if (target.configValue === "state") {
|
|
||||||
if (condition.state_not !== undefined) {
|
|
||||||
condition.state_not = target.value;
|
|
||||||
} else {
|
|
||||||
condition.state = target.value;
|
|
||||||
}
|
|
||||||
} else if (target.configValue === "invert") {
|
|
||||||
if (target.value === "true") {
|
|
||||||
if (condition.state) {
|
|
||||||
condition.state_not = condition.state;
|
|
||||||
delete condition.state;
|
|
||||||
}
|
|
||||||
} else if (condition.state_not) {
|
|
||||||
condition.state = condition.state_not;
|
|
||||||
delete condition.state_not;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
conditions[target.idx] = condition;
|
|
||||||
}
|
}
|
||||||
this._config = { ...this._config, conditions };
|
|
||||||
|
this._config = { ...this._config!, conditions };
|
||||||
fireEvent(this, "config-changed", { config: this._config });
|
fireEvent(this, "config-changed", { config: this._config });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,22 +325,13 @@ export class HuiConditionalCardEditor
|
|||||||
.condition {
|
.condition {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
border: 1px solid var(--divider-color);
|
border: 1px solid var(--divider-color);
|
||||||
|
}
|
||||||
|
.condition .content {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
.condition .state {
|
ha-button-menu {
|
||||||
display: flex;
|
margin-top: 12px;
|
||||||
align-items: flex-end;
|
|
||||||
}
|
}
|
||||||
.condition .state ha-select {
|
|
||||||
margin-right: 16px;
|
|
||||||
margin-inline-end: 16px;
|
|
||||||
margin-inline-start: initial;
|
|
||||||
direction: var(--direction);
|
|
||||||
}
|
|
||||||
.condition .state ha-textfield {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
border: 1px solid var(--divider-color);
|
border: 1px solid var(--divider-color);
|
||||||
|
@ -4682,6 +4682,8 @@
|
|||||||
"confirm_cancel": "Are you sure you want to cancel?",
|
"confirm_cancel": "Are you sure you want to cancel?",
|
||||||
"show_visual_editor": "Show visual editor",
|
"show_visual_editor": "Show visual editor",
|
||||||
"show_code_editor": "Show code editor",
|
"show_code_editor": "Show code editor",
|
||||||
|
"edit_ui": "[%key:ui::panel::config::automation::editor::edit_ui%]",
|
||||||
|
"edit_yaml": "[%key:ui::panel::config::automation::editor::edit_yaml%]",
|
||||||
"add": "Add card",
|
"add": "Add card",
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"clear": "Clear",
|
"clear": "Clear",
|
||||||
@ -4780,7 +4782,24 @@
|
|||||||
"state_not_equal": "State is not equal to",
|
"state_not_equal": "State is not equal to",
|
||||||
"current_state": "current",
|
"current_state": "current",
|
||||||
"condition_explanation": "The card will be shown when ALL conditions below are fulfilled.",
|
"condition_explanation": "The card will be shown when ALL conditions below are fulfilled.",
|
||||||
"change_type": "Change type"
|
"change_type": "Change type",
|
||||||
|
"add_condition": "Add condition",
|
||||||
|
"condition": {
|
||||||
|
"screen": {
|
||||||
|
"label": "Screen",
|
||||||
|
"breakpoints": "Screen sizes",
|
||||||
|
"breakpoints_list": {
|
||||||
|
"mobile": "Mobile",
|
||||||
|
"tablet": "Tablet",
|
||||||
|
"desktop": "Desktop",
|
||||||
|
"wide": "Wide"
|
||||||
|
},
|
||||||
|
"min": "min: {size}px"
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"label": "Entity state"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"required": "required",
|
"required": "required",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user