mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 09:46:36 +00:00
Add visibility option to sections (conditional section) (#20805)
* Add first version of section visibility option * Move visibility logic into view * Simplify section view structure * Don't add hidden section to dom * Move visilibity logic to hui-section * Setup section editor * Add visibility view * Add basic settings editor * Improve visibility editor * Update conditional base * Feedbacks * Better typings
This commit is contained in:
parent
4cc5d2d04b
commit
a2a89502d8
@ -1,3 +1,5 @@
|
||||
export type MediaQueriesListener = () => void;
|
||||
|
||||
/**
|
||||
* Attach a media query. Listener is called right away and when it matches.
|
||||
* @param mediaQuery media query to match.
|
||||
@ -7,7 +9,7 @@
|
||||
export const listenMediaQuery = (
|
||||
mediaQuery: string,
|
||||
matchesChanged: (matches: boolean) => void
|
||||
) => {
|
||||
): MediaQueriesListener => {
|
||||
const mql = matchMedia(mediaQuery);
|
||||
const listener = (e) => matchesChanged(e.matches);
|
||||
mql.addListener(listener);
|
||||
|
@ -313,31 +313,38 @@ export class HaChartBase extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _loading = false;
|
||||
|
||||
private async _setupChart() {
|
||||
if (this._loading) return;
|
||||
const ctx: CanvasRenderingContext2D = this.renderRoot
|
||||
.querySelector("canvas")!
|
||||
.getContext("2d")!;
|
||||
this._loading = true;
|
||||
try {
|
||||
const ChartConstructor = (await import("../../resources/chartjs")).Chart;
|
||||
|
||||
const ChartConstructor = (await import("../../resources/chartjs")).Chart;
|
||||
const computedStyles = getComputedStyle(this);
|
||||
|
||||
const computedStyles = getComputedStyle(this);
|
||||
ChartConstructor.defaults.borderColor =
|
||||
computedStyles.getPropertyValue("--divider-color");
|
||||
ChartConstructor.defaults.color = computedStyles.getPropertyValue(
|
||||
"--secondary-text-color"
|
||||
);
|
||||
ChartConstructor.defaults.font.family =
|
||||
computedStyles.getPropertyValue("--mdc-typography-body1-font-family") ||
|
||||
computedStyles.getPropertyValue("--mdc-typography-font-family") ||
|
||||
"Roboto, Noto, sans-serif";
|
||||
|
||||
ChartConstructor.defaults.borderColor =
|
||||
computedStyles.getPropertyValue("--divider-color");
|
||||
ChartConstructor.defaults.color = computedStyles.getPropertyValue(
|
||||
"--secondary-text-color"
|
||||
);
|
||||
ChartConstructor.defaults.font.family =
|
||||
computedStyles.getPropertyValue("--mdc-typography-body1-font-family") ||
|
||||
computedStyles.getPropertyValue("--mdc-typography-font-family") ||
|
||||
"Roboto, Noto, sans-serif";
|
||||
|
||||
this.chart = new ChartConstructor(ctx, {
|
||||
type: this.chartType,
|
||||
data: this.data,
|
||||
options: this._createOptions(),
|
||||
plugins: this._createPlugins(),
|
||||
});
|
||||
this.chart = new ChartConstructor(ctx, {
|
||||
type: this.chartType,
|
||||
data: this.data,
|
||||
options: this._createOptions(),
|
||||
plugins: this._createPlugins(),
|
||||
});
|
||||
} finally {
|
||||
this._loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private _createOptions() {
|
||||
|
@ -178,16 +178,24 @@ export class HaMap extends ReactiveElement {
|
||||
map!.classList.toggle("forced-light", this.themeMode === "light");
|
||||
}
|
||||
|
||||
private _loading = false;
|
||||
|
||||
private async _loadMap(): Promise<void> {
|
||||
if (this._loading) return;
|
||||
let map = this.shadowRoot!.getElementById("map");
|
||||
if (!map) {
|
||||
map = document.createElement("div");
|
||||
map.id = "map";
|
||||
this.shadowRoot!.append(map);
|
||||
}
|
||||
[this.leafletMap, this.Leaflet] = await setupLeafletMap(map);
|
||||
this._updateMapStyle();
|
||||
this._loaded = true;
|
||||
this._loading = true;
|
||||
try {
|
||||
[this.leafletMap, this.Leaflet] = await setupLeafletMap(map);
|
||||
this._updateMapStyle();
|
||||
this._loaded = true;
|
||||
} finally {
|
||||
this._loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
public fitMap(options?: { zoom?: number; pad?: number }): void {
|
||||
|
@ -1,8 +1,10 @@
|
||||
import type { Condition } from "../../../panels/lovelace/common/validate-condition";
|
||||
import type { LovelaceCardConfig } from "./card";
|
||||
import type { LovelaceStrategyConfig } from "./strategy";
|
||||
|
||||
export interface LovelaceBaseSectionConfig {
|
||||
title?: string;
|
||||
visibility?: Condition[];
|
||||
}
|
||||
|
||||
export interface LovelaceSectionConfig extends LovelaceBaseSectionConfig {
|
||||
|
@ -1,4 +1,8 @@
|
||||
import { ensureArray } from "../../../common/array/ensure-array";
|
||||
import {
|
||||
MediaQueriesListener,
|
||||
listenMediaQuery,
|
||||
} from "../../../common/dom/media_query";
|
||||
import { isValidEntityId } from "../../../common/entity/valid_entity_id";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
@ -308,3 +312,45 @@ export function addEntityToCondition(
|
||||
}
|
||||
return condition;
|
||||
}
|
||||
|
||||
export function extractMediaQueries(conditions: Condition[]): string[] {
|
||||
return conditions.reduce<string[]>((array, c) => {
|
||||
if ("conditions" in c && c.conditions) {
|
||||
array.push(...extractMediaQueries(c.conditions));
|
||||
}
|
||||
if (c.condition === "screen" && c.media_query) {
|
||||
array.push(c.media_query);
|
||||
}
|
||||
return array;
|
||||
}, []);
|
||||
}
|
||||
|
||||
export function attachConditionMediaQueriesListeners(
|
||||
conditions: Condition[],
|
||||
hass: HomeAssistant,
|
||||
onChange: (visibility: boolean) => void
|
||||
): MediaQueriesListener[] {
|
||||
// For performance, if there is only one condition and it's a screen condition, set the visibility directly
|
||||
if (
|
||||
conditions.length === 1 &&
|
||||
conditions[0].condition === "screen" &&
|
||||
conditions[0].media_query
|
||||
) {
|
||||
const listener = listenMediaQuery(conditions[0].media_query, (matches) => {
|
||||
onChange(matches);
|
||||
});
|
||||
return [listener];
|
||||
}
|
||||
|
||||
const mediaQueries = extractMediaQueries(conditions);
|
||||
|
||||
const listeners = mediaQueries.map((query) => {
|
||||
const listener = listenMediaQuery(query, () => {
|
||||
const visibility = checkConditionsMet(conditions, hass);
|
||||
onChange(visibility);
|
||||
});
|
||||
return listener;
|
||||
});
|
||||
|
||||
return listeners;
|
||||
}
|
||||
|
@ -1,32 +1,19 @@
|
||||
import { PropertyValues, ReactiveElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { listenMediaQuery } from "../../../common/dom/media_query";
|
||||
import { MediaQueriesListener } from "../../../common/dom/media_query";
|
||||
import { deepEqual } from "../../../common/util/deep-equal";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { ConditionalCardConfig } from "../cards/types";
|
||||
import {
|
||||
Condition,
|
||||
LegacyCondition,
|
||||
checkConditionsMet,
|
||||
attachConditionMediaQueriesListeners,
|
||||
extractMediaQueries,
|
||||
validateConditionalConfig,
|
||||
} from "../common/validate-condition";
|
||||
import { ConditionalRowConfig, LovelaceRow } from "../entity-rows/types";
|
||||
import { LovelaceCard } from "../types";
|
||||
|
||||
function extractMediaQueries(
|
||||
conditions: (Condition | LegacyCondition)[]
|
||||
): string[] {
|
||||
return conditions.reduce<string[]>((array, c) => {
|
||||
if ("conditions" in c && c.conditions) {
|
||||
array.push(...extractMediaQueries(c.conditions));
|
||||
}
|
||||
if ("condition" in c && c.condition === "screen" && c.media_query) {
|
||||
array.push(c.media_query);
|
||||
}
|
||||
return array;
|
||||
}, []);
|
||||
}
|
||||
|
||||
@customElement("hui-conditional-base")
|
||||
export class HuiConditionalBase extends ReactiveElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
@ -37,7 +24,7 @@ export class HuiConditionalBase extends ReactiveElement {
|
||||
|
||||
protected _element?: LovelaceCard | LovelaceRow;
|
||||
|
||||
private _mediaQueriesListeners: Array<() => void> = [];
|
||||
private _listeners: MediaQueriesListener[] = [];
|
||||
|
||||
private _mediaQueries: string[] = [];
|
||||
|
||||
@ -79,41 +66,31 @@ export class HuiConditionalBase extends ReactiveElement {
|
||||
}
|
||||
|
||||
private _clearMediaQueries() {
|
||||
this._mediaQueries = [];
|
||||
while (this._mediaQueriesListeners.length) {
|
||||
this._mediaQueriesListeners.pop()!();
|
||||
}
|
||||
this._listeners.forEach((unsub) => unsub());
|
||||
this._listeners = [];
|
||||
}
|
||||
|
||||
private _listenMediaQueries() {
|
||||
if (!this._config) {
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mediaQueries = extractMediaQueries(this._config.conditions);
|
||||
const supportedConditions = this._config.conditions.filter(
|
||||
(c) => "condition" in c
|
||||
) as Condition[];
|
||||
const mediaQueries = extractMediaQueries(supportedConditions);
|
||||
|
||||
if (deepEqual(mediaQueries, this._mediaQueries)) return;
|
||||
|
||||
this._mediaQueries = mediaQueries;
|
||||
while (this._mediaQueriesListeners.length) {
|
||||
this._mediaQueriesListeners.pop()!();
|
||||
}
|
||||
this._clearMediaQueries();
|
||||
|
||||
mediaQueries.forEach((query) => {
|
||||
const listener = listenMediaQuery(query, (matches) => {
|
||||
// For performance, if there is only one condition and it's a screen condition, set the visibility directly
|
||||
if (
|
||||
this._config!.conditions.length === 1 &&
|
||||
"condition" in this._config!.conditions[0] &&
|
||||
this._config!.conditions[0].condition === "screen"
|
||||
) {
|
||||
this._setVisibility(matches);
|
||||
return;
|
||||
}
|
||||
this._updateVisibility();
|
||||
});
|
||||
this._mediaQueriesListeners.push(listener);
|
||||
});
|
||||
this._listeners = attachConditionMediaQueriesListeners(
|
||||
supportedConditions,
|
||||
this.hass,
|
||||
(visibility) => {
|
||||
this._setVisibility(visibility);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected update(changed: PropertyValues): void {
|
||||
|
@ -24,15 +24,15 @@ import {
|
||||
computeCards,
|
||||
computeSection,
|
||||
} from "../../common/generate-lovelace-config";
|
||||
import {
|
||||
findLovelaceContainer,
|
||||
parseLovelaceContainerPath,
|
||||
} from "../lovelace-path";
|
||||
import "./hui-card-picker";
|
||||
import "./hui-entity-picker-table";
|
||||
import { CreateCardDialogParams } from "./show-create-card-dialog";
|
||||
import { showEditCardDialog } from "./show-edit-card-dialog";
|
||||
import { showSuggestCardDialog } from "./show-suggest-card-dialog";
|
||||
import {
|
||||
findLovelaceContainer,
|
||||
parseLovelaceContainerPath,
|
||||
} from "../lovelace-path";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
@ -274,15 +274,17 @@ export class HuiCreateDialogCard
|
||||
|
||||
let sectionOptions: Partial<LovelaceSectionConfig> = {};
|
||||
|
||||
const { sectionIndex } = parseLovelaceContainerPath(this._params!.path);
|
||||
const { viewIndex, sectionIndex } = parseLovelaceContainerPath(
|
||||
this._params!.path
|
||||
);
|
||||
const isSection = sectionIndex !== undefined;
|
||||
|
||||
// If we are in a section, we want to keep the section options for the preview
|
||||
if (isSection) {
|
||||
const containerConfig = findLovelaceContainer(
|
||||
this._params!.lovelaceConfig!,
|
||||
this._params!.path!
|
||||
) as LovelaceSectionConfig;
|
||||
[viewIndex, sectionIndex]
|
||||
);
|
||||
if (!isStrategySection(containerConfig)) {
|
||||
const { cards, title, ...rest } = containerConfig;
|
||||
sectionOptions = rest;
|
||||
|
@ -1,16 +1,8 @@
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
import {
|
||||
CSSResultGroup,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
css,
|
||||
html,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { CSSResultGroup, LitElement, PropertyValues, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||
import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-list-item";
|
||||
import type { HaSelect } from "../../../../components/ha-select";
|
||||
@ -21,12 +13,12 @@ import { Condition, LegacyCondition } from "../../common/validate-condition";
|
||||
import "./ha-card-condition-editor";
|
||||
import type { HaCardConditionEditor } from "./ha-card-condition-editor";
|
||||
import { LovelaceConditionEditorConstructor } from "./types";
|
||||
import "./types/ha-card-condition-and";
|
||||
import "./types/ha-card-condition-numeric_state";
|
||||
import "./types/ha-card-condition-or";
|
||||
import "./types/ha-card-condition-screen";
|
||||
import "./types/ha-card-condition-state";
|
||||
import "./types/ha-card-condition-user";
|
||||
import "./types/ha-card-condition-or";
|
||||
import "./types/ha-card-condition-and";
|
||||
|
||||
const UI_CONDITION = [
|
||||
"numeric_state",
|
||||
@ -46,8 +38,6 @@ export class HaCardConditionsEditor extends LitElement {
|
||||
| LegacyCondition
|
||||
)[];
|
||||
|
||||
@property({ type: Boolean }) public nested = false;
|
||||
|
||||
private _focusLastConditionOnChange = false;
|
||||
|
||||
protected firstUpdated() {
|
||||
@ -83,15 +73,6 @@ export class HaCardConditionsEditor extends LitElement {
|
||||
protected render() {
|
||||
return html`
|
||||
<div class="conditions">
|
||||
${!this.nested
|
||||
? html`
|
||||
<ha-alert alert-type="info">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.condition-editor.explanation"
|
||||
)}
|
||||
</ha-alert>
|
||||
`
|
||||
: nothing}
|
||||
${this.conditions.map(
|
||||
(cond, idx) => html`
|
||||
<ha-card-condition-editor
|
||||
@ -172,9 +153,6 @@ export class HaCardConditionsEditor extends LitElement {
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
css`
|
||||
mwc-tab-bar {
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
}
|
||||
ha-alert {
|
||||
display: block;
|
||||
margin-top: 12px;
|
||||
|
@ -8,6 +8,7 @@ import { customElement, property, query, state } from "lit/decorators";
|
||||
import { any, array, assert, assign, object, optional } from "superstruct";
|
||||
import { storage } from "../../../../common/decorators/storage";
|
||||
import { HASSDomEvent, fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-list-item";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
@ -142,6 +143,11 @@ export class HuiConditionalCardEditor
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<ha-alert alert-type="info">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.condition-editor.explanation"
|
||||
)}
|
||||
</ha-alert>
|
||||
<ha-card-conditions-editor
|
||||
.hass=${this.hass}
|
||||
.conditions=${this._config.conditions}
|
||||
@ -231,6 +237,10 @@ export class HuiConditionalCardEditor
|
||||
mwc-tab-bar {
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
}
|
||||
ha-alert {
|
||||
display: block;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.card {
|
||||
margin-top: 8px;
|
||||
border: 1px solid var(--divider-color);
|
||||
|
@ -226,7 +226,7 @@ export const addSection = (
|
||||
viewIndex: number,
|
||||
sectionConfig: LovelaceSectionRawConfig
|
||||
): LovelaceConfig => {
|
||||
const view = findLovelaceContainer(config, [viewIndex]) as LovelaceViewConfig;
|
||||
const view = findLovelaceContainer(config, [viewIndex]);
|
||||
if (isStrategyView(view)) {
|
||||
throw new Error("Deleting sections in a strategy is not supported.");
|
||||
}
|
||||
@ -246,7 +246,7 @@ export const deleteSection = (
|
||||
viewIndex: number,
|
||||
sectionIndex: number
|
||||
): LovelaceConfig => {
|
||||
const view = findLovelaceContainer(config, [viewIndex]) as LovelaceViewConfig;
|
||||
const view = findLovelaceContainer(config, [viewIndex]);
|
||||
if (isStrategyView(view)) {
|
||||
throw new Error("Deleting sections in a strategy is not supported.");
|
||||
}
|
||||
@ -267,7 +267,7 @@ export const insertSection = (
|
||||
sectionIndex: number,
|
||||
sectionConfig: LovelaceSectionRawConfig
|
||||
): LovelaceConfig => {
|
||||
const view = findLovelaceContainer(config, [viewIndex]) as LovelaceViewConfig;
|
||||
const view = findLovelaceContainer(config, [viewIndex]);
|
||||
if (isStrategyView(view)) {
|
||||
throw new Error("Inserting sections in a strategy is not supported.");
|
||||
}
|
||||
@ -291,10 +291,7 @@ export const moveSection = (
|
||||
fromPath: [number, number],
|
||||
toPath: [number, number]
|
||||
): LovelaceConfig => {
|
||||
const section = findLovelaceContainer(
|
||||
config,
|
||||
fromPath
|
||||
) as LovelaceSectionRawConfig;
|
||||
const section = findLovelaceContainer(config, fromPath);
|
||||
|
||||
let newConfig = deleteSection(config, fromPath[0], fromPath[1]);
|
||||
newConfig = insertSection(newConfig, toPath[0], toPath[1], section);
|
||||
|
@ -46,7 +46,15 @@ export const getLovelaceContainerPath = (
|
||||
path: LovelaceCardPath
|
||||
): LovelaceContainerPath => path.slice(0, -1) as LovelaceContainerPath;
|
||||
|
||||
export const findLovelaceContainer = (
|
||||
type FindLovelaceContainer = {
|
||||
(config: LovelaceConfig, path: [number]): LovelaceViewRawConfig;
|
||||
(config: LovelaceConfig, path: [number, number]): LovelaceSectionRawConfig;
|
||||
(
|
||||
config: LovelaceConfig,
|
||||
path: LovelaceContainerPath
|
||||
): LovelaceViewRawConfig | LovelaceSectionRawConfig;
|
||||
};
|
||||
export const findLovelaceContainer: FindLovelaceContainer = (
|
||||
config: LovelaceConfig,
|
||||
path: LovelaceContainerPath
|
||||
): LovelaceViewRawConfig | LovelaceSectionRawConfig => {
|
||||
|
@ -0,0 +1,319 @@
|
||||
import { ActionDetail } from "@material/mwc-list";
|
||||
import { mdiCheck, mdiClose, mdiDotsVertical } from "@mdi/js";
|
||||
import "@polymer/paper-tabs/paper-tab";
|
||||
import "@polymer/paper-tabs/paper-tabs";
|
||||
import {
|
||||
CSSResultGroup,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
css,
|
||||
html,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-button-menu";
|
||||
import "../../../../components/ha-circular-progress";
|
||||
import "../../../../components/ha-dialog";
|
||||
import "../../../../components/ha-dialog-header";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-list-item";
|
||||
import "../../../../components/ha-yaml-editor";
|
||||
import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
|
||||
import { LovelaceSectionRawConfig } from "../../../../data/lovelace/config/section";
|
||||
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
||||
import { haStyleDialog } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import {
|
||||
findLovelaceContainer,
|
||||
updateLovelaceContainer,
|
||||
} from "../lovelace-path";
|
||||
import "./hui-section-settings-editor";
|
||||
import "./hui-section-visibility-editor";
|
||||
import type { EditSectionDialogParams } from "./show-edit-section-dialog";
|
||||
|
||||
const TABS = ["tab-settings", "tab-visibility"] as const;
|
||||
|
||||
@customElement("hui-dialog-edit-section")
|
||||
export class HuiDialogEditSection
|
||||
extends LitElement
|
||||
implements HassDialog<EditSectionDialogParams>
|
||||
{
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _params?: EditSectionDialogParams;
|
||||
|
||||
@state() private _config?: LovelaceSectionRawConfig;
|
||||
|
||||
@state() private _yamlMode = false;
|
||||
|
||||
@state() private _curTab: (typeof TABS)[number] = TABS[0];
|
||||
|
||||
@query("ha-yaml-editor") private _editor?: HaYamlEditor;
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
if (this._yamlMode && changedProperties.has("_yamlMode")) {
|
||||
const viewConfig = {
|
||||
...this._config,
|
||||
};
|
||||
this._editor?.setValue(viewConfig);
|
||||
}
|
||||
}
|
||||
|
||||
public async showDialog(params: EditSectionDialogParams): Promise<void> {
|
||||
this._params = params;
|
||||
|
||||
this._config = findLovelaceContainer(this._params.lovelaceConfig, [
|
||||
this._params.viewIndex,
|
||||
this._params.sectionIndex,
|
||||
]);
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
this._params = undefined;
|
||||
this._yamlMode = false;
|
||||
this._config = undefined;
|
||||
this._curTab = TABS[0];
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._params || !this._config) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const heading = this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_section.header"
|
||||
);
|
||||
|
||||
let content: TemplateResult<1> | typeof nothing = nothing;
|
||||
|
||||
if (this._yamlMode) {
|
||||
content = html`
|
||||
<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
dialogInitialFocus
|
||||
@value-changed=${this._viewYamlChanged}
|
||||
></ha-yaml-editor>
|
||||
`;
|
||||
} else {
|
||||
switch (this._curTab) {
|
||||
case "tab-settings":
|
||||
content = html`
|
||||
<hui-section-settings-editor
|
||||
.hass=${this.hass}
|
||||
.config=${this._config}
|
||||
@value-changed=${this._configChanged}
|
||||
>
|
||||
</hui-section-settings-editor>
|
||||
`;
|
||||
break;
|
||||
case "tab-visibility":
|
||||
content = html`
|
||||
<hui-section-visibility-editor
|
||||
.hass=${this.hass}
|
||||
.config=${this._config}
|
||||
@value-changed=${this._configChanged}
|
||||
>
|
||||
</hui-section-visibility-editor>
|
||||
`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
scrimClickAction
|
||||
@keydown=${this._ignoreKeydown}
|
||||
@closed=${this._cancel}
|
||||
.heading=${heading}
|
||||
class=${classMap({
|
||||
"yaml-mode": this._yamlMode,
|
||||
})}
|
||||
>
|
||||
<ha-dialog-header show-border slot="heading">
|
||||
<ha-icon-button
|
||||
slot="navigationIcon"
|
||||
dialogAction="cancel"
|
||||
.label=${this.hass.localize("ui.common.close")}
|
||||
.path=${mdiClose}
|
||||
></ha-icon-button>
|
||||
<span slot="title">${heading}</span>
|
||||
<ha-button-menu
|
||||
slot="actionItems"
|
||||
fixed
|
||||
corner="BOTTOM_END"
|
||||
menuCorner="END"
|
||||
@closed=${stopPropagation}
|
||||
@action=${this._handleAction}
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass!.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
<ha-list-item graphic="icon">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_section.edit_ui"
|
||||
)}
|
||||
${!this._yamlMode
|
||||
? html`<ha-svg-icon
|
||||
class="selected_menu_item"
|
||||
slot="graphic"
|
||||
.path=${mdiCheck}
|
||||
></ha-svg-icon>`
|
||||
: ``}
|
||||
</ha-list-item>
|
||||
|
||||
<ha-list-item graphic="icon">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_section.edit_yaml"
|
||||
)}
|
||||
${this._yamlMode
|
||||
? html`<ha-svg-icon
|
||||
class="selected_menu_item"
|
||||
slot="graphic"
|
||||
.path=${mdiCheck}
|
||||
></ha-svg-icon>`
|
||||
: ``}
|
||||
</ha-list-item>
|
||||
</ha-button-menu>
|
||||
${!this._yamlMode
|
||||
? html`
|
||||
<paper-tabs
|
||||
scrollable
|
||||
hide-scroll-buttons
|
||||
.selected=${TABS.indexOf(this._curTab)}
|
||||
@selected-item-changed=${this._handleTabSelected}
|
||||
>
|
||||
${TABS.map(
|
||||
(tab, index) => html`
|
||||
<paper-tab id=${tab} .dialogInitialFocus=${index === 0}>
|
||||
${this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.edit_section.${tab.replace("-", "_")}`
|
||||
)}
|
||||
</paper-tab>
|
||||
`
|
||||
)}
|
||||
</paper-tabs>
|
||||
`
|
||||
: nothing}
|
||||
</ha-dialog-header>
|
||||
${content}
|
||||
<ha-button slot="secondaryAction">
|
||||
${this.hass!.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
|
||||
<ha-button slot="primaryAction" @click=${this._save}>
|
||||
${this.hass!.localize("ui.common.save")}
|
||||
</ha-button>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _configChanged(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
this._config = ev.detail.value;
|
||||
}
|
||||
|
||||
private _handleTabSelected(ev: CustomEvent): void {
|
||||
if (!ev.detail.value) {
|
||||
return;
|
||||
}
|
||||
this._curTab = ev.detail.value.id;
|
||||
}
|
||||
|
||||
private async _handleAction(ev: CustomEvent<ActionDetail>) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
this._yamlMode = false;
|
||||
break;
|
||||
case 1:
|
||||
this._yamlMode = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private _viewYamlChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
if (!ev.detail.isValid) {
|
||||
return;
|
||||
}
|
||||
this._config = ev.detail.value;
|
||||
}
|
||||
|
||||
private _ignoreKeydown(ev: KeyboardEvent) {
|
||||
ev.stopPropagation();
|
||||
}
|
||||
|
||||
private _cancel(ev?: Event) {
|
||||
if (ev) {
|
||||
ev.stopPropagation();
|
||||
}
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
private async _save(): Promise<void> {
|
||||
if (!this._params || !this._config) {
|
||||
return;
|
||||
}
|
||||
const newConfig = updateLovelaceContainer(
|
||||
this._params.lovelaceConfig,
|
||||
[this._params.viewIndex, this._params.sectionIndex],
|
||||
this._config
|
||||
);
|
||||
|
||||
this._params.saveConfig(newConfig);
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-dialog {
|
||||
/* Set the top top of the dialog to a fixed position, so it doesnt jump when the content changes size */
|
||||
--vertical-align-dialog: flex-start;
|
||||
--dialog-surface-margin-top: 40px;
|
||||
}
|
||||
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
/* When in fullscreen dialog should be attached to top */
|
||||
ha-dialog {
|
||||
--dialog-surface-margin-top: 0px;
|
||||
}
|
||||
}
|
||||
ha-dialog.yaml-mode {
|
||||
--dialog-content-padding: 0;
|
||||
}
|
||||
paper-tabs {
|
||||
--paper-tabs-selection-bar-color: var(--primary-color);
|
||||
color: var(--primary-text-color);
|
||||
text-transform: uppercase;
|
||||
padding: 0 20px;
|
||||
}
|
||||
.selected_menu_item {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
@media all and (min-width: 600px) {
|
||||
ha-dialog {
|
||||
--mdc-dialog-min-width: 600px;
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-dialog-edit-section": HuiDialogEditSection;
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { LovelaceSectionRawConfig } from "../../../../data/lovelace/config/section";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import {
|
||||
HaFormSchema,
|
||||
SchemaUnion,
|
||||
} from "../../../../components/ha-form/types";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
|
||||
const SCHEMA = [
|
||||
{
|
||||
name: "title",
|
||||
selector: { text: {} },
|
||||
},
|
||||
] as const satisfies HaFormSchema[];
|
||||
|
||||
type SettingsData = {
|
||||
title: string;
|
||||
};
|
||||
|
||||
@customElement("hui-section-settings-editor")
|
||||
export class HuiDialogEditSection extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public config!: LovelaceSectionRawConfig;
|
||||
|
||||
render() {
|
||||
const data: SettingsData = {
|
||||
title: this.config.title || "",
|
||||
};
|
||||
|
||||
return html`
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${data}
|
||||
.schema=${SCHEMA}
|
||||
.computeLabel=${this._computeLabel}
|
||||
.computeHelper=${this._computeHelper}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>
|
||||
`;
|
||||
}
|
||||
|
||||
private _computeLabel = (schema: SchemaUnion<typeof SCHEMA>) =>
|
||||
this.hass.localize(
|
||||
`ui.panel.lovelace.editor.edit_section.settings.${schema.name}`
|
||||
);
|
||||
|
||||
private _computeHelper = (schema: SchemaUnion<typeof SCHEMA>) =>
|
||||
this.hass.localize(
|
||||
`ui.panel.lovelace.editor.edit_section.settings.${schema.name}_helper`
|
||||
) || "";
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const newData = ev.detail.value as SettingsData;
|
||||
|
||||
const newConfig: LovelaceSectionRawConfig = {
|
||||
...this.config,
|
||||
title: newData.title,
|
||||
};
|
||||
|
||||
if (!newConfig.title) {
|
||||
delete newConfig.title;
|
||||
}
|
||||
|
||||
fireEvent(this, "value-changed", { value: newConfig });
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-section-settings-editor": HuiDialogEditSection;
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-alert";
|
||||
import { LovelaceSectionRawConfig } from "../../../../data/lovelace/config/section";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { Condition } from "../../common/validate-condition";
|
||||
import "../conditions/ha-card-conditions-editor";
|
||||
|
||||
@customElement("hui-section-visibility-editor")
|
||||
export class HuiDialogEditSection extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public config!: LovelaceSectionRawConfig;
|
||||
|
||||
render() {
|
||||
const conditions = this.config.visibility ?? [];
|
||||
return html`
|
||||
<ha-alert alert-type="info">
|
||||
${this.hass.localize(
|
||||
`ui.panel.lovelace.editor.edit_section.visibility.explanation`
|
||||
)}
|
||||
</ha-alert>
|
||||
<ha-card-conditions-editor
|
||||
.hass=${this.hass}
|
||||
.conditions=${conditions}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-card-conditions-editor>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
const conditions = ev.detail.value as Condition[];
|
||||
const newConfig: LovelaceSectionRawConfig = {
|
||||
...this.config,
|
||||
visibility: conditions,
|
||||
};
|
||||
if (newConfig.visibility?.length === 0) {
|
||||
delete newConfig.visibility;
|
||||
}
|
||||
fireEvent(this, "value-changed", { value: newConfig });
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-section-visibility-editor": HuiDialogEditSection;
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { LovelaceConfig } from "../../../../data/lovelace/config/types";
|
||||
|
||||
export type EditSectionDialogParams = {
|
||||
lovelaceConfig: LovelaceConfig;
|
||||
saveConfig: (config: LovelaceConfig) => void;
|
||||
viewIndex: number;
|
||||
sectionIndex: number;
|
||||
};
|
||||
|
||||
const importEditSectionDialog = () => import("./hui-dialog-edit-section");
|
||||
|
||||
export const showEditSectionDialog = (
|
||||
element: HTMLElement,
|
||||
editSectionDialogParams: EditSectionDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "hui-dialog-edit-section",
|
||||
dialogImport: importEditSectionDialog,
|
||||
dialogParams: editSectionDialogParams,
|
||||
});
|
||||
};
|
@ -1,5 +1,6 @@
|
||||
import { PropertyValues, ReactiveElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { MediaQueriesListener } from "../../../common/dom/media_query";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import type { LovelaceSectionElement } from "../../../data/lovelace";
|
||||
import { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
||||
@ -10,6 +11,10 @@ import {
|
||||
} from "../../../data/lovelace/config/section";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { HuiErrorCard } from "../cards/hui-error-card";
|
||||
import {
|
||||
checkConditionsMet,
|
||||
attachConditionMediaQueriesListeners,
|
||||
} from "../common/validate-condition";
|
||||
import { createCardElement } from "../create-element/create-card-element";
|
||||
import {
|
||||
createErrorCardConfig,
|
||||
@ -43,6 +48,8 @@ export class HuiSection extends ReactiveElement {
|
||||
|
||||
private _layoutElement?: LovelaceSectionElement;
|
||||
|
||||
private _listeners: MediaQueriesListener[] = [];
|
||||
|
||||
// Public to make demo happy
|
||||
public createCardElement(cardConfig: LovelaceCardConfig) {
|
||||
const element = createCardElement(cardConfig) as LovelaceCard;
|
||||
@ -95,6 +102,17 @@ export class HuiSection extends ReactiveElement {
|
||||
}
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._clearMediaQueries();
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._listenMediaQueries();
|
||||
this._updateElement();
|
||||
}
|
||||
|
||||
protected update(changedProperties) {
|
||||
super.update(changedProperties);
|
||||
|
||||
@ -109,7 +127,6 @@ export class HuiSection extends ReactiveElement {
|
||||
this._rebuildCard(element, createErrorCardConfig(e.message, null));
|
||||
}
|
||||
});
|
||||
|
||||
this._layoutElement.hass = this.hass;
|
||||
}
|
||||
if (changedProperties.has("lovelace")) {
|
||||
@ -118,9 +135,32 @@ export class HuiSection extends ReactiveElement {
|
||||
if (changedProperties.has("_cards")) {
|
||||
this._layoutElement.cards = this._cards;
|
||||
}
|
||||
if (changedProperties.has("hass") || changedProperties.has("lovelace")) {
|
||||
this._updateElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _clearMediaQueries() {
|
||||
this._listeners.forEach((unsub) => unsub());
|
||||
this._listeners = [];
|
||||
}
|
||||
|
||||
private _listenMediaQueries() {
|
||||
if (!this.config.visibility) {
|
||||
return;
|
||||
}
|
||||
this._clearMediaQueries();
|
||||
this._listeners = attachConditionMediaQueriesListeners(
|
||||
this.config.visibility,
|
||||
this.hass,
|
||||
(visibility) => {
|
||||
const visible = visibility || this.lovelace!.editMode;
|
||||
this._updateElement(visible);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private async _initializeConfig() {
|
||||
let sectionConfig = { ...this.config };
|
||||
let isStrategy = false;
|
||||
@ -161,7 +201,26 @@ export class HuiSection extends ReactiveElement {
|
||||
while (this.lastChild) {
|
||||
this.removeChild(this.lastChild);
|
||||
}
|
||||
this.appendChild(this._layoutElement!);
|
||||
this._updateElement();
|
||||
}
|
||||
}
|
||||
|
||||
private _updateElement(forceVisible?: boolean) {
|
||||
if (!this._layoutElement) {
|
||||
return;
|
||||
}
|
||||
const visible =
|
||||
forceVisible ??
|
||||
(this.lovelace.editMode ||
|
||||
!this.config.visibility ||
|
||||
checkConditionsMet(this.config.visibility, this.hass));
|
||||
|
||||
this.style.setProperty("display", visible ? "" : "none");
|
||||
this.toggleAttribute("hidden", !visible);
|
||||
if (!visible && this._layoutElement.parentElement) {
|
||||
this.removeChild(this._layoutElement);
|
||||
} else if (visible && !this._layoutElement.parentElement) {
|
||||
this.appendChild(this._layoutElement);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,12 @@
|
||||
import { mdiArrowAll, mdiDelete, mdiPencil, mdiViewGridPlus } from "@mdi/js";
|
||||
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||
import {
|
||||
CSSResultGroup,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
css,
|
||||
html,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
@ -7,18 +14,12 @@ import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-sortable";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import type { LovelaceViewElement } from "../../../data/lovelace";
|
||||
import { LovelaceSectionConfig as LovelaceRawSectionConfig } from "../../../data/lovelace/config/section";
|
||||
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||
import {
|
||||
showConfirmationDialog,
|
||||
showPromptDialog,
|
||||
} from "../../../dialogs/generic/show-dialog-box";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { addSection, deleteSection, moveSection } from "../editor/config-util";
|
||||
import {
|
||||
findLovelaceContainer,
|
||||
updateLovelaceContainer,
|
||||
} from "../editor/lovelace-path";
|
||||
import { findLovelaceContainer } from "../editor/lovelace-path";
|
||||
import { showEditSectionDialog } from "../editor/section-editor/show-edit-section-dialog";
|
||||
import { HuiSection } from "../sections/hui-section";
|
||||
import type { Lovelace, LovelaceBadge } from "../types";
|
||||
|
||||
@ -38,27 +39,54 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
|
||||
@state() private _config?: LovelaceViewConfig;
|
||||
|
||||
@state() private _sectionCount = 0;
|
||||
|
||||
public setConfig(config: LovelaceViewConfig): void {
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
private _sectionConfigKeys = new WeakMap<LovelaceRawSectionConfig, string>();
|
||||
private _sectionConfigKeys = new WeakMap<HuiSection, string>();
|
||||
|
||||
private _getKey(sectionConfig: LovelaceRawSectionConfig) {
|
||||
private _getKey(sectionConfig: HuiSection) {
|
||||
if (!this._sectionConfigKeys.has(sectionConfig)) {
|
||||
this._sectionConfigKeys.set(sectionConfig, Math.random().toString());
|
||||
}
|
||||
return this._sectionConfigKeys.get(sectionConfig)!;
|
||||
}
|
||||
|
||||
private _sectionObserver?: MutationObserver;
|
||||
|
||||
private _computeSectionsCount() {
|
||||
this._sectionCount = this.sections.filter(
|
||||
(section) => !section.hidden
|
||||
).length;
|
||||
}
|
||||
|
||||
willUpdate(changedProperties: PropertyValues<typeof this>): void {
|
||||
if (!this._sectionObserver) {
|
||||
this._sectionObserver = new MutationObserver(() => {
|
||||
this._computeSectionsCount();
|
||||
});
|
||||
}
|
||||
if (changedProperties.has("sections")) {
|
||||
this._computeSectionsCount();
|
||||
this._sectionObserver.disconnect();
|
||||
this.sections.forEach((section) => {
|
||||
this._sectionObserver!.observe(section, {
|
||||
attributes: true,
|
||||
attributeFilter: ["hidden"],
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.lovelace) return nothing;
|
||||
|
||||
const sectionsConfig = this._config?.sections ?? [];
|
||||
|
||||
const sections = this.sections;
|
||||
const totalCount = this._sectionCount + (this.lovelace?.editMode ? 1 : 0);
|
||||
const editMode = this.lovelace.editMode;
|
||||
|
||||
const sectionCount = sectionsConfig.length + (editMode ? 1 : 0);
|
||||
const maxColumnsCount = this._config?.max_columns;
|
||||
|
||||
return html`
|
||||
@ -77,14 +105,13 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
class="container"
|
||||
style=${styleMap({
|
||||
"--max-columns-count": maxColumnsCount,
|
||||
"--total-count": sectionCount,
|
||||
"--total-count": totalCount,
|
||||
})}
|
||||
>
|
||||
${repeat(
|
||||
sectionsConfig,
|
||||
(sectionConfig) => this._getKey(sectionConfig),
|
||||
(_sectionConfig, idx) => {
|
||||
const section = this.sections[idx];
|
||||
sections,
|
||||
(section) => this._getKey(section),
|
||||
(section, idx) => {
|
||||
(section as any).itemPath = [idx];
|
||||
return html`
|
||||
<div class="section">
|
||||
@ -113,7 +140,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
<div class="section-wrapper">${section}</div>
|
||||
${section}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@ -150,39 +177,14 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
private async _editSection(ev) {
|
||||
const index = ev.currentTarget.index;
|
||||
|
||||
const path = [this.index!, index] as [number, number];
|
||||
|
||||
const section = findLovelaceContainer(
|
||||
this.lovelace!.config,
|
||||
path
|
||||
) as LovelaceRawSectionConfig;
|
||||
|
||||
const newTitle = !section.title;
|
||||
|
||||
const title = await showPromptDialog(this, {
|
||||
title: this.hass.localize(
|
||||
`ui.panel.lovelace.editor.edit_section_title.${newTitle ? "title_new" : "title"}`
|
||||
),
|
||||
inputLabel: this.hass.localize(
|
||||
"ui.panel.lovelace.editor.edit_section_title.input_label"
|
||||
),
|
||||
inputType: "string",
|
||||
defaultValue: section.title,
|
||||
confirmText: newTitle
|
||||
? this.hass.localize("ui.common.add")
|
||||
: this.hass.localize("ui.common.save"),
|
||||
showEditSectionDialog(this, {
|
||||
lovelaceConfig: this.lovelace!.config,
|
||||
saveConfig: (newConfig) => {
|
||||
this.lovelace!.saveConfig(newConfig);
|
||||
},
|
||||
viewIndex: this.index!,
|
||||
sectionIndex: index,
|
||||
});
|
||||
|
||||
if (title === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newConfig = updateLovelaceContainer(this.lovelace!.config, path, {
|
||||
...section,
|
||||
title: title || undefined,
|
||||
});
|
||||
|
||||
this.lovelace!.saveConfig(newConfig);
|
||||
}
|
||||
|
||||
private async _deleteSection(ev) {
|
||||
@ -190,13 +192,10 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
|
||||
const path = [this.index!, index] as [number, number];
|
||||
|
||||
const section = findLovelaceContainer(
|
||||
this.lovelace!.config,
|
||||
path
|
||||
) as LovelaceRawSectionConfig;
|
||||
const section = findLovelaceContainer(this.lovelace!.config, path);
|
||||
|
||||
const title = section.title?.trim();
|
||||
const cardCount = section.cards?.length;
|
||||
const cardCount = "cards" in section && section.cards?.length;
|
||||
|
||||
if (title || cardCount) {
|
||||
const named = title ? "named" : "unnamed";
|
||||
@ -261,6 +260,10 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
border-radius: var(--ha-card-border-radius, 12px);
|
||||
}
|
||||
|
||||
.section:not(:has(> *:not([hidden]))) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.container {
|
||||
--max-count: min(var(--total-count), var(--max-columns-count, 4));
|
||||
--max-width: min(
|
||||
|
@ -148,7 +148,7 @@ export class HUIView extends ReactiveElement {
|
||||
this._applyTheme();
|
||||
}
|
||||
|
||||
public willUpdate(changedProperties: PropertyValues): void {
|
||||
public willUpdate(changedProperties: PropertyValues<typeof this>): void {
|
||||
super.willUpdate(changedProperties);
|
||||
|
||||
/*
|
||||
@ -161,7 +161,7 @@ export class HUIView extends ReactiveElement {
|
||||
- lovelace changes if edit mode is enabled or config has changed
|
||||
*/
|
||||
|
||||
const oldLovelace = changedProperties.get("lovelace") as this["lovelace"];
|
||||
const oldLovelace = changedProperties.get("lovelace");
|
||||
|
||||
// If config has changed, create element if necessary and set all values.
|
||||
if (
|
||||
|
@ -5531,10 +5531,19 @@
|
||||
"text_named_section_cards": "''{name}'' section and all its cards will be deleted.",
|
||||
"text_unnamed_section_cards": "This section and all its cards will be deleted."
|
||||
},
|
||||
"edit_section_title": {
|
||||
"title": "Edit name",
|
||||
"title_new": "Add name",
|
||||
"input_label": "Name"
|
||||
"edit_section": {
|
||||
"header": "Edit section",
|
||||
"tab_visibility": "[%key:ui::panel::lovelace::editor::edit_view::tab_visibility%]",
|
||||
"tab_settings": "[%key:ui::panel::lovelace::editor::edit_view::tab_settings%]",
|
||||
"edit_ui": "[%key:ui::panel::lovelace::editor::edit_view::edit_ui%]",
|
||||
"edit_yaml": "[%key:ui::panel::lovelace::editor::edit_view::edit_yaml%]",
|
||||
"settings": {
|
||||
"title": "Title",
|
||||
"title_helper": "The title will appear at the top of section. Leave empty to hide the title."
|
||||
},
|
||||
"visibility": {
|
||||
"explanation": "The section will be shown when ALL conditions below are fulfilled. If no conditions are set, the section will always be shown."
|
||||
}
|
||||
},
|
||||
"suggest_card": {
|
||||
"header": "We created a suggestion for you",
|
||||
|
Loading…
x
Reference in New Issue
Block a user