mirror of
https://github.com/home-assistant/frontend.git
synced 2025-11-07 18:09:47 +00:00
Compare commits
3 Commits
dev
...
home_panel
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02ea05464e | ||
|
|
d45b2ec5c1 | ||
|
|
1a14e08f12 |
@@ -35,6 +35,7 @@ const COMPONENTS = {
|
||||
light: () => import("../panels/light/ha-panel-light"),
|
||||
security: () => import("../panels/security/ha-panel-security"),
|
||||
climate: () => import("../panels/climate/ha-panel-climate"),
|
||||
home: () => import("../panels/home/ha-panel-home"),
|
||||
};
|
||||
|
||||
@customElement("partial-panel-resolver")
|
||||
|
||||
@@ -11,8 +11,7 @@ import { haStyle } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { generateLovelaceViewStrategy } from "../lovelace/strategies/get-strategy";
|
||||
import type { Lovelace } from "../lovelace/types";
|
||||
import "../lovelace/views/hui-view";
|
||||
import "../lovelace/views/hui-view-container";
|
||||
import "../lovelace/hui-lovelace";
|
||||
|
||||
const CLIMATE_LOVELACE_VIEW_CONFIG: LovelaceStrategyViewConfig = {
|
||||
strategy: {
|
||||
@@ -26,8 +25,6 @@ class PanelClimate extends LitElement {
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public narrow = false;
|
||||
|
||||
@state() private _viewIndex = 0;
|
||||
|
||||
@state() private _lovelace?: Lovelace;
|
||||
|
||||
@state() private _searchParms = new URLSearchParams(window.location.search);
|
||||
@@ -92,40 +89,33 @@ class PanelClimate extends LitElement {
|
||||
return html`
|
||||
<div class="header">
|
||||
<div class="toolbar">
|
||||
${
|
||||
this._searchParms.has("historyBack")
|
||||
? html`
|
||||
<ha-icon-button-arrow-prev
|
||||
@click=${this._back}
|
||||
slot="navigationIcon"
|
||||
></ha-icon-button-arrow-prev>
|
||||
`
|
||||
: html`
|
||||
<ha-menu-button
|
||||
slot="navigationIcon"
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
></ha-menu-button>
|
||||
`
|
||||
}
|
||||
${this._searchParms.has("historyBack")
|
||||
? html`
|
||||
<ha-icon-button-arrow-prev
|
||||
@click=${this._back}
|
||||
slot="navigationIcon"
|
||||
></ha-icon-button-arrow-prev>
|
||||
`
|
||||
: html`
|
||||
<ha-menu-button
|
||||
slot="navigationIcon"
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
></ha-menu-button>
|
||||
`}
|
||||
<div class="main-title">${this.hass.localize("panel.climate")}</div>
|
||||
</div>
|
||||
</div>
|
||||
${
|
||||
this._lovelace
|
||||
? html`
|
||||
<hui-view-container .hass=${this.hass}>
|
||||
<hui-view
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.lovelace=${this._lovelace}
|
||||
.index=${this._viewIndex}
|
||||
></hui-view
|
||||
></hui-view-container>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
</hui-view-container>
|
||||
${this._lovelace
|
||||
? html`
|
||||
<hui-lovelace
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.lovelace=${this._lovelace}
|
||||
.curView=${0}
|
||||
></hui-lovelace>
|
||||
`
|
||||
: nothing}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -143,8 +133,8 @@ class PanelClimate extends LitElement {
|
||||
}
|
||||
|
||||
this._lovelace = {
|
||||
config: config,
|
||||
rawConfig: rawConfig,
|
||||
config,
|
||||
rawConfig,
|
||||
editMode: false,
|
||||
urlPath: "climate",
|
||||
mode: "generated",
|
||||
@@ -223,7 +213,7 @@ class PanelClimate extends LitElement {
|
||||
line-height: var(--ha-line-height-normal);
|
||||
flex-grow: 1;
|
||||
}
|
||||
hui-view-container {
|
||||
hui-lovelace {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
@@ -233,14 +223,10 @@ class PanelClimate extends LitElement {
|
||||
padding-inline-end: var(--safe-area-inset-right);
|
||||
padding-bottom: var(--safe-area-inset-bottom);
|
||||
}
|
||||
:host([narrow]) hui-view-container {
|
||||
:host([narrow]) hui-lovelace {
|
||||
padding-left: var(--safe-area-inset-left);
|
||||
padding-inline-start: var(--safe-area-inset-left);
|
||||
}
|
||||
hui-view {
|
||||
flex: 1 1 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
393
src/panels/home/ha-panel-home.ts
Normal file
393
src/panels/home/ha-panel-home.ts
Normal file
@@ -0,0 +1,393 @@
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, css, html, 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 { goBack, navigate } from "../../common/navigate";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import { deepEqual } from "../../common/util/deep-equal";
|
||||
import "../../components/ha-icon";
|
||||
import "../../components/ha-icon-button-arrow-prev";
|
||||
import "../../components/ha-menu-button";
|
||||
import "../../components/ha-tab-group";
|
||||
import "../../components/ha-tab-group-tab";
|
||||
import type { LovelaceDashboardStrategyConfig } from "../../data/lovelace/config/types";
|
||||
import type { LovelaceViewConfig } from "../../data/lovelace/config/view";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import type { HomeAssistant, Route } from "../../types";
|
||||
import { generateLovelaceDashboardStrategy } from "../lovelace/strategies/get-strategy";
|
||||
import type { Lovelace } from "../lovelace/types";
|
||||
import "../lovelace/hui-lovelace";
|
||||
|
||||
const HOME_LOVELACE_CONFIG: LovelaceDashboardStrategyConfig = {
|
||||
strategy: {
|
||||
type: "home",
|
||||
},
|
||||
};
|
||||
|
||||
@customElement("ha-panel-home")
|
||||
class PanelHome extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public narrow = false;
|
||||
|
||||
@property({ attribute: false }) public route?: Route;
|
||||
|
||||
@state() private _curView?: number;
|
||||
|
||||
@state() private _lovelace?: Lovelace;
|
||||
|
||||
@state() private _searchParms = new URLSearchParams(window.location.search);
|
||||
|
||||
public willUpdate(changedProps: PropertyValues) {
|
||||
super.willUpdate(changedProps);
|
||||
// Initial setup
|
||||
if (!this.hasUpdated) {
|
||||
this.hass.loadFragmentTranslation("lovelace");
|
||||
this._setLovelace();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!changedProps.has("hass")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldHass = changedProps.get("hass") as this["hass"];
|
||||
if (oldHass && oldHass.localize !== this.hass.localize) {
|
||||
this._setLovelace();
|
||||
return;
|
||||
}
|
||||
|
||||
if (oldHass && this.hass) {
|
||||
// If the entity registry changed, ask the user if they want to refresh the config
|
||||
if (
|
||||
oldHass.entities !== this.hass.entities ||
|
||||
oldHass.devices !== this.hass.devices ||
|
||||
oldHass.areas !== this.hass.areas ||
|
||||
oldHass.floors !== this.hass.floors
|
||||
) {
|
||||
if (this.hass.config.state === "RUNNING") {
|
||||
this._debounceRegistriesChanged();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// If ha started, refresh the config
|
||||
if (
|
||||
this.hass.config.state === "RUNNING" &&
|
||||
oldHass.config.state !== "RUNNING"
|
||||
) {
|
||||
this._setLovelace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _debounceRegistriesChanged = debounce(
|
||||
() => this._registriesChanged(),
|
||||
200
|
||||
);
|
||||
|
||||
private _registriesChanged = async () => {
|
||||
this._setLovelace();
|
||||
};
|
||||
|
||||
private _isVisible = (view: LovelaceViewConfig) =>
|
||||
Boolean(
|
||||
view.visible === undefined ||
|
||||
view.visible === true ||
|
||||
(Array.isArray(view.visible) &&
|
||||
view.visible.some((show) => show.user === this.hass.user?.id))
|
||||
);
|
||||
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
super.updated(changedProperties);
|
||||
|
||||
if (!this._lovelace || !this.route) {
|
||||
return;
|
||||
}
|
||||
|
||||
let viewPath: string | undefined = this.route.path.split("/")[1];
|
||||
viewPath = viewPath ? decodeURI(viewPath) : undefined;
|
||||
|
||||
if (changedProperties.has("route")) {
|
||||
const views = this._lovelace.config.views;
|
||||
|
||||
if (!viewPath && views.length) {
|
||||
// No path: navigate to first visible view
|
||||
const newSelectView = views.findIndex(this._isVisible);
|
||||
this._navigateToView(views[newSelectView].path || newSelectView, true);
|
||||
} else if (viewPath) {
|
||||
// Match by path or index
|
||||
const selectedView = viewPath;
|
||||
const selectedViewInt = Number(selectedView);
|
||||
let index = 0;
|
||||
for (let i = 0; i < views.length; i++) {
|
||||
if (views[i].path === selectedView || i === selectedViewInt) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this._curView = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _navigateToView(path: string | number, replace?: boolean) {
|
||||
if (!this.route) {
|
||||
return;
|
||||
}
|
||||
const url = `${this.route.prefix}/${path}${location.search}`;
|
||||
const currentUrl = `${location.pathname}${location.search}`;
|
||||
if (currentUrl !== url) {
|
||||
navigate(url, { replace });
|
||||
}
|
||||
}
|
||||
|
||||
private _handleViewSelected(ev) {
|
||||
ev.preventDefault();
|
||||
const viewIndex = Number(ev.detail.name);
|
||||
if (viewIndex !== this._curView && this._lovelace?.config.views) {
|
||||
const path = this._lovelace.config.views[viewIndex].path || viewIndex;
|
||||
this._navigateToView(path);
|
||||
} else {
|
||||
scrollTo({ behavior: "smooth", top: 0 });
|
||||
}
|
||||
}
|
||||
|
||||
private _goBack(): void {
|
||||
const views = this._lovelace?.config.views ?? [];
|
||||
const curViewConfig =
|
||||
typeof this._curView === "number" ? views[this._curView] : undefined;
|
||||
|
||||
if (curViewConfig?.back_path != null) {
|
||||
navigate(curViewConfig.back_path, { replace: true });
|
||||
} else if (history.length > 1) {
|
||||
goBack();
|
||||
} else if (views[0] && !views[0].subview) {
|
||||
navigate(this.route!.prefix, { replace: true });
|
||||
} else {
|
||||
navigate("/");
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._lovelace) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const views = this._lovelace.config.views;
|
||||
const curViewConfig =
|
||||
typeof this._curView === "number" ? views[this._curView] : undefined;
|
||||
|
||||
// Helper function to determine if a tab should be hidden for user
|
||||
const _isTabHiddenForUser = (view: LovelaceViewConfig) =>
|
||||
view.visible === false ||
|
||||
(Array.isArray(view.visible) &&
|
||||
!view.visible.some((show) => show.user === this.hass.user?.id));
|
||||
|
||||
const tabs = html`<ha-tab-group @wa-tab-show=${this._handleViewSelected}>
|
||||
${views.map((view, index) => {
|
||||
const hidden = view.subview || _isTabHiddenForUser(view);
|
||||
return html`
|
||||
<ha-tab-group-tab
|
||||
slot="nav"
|
||||
panel=${index}
|
||||
.active=${this._curView === index}
|
||||
.disabled=${hidden}
|
||||
aria-label=${ifDefined(view.title)}
|
||||
class=${classMap({
|
||||
icon: Boolean(view.icon),
|
||||
"hide-tab": Boolean(hidden),
|
||||
})}
|
||||
>
|
||||
${view.icon
|
||||
? html`<ha-icon
|
||||
class=${classMap({
|
||||
"child-view-icon": Boolean(view.subview),
|
||||
})}
|
||||
title=${ifDefined(view.title)}
|
||||
.icon=${view.icon}
|
||||
></ha-icon>`
|
||||
: view.title ||
|
||||
this.hass.localize("ui.panel.lovelace.views.unnamed_view")}
|
||||
</ha-tab-group-tab>
|
||||
`;
|
||||
})}
|
||||
</ha-tab-group>`;
|
||||
|
||||
const isSubview = curViewConfig?.subview;
|
||||
const hasTabViews = views.filter((view) => !view.subview).length > 1;
|
||||
|
||||
return html`
|
||||
<div class="header">
|
||||
<div class="toolbar">
|
||||
${this._searchParms.has("historyBack") || isSubview
|
||||
? html`
|
||||
<ha-icon-button-arrow-prev
|
||||
.hass=${this.hass}
|
||||
slot="navigationIcon"
|
||||
@click=${this._goBack}
|
||||
></ha-icon-button-arrow-prev>
|
||||
`
|
||||
: html`
|
||||
<ha-menu-button
|
||||
slot="navigationIcon"
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
></ha-menu-button>
|
||||
`}
|
||||
${isSubview
|
||||
? html`<div class="main-title">${curViewConfig.title}</div>`
|
||||
: hasTabViews
|
||||
? tabs
|
||||
: html`<div class="main-title">
|
||||
${views[0]?.title ?? this.hass.localize("panel.home")}
|
||||
</div>`}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hui-lovelace
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.lovelace=${this._lovelace}
|
||||
.curView=${this._curView}
|
||||
></hui-lovelace>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _setLovelace() {
|
||||
const config = await generateLovelaceDashboardStrategy(
|
||||
HOME_LOVELACE_CONFIG,
|
||||
this.hass
|
||||
);
|
||||
|
||||
const rawConfig = HOME_LOVELACE_CONFIG;
|
||||
|
||||
if (deepEqual(config, this._lovelace?.config)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._lovelace = {
|
||||
config: config,
|
||||
rawConfig: rawConfig,
|
||||
editMode: false,
|
||||
urlPath: "home",
|
||||
mode: "generated",
|
||||
locale: this.hass.locale,
|
||||
enableFullEditMode: () => undefined,
|
||||
saveConfig: async () => undefined,
|
||||
deleteConfig: async () => undefined,
|
||||
setEditMode: () => undefined,
|
||||
showToast: () => undefined,
|
||||
};
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
:host {
|
||||
-ms-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
}
|
||||
.header {
|
||||
background-color: var(--app-header-background-color);
|
||||
color: var(--app-header-text-color, white);
|
||||
border-bottom: var(--app-header-border-bottom, none);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: calc(
|
||||
var(--mdc-top-app-bar-width, 100%) - var(
|
||||
--safe-area-inset-right,
|
||||
0px
|
||||
)
|
||||
);
|
||||
padding-top: var(--safe-area-inset-top);
|
||||
z-index: 4;
|
||||
transition: box-shadow 200ms linear;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
-webkit-backdrop-filter: var(--app-header-backdrop-filter, none);
|
||||
backdrop-filter: var(--app-header-backdrop-filter, none);
|
||||
padding-top: var(--safe-area-inset-top);
|
||||
padding-right: var(--safe-area-inset-right);
|
||||
}
|
||||
:host([narrow]) .header {
|
||||
width: calc(
|
||||
var(--mdc-top-app-bar-width, 100%) - var(
|
||||
--safe-area-inset-left,
|
||||
0px
|
||||
) - var(--safe-area-inset-right, 0px)
|
||||
);
|
||||
padding-left: var(--safe-area-inset-left);
|
||||
}
|
||||
:host([scrolled]) .header {
|
||||
box-shadow: var(
|
||||
--mdc-top-app-bar-fixed-box-shadow,
|
||||
0px 2px 4px -1px rgba(0, 0, 0, 0.2),
|
||||
0px 4px 5px 0px rgba(0, 0, 0, 0.14),
|
||||
0px 1px 10px 0px rgba(0, 0, 0, 0.12)
|
||||
);
|
||||
}
|
||||
.toolbar {
|
||||
height: var(--header-height);
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
font-size: var(--ha-font-size-xl);
|
||||
padding: 0px 12px;
|
||||
font-weight: var(--ha-font-weight-normal);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
:host([narrow]) .toolbar {
|
||||
padding: 0 4px;
|
||||
}
|
||||
.main-title {
|
||||
margin: var(--margin-title);
|
||||
line-height: var(--ha-line-height-normal);
|
||||
flex-grow: 1;
|
||||
}
|
||||
ha-tab-group {
|
||||
margin-left: 12px;
|
||||
margin-inline-start: 12px;
|
||||
margin-inline-end: initial;
|
||||
flex: 1;
|
||||
max-width: 100%;
|
||||
}
|
||||
ha-tab-group-tab {
|
||||
--mdc-icon-size: 20px;
|
||||
max-width: 200px;
|
||||
}
|
||||
ha-tab-group-tab.icon {
|
||||
height: 48px;
|
||||
}
|
||||
ha-tab-group-tab.hide-tab {
|
||||
display: none;
|
||||
}
|
||||
.child-view-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
hui-lovelace {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
box-sizing: border-box;
|
||||
padding-top: calc(var(--header-height) + var(--safe-area-inset-top));
|
||||
padding-right: var(--safe-area-inset-right);
|
||||
padding-inline-end: var(--safe-area-inset-right);
|
||||
padding-bottom: var(--safe-area-inset-bottom);
|
||||
}
|
||||
:host([narrow]) hui-lovelace {
|
||||
padding-left: var(--safe-area-inset-left);
|
||||
padding-inline-start: var(--safe-area-inset-left);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-panel-home": PanelHome;
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,7 @@ import { haStyle } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { generateLovelaceViewStrategy } from "../lovelace/strategies/get-strategy";
|
||||
import type { Lovelace } from "../lovelace/types";
|
||||
import "../lovelace/views/hui-view";
|
||||
import "../lovelace/views/hui-view-container";
|
||||
import "../lovelace/hui-lovelace";
|
||||
|
||||
const LIGHT_LOVELACE_VIEW_CONFIG: LovelaceStrategyViewConfig = {
|
||||
strategy: {
|
||||
@@ -26,8 +25,6 @@ class PanelLight extends LitElement {
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public narrow = false;
|
||||
|
||||
@state() private _viewIndex = 0;
|
||||
|
||||
@state() private _lovelace?: Lovelace;
|
||||
|
||||
@state() private _searchParms = new URLSearchParams(window.location.search);
|
||||
@@ -92,40 +89,33 @@ class PanelLight extends LitElement {
|
||||
return html`
|
||||
<div class="header">
|
||||
<div class="toolbar">
|
||||
${
|
||||
this._searchParms.has("historyBack")
|
||||
? html`
|
||||
<ha-icon-button-arrow-prev
|
||||
@click=${this._back}
|
||||
slot="navigationIcon"
|
||||
></ha-icon-button-arrow-prev>
|
||||
`
|
||||
: html`
|
||||
<ha-menu-button
|
||||
slot="navigationIcon"
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
></ha-menu-button>
|
||||
`
|
||||
}
|
||||
${this._searchParms.has("historyBack")
|
||||
? html`
|
||||
<ha-icon-button-arrow-prev
|
||||
@click=${this._back}
|
||||
slot="navigationIcon"
|
||||
></ha-icon-button-arrow-prev>
|
||||
`
|
||||
: html`
|
||||
<ha-menu-button
|
||||
slot="navigationIcon"
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
></ha-menu-button>
|
||||
`}
|
||||
<div class="main-title">${this.hass.localize("panel.light")}</div>
|
||||
</div>
|
||||
</div>
|
||||
${
|
||||
this._lovelace
|
||||
? html`
|
||||
<hui-view-container .hass=${this.hass}>
|
||||
<hui-view
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.lovelace=${this._lovelace}
|
||||
.index=${this._viewIndex}
|
||||
></hui-view
|
||||
></hui-view-container>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
</hui-view-container>
|
||||
${this._lovelace
|
||||
? html`
|
||||
<hui-lovelace
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.lovelace=${this._lovelace}
|
||||
.curView=${0}
|
||||
></hui-lovelace>
|
||||
`
|
||||
: nothing}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -143,8 +133,8 @@ class PanelLight extends LitElement {
|
||||
}
|
||||
|
||||
this._lovelace = {
|
||||
config: config,
|
||||
rawConfig: rawConfig,
|
||||
config,
|
||||
rawConfig,
|
||||
editMode: false,
|
||||
urlPath: "light",
|
||||
mode: "generated",
|
||||
@@ -223,7 +213,7 @@ class PanelLight extends LitElement {
|
||||
line-height: var(--ha-line-height-normal);
|
||||
flex-grow: 1;
|
||||
}
|
||||
hui-view-container {
|
||||
hui-lovelace {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
@@ -233,14 +223,10 @@ class PanelLight extends LitElement {
|
||||
padding-inline-end: var(--safe-area-inset-right);
|
||||
padding-bottom: var(--safe-area-inset-bottom);
|
||||
}
|
||||
:host([narrow]) hui-view-container {
|
||||
:host([narrow]) hui-lovelace {
|
||||
padding-left: var(--safe-area-inset-left);
|
||||
padding-inline-start: var(--safe-area-inset-left);
|
||||
}
|
||||
hui-view {
|
||||
flex: 1 1 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
236
src/panels/lovelace/hui-lovelace.ts
Normal file
236
src/panels/lovelace/hui-lovelace.ts
Normal file
@@ -0,0 +1,236 @@
|
||||
import type { PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { afterNextRender } from "../../common/util/render-status";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { Lovelace } from "./types";
|
||||
import "./views/hui-view";
|
||||
import type { HUIView } from "./views/hui-view";
|
||||
import "./views/hui-view-background";
|
||||
import "./views/hui-view-container";
|
||||
|
||||
@customElement("hui-lovelace")
|
||||
export class HUILovelace extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: false }) public lovelace!: Lovelace;
|
||||
|
||||
@property({ attribute: false, type: Number }) public curView?: number;
|
||||
|
||||
private _displayedView?: number;
|
||||
|
||||
private _viewCache?: Record<number, HUIView>;
|
||||
|
||||
private _viewScrollPositions: Record<number, number> = {};
|
||||
|
||||
private _restoreScroll = false;
|
||||
|
||||
private _debouncedConfigChanged = debounce(
|
||||
() => this._selectView(this._displayedView, true),
|
||||
100,
|
||||
false
|
||||
);
|
||||
|
||||
private get _viewRoot(): HTMLDivElement {
|
||||
return this.shadowRoot!.getElementById("view") as HTMLDivElement;
|
||||
}
|
||||
|
||||
private _handleWindowScroll = () => {
|
||||
this.toggleAttribute("scrolled", window.scrollY !== 0);
|
||||
};
|
||||
|
||||
private _handlePopState = () => {
|
||||
this._restoreScroll = true;
|
||||
};
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
window.addEventListener("scroll", this._handleWindowScroll, {
|
||||
passive: true,
|
||||
});
|
||||
window.addEventListener("popstate", this._handlePopState);
|
||||
// Disable history scroll restoration because it is managed manually here
|
||||
window.history.scrollRestoration = "manual";
|
||||
}
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
window.removeEventListener("scroll", this._handleWindowScroll);
|
||||
window.removeEventListener("popstate", this._handlePopState);
|
||||
this.toggleAttribute("scrolled", window.scrollY !== 0);
|
||||
// Re-enable history scroll restoration when leaving the page
|
||||
window.history.scrollRestoration = "auto";
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
super.updated(changedProperties);
|
||||
|
||||
if (!this.lovelace) {
|
||||
return;
|
||||
}
|
||||
|
||||
const view = this._viewRoot;
|
||||
const huiView = view?.lastChild as HUIView | undefined;
|
||||
|
||||
if (changedProperties.has("hass") && huiView) {
|
||||
huiView.hass = this.hass;
|
||||
}
|
||||
|
||||
if (changedProperties.has("narrow") && huiView) {
|
||||
huiView.narrow = this.narrow;
|
||||
}
|
||||
|
||||
let newSelectView: number | undefined;
|
||||
let force = false;
|
||||
|
||||
if (changedProperties.has("curView")) {
|
||||
newSelectView = this.curView;
|
||||
}
|
||||
|
||||
if (changedProperties.has("lovelace")) {
|
||||
const oldLovelace = changedProperties.get("lovelace") as
|
||||
| Lovelace
|
||||
| undefined;
|
||||
|
||||
if (!oldLovelace || oldLovelace.config !== this.lovelace.config) {
|
||||
// On config change, recreate the current view from scratch.
|
||||
force = true;
|
||||
newSelectView = this.curView;
|
||||
}
|
||||
|
||||
if (!force && huiView) {
|
||||
huiView.lovelace = this.lovelace;
|
||||
}
|
||||
}
|
||||
|
||||
if (newSelectView !== undefined || force) {
|
||||
if (force && newSelectView === undefined) {
|
||||
newSelectView = this.curView;
|
||||
}
|
||||
// Will allow for ripples to start rendering
|
||||
afterNextRender(() => {
|
||||
if (changedProperties.has("curView")) {
|
||||
const position =
|
||||
(this._restoreScroll &&
|
||||
newSelectView !== undefined &&
|
||||
this._viewScrollPositions[newSelectView]) ||
|
||||
0;
|
||||
this._restoreScroll = false;
|
||||
requestAnimationFrame(() =>
|
||||
scrollTo({ behavior: "auto", top: position })
|
||||
);
|
||||
}
|
||||
this._selectView(newSelectView, force);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _selectView(viewIndex: number | undefined, force: boolean): void {
|
||||
if (!force && this._displayedView === viewIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Save scroll position of current view
|
||||
if (this._displayedView != null) {
|
||||
this._viewScrollPositions[this._displayedView] = window.scrollY;
|
||||
}
|
||||
|
||||
viewIndex = viewIndex === undefined ? 0 : viewIndex;
|
||||
|
||||
this._displayedView = viewIndex;
|
||||
|
||||
if (force) {
|
||||
this._viewCache = {};
|
||||
this._viewScrollPositions = {};
|
||||
}
|
||||
|
||||
// Recreate a new element to clear the applied themes.
|
||||
const root = this._viewRoot;
|
||||
|
||||
if (root.lastChild) {
|
||||
root.removeChild(root.lastChild);
|
||||
}
|
||||
|
||||
if (!this.lovelace) {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewConfig = this.lovelace.config.views[viewIndex];
|
||||
|
||||
if (!viewConfig) {
|
||||
return;
|
||||
}
|
||||
|
||||
let view: HUIView;
|
||||
|
||||
// Use cached view if available
|
||||
if (!force && this._viewCache![viewIndex]) {
|
||||
view = this._viewCache![viewIndex];
|
||||
} else {
|
||||
view = document.createElement("hui-view");
|
||||
view.index = viewIndex;
|
||||
this._viewCache![viewIndex] = view;
|
||||
}
|
||||
|
||||
view.lovelace = this.lovelace;
|
||||
view.hass = this.hass;
|
||||
view.narrow = this.narrow;
|
||||
|
||||
root.appendChild(view);
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.lovelace) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const curViewConfig =
|
||||
this.curView !== undefined
|
||||
? this.lovelace.config.views[this.curView]
|
||||
: undefined;
|
||||
|
||||
const background =
|
||||
curViewConfig?.background || this.lovelace.config.background;
|
||||
|
||||
return html`
|
||||
<hui-view-container
|
||||
.hass=${this.hass}
|
||||
.theme=${curViewConfig?.theme}
|
||||
@ll-rebuild=${this._debouncedConfigChanged}
|
||||
>
|
||||
<div id="view"></div>
|
||||
</hui-view-container>
|
||||
<hui-view-background .hass=${this.hass} .background=${background}>
|
||||
</hui-view-background>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
hui-view-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
}
|
||||
#view {
|
||||
flex: 1 1 100%;
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
#view > * {
|
||||
flex: 1 1 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-lovelace": HUILovelace;
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,6 @@ import {
|
||||
extractSearchParamsObject,
|
||||
removeSearchParam,
|
||||
} from "../../common/url/search-params";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import { afterNextRender } from "../../common/util/render-status";
|
||||
import "../../components/ha-button";
|
||||
import "../../components/ha-button-menu";
|
||||
@@ -88,8 +87,8 @@ import { showEditViewDialog } from "./editor/view-editor/show-edit-view-dialog";
|
||||
import { getLovelaceStrategy } from "./strategies/get-strategy";
|
||||
import { isLegacyStrategyConfig } from "./strategies/legacy-strategy";
|
||||
import type { Lovelace } from "./types";
|
||||
import "./views/hui-view";
|
||||
import type { HUIView } from "./views/hui-view";
|
||||
import "./editor/unused-entities/hui-unused-entities";
|
||||
import "./hui-lovelace";
|
||||
import "./views/hui-view-background";
|
||||
import "./views/hui-view-container";
|
||||
|
||||
@@ -130,29 +129,10 @@ class HUIRoot extends LitElement {
|
||||
|
||||
@state() private _curView?: number | "hass-unused-entities";
|
||||
|
||||
private _viewCache?: Record<string, HUIView>;
|
||||
|
||||
private _viewScrollPositions: Record<string, number> = {};
|
||||
|
||||
private _restoreScroll = false;
|
||||
|
||||
private _debouncedConfigChanged: () => void;
|
||||
|
||||
private _conversation = memoizeOne((_components) =>
|
||||
isComponentLoaded(this.hass, "conversation")
|
||||
);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
// The view can trigger a re-render when it knows that certain
|
||||
// web components have been loaded.
|
||||
this._debouncedConfigChanged = debounce(
|
||||
() => this._selectView(this._curView, true),
|
||||
100,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
private _renderActionItems(): TemplateResult {
|
||||
const result: TemplateResult[] = [];
|
||||
if (this._editMode) {
|
||||
@@ -411,8 +391,6 @@ class HUIRoot extends LitElement {
|
||||
? getPanelTitle(this.hass, this.panel)
|
||||
: undefined;
|
||||
|
||||
const background = curViewConfig?.background || this.config.background;
|
||||
|
||||
const _isTabHiddenForUser = (view: LovelaceViewConfig) =>
|
||||
view.visible !== undefined &&
|
||||
((Array.isArray(view.visible) &&
|
||||
@@ -560,28 +538,31 @@ class HUIRoot extends LitElement {
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
<hui-view-container
|
||||
class=${this._editMode ? "has-tab-bar" : ""}
|
||||
.hass=${this.hass}
|
||||
.theme=${curViewConfig?.theme}
|
||||
id="view"
|
||||
@ll-rebuild=${this._debouncedConfigChanged}
|
||||
>
|
||||
<hui-view-background .hass=${this.hass} .background=${background}>
|
||||
</hui-view-background>
|
||||
</hui-view-container>
|
||||
${this._curView === "hass-unused-entities"
|
||||
? html`
|
||||
<hui-view-container .hass=${this.hass}>
|
||||
<hui-unused-entities
|
||||
.hass=${this.hass}
|
||||
.lovelace=${this.lovelace}
|
||||
.narrow=${this.narrow}
|
||||
></hui-unused-entities>
|
||||
</hui-view-container>
|
||||
`
|
||||
: html`
|
||||
<hui-lovelace
|
||||
class=${this._editMode ? "has-tab-bar" : ""}
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.lovelace=${this.lovelace}
|
||||
.curView=${typeof this._curView === "number"
|
||||
? this._curView
|
||||
: 0}
|
||||
></hui-lovelace>
|
||||
`}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleWindowScroll = () => {
|
||||
this.toggleAttribute("scrolled", window.scrollY !== 0);
|
||||
};
|
||||
|
||||
private _handlePopState = () => {
|
||||
this._restoreScroll = true;
|
||||
};
|
||||
|
||||
private _isVisible = (view: LovelaceViewConfig) =>
|
||||
Boolean(
|
||||
this._editMode ||
|
||||
@@ -620,59 +601,26 @@ class HUIRoot extends LitElement {
|
||||
this._showMoreInfoDialog(entityId);
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener("scroll", this._handleWindowScroll, {
|
||||
passive: true,
|
||||
});
|
||||
}
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
window.addEventListener("scroll", this._handleWindowScroll, {
|
||||
passive: true,
|
||||
});
|
||||
window.addEventListener("popstate", this._handlePopState);
|
||||
// Disable history scroll restoration because it is managed manually here
|
||||
window.history.scrollRestoration = "manual";
|
||||
}
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
window.removeEventListener("scroll", this._handleWindowScroll);
|
||||
window.removeEventListener("popstate", this._handlePopState);
|
||||
this.toggleAttribute("scrolled", window.scrollY !== 0);
|
||||
// Re-enable history scroll restoration when leaving the page
|
||||
window.history.scrollRestoration = "auto";
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
super.updated(changedProperties);
|
||||
|
||||
const view = this._viewRoot;
|
||||
const huiView = view.lastChild as HUIView;
|
||||
|
||||
if (changedProperties.has("hass") && huiView) {
|
||||
huiView.hass = this.hass;
|
||||
if (!this.route || !this.lovelace) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (changedProperties.has("narrow") && huiView) {
|
||||
huiView.narrow = this.narrow;
|
||||
}
|
||||
|
||||
let newSelectView;
|
||||
let force = false;
|
||||
|
||||
let viewPath: string | undefined = this.route!.path.split("/")[1];
|
||||
let viewPath: string | undefined = this.route.path.split("/")[1];
|
||||
viewPath = viewPath ? decodeURI(viewPath) : undefined;
|
||||
|
||||
if (changedProperties.has("route")) {
|
||||
const views = this.config.views;
|
||||
|
||||
if (!viewPath && views.length) {
|
||||
newSelectView = views.findIndex(this._isVisible);
|
||||
const newSelectView = views.findIndex(this._isVisible);
|
||||
this._navigateToView(views[newSelectView].path || newSelectView, true);
|
||||
} else if (viewPath === "hass-unused-entities") {
|
||||
newSelectView = "hass-unused-entities";
|
||||
this._curView = "hass-unused-entities";
|
||||
} else if (viewPath) {
|
||||
const selectedView = viewPath;
|
||||
const selectedViewInt = Number(selectedView);
|
||||
@@ -683,7 +631,7 @@ class HUIRoot extends LitElement {
|
||||
break;
|
||||
}
|
||||
}
|
||||
newSelectView = index;
|
||||
this._curView = index;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -692,49 +640,21 @@ class HUIRoot extends LitElement {
|
||||
| Lovelace
|
||||
| undefined;
|
||||
|
||||
if (!oldLovelace || oldLovelace.config !== this.lovelace!.config) {
|
||||
// On config change, recreate the current view from scratch.
|
||||
force = true;
|
||||
}
|
||||
|
||||
if (!oldLovelace || oldLovelace.editMode !== this.lovelace!.editMode) {
|
||||
if (!oldLovelace || oldLovelace.editMode !== this.lovelace.editMode) {
|
||||
const views = this.config && this.config.views;
|
||||
|
||||
// Leave unused entities when leaving edit mode
|
||||
if (
|
||||
this.lovelace!.mode === "storage" &&
|
||||
this.lovelace.mode === "storage" &&
|
||||
viewPath === "hass-unused-entities"
|
||||
) {
|
||||
newSelectView = views.findIndex(this._isVisible);
|
||||
const newSelectView = views.findIndex(this._isVisible);
|
||||
this._navigateToView(
|
||||
views[newSelectView].path || newSelectView,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!force && huiView) {
|
||||
huiView.lovelace = this.lovelace!;
|
||||
}
|
||||
}
|
||||
|
||||
if (newSelectView !== undefined || force) {
|
||||
if (force && newSelectView === undefined) {
|
||||
newSelectView = this._curView;
|
||||
}
|
||||
// Will allow for ripples to start rendering
|
||||
afterNextRender(() => {
|
||||
if (changedProperties.has("route")) {
|
||||
const position =
|
||||
(this._restoreScroll && this._viewScrollPositions[newSelectView]) ||
|
||||
0;
|
||||
this._restoreScroll = false;
|
||||
requestAnimationFrame(() =>
|
||||
scrollTo({ behavior: "auto", top: position })
|
||||
);
|
||||
}
|
||||
this._selectView(newSelectView, force);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -750,10 +670,6 @@ class HUIRoot extends LitElement {
|
||||
return this.lovelace!.editMode;
|
||||
}
|
||||
|
||||
private get _viewRoot(): HTMLDivElement {
|
||||
return this.shadowRoot!.getElementById("view") as HTMLDivElement;
|
||||
}
|
||||
|
||||
private _handleRefresh(ev: CustomEvent<RequestSelectedDetail>): void {
|
||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
@@ -1141,67 +1057,6 @@ class HUIRoot extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _selectView(viewIndex: HUIRoot["_curView"], force: boolean): void {
|
||||
if (!force && this._curView === viewIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Save scroll position of current view
|
||||
if (this._curView != null) {
|
||||
this._viewScrollPositions[this._curView] = window.scrollY;
|
||||
}
|
||||
|
||||
viewIndex = viewIndex === undefined ? 0 : viewIndex;
|
||||
|
||||
this._curView = viewIndex;
|
||||
|
||||
if (force) {
|
||||
this._viewCache = {};
|
||||
this._viewScrollPositions = {};
|
||||
}
|
||||
|
||||
// Recreate a new element to clear the applied themes.
|
||||
const root = this._viewRoot;
|
||||
|
||||
if (root.lastChild) {
|
||||
root.removeChild(root.lastChild);
|
||||
}
|
||||
|
||||
if (viewIndex === "hass-unused-entities") {
|
||||
const unusedEntities = document.createElement("hui-unused-entities");
|
||||
// Wait for promise to resolve so that the element has been upgraded.
|
||||
import("./editor/unused-entities/hui-unused-entities").then(() => {
|
||||
unusedEntities.hass = this.hass!;
|
||||
unusedEntities.lovelace = this.lovelace!;
|
||||
unusedEntities.narrow = this.narrow;
|
||||
});
|
||||
root.appendChild(unusedEntities);
|
||||
return;
|
||||
}
|
||||
|
||||
let view;
|
||||
const viewConfig = this.config.views[viewIndex];
|
||||
|
||||
if (!viewConfig) {
|
||||
this.lovelace!.setEditMode(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!force && this._viewCache![viewIndex]) {
|
||||
view = this._viewCache![viewIndex];
|
||||
} else {
|
||||
view = document.createElement("hui-view");
|
||||
view.index = viewIndex;
|
||||
this._viewCache![viewIndex] = view;
|
||||
}
|
||||
|
||||
view.lovelace = this.lovelace;
|
||||
view.hass = this.hass;
|
||||
view.narrow = this.narrow;
|
||||
|
||||
root.appendChild(view);
|
||||
}
|
||||
|
||||
private _openShortcutDialog(ev: Event) {
|
||||
ev.preventDefault();
|
||||
showShortcutsDialog(this);
|
||||
@@ -1395,6 +1250,7 @@ class HUIRoot extends LitElement {
|
||||
a {
|
||||
color: var(--text-primary-color, white);
|
||||
}
|
||||
hui-lovelace,
|
||||
hui-view-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
@@ -1405,21 +1261,17 @@ class HUIRoot extends LitElement {
|
||||
padding-inline-end: var(--safe-area-inset-right);
|
||||
padding-bottom: var(--safe-area-inset-bottom);
|
||||
}
|
||||
.narrow hui-lovelace,
|
||||
.narrow hui-view-container {
|
||||
padding-left: var(--safe-area-inset-left);
|
||||
padding-inline-start: var(--safe-area-inset-left);
|
||||
}
|
||||
hui-view-container > * {
|
||||
flex: 1 1 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
/**
|
||||
* In edit mode we have the tab bar on a new line *
|
||||
* In edit mode we have the tab bar on a new line
|
||||
*/
|
||||
hui-view-container.has-tab-bar {
|
||||
hui-lovelace.has-tab-bar {
|
||||
padding-top: calc(
|
||||
var(--header-height, 56px) +
|
||||
calc(var(--tab-bar-height, 56px) - 2px) +
|
||||
var(--header-height, 56px) + var(--tab-bar-height, 56px) - 2px +
|
||||
var(--safe-area-inset-top, 0px)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,8 +11,7 @@ import { haStyle } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { generateLovelaceViewStrategy } from "../lovelace/strategies/get-strategy";
|
||||
import type { Lovelace } from "../lovelace/types";
|
||||
import "../lovelace/views/hui-view";
|
||||
import "../lovelace/views/hui-view-container";
|
||||
import "../lovelace/hui-lovelace";
|
||||
|
||||
const SECURITY_LOVELACE_VIEW_CONFIG: LovelaceStrategyViewConfig = {
|
||||
strategy: {
|
||||
@@ -26,8 +25,6 @@ class PanelSecurity extends LitElement {
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public narrow = false;
|
||||
|
||||
@state() private _viewIndex = 0;
|
||||
|
||||
@state() private _lovelace?: Lovelace;
|
||||
|
||||
@state() private _searchParms = new URLSearchParams(window.location.search);
|
||||
@@ -92,40 +89,33 @@ class PanelSecurity extends LitElement {
|
||||
return html`
|
||||
<div class="header">
|
||||
<div class="toolbar">
|
||||
${
|
||||
this._searchParms.has("historyBack")
|
||||
? html`
|
||||
<ha-icon-button-arrow-prev
|
||||
@click=${this._back}
|
||||
slot="navigationIcon"
|
||||
></ha-icon-button-arrow-prev>
|
||||
`
|
||||
: html`
|
||||
<ha-menu-button
|
||||
slot="navigationIcon"
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
></ha-menu-button>
|
||||
`
|
||||
}
|
||||
${this._searchParms.has("historyBack")
|
||||
? html`
|
||||
<ha-icon-button-arrow-prev
|
||||
@click=${this._back}
|
||||
slot="navigationIcon"
|
||||
></ha-icon-button-arrow-prev>
|
||||
`
|
||||
: html`
|
||||
<ha-menu-button
|
||||
slot="navigationIcon"
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
></ha-menu-button>
|
||||
`}
|
||||
<div class="main-title">${this.hass.localize("panel.security")}</div>
|
||||
</div>
|
||||
</div>
|
||||
${
|
||||
this._lovelace
|
||||
? html`
|
||||
<hui-view-container .hass=${this.hass}>
|
||||
<hui-view
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.lovelace=${this._lovelace}
|
||||
.index=${this._viewIndex}
|
||||
></hui-view
|
||||
></hui-view-container>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
</hui-view-container>
|
||||
${this._lovelace
|
||||
? html`
|
||||
<hui-lovelace
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.lovelace=${this._lovelace}
|
||||
.curView=${0}
|
||||
></hui-lovelace>
|
||||
`
|
||||
: nothing}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -143,8 +133,8 @@ class PanelSecurity extends LitElement {
|
||||
}
|
||||
|
||||
this._lovelace = {
|
||||
config: config,
|
||||
rawConfig: rawConfig,
|
||||
config,
|
||||
rawConfig,
|
||||
editMode: false,
|
||||
urlPath: "security",
|
||||
mode: "generated",
|
||||
@@ -223,7 +213,7 @@ class PanelSecurity extends LitElement {
|
||||
line-height: var(--ha-line-height-normal);
|
||||
flex-grow: 1;
|
||||
}
|
||||
hui-view-container {
|
||||
hui-lovelace {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
@@ -233,14 +223,10 @@ class PanelSecurity extends LitElement {
|
||||
padding-inline-end: var(--safe-area-inset-right);
|
||||
padding-bottom: var(--safe-area-inset-bottom);
|
||||
}
|
||||
:host([narrow]) hui-view-container {
|
||||
:host([narrow]) hui-lovelace {
|
||||
padding-left: var(--safe-area-inset-left);
|
||||
padding-inline-start: var(--safe-area-inset-left);
|
||||
}
|
||||
hui-view {
|
||||
flex: 1 1 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
"profile": "Profile",
|
||||
"light": "Lights",
|
||||
"security": "Security",
|
||||
"climate": "Climate"
|
||||
"climate": "Climate",
|
||||
"home": "Home"
|
||||
},
|
||||
"state": {
|
||||
"default": {
|
||||
|
||||
Reference in New Issue
Block a user