mirror of
https://github.com/home-assistant/frontend.git
synced 2026-09-08 21:03:28 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1717d07049 | ||
|
|
33a31ff345 |
@@ -29,7 +29,7 @@ export const computeEntityEntryName = (
|
||||
fallbackStateObj?: HassEntity
|
||||
): string | undefined => {
|
||||
const name =
|
||||
entry.name ??
|
||||
entry.name ||
|
||||
("original_name" in entry && entry.original_name != null
|
||||
? String(entry.original_name)
|
||||
: undefined);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import { mdiContentCopy, mdiPencil, mdiRestore } from "@mdi/js";
|
||||
import { mdiContentCopy, mdiRestore } from "@mdi/js";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
@@ -8,10 +8,7 @@ import { until } from "lit/directives/until";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { consume } from "@lit/context";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import type { HASSDomCurrentTargetEvent } from "../../../common/dom/fire_event";
|
||||
import { computeDeviceNameDisplay } from "../../../common/entity/compute_device_name";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { computeEntityEntryName } from "../../../common/entity/compute_entity_name";
|
||||
import { computeObjectId } from "../../../common/entity/compute_object_id";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import { formatNumber } from "../../../common/number/format_number";
|
||||
@@ -24,13 +21,10 @@ import type {
|
||||
import { copyToClipboard } from "../../../common/util/copy-clipboard";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-area-picker";
|
||||
import "../../../components/ha-checkbox";
|
||||
import type { HaCheckbox } from "../../../components/ha-checkbox";
|
||||
import "../../../components/ha-color-picker";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import "../../../components/entity/ha-entity-picker";
|
||||
import "../../../components/ha-icon";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-icon-button-next";
|
||||
import "../../../components/ha-icon-picker";
|
||||
import "../../../components/ha-labels-picker";
|
||||
@@ -199,8 +193,6 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
|
||||
@state() private _name!: string;
|
||||
|
||||
@state() private _useDeviceName = false;
|
||||
|
||||
@state() private _icon!: string;
|
||||
|
||||
@state() private _entityId!: EntitySettingsState["entityId"];
|
||||
@@ -262,9 +254,6 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues<this>) {
|
||||
super.willUpdate(changedProperties);
|
||||
this._device = this.entry.device_id
|
||||
? this.hass.devices[this.entry.device_id]
|
||||
: undefined;
|
||||
if (
|
||||
!changedProperties.has("entry") ||
|
||||
changedProperties.get("entry")?.id === this.entry.id
|
||||
@@ -273,8 +262,6 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
}
|
||||
|
||||
this._name = this.entry.name || "";
|
||||
this._useDeviceName =
|
||||
!!this._device && !computeEntityEntryName(this.entry, this.hass.devices);
|
||||
this._icon = this.entry.icon || "";
|
||||
this._deviceClass =
|
||||
this.entry.device_class || this.entry.original_device_class;
|
||||
@@ -284,6 +271,9 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
this._entityId = this.entry.entity_id;
|
||||
this._disabledBy = this.entry.disabled_by;
|
||||
this._hiddenBy = this.entry.hidden_by;
|
||||
this._device = this.entry.device_id
|
||||
? this.hass.devices[this.entry.device_id]
|
||||
: undefined;
|
||||
this._switchAsInvert = this.entry.options?.switch_as_x?.invert === true;
|
||||
|
||||
const domain = computeDomain(this.entry.entity_id);
|
||||
@@ -396,7 +386,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
|
||||
this._dirtyState?.setState(
|
||||
{
|
||||
name: this._computeName(),
|
||||
name: this._name || null,
|
||||
icon: this._icon || null,
|
||||
entityId: this._entityId,
|
||||
areaId: this._areaId ?? null,
|
||||
@@ -474,74 +464,41 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
|
||||
const defaultPrecision =
|
||||
this.entry.options?.sensor?.suggested_display_precision ?? undefined;
|
||||
const deviceName =
|
||||
this._device && this._useDeviceName
|
||||
? computeDeviceNameDisplay(
|
||||
this._device,
|
||||
this.hass.localize,
|
||||
this.hass.states
|
||||
)
|
||||
: undefined;
|
||||
|
||||
return html`
|
||||
${
|
||||
this.hideName
|
||||
? nothing
|
||||
: html`<div
|
||||
class="name-field"
|
||||
role="group"
|
||||
aria-label=${this.hass.localize(
|
||||
: html`<ha-input
|
||||
inset-label
|
||||
class="name"
|
||||
.value=${this._name}
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.name"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
@input=${this._nameChanged}
|
||||
>
|
||||
${
|
||||
deviceName !== undefined
|
||||
? html`<div class="device-name" id="entity-name">
|
||||
<span class="device-name-label">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.name"
|
||||
)}
|
||||
</span>
|
||||
<span class="device-name-value" title=${deviceName}>
|
||||
${deviceName}
|
||||
</span>
|
||||
<ha-icon-button
|
||||
.path=${mdiPencil}
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.edit_device_name"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
@click=${this._openDeviceSettings}
|
||||
></ha-icon-button>
|
||||
</div>`
|
||||
: html`<ha-input
|
||||
inset-label
|
||||
class="name"
|
||||
id="entity-name"
|
||||
.value=${this._name}
|
||||
.placeholder=${this.entry.original_name || ""}
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.name"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
@input=${this._nameChanged}
|
||||
></ha-input>`
|
||||
}
|
||||
${
|
||||
this._device
|
||||
? html`<ha-checkbox
|
||||
.checked=${this._useDeviceName}
|
||||
.disabled=${this.disabled}
|
||||
aria-controls="entity-name"
|
||||
@change=${this._useDeviceNameChanged}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.use_device_name"
|
||||
)}
|
||||
</ha-checkbox>`
|
||||
? html`<span slot="hint"
|
||||
>${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.device_name_tip",
|
||||
{
|
||||
link: html`<button
|
||||
class="link"
|
||||
@click=${this._resetNameAndOpenDeviceSettings}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.open_device_settings"
|
||||
)}
|
||||
</button>`,
|
||||
}
|
||||
)}</span
|
||||
>`
|
||||
: nothing
|
||||
}
|
||||
</div>`
|
||||
</ha-input>`
|
||||
}
|
||||
${
|
||||
this.hideIcon
|
||||
@@ -1288,7 +1245,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
}
|
||||
|
||||
const params: Partial<EntityRegistryEntryUpdateParams> = {
|
||||
name: this._computeName(),
|
||||
name: this._name.trim() || null,
|
||||
icon: this._icon.trim() || null,
|
||||
area_id: this._areaId || null,
|
||||
labels: this._labels || [],
|
||||
@@ -1735,23 +1692,9 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _useDeviceNameChanged(
|
||||
ev: HASSDomCurrentTargetEvent<HaCheckbox>
|
||||
): void {
|
||||
this._useDeviceName = ev.currentTarget.checked;
|
||||
}
|
||||
|
||||
private _computeName(): string | null {
|
||||
if (this.hideName) {
|
||||
return this.entry.name;
|
||||
}
|
||||
if (this._device && this._useDeviceName) {
|
||||
// Preserve entities that already use the device name without an override.
|
||||
return computeEntityEntryName(this.entry, this.hass.devices)
|
||||
? ""
|
||||
: this.entry.name;
|
||||
}
|
||||
return this._name.trim() || null;
|
||||
private _resetNameAndOpenDeviceSettings() {
|
||||
this._name = this.entry.name || "";
|
||||
this._openDeviceSettings();
|
||||
}
|
||||
|
||||
private _openDeviceSettings() {
|
||||
@@ -1855,8 +1798,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
}
|
||||
|
||||
ha-input.entityId,
|
||||
ha-input.name,
|
||||
.device-name {
|
||||
ha-input.name {
|
||||
--ha-icon-button-size: 36px;
|
||||
--mdc-icon-size: 20px;
|
||||
}
|
||||
@@ -1864,60 +1806,6 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
ha-input.name {
|
||||
--ha-input-start-max-width: 35%;
|
||||
}
|
||||
.name-field {
|
||||
margin: var(--ha-space-2) 0;
|
||||
}
|
||||
.name-field ha-input {
|
||||
margin: 0;
|
||||
--ha-input-padding-bottom: 0;
|
||||
}
|
||||
.device-name {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
height: 56px;
|
||||
padding-inline: var(--ha-space-4);
|
||||
background: var(--ha-color-form-background);
|
||||
border-radius: var(--ha-border-radius-sm) var(--ha-border-radius-sm) 0
|
||||
0;
|
||||
box-shadow: inset 0 -1px var(--ha-color-border-neutral-quiet);
|
||||
font-family: var(--ha-font-family-body);
|
||||
font-weight: var(--ha-font-weight-normal);
|
||||
line-height: var(--ha-line-height-condensed);
|
||||
cursor: default;
|
||||
}
|
||||
.device-name-label {
|
||||
position: absolute;
|
||||
top: var(--ha-space-3);
|
||||
inset-inline-start: var(--ha-space-4);
|
||||
color: var(--secondary-text-color);
|
||||
font-size: var(--ha-font-size-xs);
|
||||
}
|
||||
.device-name-value {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding-top: var(--ha-space-3);
|
||||
color: var(--primary-text-color);
|
||||
font-size: var(--ha-font-size-m);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
user-select: text;
|
||||
}
|
||||
.device-name ha-icon-button {
|
||||
flex: none;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.name-field ha-checkbox {
|
||||
display: block;
|
||||
width: fit-content;
|
||||
padding-inline: var(--ha-space-4);
|
||||
--wa-form-control-label-font-size: var(--ha-font-size-s);
|
||||
}
|
||||
.name-field ha-checkbox::part(base) {
|
||||
min-height: 36px;
|
||||
}
|
||||
ha-input.entityId ha-icon-button:last-child {
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import {
|
||||
mdiAutoFix,
|
||||
mdiDelete,
|
||||
mdiDotsVertical,
|
||||
mdiDragHorizontalVariant,
|
||||
@@ -7,13 +8,14 @@ import {
|
||||
mdiPlusCircleMultipleOutline,
|
||||
} from "@mdi/js";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../components/ha-dropdown";
|
||||
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-tooltip";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
@@ -32,10 +34,32 @@ export class HuiSectionEditMode extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public viewIndex!: number;
|
||||
|
||||
@property({ type: Boolean, attribute: "is-strategy", reflect: true })
|
||||
public isStrategy = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div class="section-header">
|
||||
<div class="section-actions">
|
||||
${
|
||||
this.isStrategy
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
id="strategy-icon"
|
||||
.path=${mdiAutoFix}
|
||||
role="img"
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.section.automatic"
|
||||
)}
|
||||
></ha-svg-icon>
|
||||
<ha-tooltip for="strategy-icon">
|
||||
${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.section.automatic"
|
||||
)}
|
||||
</ha-tooltip>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
<ha-svg-icon
|
||||
aria-hidden="true"
|
||||
class="handle"
|
||||
@@ -70,7 +94,25 @@ export class HuiSectionEditMode extends LitElement {
|
||||
</div>
|
||||
</div>
|
||||
<div class="section-wrapper">
|
||||
<slot></slot>
|
||||
<div class="section-content" ?inert=${this.isStrategy}>
|
||||
<slot></slot>
|
||||
</div>
|
||||
${
|
||||
this.isStrategy
|
||||
? html`
|
||||
<button
|
||||
class="edit-overlay"
|
||||
type="button"
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.section.edit_automatic"
|
||||
)}
|
||||
@click=${this._editSection}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPencil}></ha-svg-icon>
|
||||
</button>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -183,7 +225,12 @@ export class HuiSectionEditMode extends LitElement {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
#strategy-icon {
|
||||
padding: var(--ha-space-2);
|
||||
}
|
||||
|
||||
.section-wrapper {
|
||||
position: relative;
|
||||
padding: 8px;
|
||||
border-radius: var(
|
||||
--ha-section-border-radius,
|
||||
@@ -193,6 +240,67 @@ export class HuiSectionEditMode extends LitElement {
|
||||
border: 2px dashed var(--divider-color);
|
||||
min-height: var(--row-height);
|
||||
}
|
||||
|
||||
:host([is-strategy]) .section-wrapper {
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.section-content {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.edit-overlay {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: inherit;
|
||||
background: none;
|
||||
color: var(--primary-text-color);
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transition: opacity var(--ha-animation-duration-fast) ease-in-out;
|
||||
}
|
||||
|
||||
.edit-overlay::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: inherit;
|
||||
background: var(--primary-background-color);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.edit-overlay:hover,
|
||||
.edit-overlay:focus-visible,
|
||||
.edit-overlay:active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.edit-overlay:focus-visible {
|
||||
outline: 2px solid var(--primary-color);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
.edit-overlay ha-svg-icon {
|
||||
position: relative;
|
||||
padding: var(--ha-space-2);
|
||||
border-radius: var(--ha-border-radius-circle);
|
||||
background: var(--secondary-background-color);
|
||||
--mdc-icon-size: 20px;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.edit-overlay {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import {
|
||||
mdiClose,
|
||||
mdiDotsVertical,
|
||||
mdiFileMoveOutline,
|
||||
mdiPlaylistEdit,
|
||||
} from "@mdi/js";
|
||||
import { mdiClose, mdiDotsVertical, mdiFileMoveOutline } from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { cache } from "lit/directives/cache";
|
||||
import type { HASSDomEvent } from "../../../../common/dom/fire_event";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||
import { withViewTransition } from "../../../../common/util/view-transition";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-dialog-header";
|
||||
import "../../../../components/ha-dialog-footer";
|
||||
@@ -22,6 +20,8 @@ import "../../../../components/ha-tab-group-tab";
|
||||
import "../../../../components/ha-yaml-editor";
|
||||
import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
|
||||
import type { LovelaceSectionRawConfig } from "../../../../data/lovelace/config/section";
|
||||
import { isStrategySection } from "../../../../data/lovelace/config/section";
|
||||
import type { LovelaceStrategyConfig } from "../../../../data/lovelace/config/strategy";
|
||||
import type { LovelaceConfig } from "../../../../data/lovelace/config/types";
|
||||
import { saveConfig } from "../../../../data/lovelace/config/types";
|
||||
import {
|
||||
@@ -35,7 +35,8 @@ import {
|
||||
haStyleDialog,
|
||||
haStyleDialogFixedTop,
|
||||
} from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../../../types";
|
||||
import { cleanLegacyStrategyConfig } from "../../strategies/legacy-strategy";
|
||||
import type { Lovelace } from "../../types";
|
||||
import { addSection, deleteSection, moveSection } from "../config-util";
|
||||
import {
|
||||
@@ -45,12 +46,15 @@ import {
|
||||
import { showSelectViewDialog } from "../select-view/show-select-view-dialog";
|
||||
import "./hui-section-settings-editor";
|
||||
import "./hui-section-visibility-editor";
|
||||
import "./hui-section-strategy-element-editor";
|
||||
import type { HuiSectionStrategyElementEditor } from "./hui-section-strategy-element-editor";
|
||||
import type { ConfigChangedEvent } from "../hui-element-editor";
|
||||
import type { EditSectionDialogParams } from "./show-edit-section-dialog";
|
||||
import type { HaDropdownSelectEvent } from "../../../../components/ha-dropdown";
|
||||
import { getViewType } from "../../views/get-view-type";
|
||||
import { SECTIONS_VIEW_LAYOUT } from "../../views/const";
|
||||
|
||||
const TABS = ["tab-settings", "tab-visibility"] as const;
|
||||
const TABS = ["tab-strategy", "tab-appearance", "tab-visibility"] as const;
|
||||
|
||||
@customElement("hui-dialog-edit-section")
|
||||
export class HuiDialogEditSection
|
||||
@@ -73,8 +77,15 @@ export class HuiDialogEditSection
|
||||
|
||||
@state() private _open = false;
|
||||
|
||||
@state() private _strategyError = false;
|
||||
|
||||
@state() private _yamlError = false;
|
||||
|
||||
@query("ha-yaml-editor") private _editor?: HaYamlEditor;
|
||||
|
||||
@query("hui-section-strategy-element-editor")
|
||||
private _strategyEditor?: HuiSectionStrategyElementEditor;
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
super.updated(changedProperties);
|
||||
if (this._yamlMode && changedProperties.has("_yamlMode")) {
|
||||
@@ -95,6 +106,9 @@ export class HuiDialogEditSection
|
||||
this._params.viewIndex,
|
||||
this._params.sectionIndex,
|
||||
]);
|
||||
this._currTab = isStrategySection(this._config)
|
||||
? "tab-strategy"
|
||||
: "tab-appearance";
|
||||
this._viewConfig = findLovelaceContainer(this._params.lovelaceConfig, [
|
||||
this._params.viewIndex,
|
||||
]);
|
||||
@@ -111,6 +125,8 @@ export class HuiDialogEditSection
|
||||
this._yamlMode = false;
|
||||
this._config = undefined;
|
||||
this._currTab = TABS[0];
|
||||
this._strategyError = false;
|
||||
this._yamlError = false;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
@@ -123,6 +139,15 @@ export class HuiDialogEditSection
|
||||
"ui.panel.lovelace.editor.edit_section.header"
|
||||
);
|
||||
|
||||
const strategy = isStrategySection(this._config)
|
||||
? cleanLegacyStrategyConfig(this._config.strategy)
|
||||
: undefined;
|
||||
const tabs = strategy ? TABS : TABS.filter((tab) => tab !== "tab-strategy");
|
||||
const currentTab =
|
||||
!strategy && this._currTab === "tab-strategy"
|
||||
? "tab-appearance"
|
||||
: this._currTab;
|
||||
|
||||
let content: TemplateResult<1> | typeof nothing = nothing;
|
||||
|
||||
if (this._yamlMode) {
|
||||
@@ -134,8 +159,8 @@ export class HuiDialogEditSection
|
||||
></ha-yaml-editor>
|
||||
`;
|
||||
} else {
|
||||
switch (this._currTab) {
|
||||
case "tab-settings":
|
||||
switch (currentTab) {
|
||||
case "tab-appearance":
|
||||
content = html`
|
||||
<hui-section-settings-editor
|
||||
.config=${this._config}
|
||||
@@ -155,6 +180,17 @@ export class HuiDialogEditSection
|
||||
</hui-section-visibility-editor>
|
||||
`;
|
||||
break;
|
||||
case "tab-strategy":
|
||||
content = html`
|
||||
<hui-section-strategy-element-editor
|
||||
.hass=${this.hass}
|
||||
.lovelace=${this._params.lovelaceConfig}
|
||||
.value=${strategy}
|
||||
in-dialog
|
||||
@config-changed=${this._strategyConfigChanged}
|
||||
></hui-section-strategy-element-editor>
|
||||
`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,12 +223,6 @@ export class HuiDialogEditSection
|
||||
.label=${this.hass!.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
<ha-dropdown-item value="toggle-yaml">
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
`ui.panel.lovelace.editor.edit_view.edit_${!this._yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item value="move-to-view">
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
@@ -207,12 +237,12 @@ export class HuiDialogEditSection
|
||||
!this._yamlMode
|
||||
? html`
|
||||
<ha-tab-group @wa-tab-show=${this._handleTabChanged}>
|
||||
${TABS.map(
|
||||
${tabs.map(
|
||||
(tab) => html`
|
||||
<ha-tab-group-tab
|
||||
slot="nav"
|
||||
.panel=${tab}
|
||||
.active=${this._currTab === tab}
|
||||
.active=${currentTab === tab}
|
||||
>
|
||||
${this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.edit_section.${tab.replace("-", "_")}`
|
||||
@@ -225,8 +255,21 @@ export class HuiDialogEditSection
|
||||
: nothing
|
||||
}
|
||||
</ha-dialog-header>
|
||||
${content}
|
||||
${this._yamlMode ? content : cache(content)}
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-button
|
||||
slot="secondaryAction"
|
||||
class="gui-mode-button"
|
||||
appearance="plain"
|
||||
@click=${this._toggleMode}
|
||||
.disabled=${this._yamlMode && this._yamlError}
|
||||
>
|
||||
${this.hass.localize(
|
||||
this._yamlMode
|
||||
? "ui.panel.lovelace.editor.edit_card.show_visual_editor"
|
||||
: "ui.panel.lovelace.editor.edit_card.show_code_editor"
|
||||
)}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
slot="secondaryAction"
|
||||
appearance="plain"
|
||||
@@ -238,7 +281,7 @@ export class HuiDialogEditSection
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._save}
|
||||
?disabled=${!this.isDirtyState}
|
||||
?disabled=${!this.isDirtyState || this._yamlError || this._strategyError}
|
||||
>
|
||||
${this.hass!.localize("ui.common.save")}
|
||||
</ha-button>
|
||||
@@ -247,12 +290,30 @@ export class HuiDialogEditSection
|
||||
`;
|
||||
}
|
||||
|
||||
private _configChanged(ev: CustomEvent): void {
|
||||
private _configChanged(
|
||||
ev: ValueChangedEvent<LovelaceSectionRawConfig>
|
||||
): void {
|
||||
ev.stopPropagation();
|
||||
this._config = ev.detail.value;
|
||||
this._updateDirtyState(this._config!);
|
||||
}
|
||||
|
||||
private _strategyConfigChanged(
|
||||
ev: HASSDomEvent<ConfigChangedEvent<LovelaceStrategyConfig>>
|
||||
): void {
|
||||
ev.stopPropagation();
|
||||
if (!this._config || !isStrategySection(this._config)) return;
|
||||
this._strategyError = Boolean(
|
||||
this._strategyEditor?.hasError ||
|
||||
ev.detail.error ||
|
||||
typeof ev.detail.config?.type !== "string" ||
|
||||
!ev.detail.config.type
|
||||
);
|
||||
if (this._strategyError) return;
|
||||
this._config = { ...this._config, strategy: ev.detail.config };
|
||||
this._updateDirtyState(this._config);
|
||||
}
|
||||
|
||||
private _handleTabChanged(ev: CustomEvent): void {
|
||||
const newTab = ev.detail.name;
|
||||
if (newTab === this._currTab) {
|
||||
@@ -264,15 +325,19 @@ export class HuiDialogEditSection
|
||||
private async _handleAction(ev: HaDropdownSelectEvent) {
|
||||
const value = ev.detail.item.value;
|
||||
switch (value) {
|
||||
case "toggle-yaml":
|
||||
this._yamlMode = !this._yamlMode;
|
||||
break;
|
||||
case "move-to-view":
|
||||
this._openSelectView();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private _toggleMode(): void {
|
||||
if (this._yamlMode && this._yamlError) return;
|
||||
withViewTransition(() => {
|
||||
this._yamlMode = !this._yamlMode;
|
||||
});
|
||||
}
|
||||
|
||||
private _openSelectView(): void {
|
||||
if (!this._params || !this.lovelace) {
|
||||
return;
|
||||
@@ -402,12 +467,21 @@ export class HuiDialogEditSection
|
||||
}
|
||||
};
|
||||
|
||||
private _viewYamlChanged(ev: CustomEvent) {
|
||||
private _viewYamlChanged(
|
||||
ev: ValueChangedEvent<LovelaceSectionRawConfig> &
|
||||
HASSDomEvent<{ isValid: boolean }>
|
||||
) {
|
||||
ev.stopPropagation();
|
||||
if (!ev.detail.isValid) {
|
||||
this._yamlError =
|
||||
!ev.detail.isValid ||
|
||||
!ev.detail.value ||
|
||||
typeof ev.detail.value !== "object" ||
|
||||
Array.isArray(ev.detail.value);
|
||||
if (this._yamlError) {
|
||||
return;
|
||||
}
|
||||
this._config = ev.detail.value;
|
||||
this._strategyError = false;
|
||||
this._updateDirtyState(this._config!);
|
||||
}
|
||||
|
||||
@@ -423,7 +497,12 @@ export class HuiDialogEditSection
|
||||
}
|
||||
|
||||
private async _save(): Promise<void> {
|
||||
if (!this._params || !this._config) {
|
||||
if (
|
||||
!this._params ||
|
||||
!this._config ||
|
||||
this._yamlError ||
|
||||
this._strategyError
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const newConfig = updateLovelaceContainer(
|
||||
@@ -448,6 +527,9 @@ export class HuiDialogEditSection
|
||||
ha-dialog.yaml-mode {
|
||||
--dialog-content-padding: 0;
|
||||
}
|
||||
.gui-mode-button {
|
||||
margin-inline-end: auto;
|
||||
}
|
||||
ha-tab-group-tab {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { css } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import type { LovelaceStrategyConfig } from "../../../../data/lovelace/config/strategy";
|
||||
import { getLovelaceStrategy } from "../../strategies/get-strategy";
|
||||
import type { LovelaceStrategyEditor } from "../../strategies/types";
|
||||
import { HuiTypedElementEditor } from "../hui-typed-element-editor";
|
||||
|
||||
@customElement("hui-section-strategy-element-editor")
|
||||
export class HuiSectionStrategyElementEditor extends HuiTypedElementEditor<LovelaceStrategyConfig> {
|
||||
static styles = [
|
||||
HuiTypedElementEditor.styles,
|
||||
css`
|
||||
.gui-editor,
|
||||
.yaml-editor {
|
||||
padding: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
protected async getConfigElement(): Promise<
|
||||
LovelaceStrategyEditor | undefined
|
||||
> {
|
||||
const strategy = await getLovelaceStrategy(
|
||||
"section",
|
||||
this.configElementType!
|
||||
);
|
||||
return strategy.getConfigElement?.();
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-section-strategy-element-editor": HuiSectionStrategyElementEditor;
|
||||
}
|
||||
}
|
||||
@@ -324,7 +324,7 @@ export class HuiSection extends ConditionalListenerMixin<LovelaceSectionConfig>(
|
||||
this._layoutElementType = config.type;
|
||||
this._layoutElement.addEventListener("ll-create-card", (ev) => {
|
||||
ev.stopPropagation();
|
||||
if (!this.lovelace) return;
|
||||
if (!this.lovelace || isStrategySection(this.config)) return;
|
||||
showCreateCardDialog(this, {
|
||||
lovelaceConfig: this.lovelace.config,
|
||||
saveConfig: this.lovelace.saveConfig,
|
||||
@@ -357,7 +357,7 @@ export class HuiSection extends ConditionalListenerMixin<LovelaceSectionConfig>(
|
||||
});
|
||||
this._layoutElement.addEventListener("ll-delete-card", (ev) => {
|
||||
ev.stopPropagation();
|
||||
if (!this.lovelace) return;
|
||||
if (!this.lovelace || isStrategySection(this.config)) return;
|
||||
performDeleteCard(this.hass, this.lovelace, ev.detail);
|
||||
});
|
||||
this._layoutElement.addEventListener("ll-duplicate-card", (ev) => {
|
||||
|
||||
@@ -17,7 +17,8 @@ export interface LovelaceStrategy<T = any> {
|
||||
newHass: HomeAssistant
|
||||
): boolean;
|
||||
registryDependencies?: readonly LovelaceStrategyDependency[];
|
||||
getConfigElement?: () => LovelaceStrategyEditor;
|
||||
getConfigElement?: () =>
|
||||
LovelaceStrategyEditor | Promise<LovelaceStrategyEditor>;
|
||||
noEditor?: boolean;
|
||||
configRequired?: boolean;
|
||||
}
|
||||
|
||||
+11
-1
@@ -7,7 +7,10 @@ import type { HomeAssistant } from "../../../../types";
|
||||
import type { HeadingCardConfig } from "../../cards/types";
|
||||
import type { Condition } from "../../common/validate-condition";
|
||||
import { computeFavoriteCardConfig } from "../helpers/favorite-cards";
|
||||
import type { LovelaceStrategyDependency } from "../types";
|
||||
import type {
|
||||
LovelaceStrategyDependency,
|
||||
LovelaceStrategyEditor,
|
||||
} from "../types";
|
||||
|
||||
const DEFAULT_LIMIT = 8;
|
||||
|
||||
@@ -30,6 +33,13 @@ export interface CommonControlsSectionStrategyConfig {
|
||||
export class CommonControlsSectionStrategy extends ReactiveElement {
|
||||
static registryDependencies: readonly LovelaceStrategyDependency[] = [];
|
||||
|
||||
public static async getConfigElement(): Promise<LovelaceStrategyEditor> {
|
||||
await import("./hui-common-controls-section-strategy-editor");
|
||||
return document.createElement(
|
||||
"hui-common-controls-section-strategy-editor"
|
||||
);
|
||||
}
|
||||
|
||||
static async generate(
|
||||
config: CommonControlsSectionStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
import { mdiFormatListBulleted } from "@mdi/js";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import { consumeLocalize } from "../../../../common/decorators/consume-context-entry";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import type {
|
||||
HaFormSchema,
|
||||
SchemaUnion,
|
||||
} from "../../../../components/ha-form/types";
|
||||
import type { ValueChangedEvent } from "../../../../types";
|
||||
import type { LovelaceStrategyEditor } from "../types";
|
||||
import type { CommonControlsSectionStrategyConfig } from "./common-controls-section-strategy";
|
||||
|
||||
const SCHEMA = [
|
||||
{
|
||||
name: "limit",
|
||||
selector: { number: { min: 1, mode: "box" } },
|
||||
},
|
||||
{
|
||||
name: "hide_empty",
|
||||
selector: { boolean: {} },
|
||||
},
|
||||
{
|
||||
name: "entities",
|
||||
type: "expandable",
|
||||
flatten: true,
|
||||
iconPath: mdiFormatListBulleted,
|
||||
schema: [
|
||||
{
|
||||
name: "include_entities",
|
||||
selector: { entity: { multiple: true, reorder: true } },
|
||||
},
|
||||
{
|
||||
name: "exclude_entities",
|
||||
selector: { entity: { multiple: true } },
|
||||
},
|
||||
],
|
||||
},
|
||||
] as const satisfies readonly HaFormSchema[];
|
||||
|
||||
@customElement("hui-common-controls-section-strategy-editor")
|
||||
export class HuiCommonControlsSectionStrategyEditor
|
||||
extends LitElement
|
||||
implements LovelaceStrategyEditor
|
||||
{
|
||||
@state() @consumeLocalize() private _localize!: LocalizeFunc;
|
||||
|
||||
@state() private _config?: CommonControlsSectionStrategyConfig;
|
||||
|
||||
public setConfig(config: CommonControlsSectionStrategyConfig): void {
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._config) return nothing;
|
||||
|
||||
return html`
|
||||
<ha-form
|
||||
.data=${this._config}
|
||||
.schema=${SCHEMA}
|
||||
.computeLabel=${this._computeLabel}
|
||||
.computeHelper=${this._computeHelper}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>
|
||||
`;
|
||||
}
|
||||
|
||||
private _computeLabel = (schema: SchemaUnion<typeof SCHEMA>) =>
|
||||
this._localize(
|
||||
`ui.panel.lovelace.editor.strategy.common_controls.${schema.name}`
|
||||
);
|
||||
|
||||
private _computeHelper = (schema: SchemaUnion<typeof SCHEMA>) =>
|
||||
schema.name === "entities" || schema.name === "hide_empty"
|
||||
? ""
|
||||
: this._localize(
|
||||
`ui.panel.lovelace.editor.strategy.common_controls.${schema.name}_helper`
|
||||
);
|
||||
|
||||
private _valueChanged(
|
||||
ev: ValueChangedEvent<CommonControlsSectionStrategyConfig>
|
||||
): void {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "config-changed", {
|
||||
config: { ...this._config, ...ev.detail.value },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-common-controls-section-strategy-editor": HuiCommonControlsSectionStrategyEditor;
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import "../../../components/ha-svg-icon";
|
||||
import { maxColumnsContext } from "../common/context";
|
||||
import type { LovelaceViewElement } from "../../../data/lovelace";
|
||||
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
||||
import { isStrategySection } from "../../../data/lovelace/config/section";
|
||||
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { HuiBadge } from "../badges/hui-badge";
|
||||
@@ -290,6 +291,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
.lovelace=${this.lovelace}
|
||||
.index=${idx}
|
||||
.viewIndex=${this.index}
|
||||
.isStrategy=${isStrategySection(section.config)}
|
||||
>
|
||||
${this._renderSection(
|
||||
section,
|
||||
|
||||
@@ -1939,8 +1939,6 @@
|
||||
"faq": "documentation",
|
||||
"editor": {
|
||||
"name": "Name",
|
||||
"use_device_name": "Use device name",
|
||||
"edit_device_name": "Edit device name",
|
||||
"icon": "Icon",
|
||||
"icon_error": "Icons should be in the format 'prefix:iconname', like 'mdi:home'",
|
||||
"default_code": "Default code",
|
||||
@@ -9771,6 +9769,8 @@
|
||||
"success": "View moved successfully"
|
||||
},
|
||||
"section": {
|
||||
"automatic": "Automatic section",
|
||||
"edit_automatic": "Edit automatic section",
|
||||
"add_badge": "Add badge",
|
||||
"add_card": "[%key:ui::panel::lovelace::editor::edit_card::add%]",
|
||||
"drop_card_create_section": "Drop card here to create a new section",
|
||||
@@ -9788,8 +9788,9 @@
|
||||
},
|
||||
"edit_section": {
|
||||
"header": "Edit section",
|
||||
"tab_strategy": "Configuration",
|
||||
"tab_visibility": "[%key:ui::panel::lovelace::editor::edit_view::tab_visibility%]",
|
||||
"tab_settings": "[%key:ui::panel::lovelace::editor::edit_view::tab_settings%]",
|
||||
"tab_appearance": "Appearance",
|
||||
"edit_ui": "[%key:ui::panel::lovelace::editor::edit_view::edit_ui%]",
|
||||
"edit_yaml": "[%key:ui::panel::lovelace::editor::edit_view::edit_yaml%]",
|
||||
"settings": {
|
||||
@@ -11127,6 +11128,16 @@
|
||||
}
|
||||
},
|
||||
"strategy": {
|
||||
"common_controls": {
|
||||
"limit": "Maximum number of cards",
|
||||
"limit_helper": "Defaults to 8 cards",
|
||||
"entities": "Entities",
|
||||
"include_entities": "Always include",
|
||||
"include_entities_helper": "Shown first, in the order you choose, within the card limit",
|
||||
"exclude_entities": "Exclude from suggestions",
|
||||
"exclude_entities_helper": "These entities will not be suggested based on your activity",
|
||||
"hide_empty": "Hide when empty"
|
||||
},
|
||||
"original-states": {
|
||||
"areas": "Areas to display",
|
||||
"hide_entities_without_area": "Hide entities without area",
|
||||
|
||||
@@ -8,7 +8,6 @@ import * as computeStateNameModule from "../../../src/common/entity/compute_stat
|
||||
import * as stripPrefixModule from "../../../src/common/entity/strip_prefix_from_entity_name";
|
||||
import type { HomeAssistant } from "../../../src/types";
|
||||
import {
|
||||
mockDevice,
|
||||
mockEntity,
|
||||
mockEntityEntry,
|
||||
mockStateObj,
|
||||
@@ -133,20 +132,6 @@ describe("computeEntityEntryName", () => {
|
||||
expect(computeEntityEntryName(entry, hass.devices)).toBe("Old Name");
|
||||
});
|
||||
|
||||
it("preserves an explicitly empty name instead of the integration name", () => {
|
||||
const entry = mockEntityEntry({
|
||||
device_id: "dev1",
|
||||
name: "",
|
||||
original_name: "Temperature",
|
||||
});
|
||||
const devices = { dev1: mockDevice({ id: "dev1", name: "Living room" }) };
|
||||
|
||||
expect(computeEntityEntryName(entry, devices)).toBe("");
|
||||
expect(computeEntityEntryName({ ...entry, name: null }, devices)).toBe(
|
||||
"Temperature"
|
||||
);
|
||||
});
|
||||
|
||||
it("returns undefined if no name, original_name, or device", () => {
|
||||
const entry = mockEntity({ entity_id: "light.kitchen" });
|
||||
const hass = {
|
||||
|
||||
Reference in New Issue
Block a user