mirror of
https://github.com/home-assistant/frontend.git
synced 2025-10-17 15:50:16 +00:00
Compare commits
7 Commits
add-automa
...
energy-pan
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c466b5c0b8 | ||
![]() |
7715749231 | ||
![]() |
a09a451ad8 | ||
![]() |
7563d339ea | ||
![]() |
c602eef223 | ||
![]() |
6836a81e5d | ||
![]() |
3f702540b9 |
@@ -34,7 +34,7 @@
|
||||
"@codemirror/legacy-modes": "6.5.2",
|
||||
"@codemirror/search": "6.5.11",
|
||||
"@codemirror/state": "6.5.2",
|
||||
"@codemirror/view": "6.38.6",
|
||||
"@codemirror/view": "6.38.5",
|
||||
"@date-fns/tz": "1.4.1",
|
||||
"@egjs/hammerjs": "2.0.17",
|
||||
"@formatjs/intl-datetimeformat": "6.18.2",
|
||||
@@ -52,7 +52,7 @@
|
||||
"@fullcalendar/list": "6.1.19",
|
||||
"@fullcalendar/luxon3": "6.1.19",
|
||||
"@fullcalendar/timegrid": "6.1.19",
|
||||
"@home-assistant/webawesome": "3.0.0-beta.6.ha.5",
|
||||
"@home-assistant/webawesome": "3.0.0-beta.6.ha.4",
|
||||
"@lezer/highlight": "1.2.1",
|
||||
"@lit-labs/motion": "1.0.9",
|
||||
"@lit-labs/observers": "2.0.6",
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { mdiDragHorizontalVariant, mdiEye, mdiEyeOff } from "@mdi/js";
|
||||
import { mdiDrag, mdiEye, mdiEyeOff } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
@@ -129,7 +129,7 @@ export class DialogDataTableSettings extends LitElement {
|
||||
${canMove && isVisible
|
||||
? html`<ha-svg-icon
|
||||
class="handle"
|
||||
.path=${mdiDragHorizontalVariant}
|
||||
.path=${mdiDrag}
|
||||
slot="graphic"
|
||||
></ha-svg-icon>`
|
||||
: nothing}
|
||||
|
@@ -1,13 +1,13 @@
|
||||
import { mdiDragHorizontalVariant } from "@mdi/js";
|
||||
import { mdiDrag } from "@mdi/js";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { isValidEntityId } from "../../common/entity/valid_entity_id";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "../../data/entity";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../types";
|
||||
import "../ha-sortable";
|
||||
import "./ha-entity-picker";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "../../data/entity";
|
||||
|
||||
@customElement("ha-entities-picker")
|
||||
class HaEntitiesPicker extends LitElement {
|
||||
@@ -118,7 +118,7 @@ class HaEntitiesPicker extends LitElement {
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
class="entity-handle"
|
||||
.path=${mdiDragHorizontalVariant}
|
||||
.path=${mdiDrag}
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: nothing}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import "@material/mwc-menu/mwc-menu-surface";
|
||||
import { mdiDragHorizontalVariant, mdiPlus } from "@mdi/js";
|
||||
import { mdiDrag, mdiPlus } from "@mdi/js";
|
||||
import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import type { IFuseOptions } from "fuse.js";
|
||||
import Fuse from "fuse.js";
|
||||
@@ -25,7 +25,6 @@ import "../ha-sortable";
|
||||
interface EntityNameOption {
|
||||
primary: string;
|
||||
secondary?: string;
|
||||
field_label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
@@ -42,23 +41,6 @@ const KNOWN_TYPES = new Set(["entity", "device", "area", "floor"]);
|
||||
|
||||
const UNIQUE_TYPES = new Set(["entity", "device", "area", "floor"]);
|
||||
|
||||
const formatOptionValue = (item: EntityNameItem) => {
|
||||
if (item.type === "text" && item.text) {
|
||||
return item.text;
|
||||
}
|
||||
return `___${item.type}___`;
|
||||
};
|
||||
|
||||
const parseOptionValue = (value: string): EntityNameItem => {
|
||||
if (value.startsWith("___") && value.endsWith("___")) {
|
||||
const type = value.slice(3, -3);
|
||||
if (KNOWN_TYPES.has(type)) {
|
||||
return { type: type as EntityNameType };
|
||||
}
|
||||
}
|
||||
return { type: "text", text: value };
|
||||
};
|
||||
|
||||
@customElement("ha-entity-name-picker")
|
||||
export class HaEntityNamePicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -86,8 +68,8 @@ export class HaEntityNamePicker extends LitElement {
|
||||
|
||||
private _editIndex?: number;
|
||||
|
||||
private _validTypes = memoizeOne((entityId?: string) => {
|
||||
const options = new Set<string>(["text"]);
|
||||
private _validOptions = memoizeOne((entityId?: string) => {
|
||||
const options = new Set<string>();
|
||||
if (!entityId) {
|
||||
return options;
|
||||
}
|
||||
@@ -119,43 +101,33 @@ export class HaEntityNamePicker extends LitElement {
|
||||
return [];
|
||||
}
|
||||
|
||||
const types = this._validTypes(entityId);
|
||||
const options = this._validOptions(entityId);
|
||||
|
||||
const items = (
|
||||
["entity", "device", "area", "floor"] as const
|
||||
).map<EntityNameOption>((name) => {
|
||||
const stateObj = this.hass.states[entityId];
|
||||
const isValid = types.has(name);
|
||||
const isValid = options.has(name);
|
||||
const primary = this.hass.localize(
|
||||
`ui.components.entity.entity-name-picker.types.${name}`
|
||||
);
|
||||
const secondary =
|
||||
(stateObj && isValid
|
||||
stateObj && isValid
|
||||
? this.hass.formatEntityName(stateObj, { type: name })
|
||||
: this.hass.localize(
|
||||
`ui.components.entity.entity-name-picker.types.${name}_missing` as LocalizeKeys
|
||||
)) || "-";
|
||||
) || "-";
|
||||
|
||||
return {
|
||||
primary,
|
||||
secondary,
|
||||
field_label: primary,
|
||||
value: formatOptionValue({ type: name }),
|
||||
value: name,
|
||||
};
|
||||
});
|
||||
|
||||
return items;
|
||||
});
|
||||
|
||||
private _customNameOption = memoizeOne((text: string) => ({
|
||||
primary: this.hass.localize(
|
||||
"ui.components.entity.entity-name-picker.custom_name"
|
||||
),
|
||||
secondary: `"${text}"`,
|
||||
field_label: text,
|
||||
value: formatOptionValue({ type: "text", text }),
|
||||
}));
|
||||
|
||||
private _formatItem = (item: EntityNameItem) => {
|
||||
if (item.type === "text") {
|
||||
return `"${item.text}"`;
|
||||
@@ -169,9 +141,9 @@ export class HaEntityNamePicker extends LitElement {
|
||||
};
|
||||
|
||||
protected render() {
|
||||
const value = this._items;
|
||||
const value = this._value;
|
||||
const options = this._getOptions(this.entityId);
|
||||
const validTypes = this._validTypes(this.entityId);
|
||||
const validOptions = this._validOptions(this.entityId);
|
||||
|
||||
return html`
|
||||
${this.label ? html`<label>${this.label}</label>` : nothing}
|
||||
@@ -185,11 +157,12 @@ export class HaEntityNamePicker extends LitElement {
|
||||
>
|
||||
<ha-chip-set>
|
||||
${repeat(
|
||||
this._items,
|
||||
this._value,
|
||||
(item) => item,
|
||||
(item: EntityNameItem, idx) => {
|
||||
const label = this._formatItem(item);
|
||||
const isValid = validTypes.has(item.type);
|
||||
const isValid =
|
||||
item.type === "text" || validOptions.has(item.type);
|
||||
return html`
|
||||
<ha-input-chip
|
||||
data-idx=${idx}
|
||||
@@ -200,10 +173,7 @@ export class HaEntityNamePicker extends LitElement {
|
||||
.disabled=${this.disabled}
|
||||
class=${!isValid ? "invalid" : ""}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${mdiDragHorizontalVariant}
|
||||
></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiDrag}></ha-svg-icon>
|
||||
<span>${label}</span>
|
||||
</ha-input-chip>
|
||||
`;
|
||||
@@ -237,14 +207,14 @@ export class HaEntityNamePicker extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.value=${""}
|
||||
.autofocus=${this.autofocus}
|
||||
.disabled=${this.disabled}
|
||||
.disabled=${this.disabled || !this.entityId}
|
||||
.required=${this.required && !value.length}
|
||||
.helper=${this.helper}
|
||||
.items=${options}
|
||||
allow-custom-value
|
||||
item-id-path="value"
|
||||
item-value-path="value"
|
||||
item-label-path="field_label"
|
||||
item-label-path="primary"
|
||||
.renderer=${rowRenderer}
|
||||
@opened-changed=${this._openedChanged}
|
||||
@value-changed=${this._comboBoxValueChanged}
|
||||
@@ -284,16 +254,13 @@ export class HaEntityNamePicker extends LitElement {
|
||||
this._opened = true;
|
||||
}
|
||||
|
||||
private get _items(): EntityNameItem[] {
|
||||
private get _value(): EntityNameItem[] {
|
||||
return this._toItems(this.value);
|
||||
}
|
||||
|
||||
private _toItems = memoizeOne((value?: typeof this.value) => {
|
||||
if (typeof value === "string") {
|
||||
if (value === "") {
|
||||
return [];
|
||||
}
|
||||
return [{ type: "text", text: value } satisfies EntityNameItem];
|
||||
return [{ type: "text", text: value } as const];
|
||||
}
|
||||
return value ? ensureArray(value) : [];
|
||||
});
|
||||
@@ -301,7 +268,7 @@ export class HaEntityNamePicker extends LitElement {
|
||||
private _toValue = memoizeOne(
|
||||
(items: EntityNameItem[]): typeof this.value => {
|
||||
if (items.length === 0) {
|
||||
return "";
|
||||
return [];
|
||||
}
|
||||
if (items.length === 1) {
|
||||
const item = items[0];
|
||||
@@ -317,21 +284,20 @@ export class HaEntityNamePicker extends LitElement {
|
||||
const options = this._comboBox.items || [];
|
||||
|
||||
const initialItem =
|
||||
this._editIndex != null ? this._items[this._editIndex] : undefined;
|
||||
this._editIndex != null ? this._value[this._editIndex] : undefined;
|
||||
|
||||
const initialValue = initialItem ? formatOptionValue(initialItem) : "";
|
||||
const initialValue = initialItem
|
||||
? initialItem.type === "text"
|
||||
? initialItem.text
|
||||
: initialItem.type
|
||||
: "";
|
||||
|
||||
const filteredItems = this._filterSelectedOptions(options, initialValue);
|
||||
|
||||
if (initialItem?.type === "text" && initialItem.text) {
|
||||
filteredItems.push(this._customNameOption(initialItem.text));
|
||||
}
|
||||
|
||||
this._comboBox.filteredItems = filteredItems;
|
||||
this._comboBox.setInputValue(initialValue);
|
||||
} else {
|
||||
this._opened = false;
|
||||
this._comboBox.setInputValue("");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,16 +305,15 @@ export class HaEntityNamePicker extends LitElement {
|
||||
options: EntityNameOption[],
|
||||
current?: string
|
||||
) => {
|
||||
const items = this._items;
|
||||
const value = this._value;
|
||||
|
||||
const excludedValues = new Set(
|
||||
items
|
||||
.filter((item) => UNIQUE_TYPES.has(item.type))
|
||||
.map((item) => formatOptionValue(item))
|
||||
);
|
||||
const types = value.map((item) => item.type) as string[];
|
||||
|
||||
const filteredOptions = options.filter(
|
||||
(option) => !excludedValues.has(option.value) || option.value === current
|
||||
(option) =>
|
||||
!UNIQUE_TYPES.has(option.value) ||
|
||||
!types.includes(option.value) ||
|
||||
option.value === current
|
||||
);
|
||||
return filteredOptions;
|
||||
};
|
||||
@@ -359,14 +324,20 @@ export class HaEntityNamePicker extends LitElement {
|
||||
const options = this._comboBox.items || [];
|
||||
|
||||
const currentItem =
|
||||
this._editIndex != null ? this._items[this._editIndex] : undefined;
|
||||
this._editIndex != null ? this._value[this._editIndex] : undefined;
|
||||
|
||||
const currentValue = currentItem ? formatOptionValue(currentItem) : "";
|
||||
const currentValue = currentItem
|
||||
? currentItem.type === "text"
|
||||
? currentItem.text
|
||||
: currentItem.type
|
||||
: "";
|
||||
|
||||
let filteredItems = this._filterSelectedOptions(options, currentValue);
|
||||
this._comboBox.filteredItems = this._filterSelectedOptions(
|
||||
options,
|
||||
currentValue
|
||||
);
|
||||
|
||||
if (!filter) {
|
||||
this._comboBox.filteredItems = filteredItems;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -378,16 +349,16 @@ export class HaEntityNamePicker extends LitElement {
|
||||
ignoreDiacritics: true,
|
||||
};
|
||||
|
||||
const fuse = new Fuse(filteredItems, fuseOptions);
|
||||
filteredItems = fuse.search(filter).map((result) => result.item);
|
||||
filteredItems.push(this._customNameOption(input));
|
||||
const fuse = new Fuse(this._comboBox.filteredItems, fuseOptions);
|
||||
const filteredItems = fuse.search(filter).map((result) => result.item);
|
||||
|
||||
this._comboBox.filteredItems = filteredItems;
|
||||
}
|
||||
|
||||
private async _moveItem(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const { oldIndex, newIndex } = ev.detail;
|
||||
const value = this._items;
|
||||
const value = this._value;
|
||||
const newValue = value.concat();
|
||||
const element = newValue.splice(oldIndex, 1)[0];
|
||||
newValue.splice(newIndex, 0, element);
|
||||
@@ -398,7 +369,7 @@ export class HaEntityNamePicker extends LitElement {
|
||||
|
||||
private async _removeItem(ev) {
|
||||
ev.stopPropagation();
|
||||
const value = [...this._items];
|
||||
const value = [...this._value];
|
||||
const idx = parseInt(ev.target.dataset.idx, 10);
|
||||
value.splice(idx, 1);
|
||||
this._setValue(value);
|
||||
@@ -414,9 +385,11 @@ export class HaEntityNamePicker extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
const item: EntityNameItem = parseOptionValue(value);
|
||||
const item: EntityNameItem = KNOWN_TYPES.has(value as any)
|
||||
? { type: value as EntityNameType }
|
||||
: { type: "text", text: value };
|
||||
|
||||
const newValue = [...this._items];
|
||||
const newValue = [...this._value];
|
||||
|
||||
if (this._editIndex != null) {
|
||||
newValue[this._editIndex] = item;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { mdiDragHorizontalVariant } from "@mdi/js";
|
||||
import { mdiDrag } from "@mdi/js";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
@@ -195,10 +195,7 @@ class HaEntityStatePicker extends LitElement {
|
||||
.label=${label}
|
||||
selected
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${mdiDragHorizontalVariant}
|
||||
></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiDrag}></ha-svg-icon>
|
||||
${label}
|
||||
</ha-input-chip>
|
||||
`;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { mdiDragHorizontalVariant, mdiTextureBox } from "@mdi/js";
|
||||
import { mdiDrag, mdiTextureBox } from "@mdi/js";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
@@ -105,7 +105,7 @@ export class HaAreasFloorsDisplayEditor extends LitElement {
|
||||
<ha-svg-icon
|
||||
class="handle"
|
||||
slot="icons"
|
||||
.path=${mdiDragHorizontalVariant}
|
||||
.path=${mdiDrag}
|
||||
></ha-svg-icon>
|
||||
`}
|
||||
<ha-items-display-editor
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import "@home-assistant/webawesome/dist/components/drawer/drawer";
|
||||
import { css, html, LitElement, type PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
|
||||
export const BOTTOM_SHEET_ANIMATION_DURATION_MS = 300;
|
||||
|
||||
@@ -38,61 +37,49 @@ export class HaBottomSheet extends LitElement {
|
||||
@wa-after-hide=${this._handleAfterHide}
|
||||
without-header
|
||||
>
|
||||
<slot name="header"></slot>
|
||||
<div class="body ha-scrollbar">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<slot></slot>
|
||||
</wa-drawer>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
haStyleScrollbar,
|
||||
css`
|
||||
wa-drawer {
|
||||
--wa-color-surface-raised: transparent;
|
||||
--spacing: 0;
|
||||
--size: var(--ha-bottom-sheet-height, auto);
|
||||
--show-duration: ${BOTTOM_SHEET_ANIMATION_DURATION_MS}ms;
|
||||
--hide-duration: ${BOTTOM_SHEET_ANIMATION_DURATION_MS}ms;
|
||||
}
|
||||
wa-drawer::part(dialog) {
|
||||
max-height: var(--ha-bottom-sheet-max-height, 90vh);
|
||||
align-items: center;
|
||||
}
|
||||
wa-drawer::part(body) {
|
||||
max-width: var(--ha-bottom-sheet-max-width);
|
||||
width: 100%;
|
||||
border-top-left-radius: var(
|
||||
--ha-bottom-sheet-border-radius,
|
||||
var(--ha-dialog-border-radius, var(--ha-border-radius-2xl))
|
||||
);
|
||||
border-top-right-radius: var(
|
||||
--ha-bottom-sheet-border-radius,
|
||||
var(--ha-dialog-border-radius, var(--ha-border-radius-2xl))
|
||||
);
|
||||
background-color: var(
|
||||
--ha-bottom-sheet-surface-background,
|
||||
var(--ha-dialog-surface-background, var(--mdc-theme-surface, #fff)),
|
||||
);
|
||||
padding: var(
|
||||
--ha-bottom-sheet-padding,
|
||||
0 var(--safe-area-inset-right) var(--safe-area-inset-bottom)
|
||||
var(--safe-area-inset-left)
|
||||
);
|
||||
}
|
||||
:host([flexcontent]) wa-drawer::part(body) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
:host([flexcontent]) .body {
|
||||
flex: 1;
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
`,
|
||||
];
|
||||
static styles = css`
|
||||
wa-drawer {
|
||||
--wa-color-surface-raised: transparent;
|
||||
--spacing: 0;
|
||||
--size: var(--ha-bottom-sheet-height, auto);
|
||||
--show-duration: ${BOTTOM_SHEET_ANIMATION_DURATION_MS}ms;
|
||||
--hide-duration: ${BOTTOM_SHEET_ANIMATION_DURATION_MS}ms;
|
||||
}
|
||||
wa-drawer::part(dialog) {
|
||||
max-height: var(--ha-bottom-sheet-max-height, 90vh);
|
||||
align-items: center;
|
||||
}
|
||||
wa-drawer::part(body) {
|
||||
max-width: var(--ha-bottom-sheet-max-width);
|
||||
width: 100%;
|
||||
border-top-left-radius: var(
|
||||
--ha-bottom-sheet-border-radius,
|
||||
var(--ha-dialog-border-radius, var(--ha-border-radius-2xl))
|
||||
);
|
||||
border-top-right-radius: var(
|
||||
--ha-bottom-sheet-border-radius,
|
||||
var(--ha-dialog-border-radius, var(--ha-border-radius-2xl))
|
||||
);
|
||||
background-color: var(
|
||||
--ha-bottom-sheet-surface-background,
|
||||
var(--ha-dialog-surface-background, var(--mdc-theme-surface, #fff)),
|
||||
);
|
||||
padding: var(
|
||||
--ha-bottom-sheet-padding,
|
||||
0 var(--safe-area-inset-right) var(--safe-area-inset-bottom)
|
||||
var(--safe-area-inset-left)
|
||||
);
|
||||
}
|
||||
|
||||
:host([flexcontent]) wa-drawer::part(body) {
|
||||
display: flex;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -31,9 +31,6 @@ export class HaButtonToggleGroup extends LitElement {
|
||||
@property({ type: Boolean, reflect: true, attribute: "no-wrap" })
|
||||
public nowrap = false;
|
||||
|
||||
@property({ type: Boolean, reflect: true, attribute: "full-width" })
|
||||
public fullWidth = false;
|
||||
|
||||
@property() public variant:
|
||||
| "brand"
|
||||
| "neutral"
|
||||
@@ -41,13 +38,6 @@ export class HaButtonToggleGroup extends LitElement {
|
||||
| "warning"
|
||||
| "danger" = "brand";
|
||||
|
||||
@property({ attribute: "active-variant" }) public activeVariant?:
|
||||
| "brand"
|
||||
| "neutral"
|
||||
| "success"
|
||||
| "warning"
|
||||
| "danger";
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<wa-button-group childSelector="ha-button">
|
||||
@@ -56,9 +46,7 @@ export class HaButtonToggleGroup extends LitElement {
|
||||
html`<ha-button
|
||||
iconTag="ha-svg-icon"
|
||||
class="icon"
|
||||
.variant=${this.active !== button.value || !this.activeVariant
|
||||
? this.variant
|
||||
: this.activeVariant}
|
||||
.variant=${this.variant}
|
||||
.size=${this.size}
|
||||
.value=${button.value}
|
||||
@click=${this._handleClick}
|
||||
@@ -90,19 +78,6 @@ export class HaButtonToggleGroup extends LitElement {
|
||||
:host([no-wrap]) wa-button-group::part(base) {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
wa-button-group {
|
||||
padding: var(--ha-button-toggle-group-padding);
|
||||
}
|
||||
|
||||
:host([full-width]) wa-button-group,
|
||||
:host([full-width]) wa-button-group::part(base) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:host([full-width]) ha-button {
|
||||
flex: 1;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -49,16 +49,12 @@ export class HaDialogHeader extends LitElement {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 var(--ha-space-1);
|
||||
padding: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.header-content {
|
||||
flex: 1;
|
||||
padding: 10px var(--ha-space-1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
min-height: var(--ha-space-12);
|
||||
padding: 10px 4px;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -67,7 +63,7 @@ export class HaDialogHeader extends LitElement {
|
||||
.header-title {
|
||||
height: var(
|
||||
--ha-dialog-header-title-height,
|
||||
calc(var(--ha-font-size-xl) + var(--ha-space-1))
|
||||
calc(var(--ha-font-size-xl) + 4px)
|
||||
);
|
||||
font-size: var(--ha-font-size-xl);
|
||||
line-height: var(--ha-line-height-condensed);
|
||||
@@ -80,19 +76,19 @@ export class HaDialogHeader extends LitElement {
|
||||
}
|
||||
@media all and (min-width: 450px) and (min-height: 500px) {
|
||||
.header-bar {
|
||||
padding: 0 var(--ha-space-2);
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
.header-navigation-icon {
|
||||
flex: none;
|
||||
min-width: var(--ha-space-2);
|
||||
min-width: 8px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.header-action-items {
|
||||
flex: none;
|
||||
min-width: var(--ha-space-2);
|
||||
min-width: 8px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { ResizeController } from "@lit-labs/observers/resize-controller";
|
||||
import { mdiDragHorizontalVariant, mdiEye, mdiEyeOff } from "@mdi/js";
|
||||
import { mdiDrag, mdiEye, mdiEyeOff } from "@mdi/js";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
@@ -178,7 +178,7 @@ export class HaItemDisplayEditor extends LitElement {
|
||||
? this._dragHandleKeydown
|
||||
: undefined}
|
||||
class="handle"
|
||||
.path=${mdiDragHorizontalVariant}
|
||||
.path=${mdiDrag}
|
||||
slot="end"
|
||||
></ha-svg-icon>
|
||||
`
|
||||
|
152
src/components/ha-selector/ha-selector-image.ts
Normal file
152
src/components/ha-selector/ha-selector-image.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { ImageSelector } from "../../data/selector";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-icon-button";
|
||||
import "../ha-textarea";
|
||||
import "../ha-textfield";
|
||||
import "../ha-picture-upload";
|
||||
import "../ha-radio";
|
||||
import "../ha-formfield";
|
||||
import type { HaPictureUpload } from "../ha-picture-upload";
|
||||
import { URL_PREFIX } from "../../data/image_upload";
|
||||
|
||||
@customElement("ha-selector-image")
|
||||
export class HaImageSelector extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public value?: any;
|
||||
|
||||
@property() public name?: string;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public placeholder?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ attribute: false }) public selector!: ImageSelector;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
|
||||
@state() private showUpload = false;
|
||||
|
||||
protected firstUpdated(changedProps): void {
|
||||
super.firstUpdated(changedProps);
|
||||
|
||||
if (!this.value || this.value.startsWith(URL_PREFIX)) {
|
||||
this.showUpload = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<div>
|
||||
<label>
|
||||
${this.hass.localize(
|
||||
"ui.components.selectors.image.select_image_with_label",
|
||||
{
|
||||
label:
|
||||
this.label ||
|
||||
this.hass.localize("ui.components.selectors.image.image"),
|
||||
}
|
||||
)}
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize("ui.components.selectors.image.upload")}
|
||||
>
|
||||
<ha-radio
|
||||
name="mode"
|
||||
value="upload"
|
||||
.checked=${this.showUpload}
|
||||
@change=${this._radioGroupPicked}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize("ui.components.selectors.image.url")}
|
||||
>
|
||||
<ha-radio
|
||||
name="mode"
|
||||
value="url"
|
||||
.checked=${!this.showUpload}
|
||||
@change=${this._radioGroupPicked}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
</label>
|
||||
${!this.showUpload
|
||||
? html`
|
||||
<ha-textfield
|
||||
.name=${this.name}
|
||||
.value=${this.value || ""}
|
||||
.placeholder=${this.placeholder || ""}
|
||||
.helper=${this.helper}
|
||||
helperPersistent
|
||||
.disabled=${this.disabled}
|
||||
@input=${this._handleChange}
|
||||
.label=${this.label || ""}
|
||||
.required=${this.required}
|
||||
></ha-textfield>
|
||||
`
|
||||
: html`
|
||||
<ha-picture-upload
|
||||
.hass=${this.hass}
|
||||
.value=${this.value?.startsWith(URL_PREFIX) ? this.value : null}
|
||||
.original=${this.selector.image?.original}
|
||||
.cropOptions=${this.selector.image?.crop}
|
||||
select-media
|
||||
@change=${this._pictureChanged}
|
||||
></ha-picture-upload>
|
||||
`}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _radioGroupPicked(ev): void {
|
||||
this.showUpload = ev.target.value === "upload";
|
||||
}
|
||||
|
||||
private _pictureChanged(ev) {
|
||||
const value = (ev.target as HaPictureUpload).value;
|
||||
|
||||
fireEvent(this, "value-changed", { value: value ?? undefined });
|
||||
}
|
||||
|
||||
private _handleChange(ev) {
|
||||
let value = ev.target.value;
|
||||
if (this.value === value) {
|
||||
return;
|
||||
}
|
||||
if (value === "" && !this.required) {
|
||||
value = undefined;
|
||||
}
|
||||
|
||||
fireEvent(this, "value-changed", { value });
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
ha-textarea,
|
||||
ha-textfield {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-selector-image": HaImageSelector;
|
||||
}
|
||||
}
|
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
mdiClose,
|
||||
mdiDelete,
|
||||
mdiDragHorizontalVariant,
|
||||
mdiPencil,
|
||||
} from "@mdi/js";
|
||||
import { mdiClose, mdiDelete, mdiDrag, mdiPencil } from "@mdi/js";
|
||||
import { css, html, LitElement, nothing, type PropertyValues } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
@@ -97,7 +92,7 @@ export class HaObjectSelector extends LitElement {
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
class="handle"
|
||||
.path=${mdiDragHorizontalVariant}
|
||||
.path=${mdiDrag}
|
||||
slot="start"
|
||||
></ha-svg-icon>
|
||||
`
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { mdiDragHorizontalVariant } from "@mdi/js";
|
||||
import { mdiDrag } from "@mdi/js";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
@@ -197,7 +197,7 @@ export class HaSelectSelector extends LitElement {
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${mdiDragHorizontalVariant}
|
||||
.path=${mdiDrag}
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: nothing}
|
||||
|
@@ -34,6 +34,7 @@ const LOAD_ELEMENTS = {
|
||||
file: () => import("./ha-selector-file"),
|
||||
floor: () => import("./ha-selector-floor"),
|
||||
label: () => import("./ha-selector-label"),
|
||||
image: () => import("./ha-selector-image"),
|
||||
background: () => import("./ha-selector-background"),
|
||||
language: () => import("./ha-selector-language"),
|
||||
navigation: () => import("./ha-selector-navigation"),
|
||||
|
@@ -36,6 +36,8 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property({ attribute: false }) public value?: HassServiceTarget;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public compact = false;
|
||||
@@ -99,7 +101,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
(floor_id) => html`
|
||||
<ha-target-picker-value-chip
|
||||
.hass=${this.hass}
|
||||
type="floor"
|
||||
.type=${"floor"}
|
||||
.itemId=${floor_id}
|
||||
@remove-target-item=${this._handleRemove}
|
||||
@expand-target-item=${this._handleExpand}
|
||||
@@ -112,7 +114,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
(area_id) => html`
|
||||
<ha-target-picker-value-chip
|
||||
.hass=${this.hass}
|
||||
type="area"
|
||||
.type=${"area"}
|
||||
.itemId=${area_id}
|
||||
@remove-target-item=${this._handleRemove}
|
||||
@expand-target-item=${this._handleExpand}
|
||||
@@ -125,7 +127,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
(device_id) => html`
|
||||
<ha-target-picker-value-chip
|
||||
.hass=${this.hass}
|
||||
type="device"
|
||||
.type=${"device"}
|
||||
.itemId=${device_id}
|
||||
@remove-target-item=${this._handleRemove}
|
||||
@expand-target-item=${this._handleExpand}
|
||||
@@ -138,7 +140,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
(entity_id) => html`
|
||||
<ha-target-picker-value-chip
|
||||
.hass=${this.hass}
|
||||
type="entity"
|
||||
.type=${"entity"}
|
||||
.itemId=${entity_id}
|
||||
@remove-target-item=${this._handleRemove}
|
||||
@expand-target-item=${this._handleExpand}
|
||||
@@ -151,7 +153,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
(label_id) => html`
|
||||
<ha-target-picker-value-chip
|
||||
.hass=${this.hass}
|
||||
type="label"
|
||||
.type=${"label"}
|
||||
.itemId=${label_id}
|
||||
@remove-target-item=${this._handleRemove}
|
||||
@expand-target-item=${this._handleExpand}
|
||||
@@ -171,6 +173,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
type="entity"
|
||||
.hass=${this.hass}
|
||||
.items=${{ entity: ensureArray(this.value?.entity_id) }}
|
||||
.collapsed=${this.compact}
|
||||
.deviceFilter=${this.deviceFilter}
|
||||
.entityFilter=${this.entityFilter}
|
||||
.includeDomains=${this.includeDomains}
|
||||
@@ -186,6 +189,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
type="device"
|
||||
.hass=${this.hass}
|
||||
.items=${{ device: ensureArray(this.value?.device_id) }}
|
||||
.collapsed=${this.compact}
|
||||
.deviceFilter=${this.deviceFilter}
|
||||
.entityFilter=${this.entityFilter}
|
||||
.includeDomains=${this.includeDomains}
|
||||
@@ -204,6 +208,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
floor: ensureArray(this.value?.floor_id),
|
||||
area: ensureArray(this.value?.area_id),
|
||||
}}
|
||||
.collapsed=${this.compact}
|
||||
.deviceFilter=${this.deviceFilter}
|
||||
.entityFilter=${this.entityFilter}
|
||||
.includeDomains=${this.includeDomains}
|
||||
@@ -219,6 +224,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
type="label"
|
||||
.hass=${this.hass}
|
||||
.items=${{ label: ensureArray(this.value?.label_id) }}
|
||||
.collapsed=${this.compact}
|
||||
.deviceFilter=${this.deviceFilter}
|
||||
.entityFilter=${this.entityFilter}
|
||||
.includeDomains=${this.includeDomains}
|
||||
@@ -271,12 +277,6 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
auto-size-padding="16"
|
||||
@wa-after-show=${this._showSelector}
|
||||
@wa-after-hide=${this._hidePicker}
|
||||
trap-focus
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.components.target-picker.add_target"
|
||||
)}
|
||||
>
|
||||
${this._renderTargetSelector()}
|
||||
</wa-popover>
|
||||
@@ -287,11 +287,6 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
.open=${this._pickerWrapperOpen}
|
||||
@wa-after-show=${this._showSelector}
|
||||
@closed=${this._hidePicker}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.components.target-picker.add_target"
|
||||
)}
|
||||
>
|
||||
${this._renderTargetSelector(true)}
|
||||
</ha-bottom-sheet>`
|
||||
@@ -399,12 +394,6 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
: { [typeId]: id },
|
||||
});
|
||||
|
||||
this.shadowRoot
|
||||
?.querySelector(
|
||||
`ha-target-picker-item-group[type='${this._newTarget?.type}']`
|
||||
)
|
||||
?.removeAttribute("collapsed");
|
||||
}
|
||||
|
||||
private _handleTargetPicked = async (
|
||||
|
@@ -1,12 +1,12 @@
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "@home-assistant/webawesome/dist/components/dialog/dialog";
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-dialog-header";
|
||||
import "./ha-icon-button";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
|
||||
export type DialogWidth = "small" | "medium" | "large" | "full";
|
||||
|
||||
@@ -90,8 +90,6 @@ export class HaWaDialog extends LitElement {
|
||||
@state()
|
||||
private _open = false;
|
||||
|
||||
@query(".body") public bodyContainer!: HTMLDivElement;
|
||||
|
||||
protected updated(
|
||||
changedProperties: Map<string | number | symbol, unknown>
|
||||
): void {
|
||||
@@ -109,7 +107,6 @@ export class HaWaDialog extends LitElement {
|
||||
.lightDismiss=${!this.preventScrimClose}
|
||||
without-header
|
||||
@wa-show=${this._handleShow}
|
||||
@wa-after-show=${this._handleAfterShow}
|
||||
@wa-after-hide=${this._handleAfterHide}
|
||||
>
|
||||
<slot name="header">
|
||||
@@ -149,10 +146,6 @@ export class HaWaDialog extends LitElement {
|
||||
(this.querySelector("[autofocus]") as HTMLElement | null)?.focus();
|
||||
};
|
||||
|
||||
private _handleAfterShow = () => {
|
||||
fireEvent(this, "after-show");
|
||||
};
|
||||
|
||||
private _handleAfterHide = () => {
|
||||
this._open = false;
|
||||
fireEvent(this, "closed");
|
||||
@@ -179,7 +172,7 @@ export class HaWaDialog extends LitElement {
|
||||
)
|
||||
)
|
||||
);
|
||||
--width: min(var(--ha-dialog-width-md, 580px), var(--full-width));
|
||||
--width: var(--ha-dialog-width-md, min(580px, var(--full-width)));
|
||||
--spacing: var(--dialog-content-padding, var(--ha-space-6));
|
||||
--show-duration: var(--ha-dialog-show-duration, 200ms);
|
||||
--hide-duration: var(--ha-dialog-hide-duration, 200ms);
|
||||
@@ -200,11 +193,11 @@ export class HaWaDialog extends LitElement {
|
||||
}
|
||||
|
||||
:host([width="small"]) wa-dialog {
|
||||
--width: min(var(--ha-dialog-width-sm, 320px), var(--full-width));
|
||||
--width: var(--ha-dialog-width-sm, min(320px, var(--full-width)));
|
||||
}
|
||||
|
||||
:host([width="large"]) wa-dialog {
|
||||
--width: min(var(--ha-dialog-width-lg, 720px), var(--full-width));
|
||||
--width: var(--ha-dialog-width-lg, min(720px, var(--full-width)));
|
||||
}
|
||||
|
||||
:host([width="full"]) wa-dialog {
|
||||
@@ -218,7 +211,6 @@ export class HaWaDialog extends LitElement {
|
||||
--ha-dialog-max-height,
|
||||
calc(100% - var(--ha-space-20))
|
||||
);
|
||||
min-height: var(--ha-dialog-min-height);
|
||||
position: var(--dialog-surface-position, relative);
|
||||
margin-top: var(--dialog-surface-margin-top, auto);
|
||||
display: flex;
|
||||
@@ -255,7 +247,10 @@ export class HaWaDialog extends LitElement {
|
||||
.header-title {
|
||||
margin: 0;
|
||||
margin-bottom: 0;
|
||||
color: var(--ha-dialog-header-title-color, var(--primary-text-color));
|
||||
color: var(
|
||||
--ha-dialog-header-title-color,
|
||||
var(--ha-color-on-surface-default, var(--primary-text-color))
|
||||
);
|
||||
font-size: var(
|
||||
--ha-dialog-header-title-font-size,
|
||||
var(--ha-font-size-2xl)
|
||||
@@ -292,7 +287,6 @@ export class HaWaDialog extends LitElement {
|
||||
}
|
||||
:host([flexcontent]) .body {
|
||||
max-width: 100%;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -321,7 +315,6 @@ declare global {
|
||||
|
||||
interface HASSDomEvents {
|
||||
opened: undefined;
|
||||
"after-show": undefined;
|
||||
closed: undefined;
|
||||
}
|
||||
}
|
||||
|
@@ -18,7 +18,7 @@ export class HaTargetPickerItemGroup extends LitElement {
|
||||
Record<TargetType, string[]>
|
||||
>;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public collapsed = false;
|
||||
@property({ type: Boolean }) public collapsed = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
public deviceFilter?: HaDevicePickerDeviceFilterFunc;
|
||||
@@ -50,11 +50,7 @@ export class HaTargetPickerItemGroup extends LitElement {
|
||||
}
|
||||
});
|
||||
|
||||
return html`<ha-expansion-panel
|
||||
.expanded=${!this.collapsed}
|
||||
left-chevron
|
||||
@expanded-changed=${this._expandedChanged}
|
||||
>
|
||||
return html`<ha-expansion-panel .expanded=${!this.collapsed} left-chevron>
|
||||
<div slot="header" class="heading">
|
||||
${this.hass.localize(
|
||||
`ui.components.target-picker.selected.${this.type}`,
|
||||
@@ -82,10 +78,6 @@ export class HaTargetPickerItemGroup extends LitElement {
|
||||
</ha-expansion-panel>`;
|
||||
}
|
||||
|
||||
private _expandedChanged(ev: CustomEvent) {
|
||||
this.collapsed = !ev.detail.expanded;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
|
@@ -130,7 +130,7 @@ export class HaTargetPickerItemRow extends LitElement {
|
||||
|
||||
return html`
|
||||
<ha-md-list-item type="text">
|
||||
<div class="icon" slot="start">
|
||||
<div slot="start">
|
||||
${this.subEntry
|
||||
? html`
|
||||
<div class="horizontal-line-wrapper">
|
||||
@@ -172,9 +172,7 @@ export class HaTargetPickerItemRow extends LitElement {
|
||||
((entries && (showEntities || showDevices)) || this._domainName)
|
||||
? html`
|
||||
<div slot="end" class="summary">
|
||||
${showEntities &&
|
||||
!this.expand &&
|
||||
entries?.referenced_entities.length
|
||||
${showEntities && !this.expand
|
||||
? html`<button class="main link" @click=${this._openDetails}>
|
||||
${this.hass.localize(
|
||||
"ui.components.target-picker.entities_count",
|
||||
@@ -608,11 +606,6 @@ export class HaTargetPickerItemRow extends LitElement {
|
||||
state-badge {
|
||||
color: var(--ha-color-on-neutral-quiet);
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
@@ -636,6 +629,9 @@ export class HaTargetPickerItemRow extends LitElement {
|
||||
font-size: var(--ha-font-size-s);
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.domain {
|
||||
font-family: var(--ha-font-family-code);
|
||||
}
|
||||
|
||||
.entries-tree {
|
||||
display: flex;
|
||||
|
@@ -520,7 +520,6 @@ export class HaTargetPickerSelector extends LitElement {
|
||||
id=${`list-item-${index}`}
|
||||
tabindex="-1"
|
||||
.type=${type === "empty" ? "text" : "button"}
|
||||
class=${type === "empty" ? "empty" : ""}
|
||||
@click=${this._handlePickTarget}
|
||||
.targetType=${type}
|
||||
.targetId=${type !== "empty" ? item.id : undefined}
|
||||
@@ -575,7 +574,9 @@ export class HaTargetPickerSelector extends LitElement {
|
||||
})}
|
||||
/>
|
||||
`
|
||||
: type === "floor"
|
||||
: type === "area" &&
|
||||
(item as FloorComboBoxItem).type === "floor" &&
|
||||
(item as FloorComboBoxItem).floor
|
||||
? html`<ha-floor-icon
|
||||
slot="start"
|
||||
.floor=${(item as FloorComboBoxItem).floor!}
|
||||
@@ -835,7 +836,7 @@ export class HaTargetPickerSelector extends LitElement {
|
||||
id: EMPTY_SEARCH,
|
||||
primary: this.hass.localize(
|
||||
"ui.components.target-picker.no_target_found",
|
||||
{ term: html`<div><b>‘${searchTerm}’</b></div>` }
|
||||
{ term: html`<span class="search-term">"${searchTerm}"</span>` }
|
||||
),
|
||||
});
|
||||
} else if (items.length === 0) {
|
||||
@@ -1019,14 +1020,10 @@ export class HaTargetPickerSelector extends LitElement {
|
||||
padding: var(--ha-space-1) var(--ha-space-2);
|
||||
font-weight: var(--ha-font-weight-bold);
|
||||
color: var(--secondary-text-color);
|
||||
min-height: var(--ha-space-6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
width: 100%;
|
||||
min-height: var(--ha-space-8);
|
||||
}
|
||||
|
||||
:host([mode="dialog"]) .title {
|
||||
@@ -1058,6 +1055,7 @@ export class HaTargetPickerSelector extends LitElement {
|
||||
|
||||
.filter-header {
|
||||
opacity: 0;
|
||||
transition: opacity 300ms ease-in;
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
width: calc(100% - var(--ha-space-8));
|
||||
@@ -1085,8 +1083,9 @@ export class HaTargetPickerSelector extends LitElement {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.empty {
|
||||
text-align: center;
|
||||
.search-term {
|
||||
color: var(--primary-color);
|
||||
font-weight: var(--ha-font-weight-medium);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -6,6 +6,8 @@ import {
|
||||
mdiCallSplit,
|
||||
mdiCodeBraces,
|
||||
mdiDevices,
|
||||
mdiDotsHorizontal,
|
||||
mdiExcavator,
|
||||
mdiFormatListNumbered,
|
||||
mdiGestureDoubleTap,
|
||||
mdiHandBackRight,
|
||||
@@ -14,10 +16,10 @@ import {
|
||||
mdiRoomService,
|
||||
mdiShuffleDisabled,
|
||||
mdiTimerOutline,
|
||||
mdiTools,
|
||||
mdiTrafficLight,
|
||||
} from "@mdi/js";
|
||||
import type { AutomationElementGroupCollection } from "./automation";
|
||||
import type { Action } from "./script";
|
||||
import type { AutomationElementGroup } from "./automation";
|
||||
|
||||
export const ACTION_ICONS = {
|
||||
condition: mdiAbTesting,
|
||||
@@ -46,73 +48,37 @@ export const YAML_ONLY_ACTION_TYPES = new Set<keyof typeof ACTION_ICONS>([
|
||||
"variables",
|
||||
]);
|
||||
|
||||
export const ACTION_COLLECTIONS: AutomationElementGroupCollection[] = [
|
||||
{
|
||||
groups: {
|
||||
device_id: {},
|
||||
serviceGroups: {},
|
||||
export const ACTION_GROUPS: AutomationElementGroup = {
|
||||
device_id: {},
|
||||
helpers: {
|
||||
icon: mdiTools,
|
||||
members: {},
|
||||
},
|
||||
building_blocks: {
|
||||
icon: mdiExcavator,
|
||||
members: {
|
||||
condition: {},
|
||||
delay: {},
|
||||
wait_template: {},
|
||||
wait_for_trigger: {},
|
||||
repeat_count: {},
|
||||
repeat_while: {},
|
||||
repeat_until: {},
|
||||
repeat_for_each: {},
|
||||
choose: {},
|
||||
if: {},
|
||||
stop: {},
|
||||
sequence: {},
|
||||
parallel: {},
|
||||
variables: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
titleKey: "ui.panel.config.automation.editor.actions.groups.helpers.label",
|
||||
groups: {
|
||||
helpers: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
titleKey: "ui.panel.config.automation.editor.actions.groups.other.label",
|
||||
groups: {
|
||||
other: {
|
||||
icon: mdiDotsHorizontal,
|
||||
members: {
|
||||
event: {},
|
||||
service: {},
|
||||
set_conversation_response: {},
|
||||
other: {},
|
||||
},
|
||||
},
|
||||
] as const;
|
||||
|
||||
export const ACTION_BUILDING_BLOCKS_GROUP = {
|
||||
condition: {},
|
||||
delay: {},
|
||||
wait_template: {},
|
||||
wait_for_trigger: {},
|
||||
repeat_count: {},
|
||||
repeat_while: {},
|
||||
repeat_until: {},
|
||||
repeat_for_each: {},
|
||||
choose: {},
|
||||
if: {},
|
||||
stop: {},
|
||||
sequence: {},
|
||||
parallel: {},
|
||||
variables: {},
|
||||
};
|
||||
|
||||
// These will be replaced with the correct action
|
||||
export const VIRTUAL_ACTIONS: Partial<
|
||||
Record<keyof typeof ACTION_BUILDING_BLOCKS_GROUP, Action>
|
||||
> = {
|
||||
repeat_count: {
|
||||
repeat: {
|
||||
count: 2,
|
||||
sequence: [],
|
||||
},
|
||||
},
|
||||
repeat_while: {
|
||||
repeat: {
|
||||
while: [],
|
||||
sequence: [],
|
||||
},
|
||||
},
|
||||
repeat_until: {
|
||||
repeat: {
|
||||
until: [],
|
||||
sequence: [],
|
||||
},
|
||||
},
|
||||
repeat_for_each: {
|
||||
repeat: {
|
||||
for_each: {},
|
||||
sequence: [],
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
@@ -218,7 +218,6 @@ export const getAreasAndFloors = (
|
||||
type: "floor",
|
||||
primary: floorName,
|
||||
floor: floor,
|
||||
icon: floor.icon || undefined,
|
||||
search_labels: [
|
||||
floor.floor_id,
|
||||
floorName,
|
||||
|
@@ -4,7 +4,6 @@ import type {
|
||||
} from "home-assistant-js-websocket";
|
||||
import { ensureArray } from "../common/array/ensure-array";
|
||||
import { navigate } from "../common/navigate";
|
||||
import type { LocalizeKeys } from "../common/translations/localize";
|
||||
import { createSearchParam } from "../common/url/search-params";
|
||||
import type { Context, HomeAssistant } from "../types";
|
||||
import type { BlueprintInput } from "./blueprint";
|
||||
@@ -294,11 +293,6 @@ export interface ShorthandNotCondition extends ShorthandBaseCondition {
|
||||
not: Condition[];
|
||||
}
|
||||
|
||||
export interface AutomationElementGroupCollection {
|
||||
titleKey?: LocalizeKeys;
|
||||
groups: AutomationElementGroup;
|
||||
}
|
||||
|
||||
export type AutomationElementGroup = Record<
|
||||
string,
|
||||
{ icon?: string; members?: AutomationElementGroup }
|
||||
|
@@ -3,6 +3,8 @@ import {
|
||||
mdiClockOutline,
|
||||
mdiCodeBraces,
|
||||
mdiDevices,
|
||||
mdiDotsHorizontal,
|
||||
mdiExcavator,
|
||||
mdiGateOr,
|
||||
mdiIdentifier,
|
||||
mdiMapClock,
|
||||
@@ -13,7 +15,7 @@ import {
|
||||
mdiStateMachine,
|
||||
mdiWeatherSunny,
|
||||
} from "@mdi/js";
|
||||
import type { AutomationElementGroupCollection } from "./automation";
|
||||
import type { AutomationElementGroup } from "./automation";
|
||||
|
||||
export const CONDITION_ICONS = {
|
||||
device: mdiDevices,
|
||||
@@ -29,31 +31,25 @@ export const CONDITION_ICONS = {
|
||||
zone: mdiMapMarkerRadius,
|
||||
};
|
||||
|
||||
export const CONDITION_COLLECTIONS: AutomationElementGroupCollection[] = [
|
||||
{
|
||||
groups: {
|
||||
device: {},
|
||||
entity: { icon: mdiShape, members: { state: {}, numeric_state: {} } },
|
||||
time_location: {
|
||||
icon: mdiMapClock,
|
||||
members: { sun: {}, time: {}, zone: {} },
|
||||
},
|
||||
},
|
||||
export const CONDITION_GROUPS: AutomationElementGroup = {
|
||||
device: {},
|
||||
entity: { icon: mdiShape, members: { state: {}, numeric_state: {} } },
|
||||
time_location: {
|
||||
icon: mdiMapClock,
|
||||
members: { sun: {}, time: {}, zone: {} },
|
||||
},
|
||||
{
|
||||
titleKey: "ui.panel.config.automation.editor.conditions.groups.other.label",
|
||||
groups: {
|
||||
building_blocks: {
|
||||
icon: mdiExcavator,
|
||||
members: { and: {}, or: {}, not: {} },
|
||||
},
|
||||
other: {
|
||||
icon: mdiDotsHorizontal,
|
||||
members: {
|
||||
template: {},
|
||||
trigger: {},
|
||||
},
|
||||
},
|
||||
] as const;
|
||||
|
||||
export const CONDITION_BUILDING_BLOCKS_GROUP = {
|
||||
and: {},
|
||||
or: {},
|
||||
not: {},
|
||||
};
|
||||
} as const;
|
||||
|
||||
export const CONDITION_BUILDING_BLOCKS = ["and", "or", "not"];
|
||||
|
||||
|
@@ -4,6 +4,7 @@ import {
|
||||
mdiClockOutline,
|
||||
mdiCodeBraces,
|
||||
mdiDevices,
|
||||
mdiDotsHorizontal,
|
||||
mdiFormatListBulleted,
|
||||
mdiGestureDoubleTap,
|
||||
mdiMapClock,
|
||||
@@ -22,7 +23,7 @@ import {
|
||||
|
||||
import { mdiHomeAssistant } from "../resources/home-assistant-logo-svg";
|
||||
import type {
|
||||
AutomationElementGroupCollection,
|
||||
AutomationElementGroup,
|
||||
Trigger,
|
||||
TriggerList,
|
||||
} from "./automation";
|
||||
@@ -48,26 +49,16 @@ export const TRIGGER_ICONS = {
|
||||
list: mdiFormatListBulleted,
|
||||
};
|
||||
|
||||
export const TRIGGER_COLLECTIONS: AutomationElementGroupCollection[] = [
|
||||
{
|
||||
groups: {
|
||||
device: {},
|
||||
entity: { icon: mdiShape, members: { state: {}, numeric_state: {} } },
|
||||
time_location: {
|
||||
icon: mdiMapClock,
|
||||
members: {
|
||||
calendar: {},
|
||||
sun: {},
|
||||
time: {},
|
||||
time_pattern: {},
|
||||
zone: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
export const TRIGGER_GROUPS: AutomationElementGroup = {
|
||||
device: {},
|
||||
entity: { icon: mdiShape, members: { state: {}, numeric_state: {} } },
|
||||
time_location: {
|
||||
icon: mdiMapClock,
|
||||
members: { calendar: {}, sun: {}, time: {}, time_pattern: {}, zone: {} },
|
||||
},
|
||||
{
|
||||
titleKey: "ui.panel.config.automation.editor.triggers.groups.other.label",
|
||||
groups: {
|
||||
other: {
|
||||
icon: mdiDotsHorizontal,
|
||||
members: {
|
||||
event: {},
|
||||
geo_location: {},
|
||||
homeassistant: {},
|
||||
@@ -79,7 +70,7 @@ export const TRIGGER_COLLECTIONS: AutomationElementGroupCollection[] = [
|
||||
persistent_notification: {},
|
||||
},
|
||||
},
|
||||
] as const;
|
||||
} as const;
|
||||
|
||||
export const isTriggerList = (trigger: Trigger): trigger is TriggerList =>
|
||||
"triggers" in trigger;
|
||||
|
@@ -1,10 +1,9 @@
|
||||
import { mdiDragHorizontalVariant, mdiPlus } from "@mdi/js";
|
||||
import { mdiDrag, mdiPlus } from "@mdi/js";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { LitElement, html, nothing } from "lit";
|
||||
import { customElement, property, queryAll, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { ensureArray } from "../../../../common/array/ensure-array";
|
||||
import { storage } from "../../../../common/decorators/storage";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||
@@ -16,18 +15,19 @@ import {
|
||||
ACTION_BUILDING_BLOCKS,
|
||||
getService,
|
||||
isService,
|
||||
VIRTUAL_ACTIONS,
|
||||
} from "../../../../data/action";
|
||||
import type { AutomationClipboard } from "../../../../data/automation";
|
||||
import type { Action } from "../../../../data/script";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import {
|
||||
PASTE_VALUE,
|
||||
VIRTUAL_ACTIONS,
|
||||
showAddAutomationElementDialog,
|
||||
} from "../show-add-automation-element-dialog";
|
||||
import { automationRowsStyles } from "../styles";
|
||||
import type HaAutomationActionRow from "./ha-automation-action-row";
|
||||
import { getAutomationActionType } from "./ha-automation-action-row";
|
||||
import { ensureArray } from "../../../../common/array/ensure-array";
|
||||
|
||||
@customElement("ha-automation-action")
|
||||
export default class HaAutomationAction extends LitElement {
|
||||
@@ -115,9 +115,7 @@ export default class HaAutomationAction extends LitElement {
|
||||
@click=${stopPropagation}
|
||||
.index=${idx}
|
||||
>
|
||||
<ha-svg-icon
|
||||
.path=${mdiDragHorizontalVariant}
|
||||
></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
@@ -136,6 +134,17 @@ export default class HaAutomationAction extends LitElement {
|
||||
"ui.panel.config.automation.editor.actions.add"
|
||||
)}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
.disabled=${this.disabled}
|
||||
@click=${this._addActionBuildingBlockDialog}
|
||||
appearance="plain"
|
||||
.size=${this.root ? "medium" : "small"}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlus} slot="start"></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.add_building_block"
|
||||
)}
|
||||
</ha-button>
|
||||
</div>
|
||||
</div>
|
||||
</ha-sortable>
|
||||
@@ -211,6 +220,15 @@ export default class HaAutomationAction extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _addActionBuildingBlockDialog() {
|
||||
showAddAutomationElementDialog(this, {
|
||||
type: "action",
|
||||
add: this._addAction,
|
||||
clipboardItem: getAutomationActionType(this._clipboard?.action),
|
||||
group: "building_blocks",
|
||||
});
|
||||
}
|
||||
|
||||
private _addAction = (action: string) => {
|
||||
let actions: Action[];
|
||||
if (action === PASTE_VALUE) {
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
import { mdiDragHorizontalVariant, mdiPlus } from "@mdi/js";
|
||||
import { mdiDrag, mdiPlus } from "@mdi/js";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
@@ -193,9 +193,7 @@ export default class HaAutomationCondition extends LitElement {
|
||||
@click=${stopPropagation}
|
||||
.index=${idx}
|
||||
>
|
||||
<ha-svg-icon
|
||||
.path=${mdiDragHorizontalVariant}
|
||||
></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
@@ -214,6 +212,17 @@ export default class HaAutomationCondition extends LitElement {
|
||||
"ui.panel.config.automation.editor.conditions.add"
|
||||
)}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
.disabled=${this.disabled}
|
||||
appearance="plain"
|
||||
.size=${this.root ? "medium" : "small"}
|
||||
@click=${this._addConditionBuildingBlockDialog}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlus} slot="start"></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.add_building_block"
|
||||
)}
|
||||
</ha-button>
|
||||
</div>
|
||||
</div>
|
||||
</ha-sortable>
|
||||
@@ -231,6 +240,15 @@ export default class HaAutomationCondition extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _addConditionBuildingBlockDialog() {
|
||||
showAddAutomationElementDialog(this, {
|
||||
type: "condition",
|
||||
add: this._addCondition,
|
||||
clipboardItem: this._clipboard?.condition?.condition,
|
||||
group: "building_blocks",
|
||||
});
|
||||
}
|
||||
|
||||
private _addCondition = (value) => {
|
||||
let conditions: Condition[];
|
||||
if (value === PASTE_VALUE) {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { mdiDragHorizontalVariant, mdiPlus } from "@mdi/js";
|
||||
import { mdiDrag, mdiPlus } from "@mdi/js";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
@@ -100,9 +100,7 @@ export default class HaAutomationOption extends LitElement {
|
||||
@click=${stopPropagation}
|
||||
.index=${idx}
|
||||
>
|
||||
<ha-svg-icon
|
||||
.path=${mdiDragHorizontalVariant}
|
||||
></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
|
@@ -1,11 +1,45 @@
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import type { ACTION_GROUPS } from "../../../data/action";
|
||||
import type { ActionType } from "../../../data/script";
|
||||
|
||||
export const PASTE_VALUE = "__paste__";
|
||||
|
||||
// These will be replaced with the correct action
|
||||
export const VIRTUAL_ACTIONS: Record<
|
||||
keyof (typeof ACTION_GROUPS)["building_blocks"]["members"],
|
||||
ActionType
|
||||
> = {
|
||||
repeat_count: {
|
||||
repeat: {
|
||||
count: 2,
|
||||
sequence: [],
|
||||
},
|
||||
},
|
||||
repeat_while: {
|
||||
repeat: {
|
||||
while: [],
|
||||
sequence: [],
|
||||
},
|
||||
},
|
||||
repeat_until: {
|
||||
repeat: {
|
||||
until: [],
|
||||
sequence: [],
|
||||
},
|
||||
},
|
||||
repeat_for_each: {
|
||||
repeat: {
|
||||
for_each: {},
|
||||
sequence: [],
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export interface AddAutomationElementDialogParams {
|
||||
type: "trigger" | "condition" | "action";
|
||||
add: (key: string) => void;
|
||||
clipboardItem: string | undefined;
|
||||
group?: string;
|
||||
}
|
||||
const loadDialog = () => import("./add-automation-element-dialog");
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { mdiDragHorizontalVariant, mdiPlus } from "@mdi/js";
|
||||
import { mdiDrag, mdiPlus } from "@mdi/js";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
@@ -110,9 +110,7 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
@click=${stopPropagation}
|
||||
.index=${idx}
|
||||
>
|
||||
<ha-svg-icon
|
||||
.path=${mdiDragHorizontalVariant}
|
||||
></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
|
@@ -244,8 +244,7 @@ class HaConfigBackupSettings extends LitElement {
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
${!this.cloudStatus?.logged_in &&
|
||||
isComponentLoaded(this.hass, "cloud")
|
||||
${!this.cloudStatus?.logged_in
|
||||
? html`<ha-card class="cloud-info">
|
||||
<div class="cloud-header">
|
||||
<img
|
||||
@@ -280,10 +279,7 @@ class HaConfigBackupSettings extends LitElement {
|
||||
"ui.panel.config.voice_assistants.assistants.cloud.sign_in"
|
||||
)}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
href="/config/cloud/register"
|
||||
appearance="filled"
|
||||
>
|
||||
<ha-button href="/config/cloud/register">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.voice_assistants.assistants.cloud.try_one_month"
|
||||
)}
|
||||
|
@@ -1,10 +1,4 @@
|
||||
import {
|
||||
mdiDelete,
|
||||
mdiDevices,
|
||||
mdiDragHorizontalVariant,
|
||||
mdiPencil,
|
||||
mdiPlus,
|
||||
} from "@mdi/js";
|
||||
import { mdiDelete, mdiDevices, mdiDrag, mdiPencil, mdiPlus } from "@mdi/js";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
@@ -95,9 +89,7 @@ export class EnergyDeviceSettings extends LitElement {
|
||||
(device) => html`
|
||||
<div class="row" .device=${device}>
|
||||
<div class="handle">
|
||||
<ha-svg-icon
|
||||
.path=${mdiDragHorizontalVariant}
|
||||
></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
|
||||
</div>
|
||||
<span class="content"
|
||||
>${device.name ||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { mdiDelete, mdiDragHorizontalVariant } from "@mdi/js";
|
||||
import { mdiDelete, mdiDrag } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
@@ -111,9 +111,7 @@ class HaInputSelectForm extends LitElement {
|
||||
<ha-list-item class="option" hasMeta>
|
||||
<div class="optioncontent">
|
||||
<div class="handle">
|
||||
<ha-svg-icon
|
||||
.path=${mdiDragHorizontalVariant}
|
||||
></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
|
||||
</div>
|
||||
${option}
|
||||
</div>
|
||||
|
@@ -9,6 +9,7 @@ import {
|
||||
import type { PropertyValues } from "lit";
|
||||
import { LitElement, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import memoize from "memoize-one";
|
||||
import { isComponentLoaded } from "../../../../common/config/is_component_loaded";
|
||||
import { storage } from "../../../../common/decorators/storage";
|
||||
@@ -61,7 +62,7 @@ type DataTableItem = Pick<
|
||||
> & {
|
||||
default: boolean;
|
||||
filename: string;
|
||||
type: string;
|
||||
iconColor?: string;
|
||||
};
|
||||
|
||||
@customElement("ha-config-lovelace-dashboards")
|
||||
@@ -106,20 +107,6 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
})
|
||||
private _activeHiddenColumns?: string[];
|
||||
|
||||
@storage({
|
||||
key: "lovelace-dashboards-table-grouping",
|
||||
state: false,
|
||||
subscribe: false,
|
||||
})
|
||||
private _activeGrouping?: string = "type";
|
||||
|
||||
@storage({
|
||||
key: "lovelace-dashboards-table-collapsed",
|
||||
state: false,
|
||||
subscribe: false,
|
||||
})
|
||||
private _activeCollapsed: string[] = [];
|
||||
|
||||
public willUpdate() {
|
||||
if (!this.hasUpdated) {
|
||||
this.hass.loadFragmentTranslation("lovelace");
|
||||
@@ -145,7 +132,15 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
template: (dashboard) =>
|
||||
dashboard.icon
|
||||
? html`
|
||||
<ha-icon slot="item-icon" .icon=${dashboard.icon}></ha-icon>
|
||||
<ha-icon
|
||||
slot="item-icon"
|
||||
.icon=${dashboard.icon}
|
||||
style=${ifDefined(
|
||||
dashboard.iconColor
|
||||
? `color: ${dashboard.iconColor}`
|
||||
: undefined
|
||||
)}
|
||||
></ha-icon>
|
||||
`
|
||||
: nothing,
|
||||
},
|
||||
@@ -182,15 +177,6 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
},
|
||||
};
|
||||
|
||||
columns.type = {
|
||||
title: localize(
|
||||
"ui.panel.config.lovelace.dashboards.picker.headers.type"
|
||||
),
|
||||
sortable: true,
|
||||
groupable: true,
|
||||
filterable: true,
|
||||
};
|
||||
|
||||
columns.mode = {
|
||||
title: localize(
|
||||
"ui.panel.config.lovelace.dashboards.picker.headers.conf_mode"
|
||||
@@ -301,7 +287,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
url_path: "lovelace",
|
||||
mode: defaultMode,
|
||||
filename: defaultMode === "yaml" ? "ui-lovelace.yaml" : "",
|
||||
type: this._localizeType("built_in"),
|
||||
iconColor: "var(--primary-color)",
|
||||
},
|
||||
];
|
||||
if (isComponentLoaded(this.hass, "energy")) {
|
||||
@@ -312,9 +298,9 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
mode: "storage",
|
||||
url_path: "energy",
|
||||
filename: "",
|
||||
iconColor: "var(--orange-color)",
|
||||
default: false,
|
||||
require_admin: false,
|
||||
type: this._localizeType("built_in"),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -326,9 +312,9 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
mode: "storage",
|
||||
url_path: "light",
|
||||
filename: "",
|
||||
iconColor: "var(--amber-color)",
|
||||
default: false,
|
||||
require_admin: false,
|
||||
type: this._localizeType("built_in"),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -340,9 +326,9 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
mode: "storage",
|
||||
url_path: "safety",
|
||||
filename: "",
|
||||
iconColor: "var(--blue-grey-color)",
|
||||
default: false,
|
||||
require_admin: false,
|
||||
type: this._localizeType("built_in"),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -354,9 +340,9 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
mode: "storage",
|
||||
url_path: "climate",
|
||||
filename: "",
|
||||
iconColor: "var(--deep-orange-color)",
|
||||
default: false,
|
||||
require_admin: false,
|
||||
type: this._localizeType("built_in"),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -365,25 +351,16 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
.sort((a, b) =>
|
||||
stringCompare(a.title, b.title, this.hass.locale.language)
|
||||
)
|
||||
.map(
|
||||
(dashboard) =>
|
||||
({
|
||||
filename: "",
|
||||
...dashboard,
|
||||
default: defaultUrlPath === dashboard.url_path,
|
||||
type: this._localizeType("user_created"),
|
||||
}) satisfies DataTableItem
|
||||
)
|
||||
.map((dashboard) => ({
|
||||
filename: "",
|
||||
...dashboard,
|
||||
default: defaultUrlPath === dashboard.url_path,
|
||||
}))
|
||||
);
|
||||
return result;
|
||||
}
|
||||
);
|
||||
|
||||
private _localizeType = (type: "user_created" | "built_in") =>
|
||||
this.hass.localize(
|
||||
`ui.panel.config.lovelace.dashboards.picker.type.${type}`
|
||||
);
|
||||
|
||||
protected render() {
|
||||
if (!this.hass || this._dashboards === undefined) {
|
||||
return html` <hass-loading-screen></hass-loading-screen> `;
|
||||
@@ -403,13 +380,9 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
this.hass.localize
|
||||
)}
|
||||
.data=${this._getItems(this._dashboards, this.hass.defaultPanel)}
|
||||
.initialGroupColumn=${this._activeGrouping}
|
||||
.initialCollapsedGroups=${this._activeCollapsed}
|
||||
.initialSorting=${this._activeSorting}
|
||||
.columnOrder=${this._activeColumnOrder}
|
||||
.hiddenColumns=${this._activeHiddenColumns}
|
||||
@grouping-changed=${this._handleGroupingChanged}
|
||||
@collapsed-changed=${this._handleCollapseChanged}
|
||||
@columns-changed=${this._handleColumnsChanged}
|
||||
@sorting-changed=${this._handleSortingChanged}
|
||||
.filter=${this._filter}
|
||||
@@ -470,13 +443,13 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
}
|
||||
|
||||
private _canDelete(urlPath: string) {
|
||||
return !["lovelace", "energy", "light", "safety", "climate"].includes(
|
||||
return !["lovelace", "energy", "light", "security", "climate"].includes(
|
||||
urlPath
|
||||
);
|
||||
}
|
||||
|
||||
private _canEdit(urlPath: string) {
|
||||
return !["light", "safety", "climate"].includes(urlPath);
|
||||
return !["light", "security", "climate"].includes(urlPath);
|
||||
}
|
||||
|
||||
private _handleDelete = async (item: DataTableItem) => {
|
||||
@@ -598,14 +571,6 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
this._activeColumnOrder = ev.detail.columnOrder;
|
||||
this._activeHiddenColumns = ev.detail.hiddenColumns;
|
||||
}
|
||||
|
||||
private _handleGroupingChanged(ev: CustomEvent) {
|
||||
this._activeGrouping = ev.detail.value;
|
||||
}
|
||||
|
||||
private _handleCollapseChanged(ev: CustomEvent) {
|
||||
this._activeCollapsed = ev.detail.value;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { mdiPencil, mdiDownload } from "@mdi/js";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
@@ -6,6 +6,7 @@ import "../../components/ha-menu-button";
|
||||
import "../../components/ha-icon-button-arrow-prev";
|
||||
import "../../components/ha-list-item";
|
||||
import "../../components/ha-top-app-bar-fixed";
|
||||
import "../../components/ha-alert";
|
||||
import type { LovelaceConfig } from "../../data/lovelace/config/types";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
@@ -21,6 +22,7 @@ import type {
|
||||
GasSourceTypeEnergyPreference,
|
||||
WaterSourceTypeEnergyPreference,
|
||||
DeviceConsumptionEnergyPreference,
|
||||
EnergyCollection,
|
||||
} from "../../data/energy";
|
||||
import {
|
||||
computeConsumptionData,
|
||||
@@ -30,13 +32,28 @@ import {
|
||||
import { fileDownload } from "../../util/file_download";
|
||||
import type { StatisticValue } from "../../data/recorder";
|
||||
|
||||
export const DEFAULT_ENERGY_COLLECTION_KEY = "energy_dashboard";
|
||||
|
||||
const ENERGY_LOVELACE_CONFIG: LovelaceConfig = {
|
||||
views: [
|
||||
{
|
||||
strategy: {
|
||||
type: "energy",
|
||||
type: "energy-overview",
|
||||
collection_key: DEFAULT_ENERGY_COLLECTION_KEY,
|
||||
},
|
||||
},
|
||||
{
|
||||
strategy: {
|
||||
type: "energy",
|
||||
collection_key: DEFAULT_ENERGY_COLLECTION_KEY,
|
||||
},
|
||||
path: "electricity",
|
||||
},
|
||||
{
|
||||
type: "panel",
|
||||
path: "setup",
|
||||
cards: [{ type: "custom:energy-setup-wizard-card" }],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -46,13 +63,30 @@ class PanelEnergy extends LitElement {
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public narrow = false;
|
||||
|
||||
@state() private _viewIndex = 0;
|
||||
|
||||
@state() private _lovelace?: Lovelace;
|
||||
|
||||
@state() private _searchParms = new URLSearchParams(window.location.search);
|
||||
|
||||
public willUpdate(changedProps: PropertyValues) {
|
||||
@state() private _error?: string;
|
||||
|
||||
@property({ attribute: false }) public route?: {
|
||||
path: string;
|
||||
prefix: string;
|
||||
};
|
||||
|
||||
private _energyCollection?: EnergyCollection;
|
||||
|
||||
get _viewPath(): string | undefined {
|
||||
const viewPath: string | undefined = this.route!.path.split("/")[1];
|
||||
return viewPath ? decodeURI(viewPath) : undefined;
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._loadPrefs();
|
||||
}
|
||||
|
||||
public async willUpdate(changedProps: PropertyValues) {
|
||||
if (!this.hasUpdated) {
|
||||
this.hass.loadFragmentTranslation("lovelace");
|
||||
}
|
||||
@@ -61,19 +95,67 @@ class PanelEnergy extends LitElement {
|
||||
}
|
||||
const oldHass = changedProps.get("hass") as this["hass"];
|
||||
if (oldHass?.locale !== this.hass.locale) {
|
||||
this._setLovelace();
|
||||
}
|
||||
if (oldHass && oldHass.localize !== this.hass.localize) {
|
||||
await this._setLovelace();
|
||||
} else if (oldHass && oldHass.localize !== this.hass.localize) {
|
||||
this._reloadView();
|
||||
}
|
||||
}
|
||||
|
||||
private async _loadPrefs() {
|
||||
if (this._viewPath === "setup") {
|
||||
await import("./cards/energy-setup-wizard-card");
|
||||
} else {
|
||||
this._energyCollection = getEnergyDataCollection(this.hass, {
|
||||
key: DEFAULT_ENERGY_COLLECTION_KEY,
|
||||
});
|
||||
try {
|
||||
// Have to manually refresh here as we don't want to subscribe yet
|
||||
await this._energyCollection.refresh();
|
||||
} catch (err: any) {
|
||||
if (err.code === "not_found") {
|
||||
navigate("/energy/setup");
|
||||
}
|
||||
this._error = err.message;
|
||||
return;
|
||||
}
|
||||
const prefs = this._energyCollection.prefs!;
|
||||
if (
|
||||
prefs.device_consumption.length === 0 &&
|
||||
prefs.energy_sources.length === 0
|
||||
) {
|
||||
// No energy sources available, start from scratch
|
||||
navigate("/energy/setup");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _back(ev) {
|
||||
ev.stopPropagation();
|
||||
goBack();
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
protected render() {
|
||||
if (!this._energyCollection?.prefs) {
|
||||
// Still loading
|
||||
return html`<div class="centered">
|
||||
<ha-spinner size="large"></ha-spinner>
|
||||
</div>`;
|
||||
}
|
||||
let viewPath = this._viewPath;
|
||||
const { prefs } = this._energyCollection;
|
||||
if (
|
||||
prefs.energy_sources.every((source) =>
|
||||
["grid", "solar", "battery"].includes(source.type)
|
||||
)
|
||||
) {
|
||||
// if only electricity sources, show electricity view directly
|
||||
viewPath = "electricity";
|
||||
}
|
||||
const viewIndex = Math.max(
|
||||
ENERGY_LOVELACE_CONFIG.views.findIndex((view) => view.path === viewPath),
|
||||
0
|
||||
);
|
||||
|
||||
return html`
|
||||
<div class="header">
|
||||
<div class="toolbar">
|
||||
@@ -99,7 +181,7 @@ class PanelEnergy extends LitElement {
|
||||
|
||||
<hui-energy-period-selector
|
||||
.hass=${this.hass}
|
||||
collection-key="energy_dashboard"
|
||||
.collectionKey=${DEFAULT_ENERGY_COLLECTION_KEY}
|
||||
>
|
||||
${this.hass.user?.is_admin
|
||||
? html` <ha-list-item
|
||||
@@ -127,12 +209,21 @@ class PanelEnergy extends LitElement {
|
||||
.hass=${this.hass}
|
||||
@reload-energy-panel=${this._reloadView}
|
||||
>
|
||||
<hui-view
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.lovelace=${this._lovelace}
|
||||
.index=${this._viewIndex}
|
||||
></hui-view>
|
||||
${this._error
|
||||
? html`<div class="centered">
|
||||
<ha-alert alert-type="error">
|
||||
An error occurred while fetching your energy preferences:
|
||||
${this._error}
|
||||
</ha-alert>
|
||||
</div>`
|
||||
: this._lovelace
|
||||
? html`<hui-view
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.lovelace=${this._lovelace}
|
||||
.index=${viewIndex}
|
||||
></hui-view>`
|
||||
: nothing}
|
||||
</hui-view-container>
|
||||
`;
|
||||
}
|
||||
@@ -160,9 +251,7 @@ class PanelEnergy extends LitElement {
|
||||
|
||||
private async _dumpCSV(ev) {
|
||||
ev.stopPropagation();
|
||||
const energyData = getEnergyDataCollection(this.hass, {
|
||||
key: "energy_dashboard",
|
||||
});
|
||||
const energyData = this._energyCollection!;
|
||||
|
||||
if (!energyData.prefs || !energyData.state.stats) {
|
||||
return;
|
||||
@@ -459,11 +548,11 @@ class PanelEnergy extends LitElement {
|
||||
}
|
||||
|
||||
private _reloadView() {
|
||||
// Force strategy to be re-run by make a copy of the view
|
||||
// Force strategy to be re-run by making a copy of the view
|
||||
const config = this._lovelace!.config;
|
||||
this._lovelace = {
|
||||
...this._lovelace!,
|
||||
config: { ...config, views: [{ ...config.views[0] }] },
|
||||
config: { ...config, views: config.views.map((view) => ({ ...view })) },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -565,6 +654,13 @@ class PanelEnergy extends LitElement {
|
||||
flex: 1 1 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
.centered {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
193
src/panels/energy/strategies/energy-overview-view-strategy.ts
Normal file
193
src/panels/energy/strategies/energy-overview-view-strategy.ts
Normal file
@@ -0,0 +1,193 @@
|
||||
import { ReactiveElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import type { GridSourceTypeEnergyPreference } from "../../../data/energy";
|
||||
import { getEnergyDataCollection } from "../../../data/energy";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
|
||||
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
|
||||
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
||||
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../ha-panel-energy";
|
||||
|
||||
@customElement("energy-overview-view-strategy")
|
||||
export class EnergyViewStrategy extends ReactiveElement {
|
||||
static async generate(
|
||||
_config: LovelaceStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
): Promise<LovelaceViewConfig> {
|
||||
const view: LovelaceViewConfig = { type: "sections", sections: [] };
|
||||
|
||||
const collectionKey =
|
||||
_config.collection_key || DEFAULT_ENERGY_COLLECTION_KEY;
|
||||
|
||||
const energyCollection = getEnergyDataCollection(hass, {
|
||||
key: collectionKey,
|
||||
});
|
||||
const prefs = energyCollection.prefs;
|
||||
|
||||
// No energy sources available
|
||||
if (
|
||||
!prefs ||
|
||||
(prefs.device_consumption.length === 0 &&
|
||||
prefs.energy_sources.length === 0)
|
||||
) {
|
||||
return view;
|
||||
}
|
||||
|
||||
const hasGrid = prefs.energy_sources.find(
|
||||
(source) =>
|
||||
source.type === "grid" &&
|
||||
(source.flow_from?.length || source.flow_to?.length)
|
||||
) as GridSourceTypeEnergyPreference;
|
||||
const hasReturn = hasGrid && hasGrid.flow_to.length;
|
||||
const hasSolar = prefs.energy_sources.some(
|
||||
(source) => source.type === "solar"
|
||||
);
|
||||
const hasGas = prefs.energy_sources.some((source) => source.type === "gas");
|
||||
const hasBattery = prefs.energy_sources.some(
|
||||
(source) => source.type === "battery"
|
||||
);
|
||||
const hasWater = prefs.energy_sources.some(
|
||||
(source) => source.type === "water"
|
||||
);
|
||||
|
||||
const energySection: LovelaceSectionConfig = {
|
||||
type: "grid",
|
||||
cards: [
|
||||
{
|
||||
type: "heading",
|
||||
heading: hass.localize("ui.panel.energy.overview.electricity"),
|
||||
tap_action: {
|
||||
action: "navigate",
|
||||
navigation_path: "/energy/electricity",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
// Only include if we have a grid or battery.
|
||||
if (hasGrid || hasBattery) {
|
||||
energySection.cards!.push({
|
||||
title: hass.localize("ui.panel.energy.cards.energy_distribution_title"),
|
||||
type: "energy-distribution",
|
||||
view_layout: { position: "sidebar" },
|
||||
collection_key: collectionKey,
|
||||
});
|
||||
}
|
||||
|
||||
if (prefs!.device_consumption.length > 0) {
|
||||
energySection.cards!.push({
|
||||
title: hass.localize(
|
||||
"ui.panel.energy.cards.energy_top_consumers_title"
|
||||
),
|
||||
type: "energy-devices-graph",
|
||||
collection_key: collectionKey,
|
||||
max_devices: 5,
|
||||
});
|
||||
} else if (hasGrid) {
|
||||
const gauges: LovelaceCardConfig[] = [];
|
||||
// Only include if we have a grid source & return.
|
||||
if (hasReturn) {
|
||||
gauges.push({
|
||||
type: "energy-grid-neutrality-gauge",
|
||||
view_layout: { position: "sidebar" },
|
||||
collection_key: collectionKey,
|
||||
});
|
||||
}
|
||||
|
||||
gauges.push({
|
||||
type: "energy-carbon-consumed-gauge",
|
||||
view_layout: { position: "sidebar" },
|
||||
collection_key: collectionKey,
|
||||
});
|
||||
|
||||
// Only include if we have a solar source.
|
||||
if (hasSolar) {
|
||||
if (hasReturn) {
|
||||
gauges.push({
|
||||
type: "energy-solar-consumed-gauge",
|
||||
view_layout: { position: "sidebar" },
|
||||
collection_key: collectionKey,
|
||||
});
|
||||
}
|
||||
gauges.push({
|
||||
type: "energy-self-sufficiency-gauge",
|
||||
view_layout: { position: "sidebar" },
|
||||
collection_key: collectionKey,
|
||||
});
|
||||
}
|
||||
|
||||
energySection.cards!.push({
|
||||
type: "grid",
|
||||
columns: 2,
|
||||
square: true,
|
||||
cards: gauges,
|
||||
});
|
||||
}
|
||||
|
||||
view.sections!.push(energySection);
|
||||
|
||||
if (hasGrid || hasSolar || hasBattery || hasGas || hasWater) {
|
||||
view.sections!.push({
|
||||
type: "grid",
|
||||
cards: [
|
||||
{
|
||||
type: "heading",
|
||||
heading: hass.localize(
|
||||
"ui.panel.energy.cards.energy_sources_table_title"
|
||||
),
|
||||
},
|
||||
{
|
||||
type: "energy-sources-table",
|
||||
collection_key: collectionKey,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
if (hasGas) {
|
||||
view.sections!.push({
|
||||
type: "grid",
|
||||
cards: [
|
||||
{
|
||||
type: "heading",
|
||||
heading: hass.localize("ui.panel.energy.overview.gas"),
|
||||
},
|
||||
{
|
||||
title: hass.localize(
|
||||
"ui.panel.energy.cards.energy_gas_graph_title"
|
||||
),
|
||||
type: "energy-gas-graph",
|
||||
collection_key: collectionKey,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
if (hasWater) {
|
||||
view.sections!.push({
|
||||
type: "grid",
|
||||
cards: [
|
||||
{
|
||||
type: "heading",
|
||||
heading: hass.localize("ui.panel.energy.overview.water"),
|
||||
},
|
||||
{
|
||||
title: hass.localize(
|
||||
"ui.panel.energy.cards.energy_water_graph_title"
|
||||
),
|
||||
type: "energy-water-graph",
|
||||
collection_key: collectionKey,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"energy-overview-view-strategy": EnergyViewStrategy;
|
||||
}
|
||||
}
|
@@ -1,25 +1,11 @@
|
||||
import { ReactiveElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import type {
|
||||
EnergyPreferences,
|
||||
GridSourceTypeEnergyPreference,
|
||||
} from "../../../data/energy";
|
||||
import { getEnergyPreferences } from "../../../data/energy";
|
||||
import type { GridSourceTypeEnergyPreference } from "../../../data/energy";
|
||||
import { getEnergyDataCollection } from "../../../data/energy";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
|
||||
|
||||
const setupWizard = async (): Promise<LovelaceViewConfig> => {
|
||||
await import("../cards/energy-setup-wizard-card");
|
||||
return {
|
||||
type: "panel",
|
||||
cards: [
|
||||
{
|
||||
type: "custom:energy-setup-wizard-card",
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../ha-panel-energy";
|
||||
|
||||
@customElement("energy-view-strategy")
|
||||
export class EnergyViewStrategy extends ReactiveElement {
|
||||
@@ -29,27 +15,21 @@ export class EnergyViewStrategy extends ReactiveElement {
|
||||
): Promise<LovelaceViewConfig> {
|
||||
const view: LovelaceViewConfig = { cards: [] };
|
||||
|
||||
let prefs: EnergyPreferences;
|
||||
const collectionKey =
|
||||
_config.collection_key || DEFAULT_ENERGY_COLLECTION_KEY;
|
||||
|
||||
try {
|
||||
prefs = await getEnergyPreferences(hass);
|
||||
} catch (err: any) {
|
||||
if (err.code === "not_found") {
|
||||
return setupWizard();
|
||||
}
|
||||
view.cards!.push({
|
||||
type: "markdown",
|
||||
content: `An error occurred while fetching your energy preferences: ${err.message}.`,
|
||||
});
|
||||
return view;
|
||||
}
|
||||
const energyCollection = getEnergyDataCollection(hass, {
|
||||
key: collectionKey,
|
||||
});
|
||||
const prefs = energyCollection.prefs;
|
||||
|
||||
// No energy sources available, start from scratch
|
||||
// No energy sources available
|
||||
if (
|
||||
prefs!.device_consumption.length === 0 &&
|
||||
prefs!.energy_sources.length === 0
|
||||
!prefs ||
|
||||
(prefs.device_consumption.length === 0 &&
|
||||
prefs.energy_sources.length === 0)
|
||||
) {
|
||||
return setupWizard();
|
||||
return view;
|
||||
}
|
||||
|
||||
view.type = "sidebar";
|
||||
@@ -63,13 +43,9 @@ export class EnergyViewStrategy extends ReactiveElement {
|
||||
const hasSolar = prefs.energy_sources.some(
|
||||
(source) => source.type === "solar"
|
||||
);
|
||||
const hasGas = prefs.energy_sources.some((source) => source.type === "gas");
|
||||
const hasBattery = prefs.energy_sources.some(
|
||||
(source) => source.type === "battery"
|
||||
);
|
||||
const hasWater = prefs.energy_sources.some(
|
||||
(source) => source.type === "water"
|
||||
);
|
||||
|
||||
view.cards!.push({
|
||||
type: "energy-compare",
|
||||
@@ -94,24 +70,6 @@ export class EnergyViewStrategy extends ReactiveElement {
|
||||
});
|
||||
}
|
||||
|
||||
// Only include if we have a gas source.
|
||||
if (hasGas) {
|
||||
view.cards!.push({
|
||||
title: hass.localize("ui.panel.energy.cards.energy_gas_graph_title"),
|
||||
type: "energy-gas-graph",
|
||||
collection_key: "energy_dashboard",
|
||||
});
|
||||
}
|
||||
|
||||
// Only include if we have a water source.
|
||||
if (hasWater) {
|
||||
view.cards!.push({
|
||||
title: hass.localize("ui.panel.energy.cards.energy_water_graph_title"),
|
||||
type: "energy-water-graph",
|
||||
collection_key: "energy_dashboard",
|
||||
});
|
||||
}
|
||||
|
||||
// Only include if we have a grid or battery.
|
||||
if (hasGrid || hasBattery) {
|
||||
view.cards!.push({
|
||||
@@ -122,13 +80,14 @@ export class EnergyViewStrategy extends ReactiveElement {
|
||||
});
|
||||
}
|
||||
|
||||
if (hasGrid || hasSolar || hasGas || hasWater || hasBattery) {
|
||||
if (hasGrid || hasSolar || hasBattery) {
|
||||
view.cards!.push({
|
||||
title: hass.localize(
|
||||
"ui.panel.energy.cards.energy_sources_table_title"
|
||||
),
|
||||
type: "energy-sources-table",
|
||||
collection_key: "energy_dashboard",
|
||||
types: ["grid", "solar", "battery"],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -170,20 +129,6 @@ export class EnergyViewStrategy extends ReactiveElement {
|
||||
|
||||
// Only include if we have at least 1 device in the config.
|
||||
if (prefs.device_consumption.length) {
|
||||
view.cards!.push({
|
||||
title: hass.localize(
|
||||
"ui.panel.energy.cards.energy_devices_detail_graph_title"
|
||||
),
|
||||
type: "energy-devices-detail-graph",
|
||||
collection_key: "energy_dashboard",
|
||||
});
|
||||
view.cards!.push({
|
||||
title: hass.localize(
|
||||
"ui.panel.energy.cards.energy_devices_graph_title"
|
||||
),
|
||||
type: "energy-devices-graph",
|
||||
collection_key: "energy_dashboard",
|
||||
});
|
||||
const showFloorsNAreas = !prefs.device_consumption.some(
|
||||
(d) => d.included_in_stat
|
||||
);
|
||||
@@ -194,6 +139,20 @@ export class EnergyViewStrategy extends ReactiveElement {
|
||||
group_by_floor: showFloorsNAreas,
|
||||
group_by_area: showFloorsNAreas,
|
||||
});
|
||||
view.cards!.push({
|
||||
title: hass.localize(
|
||||
"ui.panel.energy.cards.energy_devices_graph_title"
|
||||
),
|
||||
type: "energy-devices-graph",
|
||||
collection_key: "energy_dashboard",
|
||||
});
|
||||
view.cards!.push({
|
||||
title: hass.localize(
|
||||
"ui.panel.energy.cards.energy_devices_detail_graph_title"
|
||||
),
|
||||
type: "energy-devices-detail-graph",
|
||||
collection_key: "energy_dashboard",
|
||||
});
|
||||
}
|
||||
|
||||
return view;
|
||||
|
@@ -11,6 +11,7 @@ import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
import { stateActive } from "../../../common/entity/state_active";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import { computeLovelaceEntityName } from "../common/entity/compute-lovelace-entity-name";
|
||||
import "../../../components/ha-badge";
|
||||
import "../../../components/ha-ripple";
|
||||
import "../../../components/ha-state-icon";
|
||||
@@ -19,7 +20,6 @@ import { cameraUrlWithWidthHeight } from "../../../data/camera";
|
||||
import type { ActionHandlerEvent } from "../../../data/lovelace/action_handler";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { computeLovelaceEntityName } from "../common/entity/compute-lovelace-entity-name";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
@@ -162,7 +162,11 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
|
||||
if (!stateObj) {
|
||||
return html`
|
||||
<ha-badge .label=${entityId} class="error">
|
||||
<ha-svg-icon slot="icon" .path=${mdiAlertCircle}></ha-svg-icon>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.hass=${this.hass}
|
||||
.path=${mdiAlertCircle}
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize("ui.badge.entity.not_found")}
|
||||
</ha-badge>
|
||||
`;
|
||||
@@ -175,22 +179,22 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
|
||||
"--badge-color": color,
|
||||
};
|
||||
|
||||
const name = computeLovelaceEntityName(
|
||||
this.hass,
|
||||
stateObj,
|
||||
this._config.name
|
||||
);
|
||||
|
||||
const stateDisplay = html`
|
||||
<state-display
|
||||
.stateObj=${stateObj}
|
||||
.hass=${this.hass}
|
||||
.content=${this._config.state_content}
|
||||
.name=${name}
|
||||
.name=${this._config.name}
|
||||
>
|
||||
</state-display>
|
||||
`;
|
||||
|
||||
const name = computeLovelaceEntityName(
|
||||
this.hass,
|
||||
stateObj,
|
||||
this._config.name
|
||||
);
|
||||
|
||||
const showState = this._config.show_state;
|
||||
const showName = this._config.show_name;
|
||||
const showIcon = this._config.show_icon;
|
||||
|
@@ -379,7 +379,7 @@ export class HuiEnergyDevicesGraphCard
|
||||
show: true,
|
||||
position: "center",
|
||||
color: computedStyle.getPropertyValue("--secondary-text-color"),
|
||||
fontSize: computedStyle.getPropertyValue("--ha-font-size-l"),
|
||||
fontSize: computedStyle.getPropertyValue("--ha-font-size-m"),
|
||||
lineHeight: 24,
|
||||
fontWeight: "bold",
|
||||
formatter: `{a}\n${formatNumber(totalUsed, this.hass.locale)} kWh`,
|
||||
|
@@ -5,7 +5,7 @@ import {
|
||||
mdiDelete,
|
||||
mdiDeleteSweep,
|
||||
mdiDotsVertical,
|
||||
mdiDragHorizontalVariant,
|
||||
mdiDrag,
|
||||
mdiPlus,
|
||||
mdiSort,
|
||||
} from "@mdi/js";
|
||||
@@ -522,7 +522,7 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {
|
||||
"ui.panel.lovelace.cards.todo-list.drag_and_drop"
|
||||
)}
|
||||
class="reorderButton handle"
|
||||
.path=${mdiDragHorizontalVariant}
|
||||
.path=${mdiDrag}
|
||||
slot="meta"
|
||||
>
|
||||
</ha-svg-icon>
|
||||
|
@@ -150,11 +150,6 @@ export interface EnergyCardBaseConfig extends LovelaceCardConfig {
|
||||
collection_key?: string;
|
||||
}
|
||||
|
||||
export interface EnergySummaryCardConfig extends EnergyCardBaseConfig {
|
||||
type: "energy-summary";
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface EnergyDistributionCardConfig extends EnergyCardBaseConfig {
|
||||
type: "energy-distribution";
|
||||
title?: string;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { mdiClose, mdiDragHorizontalVariant, mdiPencil } from "@mdi/js";
|
||||
import { mdiClose, mdiDrag, mdiPencil } from "@mdi/js";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
@@ -66,11 +66,7 @@ export class HuiEntityEditor extends LitElement {
|
||||
|
||||
return html`
|
||||
<ha-md-list-item class="item">
|
||||
<ha-svg-icon
|
||||
class="handle"
|
||||
.path=${mdiDragHorizontalVariant}
|
||||
slot="start"
|
||||
></ha-svg-icon>
|
||||
<ha-svg-icon class="handle" .path=${mdiDrag} slot="start"></ha-svg-icon>
|
||||
|
||||
<div slot="headline" class="label">${primary}</div>
|
||||
${secondary
|
||||
@@ -156,9 +152,7 @@ export class HuiEntityEditor extends LitElement {
|
||||
(entityConf, index) => html`
|
||||
<div class="entity" data-entity-id=${entityConf.entity}>
|
||||
<div class="handle">
|
||||
<ha-svg-icon
|
||||
.path=${mdiDragHorizontalVariant}
|
||||
></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
|
||||
</div>
|
||||
<ha-entity-picker
|
||||
.hass=${this.hass}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { mdiDelete, mdiDragHorizontalVariant, mdiPencil } from "@mdi/js";
|
||||
import { mdiDelete, mdiDrag, mdiPencil } from "@mdi/js";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
@@ -31,7 +31,7 @@ export class HuiSectionEditMode extends LitElement {
|
||||
<ha-svg-icon
|
||||
aria-hidden="true"
|
||||
class="handle"
|
||||
.path=${mdiDragHorizontalVariant}
|
||||
.path=${mdiDrag}
|
||||
></ha-svg-icon>
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize("ui.common.edit")}
|
||||
|
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
mdiDelete,
|
||||
mdiDragHorizontalVariant,
|
||||
mdiPencil,
|
||||
mdiPlus,
|
||||
} from "@mdi/js";
|
||||
import { mdiDelete, mdiDrag, mdiPencil, mdiPlus } from "@mdi/js";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
@@ -350,9 +345,7 @@ export class HuiCardFeaturesEditor extends LitElement {
|
||||
return html`
|
||||
<div class="feature">
|
||||
<div class="handle">
|
||||
<ha-svg-icon
|
||||
.path=${mdiDragHorizontalVariant}
|
||||
></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
|
||||
</div>
|
||||
<div class="feature-content">
|
||||
<div>
|
||||
|
@@ -1,10 +1,5 @@
|
||||
import "@material/mwc-menu/mwc-menu-surface";
|
||||
import {
|
||||
mdiDelete,
|
||||
mdiDragHorizontalVariant,
|
||||
mdiPencil,
|
||||
mdiPlus,
|
||||
} from "@mdi/js";
|
||||
import { mdiDelete, mdiDrag, mdiPencil, mdiPlus } from "@mdi/js";
|
||||
import type { ComboBoxLightOpenedChangedEvent } from "@vaadin/combo-box/vaadin-combo-box-light";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
@@ -91,9 +86,7 @@ export class HuiHeadingBadgesEditor extends LitElement {
|
||||
return html`
|
||||
<div class="badge">
|
||||
<div class="handle">
|
||||
<ha-svg-icon
|
||||
.path=${mdiDragHorizontalVariant}
|
||||
></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
|
||||
</div>
|
||||
<div class="badge-content">
|
||||
<span>${label}</span>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { mdiClose, mdiDragHorizontalVariant, mdiPencil } from "@mdi/js";
|
||||
import { mdiClose, mdiDrag, mdiPencil } from "@mdi/js";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
@@ -59,7 +59,7 @@ export class HuiEntitiesCardRowEditor extends LitElement {
|
||||
(entityConf, index) => html`
|
||||
<div class="entity">
|
||||
<div class="handle">
|
||||
<ha-svg-icon .path=${mdiDragHorizontalVariant}></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
|
||||
</div>
|
||||
${entityConf.type
|
||||
? html`
|
||||
|
@@ -38,6 +38,8 @@ const STRATEGIES: Record<LovelaceStrategyConfigType, Record<string, any>> = {
|
||||
view: {
|
||||
"original-states": () =>
|
||||
import("./original-states/original-states-view-strategy"),
|
||||
"energy-overview": () =>
|
||||
import("../../energy/strategies/energy-overview-view-strategy"),
|
||||
energy: () => import("../../energy/strategies/energy-view-strategy"),
|
||||
map: () => import("./map/map-view-strategy"),
|
||||
iframe: () => import("./iframe/iframe-view-strategy"),
|
||||
|
@@ -1,12 +1,8 @@
|
||||
import { css, LitElement, nothing } from "lit";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { LovelaceViewBackgroundConfig } from "../../../data/lovelace/config/view";
|
||||
import {
|
||||
isMediaSourceContentId,
|
||||
resolveMediaSource,
|
||||
} from "../../../data/media_source";
|
||||
|
||||
@customElement("hui-view-background")
|
||||
export class HUIViewBackground extends LitElement {
|
||||
@@ -17,27 +13,10 @@ export class HUIViewBackground extends LitElement {
|
||||
| LovelaceViewBackgroundConfig
|
||||
| undefined;
|
||||
|
||||
@state({ attribute: false }) resolvedImage?: string;
|
||||
|
||||
protected render() {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
private _fetchMedia() {
|
||||
const backgroundImage =
|
||||
typeof this.background === "string"
|
||||
? this.background
|
||||
: this.background?.image;
|
||||
|
||||
if (backgroundImage && isMediaSourceContentId(backgroundImage)) {
|
||||
resolveMediaSource(this.hass, backgroundImage).then((result) => {
|
||||
this.resolvedImage = result.url;
|
||||
});
|
||||
} else {
|
||||
this.resolvedImage = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private _applyTheme() {
|
||||
const computedStyles = getComputedStyle(this);
|
||||
const themeBackground = computedStyles.getPropertyValue(
|
||||
@@ -73,19 +52,13 @@ export class HUIViewBackground extends LitElement {
|
||||
background?: string | LovelaceViewBackgroundConfig
|
||||
) {
|
||||
if (typeof background === "object" && background.image) {
|
||||
if (isMediaSourceContentId(background.image) && !this.resolvedImage) {
|
||||
return null;
|
||||
}
|
||||
const alignment = background.alignment ?? "center";
|
||||
const size = background.size ?? "cover";
|
||||
const repeat = background.repeat ?? "no-repeat";
|
||||
return `${alignment} / ${size} ${repeat} url('${this.hass.hassUrl(this.resolvedImage || background.image)}')`;
|
||||
return `${alignment} / ${size} ${repeat} url('${this.hass.hassUrl(background.image)}')`;
|
||||
}
|
||||
if (typeof background === "string") {
|
||||
if (isMediaSourceContentId(background) && !this.resolvedImage) {
|
||||
return null;
|
||||
}
|
||||
return this.resolvedImage || background;
|
||||
return background;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -117,10 +90,6 @@ export class HUIViewBackground extends LitElement {
|
||||
|
||||
if (changedProperties.has("background")) {
|
||||
this._applyTheme();
|
||||
this._fetchMedia();
|
||||
}
|
||||
if (changedProperties.has("resolvedImage")) {
|
||||
this._applyTheme();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -155,6 +155,7 @@ export const semanticColorStyles = css`
|
||||
|
||||
/* Surfaces */
|
||||
--ha-color-surface-default: var(--ha-color-neutral-95);
|
||||
--ha-color-on-surface-default: var(--ha-color-neutral-05);
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -286,5 +287,6 @@ export const darkSemanticColorStyles = css`
|
||||
|
||||
/* Surfaces */
|
||||
--ha-color-surface-default: var(--ha-color-neutral-10);
|
||||
--ha-color-on-surface-default: var(--ha-color-neutral-95);
|
||||
}
|
||||
`;
|
||||
|
@@ -667,8 +667,7 @@
|
||||
"floor_missing": "No floor assigned",
|
||||
"device_missing": "No related device"
|
||||
},
|
||||
"add": "Add",
|
||||
"custom_name": "Custom name"
|
||||
"add": "Add"
|
||||
},
|
||||
"entity-attribute-picker": {
|
||||
"attribute": "Attribute",
|
||||
@@ -3455,17 +3454,12 @@
|
||||
"require_admin": "Admin only",
|
||||
"sidebar": "In sidebar",
|
||||
"filename": "Filename",
|
||||
"url": "Open",
|
||||
"type": "Type"
|
||||
"url": "Open"
|
||||
},
|
||||
"open": "Open",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"add_dashboard": "Add dashboard",
|
||||
"type": {
|
||||
"user_created": "User created",
|
||||
"built_in": "Built-in"
|
||||
}
|
||||
"add_dashboard": "Add dashboard"
|
||||
},
|
||||
"confirm_delete_title": "Delete {dashboard_title}?",
|
||||
"confirm_delete_text": "This dashboard will be permanently deleted.",
|
||||
@@ -3915,6 +3909,7 @@
|
||||
"edit_yaml": "Edit in YAML",
|
||||
"edit_ui": "Edit in visual editor",
|
||||
"copy_to_clipboard": "Copy to clipboard",
|
||||
"search_in": "Search · {group}",
|
||||
"unknown_entity": "unknown entity",
|
||||
"edit_unknown_device": "Editor not available for unknown device",
|
||||
"switch_ui_yaml_error": "There are currently YAML errors in the automation, and it cannot be parsed. Switching to UI mode may cause pending changes to be lost. Press cancel to correct any errors before proceeding to prevent loss of pending changes, or continue if you are sure.",
|
||||
@@ -3927,7 +3922,6 @@
|
||||
"item_pasted": "{item} pasted",
|
||||
"ctrl": "Ctrl",
|
||||
"del": "Del",
|
||||
"blocks": "Blocks",
|
||||
"triggers": {
|
||||
"name": "Triggers",
|
||||
"header": "When",
|
||||
@@ -3935,7 +3929,7 @@
|
||||
"learn_more": "Learn more about triggers",
|
||||
"triggered": "Triggered",
|
||||
"add": "Add trigger",
|
||||
"empty_search": "No triggers found for {term}",
|
||||
"search": "Search trigger",
|
||||
"id": "Trigger ID",
|
||||
"id_helper": "Helps identify each run based on which trigger fired.",
|
||||
"optional": "Optional",
|
||||
@@ -3956,16 +3950,14 @@
|
||||
"trigger": "Trigger",
|
||||
"copied_to_clipboard": "Trigger copied to clipboard",
|
||||
"cut_to_clipboard": "Trigger cut to clipboard",
|
||||
"select": "Select a trigger",
|
||||
"groups": {
|
||||
"device": {
|
||||
"label": "Device"
|
||||
},
|
||||
"entity": {
|
||||
"label": "Entity"
|
||||
"label": "Entity",
|
||||
"description": "When something happens to an entity."
|
||||
},
|
||||
"time_location": {
|
||||
"label": "Time and location"
|
||||
"label": "Time and location",
|
||||
"description": "When someone enters or leaves a zone, or at a specific time."
|
||||
},
|
||||
"other": {
|
||||
"label": "Other triggers"
|
||||
@@ -4198,7 +4190,7 @@
|
||||
"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",
|
||||
"empty_search": "No conditions and blocks found for {term}",
|
||||
"search": "Search condition",
|
||||
"add_building_block": "Add building block",
|
||||
"test": "Test",
|
||||
"testing_error": "Condition did not pass",
|
||||
@@ -4220,22 +4212,21 @@
|
||||
"condition": "Condition",
|
||||
"copied_to_clipboard": "Condition copied to clipboard",
|
||||
"cut_to_clipboard": "Condition cut to clipboard",
|
||||
"select": "Select a condition",
|
||||
"groups": {
|
||||
"device": {
|
||||
"label": "Device"
|
||||
},
|
||||
"entity": {
|
||||
"label": "Entity"
|
||||
"label": "Entity",
|
||||
"description": "If an entity is in a specific state."
|
||||
},
|
||||
"time_location": {
|
||||
"label": "Time and location"
|
||||
"label": "Time and location",
|
||||
"description": "If someone is in a zone or if the current time is before or after a specified time."
|
||||
},
|
||||
"other": {
|
||||
"label": "Other conditions"
|
||||
},
|
||||
"building_blocks": {
|
||||
"label": "Building blocks"
|
||||
"label": "Building blocks",
|
||||
"description": "Build more complex conditions."
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
@@ -4366,7 +4357,7 @@
|
||||
"description": "All actions added here will be performed in sequence when the automation runs. An action usually controls one of your areas, devices, or entities, for example: 'Turn on the lights'. You can use building blocks to create more complex sequences of actions.",
|
||||
"learn_more": "Learn more about actions",
|
||||
"add": "Add action",
|
||||
"empty_search": "No actions and blocks found for {term}",
|
||||
"search": "Search action",
|
||||
"add_building_block": "Add building block",
|
||||
"invalid_action": "Invalid action",
|
||||
"run": "Run action",
|
||||
@@ -4390,11 +4381,7 @@
|
||||
"action": "Action",
|
||||
"copied_to_clipboard": "Action copied to clipboard",
|
||||
"cut_to_clipboard": "Action cut to clipboard",
|
||||
"select": "Select an action",
|
||||
"groups": {
|
||||
"device_id": {
|
||||
"label": "Device"
|
||||
},
|
||||
"helpers": {
|
||||
"label": "Helpers"
|
||||
},
|
||||
@@ -4402,7 +4389,8 @@
|
||||
"label": "Other actions"
|
||||
},
|
||||
"building_blocks": {
|
||||
"label": "Building blocks"
|
||||
"label": "Building blocks",
|
||||
"description": "Build more complex sequences of actions."
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
@@ -9322,6 +9310,11 @@
|
||||
}
|
||||
},
|
||||
"energy": {
|
||||
"overview": {
|
||||
"electricity": "Electricity",
|
||||
"gas": "Gas",
|
||||
"water": "Water"
|
||||
},
|
||||
"download_data": "[%key:ui::panel::history::download_data%]",
|
||||
"configure": "[%key:ui::dialogs::quick-bar::commands::navigation::energy%]",
|
||||
"compare": {
|
||||
@@ -9351,7 +9344,8 @@
|
||||
"energy_sources_table_title": "Sources",
|
||||
"energy_devices_graph_title": "Individual devices total usage",
|
||||
"energy_devices_detail_graph_title": "Individual devices detail usage",
|
||||
"energy_sankey_title": "Energy flow"
|
||||
"energy_sankey_title": "Energy flow",
|
||||
"energy_top_consumers_title": "Top consumers"
|
||||
}
|
||||
},
|
||||
"history": {
|
||||
|
20
yarn.lock
20
yarn.lock
@@ -1284,15 +1284,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@codemirror/view@npm:6.38.6, @codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.27.0":
|
||||
version: 6.38.6
|
||||
resolution: "@codemirror/view@npm:6.38.6"
|
||||
"@codemirror/view@npm:6.38.5, @codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.27.0":
|
||||
version: 6.38.5
|
||||
resolution: "@codemirror/view@npm:6.38.5"
|
||||
dependencies:
|
||||
"@codemirror/state": "npm:^6.5.0"
|
||||
crelt: "npm:^1.0.6"
|
||||
style-mod: "npm:^4.1.0"
|
||||
w3c-keyname: "npm:^2.2.4"
|
||||
checksum: 10/5a047337a98de111817ce8c8d39e6429c90ca0b0a4d2678d6e161e9e5961b1d476a891f447ab7a05cac395d4a93530e7c68bedd93191285265f0742a308ad00b
|
||||
checksum: 10/2335b593770042eb3adfe369073432b07cd2d15f1e230ae4dc7be7a7b8edd74e57c13e59b92a11e7e5d59ae030aabf7f55478dfec1cf2a2fe3a1ef3f091676a4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -1942,9 +1942,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@home-assistant/webawesome@npm:3.0.0-beta.6.ha.5":
|
||||
version: 3.0.0-beta.6.ha.5
|
||||
resolution: "@home-assistant/webawesome@npm:3.0.0-beta.6.ha.5"
|
||||
"@home-assistant/webawesome@npm:3.0.0-beta.6.ha.4":
|
||||
version: 3.0.0-beta.6.ha.4
|
||||
resolution: "@home-assistant/webawesome@npm:3.0.0-beta.6.ha.4"
|
||||
dependencies:
|
||||
"@ctrl/tinycolor": "npm:4.1.0"
|
||||
"@floating-ui/dom": "npm:^1.6.13"
|
||||
@@ -1955,7 +1955,7 @@ __metadata:
|
||||
lit: "npm:^3.2.1"
|
||||
nanoid: "npm:^5.1.5"
|
||||
qr-creator: "npm:^1.0.0"
|
||||
checksum: 10/6bfa5e06b91df06402c348bc19ec59a7fe6ed70080989d60a3c6519f99f5dc72da8b42c5dc2cad9d1ab211c51c4c67a74c0e22f21368da3c9f2565cbf8646a90
|
||||
checksum: 10/d9072b321126ef458468ed2cf040e0b04cb2aff73336c6e742c0cfb25d9fb674b7672e7c9abcf5bcb0aa0b2fe953c20186f0910f485024c827bfe4cf399f10a4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -9255,7 +9255,7 @@ __metadata:
|
||||
"@codemirror/legacy-modes": "npm:6.5.2"
|
||||
"@codemirror/search": "npm:6.5.11"
|
||||
"@codemirror/state": "npm:6.5.2"
|
||||
"@codemirror/view": "npm:6.38.6"
|
||||
"@codemirror/view": "npm:6.38.5"
|
||||
"@date-fns/tz": "npm:1.4.1"
|
||||
"@egjs/hammerjs": "npm:2.0.17"
|
||||
"@formatjs/intl-datetimeformat": "npm:6.18.2"
|
||||
@@ -9273,7 +9273,7 @@ __metadata:
|
||||
"@fullcalendar/list": "npm:6.1.19"
|
||||
"@fullcalendar/luxon3": "npm:6.1.19"
|
||||
"@fullcalendar/timegrid": "npm:6.1.19"
|
||||
"@home-assistant/webawesome": "npm:3.0.0-beta.6.ha.5"
|
||||
"@home-assistant/webawesome": "npm:3.0.0-beta.6.ha.4"
|
||||
"@lezer/highlight": "npm:1.2.1"
|
||||
"@lit-labs/motion": "npm:1.0.9"
|
||||
"@lit-labs/observers": "npm:2.0.6"
|
||||
|
Reference in New Issue
Block a user