mirror of
https://github.com/home-assistant/frontend.git
synced 2025-12-19 18:28:42 +00:00
Show summaries at top on mobile, sidebar on desktop (#28573)
* Use weather tile card and energy summary in home dashboard * Only use sidebar on desktop * Hide sidebar on mobile * Rename widget to summaries * Improve commonly used * Feedbacks * Use key instead of section
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import type { Condition } from "../../../panels/lovelace/common/validate-condition";
|
||||
import type { MediaSelectorValue } from "../../selector";
|
||||
import type { LovelaceBadgeConfig } from "./badge";
|
||||
import type { LovelaceCardConfig } from "./card";
|
||||
@@ -40,6 +41,7 @@ export interface LovelaceViewSidebarConfig {
|
||||
sections?: LovelaceSectionConfig[];
|
||||
content_label?: string;
|
||||
sidebar_label?: string;
|
||||
visibility?: Condition[];
|
||||
}
|
||||
|
||||
export interface LovelaceBaseViewConfig {
|
||||
|
||||
@@ -182,7 +182,7 @@ class PanelEnergy extends LitElement {
|
||||
const validPaths = views.map((view) => view.path);
|
||||
const viewPath: string | undefined = this.route!.path.split("/")[1];
|
||||
if (!viewPath || !validPaths.includes(viewPath)) {
|
||||
navigate(`${this.route!.prefix}/${validPaths[0]}`);
|
||||
navigate(`${this.route!.prefix}/${validPaths[0]}`, { replace: true });
|
||||
} else {
|
||||
// Force hui-root to re-process the route by creating a new route object
|
||||
this.route = { ...this.route! };
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { endOfDay, startOfDay } from "date-fns";
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { computeCssColor } from "../../../common/color/compute-color";
|
||||
import { calcDate } from "../../../common/datetime/calc_date";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import {
|
||||
findEntities,
|
||||
@@ -15,8 +18,15 @@ import "../../../components/ha-icon";
|
||||
import "../../../components/ha-ripple";
|
||||
import "../../../components/tile/ha-tile-icon";
|
||||
import "../../../components/tile/ha-tile-info";
|
||||
import type { EnergyData } from "../../../data/energy";
|
||||
import {
|
||||
computeConsumptionData,
|
||||
formatConsumptionShort,
|
||||
getEnergyDataCollection,
|
||||
getSummedData,
|
||||
} from "../../../data/energy";
|
||||
import type { ActionHandlerEvent } from "../../../data/lovelace/action_handler";
|
||||
import "../../../state-display/state-display";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
@@ -35,14 +45,41 @@ const COLORS: Record<HomeSummary, string> = {
|
||||
climate: "deep-orange",
|
||||
security: "blue-grey",
|
||||
media_players: "blue",
|
||||
energy: "amber",
|
||||
};
|
||||
|
||||
@customElement("hui-home-summary-card")
|
||||
export class HuiHomeSummaryCard extends LitElement implements LovelaceCard {
|
||||
export class HuiHomeSummaryCard
|
||||
extends SubscribeMixin(LitElement)
|
||||
implements LovelaceCard
|
||||
{
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@state() private _config?: HomeSummaryCard;
|
||||
|
||||
@state() private _energyData?: EnergyData;
|
||||
|
||||
protected hassSubscribeRequiredHostProps = ["_config"];
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
if (this._config?.summary !== "energy") {
|
||||
return [];
|
||||
}
|
||||
const collection = getEnergyDataCollection(this.hass!, {
|
||||
key: "energy_home_dashboard",
|
||||
});
|
||||
// Ensure we always show today's energy data
|
||||
collection.setPeriod(
|
||||
calcDate(new Date(), startOfDay, this.hass!.locale, this.hass!.config),
|
||||
calcDate(new Date(), endOfDay, this.hass!.locale, this.hass!.config)
|
||||
);
|
||||
return [
|
||||
collection.subscribe((data) => {
|
||||
this._energyData = data;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
public setConfig(config: HomeSummaryCard): void {
|
||||
this._config = config;
|
||||
}
|
||||
@@ -214,6 +251,15 @@ export class HuiHomeSummaryCard extends LitElement implements LovelaceCard {
|
||||
})
|
||||
: this.hass.localize("ui.card.home-summary.no_media_playing");
|
||||
}
|
||||
case "energy": {
|
||||
if (!this._energyData) {
|
||||
return "";
|
||||
}
|
||||
const { summedData } = getSummedData(this._energyData);
|
||||
const { consumption } = computeConsumptionData(summedData, undefined);
|
||||
const totalConsumption = consumption.total.used_total;
|
||||
return formatConsumptionShort(this.hass, totalConsumption, "kWh");
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ export const HOME_SUMMARIES = [
|
||||
"climate",
|
||||
"security",
|
||||
"media_players",
|
||||
"energy",
|
||||
] as const;
|
||||
|
||||
export type HomeSummary = (typeof HOME_SUMMARIES)[number];
|
||||
@@ -18,6 +19,7 @@ export const HOME_SUMMARIES_ICONS: Record<HomeSummary, string> = {
|
||||
climate: "mdi:home-thermometer",
|
||||
security: "mdi:security",
|
||||
media_players: "mdi:multimedia",
|
||||
energy: "mdi:lightning-bolt",
|
||||
};
|
||||
|
||||
export const HOME_SUMMARIES_FILTERS: Record<HomeSummary, EntityFilter[]> = {
|
||||
@@ -25,6 +27,7 @@ export const HOME_SUMMARIES_FILTERS: Record<HomeSummary, EntityFilter[]> = {
|
||||
climate: climateEntityFilters,
|
||||
security: securityEntityFilters,
|
||||
media_players: [{ domain: "media_player", entity_category: "none" }],
|
||||
energy: [], // Uses energy collection data
|
||||
};
|
||||
|
||||
export const getSummaryLabel = (
|
||||
|
||||
@@ -21,7 +21,7 @@ import type {
|
||||
AreaCardConfig,
|
||||
HomeSummaryCard,
|
||||
MarkdownCardConfig,
|
||||
WeatherForecastCardConfig,
|
||||
TileCardConfig,
|
||||
} from "../../cards/types";
|
||||
import type { CommonControlSectionStrategyConfig } from "../usage_prediction/common-controls-section-strategy";
|
||||
import { HOME_SUMMARIES_FILTERS } from "./helpers/home-summaries";
|
||||
@@ -78,6 +78,11 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
|
||||
media_query: "(min-width: 871px)",
|
||||
};
|
||||
|
||||
const smallScreenCondition: Condition = {
|
||||
condition: "screen",
|
||||
media_query: "(max-width: 870px)",
|
||||
};
|
||||
|
||||
const floorsSections: LovelaceSectionConfig[] = [];
|
||||
for (const floorStructure of home.floors) {
|
||||
const floorId = floorStructure.id;
|
||||
@@ -136,7 +141,7 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
|
||||
);
|
||||
const maxCommonControls = Math.max(8, favoriteEntities.length);
|
||||
|
||||
const commonControlsSection = {
|
||||
const commonControlsSectionBase = {
|
||||
strategy: {
|
||||
type: "common-controls",
|
||||
limit: maxCommonControls,
|
||||
@@ -146,6 +151,20 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
|
||||
column_span: maxColumns,
|
||||
} as LovelaceStrategySectionConfig;
|
||||
|
||||
const commonControlsSectionMobile = {
|
||||
...commonControlsSectionBase,
|
||||
strategy: {
|
||||
...commonControlsSectionBase.strategy,
|
||||
title: hass.localize("ui.panel.lovelace.strategy.home.commonly_used"),
|
||||
},
|
||||
visibility: [smallScreenCondition],
|
||||
} as LovelaceStrategySectionConfig;
|
||||
|
||||
const commonControlsSectionDesktop = {
|
||||
...commonControlsSectionBase,
|
||||
visibility: [largeScreenCondition],
|
||||
} as LovelaceStrategySectionConfig;
|
||||
|
||||
const allEntities = Object.keys(hass.states);
|
||||
|
||||
const mediaPlayerFilter = HOME_SUMMARIES_FILTERS.media_players.map(
|
||||
@@ -170,6 +189,26 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
|
||||
const hasClimate = findEntities(allEntities, climateFilters).length > 0;
|
||||
const hasSecurity = findEntities(allEntities, securityFilters).length > 0;
|
||||
|
||||
const weatherFilter = generateEntityFilter(hass, {
|
||||
domain: "weather",
|
||||
entity_category: "none",
|
||||
});
|
||||
|
||||
const weatherEntity = Object.keys(hass.states)
|
||||
.filter(weatherFilter)
|
||||
.sort()[0];
|
||||
|
||||
const energyPrefs = isComponentLoaded(hass, "energy")
|
||||
? // It raises if not configured, just swallow that.
|
||||
await getEnergyPreferences(hass).catch(() => undefined)
|
||||
: undefined;
|
||||
|
||||
const hasEnergy =
|
||||
energyPrefs?.energy_sources.some(
|
||||
(source) => source.type === "grid" && source.flow_from.length > 0
|
||||
) ?? false;
|
||||
|
||||
// Build summary cards (used in both mobile section and sidebar)
|
||||
const summaryCards: LovelaceCardConfig[] = [
|
||||
hasLights &&
|
||||
({
|
||||
@@ -179,9 +218,6 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
|
||||
action: "navigate",
|
||||
navigation_path: "/light?historyBack=1",
|
||||
},
|
||||
grid_options: {
|
||||
columns: 12,
|
||||
},
|
||||
} satisfies HomeSummaryCard),
|
||||
hasClimate &&
|
||||
({
|
||||
@@ -191,9 +227,6 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
|
||||
action: "navigate",
|
||||
navigation_path: "/climate?historyBack=1",
|
||||
},
|
||||
grid_options: {
|
||||
columns: 12,
|
||||
},
|
||||
} satisfies HomeSummaryCard),
|
||||
hasSecurity &&
|
||||
({
|
||||
@@ -203,9 +236,6 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
|
||||
action: "navigate",
|
||||
navigation_path: "/security?historyBack=1",
|
||||
},
|
||||
grid_options: {
|
||||
columns: 12,
|
||||
},
|
||||
} satisfies HomeSummaryCard),
|
||||
hasMediaPlayers &&
|
||||
({
|
||||
@@ -215,75 +245,67 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
|
||||
action: "navigate",
|
||||
navigation_path: "media-players",
|
||||
},
|
||||
grid_options: {
|
||||
columns: 12,
|
||||
} satisfies HomeSummaryCard),
|
||||
weatherEntity &&
|
||||
({
|
||||
type: "tile",
|
||||
entity: weatherEntity,
|
||||
name: hass.localize(
|
||||
"ui.panel.lovelace.strategy.home.summary_list.weather"
|
||||
),
|
||||
state_content: ["temperature", "state"],
|
||||
} satisfies TileCardConfig),
|
||||
hasEnergy &&
|
||||
({
|
||||
type: "home-summary",
|
||||
summary: "energy",
|
||||
tap_action: {
|
||||
action: "navigate",
|
||||
navigation_path: "/energy?historyBack=1",
|
||||
},
|
||||
} satisfies HomeSummaryCard),
|
||||
].filter(Boolean) as LovelaceCardConfig[];
|
||||
|
||||
const forYouSection: LovelaceSectionConfig = {
|
||||
type: "grid",
|
||||
cards: [
|
||||
{
|
||||
type: "heading",
|
||||
heading: hass.localize("ui.panel.lovelace.strategy.home.for_you"),
|
||||
heading_style: "title",
|
||||
visibility: [largeScreenCondition],
|
||||
},
|
||||
],
|
||||
};
|
||||
// Build summary cards for sidebar (full width: columns 12)
|
||||
const sidebarSummaryCards = summaryCards.map((card) => ({
|
||||
...card,
|
||||
grid_options: { columns: 12 },
|
||||
}));
|
||||
|
||||
const widgetSection: LovelaceSectionConfig = {
|
||||
cards: [],
|
||||
};
|
||||
// Build summary cards for mobile section (half width: columns 6)
|
||||
const mobileSummaryCards = summaryCards.map((card) => ({
|
||||
...card,
|
||||
grid_options: { columns: 6 },
|
||||
}));
|
||||
|
||||
if (summaryCards.length) {
|
||||
widgetSection.cards!.push(...summaryCards);
|
||||
}
|
||||
// Mobile summary section (visible on small screens only)
|
||||
const mobileSummarySection: LovelaceSectionConfig | undefined =
|
||||
mobileSummaryCards.length > 0
|
||||
? {
|
||||
type: "grid",
|
||||
column_span: maxColumns,
|
||||
visibility: [smallScreenCondition],
|
||||
cards: mobileSummaryCards,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const weatherFilter = generateEntityFilter(hass, {
|
||||
domain: "weather",
|
||||
entity_category: "none",
|
||||
});
|
||||
|
||||
const weatherEntity = Object.keys(hass.states)
|
||||
.filter(weatherFilter)
|
||||
.sort()[0];
|
||||
|
||||
if (weatherEntity) {
|
||||
widgetSection.cards!.push({
|
||||
type: "weather-forecast",
|
||||
entity: weatherEntity,
|
||||
show_forecast: false,
|
||||
show_current: true,
|
||||
grid_options: {
|
||||
columns: 12,
|
||||
rows: "auto",
|
||||
},
|
||||
} as WeatherForecastCardConfig);
|
||||
}
|
||||
|
||||
const energyPrefs = isComponentLoaded(hass, "energy")
|
||||
? // It raises if not configured, just swallow that.
|
||||
await getEnergyPreferences(hass).catch(() => undefined)
|
||||
: undefined;
|
||||
|
||||
if (energyPrefs) {
|
||||
const grid = energyPrefs.energy_sources.find(
|
||||
(source) => source.type === "grid"
|
||||
);
|
||||
|
||||
if (grid && grid.flow_from.length > 0) {
|
||||
widgetSection.cards!.push({
|
||||
title: hass.localize(
|
||||
"ui.panel.lovelace.cards.energy.energy_distribution.title_today"
|
||||
),
|
||||
type: "energy-distribution",
|
||||
collection_key: "energy_home_dashboard",
|
||||
link_dashboard: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
// Sidebar section
|
||||
const sidebarSection: LovelaceSectionConfig | undefined =
|
||||
sidebarSummaryCards.length > 0
|
||||
? {
|
||||
type: "grid",
|
||||
cards: [
|
||||
{
|
||||
type: "heading",
|
||||
heading: hass.localize(
|
||||
"ui.panel.lovelace.strategy.home.for_you"
|
||||
),
|
||||
heading_style: "title",
|
||||
},
|
||||
...sidebarSummaryCards,
|
||||
],
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const sections = (
|
||||
[
|
||||
@@ -298,7 +320,9 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
|
||||
},
|
||||
],
|
||||
},
|
||||
commonControlsSection,
|
||||
mobileSummarySection,
|
||||
commonControlsSectionMobile,
|
||||
commonControlsSectionDesktop,
|
||||
...floorsSections,
|
||||
] satisfies (LovelaceSectionRawConfig | undefined)[]
|
||||
).filter(Boolean) as LovelaceSectionRawConfig[];
|
||||
@@ -315,11 +339,16 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
|
||||
content: `## ${hass.localize("ui.panel.lovelace.strategy.home.welcome_user", { user: "{{ user }}" })}`,
|
||||
} satisfies MarkdownCardConfig,
|
||||
},
|
||||
sidebar: {
|
||||
sections: [forYouSection, widgetSection],
|
||||
content_label: hass.localize("ui.panel.lovelace.strategy.home.home"),
|
||||
sidebar_label: hass.localize("ui.panel.lovelace.strategy.home.for_you"),
|
||||
},
|
||||
...(sidebarSection && {
|
||||
sidebar: {
|
||||
sections: [sidebarSection],
|
||||
content_label: hass.localize("ui.panel.lovelace.strategy.home.home"),
|
||||
sidebar_label: hass.localize(
|
||||
"ui.panel.lovelace.strategy.home.for_you"
|
||||
),
|
||||
visibility: [largeScreenCondition],
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,9 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
|
||||
@state() _dragging = false;
|
||||
|
||||
@state() private _showSidebar = false;
|
||||
@state() private _sidebarTabActive = false;
|
||||
|
||||
@state() private _sidebarVisible = true;
|
||||
|
||||
private _contentScrollTop = 0;
|
||||
|
||||
@@ -123,7 +125,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
"section-visibility-changed",
|
||||
this._sectionVisibilityChanged
|
||||
);
|
||||
this._showSidebar = Boolean(window.history.state?.sidebar);
|
||||
this._sidebarTabActive = Boolean(window.history.state?.sidebar);
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
@@ -144,26 +146,25 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
if (!this.lovelace) return nothing;
|
||||
|
||||
const sections = this.sections;
|
||||
const totalSectionCount =
|
||||
this._sectionColumnCount +
|
||||
(this.lovelace?.editMode ? 1 : 0) +
|
||||
(this._config?.sidebar ? 1 : 0);
|
||||
const editMode = this.lovelace.editMode;
|
||||
const hasSidebar =
|
||||
this._config?.sidebar && (this._sidebarVisible || editMode);
|
||||
|
||||
const totalSectionCount =
|
||||
this._sectionColumnCount + (editMode ? 1 : 0) + (hasSidebar ? 1 : 0);
|
||||
|
||||
const maxColumnCount = this._columnsController.value ?? 1;
|
||||
|
||||
const columnCount = Math.min(maxColumnCount, totalSectionCount);
|
||||
// On mobile with sidebar, use full width for whichever view is active
|
||||
const contentColumnCount =
|
||||
this._config?.sidebar && !this.narrow
|
||||
? Math.max(1, columnCount - 1)
|
||||
: columnCount;
|
||||
hasSidebar && !this.narrow ? Math.max(1, columnCount - 1) : columnCount;
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="wrapper ${classMap({
|
||||
"top-margin": Boolean(this._config?.top_margin),
|
||||
"has-sidebar": Boolean(this._config?.sidebar),
|
||||
"has-sidebar": Boolean(hasSidebar),
|
||||
narrow: this.narrow,
|
||||
})}"
|
||||
style=${styleMap({
|
||||
@@ -178,20 +179,20 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
.viewIndex=${this.index}
|
||||
.config=${this._config?.header}
|
||||
></hui-view-header>
|
||||
${this.narrow && this._config?.sidebar
|
||||
${this.narrow && hasSidebar
|
||||
? html`
|
||||
<div class="mobile-tabs">
|
||||
<ha-control-select
|
||||
.value=${this._showSidebar ? "sidebar" : "content"}
|
||||
.value=${this._sidebarTabActive ? "sidebar" : "content"}
|
||||
@value-changed=${this._viewChanged}
|
||||
.options=${[
|
||||
{
|
||||
value: "content",
|
||||
label: this._config.sidebar.content_label,
|
||||
label: this._config!.sidebar!.content_label,
|
||||
},
|
||||
{
|
||||
value: "sidebar",
|
||||
label: this._config.sidebar.sidebar_label,
|
||||
label: this._config!.sidebar!.sidebar_label,
|
||||
},
|
||||
]}
|
||||
>
|
||||
@@ -211,7 +212,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
<div
|
||||
class="content ${classMap({
|
||||
dense: Boolean(this._config?.dense_section_placement),
|
||||
"mobile-hidden": this.narrow && this._showSidebar,
|
||||
"mobile-hidden": this.narrow && this._sidebarTabActive,
|
||||
})}"
|
||||
>
|
||||
${repeat(
|
||||
@@ -290,13 +291,16 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
? html`
|
||||
<hui-view-sidebar
|
||||
class=${classMap({
|
||||
"mobile-hidden": this.narrow && !this._showSidebar,
|
||||
"mobile-hidden":
|
||||
!hasSidebar || (this.narrow && !this._sidebarTabActive),
|
||||
})}
|
||||
.hass=${this.hass}
|
||||
.badges=${this.badges}
|
||||
.lovelace=${this.lovelace}
|
||||
.viewIndex=${this.index}
|
||||
.config=${this._config.sidebar}
|
||||
@sidebar-visibility-changed=${this
|
||||
._handleSidebarVisibilityChanged}
|
||||
></hui-view-sidebar>
|
||||
`
|
||||
: nothing}
|
||||
@@ -414,36 +418,46 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
const newValue = ev.detail.value;
|
||||
const shouldShowSidebar = newValue === "sidebar";
|
||||
|
||||
if (shouldShowSidebar !== this._showSidebar) {
|
||||
if (shouldShowSidebar !== this._sidebarTabActive) {
|
||||
this._toggleView();
|
||||
}
|
||||
}
|
||||
|
||||
private _toggleView() {
|
||||
// Save current scroll position
|
||||
if (this._showSidebar) {
|
||||
if (this._sidebarTabActive) {
|
||||
this._sidebarScrollTop = window.scrollY;
|
||||
} else {
|
||||
this._contentScrollTop = window.scrollY;
|
||||
}
|
||||
|
||||
this._showSidebar = !this._showSidebar;
|
||||
this._sidebarTabActive = !this._sidebarTabActive;
|
||||
|
||||
// Add sidebar state to history
|
||||
window.history.replaceState(
|
||||
{ ...window.history.state, sidebar: this._showSidebar },
|
||||
{ ...window.history.state, sidebar: this._sidebarTabActive },
|
||||
""
|
||||
);
|
||||
|
||||
// Restore scroll position after view updates
|
||||
this.updateComplete.then(() => {
|
||||
const scrollY = this._showSidebar
|
||||
const scrollY = this._sidebarTabActive
|
||||
? this._sidebarScrollTop
|
||||
: this._contentScrollTop;
|
||||
window.scrollTo(0, scrollY);
|
||||
});
|
||||
}
|
||||
|
||||
private _handleSidebarVisibilityChanged = (
|
||||
e: CustomEvent<{ visible: boolean }>
|
||||
) => {
|
||||
this._sidebarVisible = e.detail.visible;
|
||||
// Reset sidebar tab when sidebar becomes hidden
|
||||
if (!e.detail.visible) {
|
||||
this._sidebarTabActive = false;
|
||||
}
|
||||
};
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
--row-height: var(--ha-view-sections-row-height, 56px);
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import type { LovelaceViewSidebarConfig } from "../../../data/lovelace/config/view";
|
||||
import { ConditionalListenerMixin } from "../../../mixins/conditional-listener-mixin";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { checkConditionsMet } from "../common/validate-condition";
|
||||
import "../sections/hui-section";
|
||||
import type { Lovelace } from "../types";
|
||||
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
|
||||
|
||||
export const DEFAULT_VIEW_SIDEBAR_LAYOUT = "start";
|
||||
|
||||
@customElement("hui-view-sidebar")
|
||||
export class HuiViewSidebar extends LitElement {
|
||||
export class HuiViewSidebar extends ConditionalListenerMixin<LovelaceViewSidebarConfig>(
|
||||
LitElement
|
||||
) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public lovelace!: Lovelace;
|
||||
@@ -18,6 +25,38 @@ export class HuiViewSidebar extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public viewIndex!: number;
|
||||
|
||||
private _visible = true;
|
||||
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
super.updated(changedProperties);
|
||||
if (changedProperties.has("hass") || changedProperties.has("config")) {
|
||||
this._updateVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
protected _updateVisibility(conditionsMet?: boolean) {
|
||||
if (!this.hass || !this.config) return;
|
||||
|
||||
const visible =
|
||||
conditionsMet ??
|
||||
(!this.config.visibility ||
|
||||
checkConditionsMet(this.config.visibility, this.hass));
|
||||
|
||||
if (visible !== this._visible) {
|
||||
this._visible = visible;
|
||||
fireEvent(this, "sidebar-visibility-changed", { visible });
|
||||
}
|
||||
}
|
||||
|
||||
private _sectionConfigKeys = new WeakMap<LovelaceSectionConfig, string>();
|
||||
|
||||
private _getSectionKey(section: LovelaceSectionConfig) {
|
||||
if (!this._sectionConfigKeys.has(section)) {
|
||||
this._sectionConfigKeys.set(section, Math.random().toString());
|
||||
}
|
||||
return this._sectionConfigKeys.get(section)!;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.lovelace) return nothing;
|
||||
|
||||
@@ -26,7 +65,8 @@ export class HuiViewSidebar extends LitElement {
|
||||
return html`
|
||||
<div class="container">
|
||||
${repeat(
|
||||
this.config?.sections || [],
|
||||
this.config?.sections ?? [],
|
||||
(section) => this._getSectionKey(section),
|
||||
(section) => html`
|
||||
<hui-section
|
||||
.config=${section}
|
||||
@@ -54,4 +94,7 @@ declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-view-sidebar": HuiViewSidebar;
|
||||
}
|
||||
interface HASSDomEvents {
|
||||
"sidebar-visibility-changed": { visible: boolean };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7163,7 +7163,9 @@
|
||||
},
|
||||
"home": {
|
||||
"summary_list": {
|
||||
"media_players": "Media players"
|
||||
"media_players": "Media players",
|
||||
"weather": "Weather",
|
||||
"energy": "Today's energy"
|
||||
},
|
||||
"welcome_user": "Welcome {user}",
|
||||
"summaries": "Summaries",
|
||||
@@ -7174,7 +7176,8 @@
|
||||
"scenes": "Scenes",
|
||||
"automations": "Automations",
|
||||
"for_you": "For you",
|
||||
"home": "Home"
|
||||
"home": "Home",
|
||||
"commonly_used": "Commonly used"
|
||||
},
|
||||
"common_controls": {
|
||||
"not_loaded": "Usage Prediction integration is not loaded.",
|
||||
|
||||
Reference in New Issue
Block a user