mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-27 06:47:20 +00:00
803 lines
27 KiB
TypeScript
803 lines
27 KiB
TypeScript
import "@material/mwc-button";
|
|
import "@material/mwc-list/mwc-list-item";
|
|
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
|
|
import {
|
|
mdiClose,
|
|
mdiDotsVertical,
|
|
mdiHelpCircle,
|
|
mdiMicrophone,
|
|
mdiPencil,
|
|
mdiPlus,
|
|
} from "@mdi/js";
|
|
import "@polymer/app-layout/app-header/app-header";
|
|
import "@polymer/app-layout/app-scroll-effects/effects/waterfall";
|
|
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
|
import "@polymer/paper-tabs/paper-tab";
|
|
import "@polymer/paper-tabs/paper-tabs";
|
|
import {
|
|
css,
|
|
CSSResult,
|
|
html,
|
|
internalProperty,
|
|
LitElement,
|
|
property,
|
|
PropertyValues,
|
|
TemplateResult,
|
|
} from "lit-element";
|
|
import { classMap } from "lit-html/directives/class-map";
|
|
import memoizeOne from "memoize-one";
|
|
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
|
import { fireEvent } from "../../common/dom/fire_event";
|
|
import scrollToTarget from "../../common/dom/scroll-to-target";
|
|
import { shouldHandleRequestSelectedEvent } from "../../common/mwc/handle-request-selected-event";
|
|
import { navigate } from "../../common/navigate";
|
|
import { computeRTLDirection } from "../../common/util/compute_rtl";
|
|
import { debounce } from "../../common/util/debounce";
|
|
import { afterNextRender } from "../../common/util/render-status";
|
|
import "../../components/ha-button-menu";
|
|
import "../../components/ha-icon";
|
|
import "../../components/ha-icon-button-arrow-next";
|
|
import "../../components/ha-icon-button-arrow-prev";
|
|
import "../../components/ha-menu-button";
|
|
import "../../components/ha-svg-icon";
|
|
import type {
|
|
LovelaceConfig,
|
|
LovelacePanelConfig,
|
|
LovelaceViewConfig,
|
|
} from "../../data/lovelace";
|
|
import {
|
|
showAlertDialog,
|
|
showConfirmationDialog,
|
|
} from "../../dialogs/generic/show-dialog-box";
|
|
import { showVoiceCommandDialog } from "../../dialogs/voice-command-dialog/show-ha-voice-command-dialog";
|
|
import "../../layouts/ha-app-layout";
|
|
import { haStyle } from "../../resources/styles";
|
|
import type { HomeAssistant } from "../../types";
|
|
import { documentationUrl } from "../../util/documentation-url";
|
|
import { swapView } from "./editor/config-util";
|
|
import { showEditLovelaceDialog } from "./editor/lovelace-editor/show-edit-lovelace-dialog";
|
|
import { showEditViewDialog } from "./editor/view-editor/show-edit-view-dialog";
|
|
import type { Lovelace } from "./types";
|
|
import "./views/hui-panel-view";
|
|
import type { HUIPanelView } from "./views/hui-panel-view";
|
|
import { HUIView } from "./views/hui-view";
|
|
|
|
class HUIRoot extends LitElement {
|
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
|
|
@property({ attribute: false }) public lovelace?: Lovelace;
|
|
|
|
@property() public columns?: number;
|
|
|
|
@property({ type: Boolean }) public narrow = false;
|
|
|
|
@property() public route?: { path: string; prefix: string };
|
|
|
|
@internalProperty() private _curView?: number | "hass-unused-entities";
|
|
|
|
private _viewCache?: { [viewId: string]: HUIView };
|
|
|
|
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
|
|
);
|
|
}
|
|
|
|
protected render(): TemplateResult {
|
|
return html`
|
|
<ha-app-layout id="layout">
|
|
<app-header
|
|
slot="header"
|
|
effects="waterfall"
|
|
class=${classMap({
|
|
"edit-mode": this._editMode,
|
|
})}
|
|
fixed
|
|
condenses
|
|
>
|
|
${this._editMode
|
|
? html`
|
|
<app-toolbar class="edit-mode">
|
|
<mwc-icon-button
|
|
.label="${this.hass!.localize(
|
|
"ui.panel.lovelace.menu.exit_edit_mode"
|
|
)}"
|
|
title="${this.hass!.localize(
|
|
"ui.panel.lovelace.menu.close"
|
|
)}"
|
|
@click="${this._editModeDisable}"
|
|
>
|
|
<ha-svg-icon path=${mdiClose}></ha-svg-icon>
|
|
</mwc-icon-button>
|
|
<div main-title>
|
|
${this.config.title ||
|
|
this.hass!.localize("ui.panel.lovelace.editor.header")}
|
|
<mwc-icon-button
|
|
aria-label="${this.hass!.localize(
|
|
"ui.panel.lovelace.editor.edit_lovelace.edit_title"
|
|
)}"
|
|
title="${this.hass!.localize(
|
|
"ui.panel.lovelace.editor.edit_lovelace.edit_title"
|
|
)}"
|
|
class="edit-icon"
|
|
@click="${this._editLovelace}"
|
|
>
|
|
<ha-svg-icon path=${mdiPencil}></ha-svg-icon>
|
|
</mwc-icon-button>
|
|
</div>
|
|
<a
|
|
href="${documentationUrl(this.hass, "/lovelace/")}"
|
|
rel="noreferrer"
|
|
class="menu-link"
|
|
target="_blank"
|
|
>
|
|
<mwc-icon-button
|
|
title="${this.hass!.localize(
|
|
"ui.panel.lovelace.menu.help"
|
|
)}"
|
|
>
|
|
<ha-svg-icon path=${mdiHelpCircle}></ha-svg-icon>
|
|
</mwc-icon-button>
|
|
</a>
|
|
<ha-button-menu corner="BOTTOM_START">
|
|
<mwc-icon-button
|
|
slot="trigger"
|
|
.title="${this.hass!.localize(
|
|
"ui.panel.lovelace.editor.menu.open"
|
|
)}"
|
|
.label=${this.hass!.localize(
|
|
"ui.panel.lovelace.editor.menu.open"
|
|
)}
|
|
>
|
|
<ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon>
|
|
</mwc-icon-button>
|
|
${__DEMO__ /* No unused entities available in the demo */
|
|
? ""
|
|
: html`
|
|
<mwc-list-item
|
|
aria-label=${this.hass!.localize(
|
|
"ui.panel.lovelace.unused_entities.title"
|
|
)}
|
|
@request-selected="${this._handleUnusedEntities}"
|
|
>
|
|
${this.hass!.localize(
|
|
"ui.panel.lovelace.unused_entities.title"
|
|
)}
|
|
</mwc-list-item>
|
|
`}
|
|
<mwc-list-item @request-selected="${this._handleRawEditor}">
|
|
${this.hass!.localize(
|
|
"ui.panel.lovelace.editor.menu.raw_editor"
|
|
)}
|
|
</mwc-list-item>
|
|
</ha-button-menu>
|
|
</app-toolbar>
|
|
`
|
|
: html`
|
|
<app-toolbar>
|
|
<ha-menu-button
|
|
.hass=${this.hass}
|
|
.narrow=${this.narrow}
|
|
></ha-menu-button>
|
|
<div main-title>${this.config.title || "Home Assistant"}</div>
|
|
${this._conversation(this.hass.config.components)
|
|
? html`
|
|
<mwc-icon-button
|
|
label="Start conversation"
|
|
@click=${this._showVoiceCommandDialog}
|
|
>
|
|
<ha-svg-icon path=${mdiMicrophone}></ha-svg-icon>
|
|
</mwc-icon-button>
|
|
`
|
|
: ""}
|
|
<ha-button-menu corner="BOTTOM_START">
|
|
<mwc-icon-button
|
|
slot="trigger"
|
|
.label=${this.hass!.localize(
|
|
"ui.panel.lovelace.editor.menu.open"
|
|
)}
|
|
.title="${this.hass!.localize(
|
|
"ui.panel.lovelace.editor.menu.open"
|
|
)}"
|
|
>
|
|
<ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon>
|
|
</mwc-icon-button>
|
|
${this._yamlMode
|
|
? html`
|
|
<mwc-list-item
|
|
aria-label=${this.hass!.localize(
|
|
"ui.panel.lovelace.menu.refresh"
|
|
)}
|
|
@request-selected="${this._handleRefresh}"
|
|
>
|
|
${this.hass!.localize(
|
|
"ui.panel.lovelace.menu.refresh"
|
|
)}
|
|
</mwc-list-item>
|
|
<mwc-list-item
|
|
aria-label=${this.hass!.localize(
|
|
"ui.panel.lovelace.unused_entities.title"
|
|
)}
|
|
@request-selected="${this._handleUnusedEntities}"
|
|
>
|
|
${this.hass!.localize(
|
|
"ui.panel.lovelace.unused_entities.title"
|
|
)}
|
|
</mwc-list-item>
|
|
`
|
|
: ""}
|
|
${(this.hass.panels.lovelace?.config as LovelacePanelConfig)
|
|
?.mode === "yaml"
|
|
? html`
|
|
<mwc-list-item
|
|
aria-label=${this.hass!.localize(
|
|
"ui.panel.lovelace.menu.reload_resources"
|
|
)}
|
|
@request-selected=${this._handleReloadResources}
|
|
>
|
|
${this.hass!.localize(
|
|
"ui.panel.lovelace.menu.reload_resources"
|
|
)}
|
|
</mwc-list-item>
|
|
`
|
|
: ""}
|
|
${this.hass!.user?.is_admin && !this.hass!.config.safe_mode
|
|
? html`
|
|
<mwc-list-item
|
|
aria-label=${this.hass!.localize(
|
|
"ui.panel.lovelace.menu.configure_ui"
|
|
)}
|
|
@request-selected=${this._handleEnableEditMode}
|
|
>
|
|
${this.hass!.localize(
|
|
"ui.panel.lovelace.menu.configure_ui"
|
|
)}
|
|
</mwc-list-item>
|
|
`
|
|
: ""}
|
|
<a
|
|
href="${documentationUrl(this.hass, "/lovelace/")}"
|
|
rel="noreferrer"
|
|
class="menu-link"
|
|
target="_blank"
|
|
>
|
|
<mwc-list-item
|
|
aria-label=${this.hass!.localize(
|
|
"ui.panel.lovelace.menu.help"
|
|
)}
|
|
>
|
|
${this.hass!.localize("ui.panel.lovelace.menu.help")}
|
|
</mwc-list-item>
|
|
</a>
|
|
</ha-button-menu>
|
|
</app-toolbar>
|
|
`}
|
|
${this.lovelace!.config.views.length > 1 || this._editMode
|
|
? html`
|
|
<div sticky>
|
|
<paper-tabs
|
|
scrollable
|
|
.selected="${this._curView}"
|
|
@iron-activate="${this._handleViewSelected}"
|
|
dir="${computeRTLDirection(this.hass!)}"
|
|
>
|
|
${this.lovelace!.config.views.map(
|
|
(view) => html`
|
|
<paper-tab
|
|
aria-label="${view.title}"
|
|
class="${classMap({
|
|
"hide-tab": Boolean(
|
|
!this._editMode &&
|
|
view.visible !== undefined &&
|
|
((Array.isArray(view.visible) &&
|
|
!view.visible.some(
|
|
(e) => e.user === this.hass!.user!.id
|
|
)) ||
|
|
view.visible === false)
|
|
),
|
|
})}"
|
|
>
|
|
${this._editMode
|
|
? html`
|
|
<ha-icon-button-arrow-prev
|
|
title="${this.hass!.localize(
|
|
"ui.panel.lovelace.editor.edit_view.move_left"
|
|
)}"
|
|
class="edit-icon view"
|
|
@click="${this._moveViewLeft}"
|
|
?disabled="${this._curView === 0}"
|
|
></ha-icon-button-arrow-prev>
|
|
`
|
|
: ""}
|
|
${view.icon
|
|
? html`
|
|
<ha-icon
|
|
title="${view.title}"
|
|
.icon="${view.icon}"
|
|
></ha-icon>
|
|
`
|
|
: view.title || "Unnamed view"}
|
|
${this._editMode
|
|
? html`
|
|
<ha-svg-icon
|
|
title="${this.hass!.localize(
|
|
"ui.panel.lovelace.editor.edit_view.edit"
|
|
)}"
|
|
class="edit-icon view"
|
|
path=${mdiPencil}
|
|
@click="${this._editView}"
|
|
></ha-svg-icon>
|
|
<ha-icon-button-arrow-next
|
|
title="${this.hass!.localize(
|
|
"ui.panel.lovelace.editor.edit_view.move_right"
|
|
)}"
|
|
class="edit-icon view"
|
|
@click="${this._moveViewRight}"
|
|
?disabled="${(this._curView! as number) +
|
|
1 ===
|
|
this.lovelace!.config.views.length}"
|
|
></ha-icon-button-arrow-next>
|
|
`
|
|
: ""}
|
|
</paper-tab>
|
|
`
|
|
)}
|
|
${this._editMode
|
|
? html`
|
|
<mwc-icon-button
|
|
id="add-view"
|
|
@click="${this._addView}"
|
|
title="${this.hass!.localize(
|
|
"ui.panel.lovelace.editor.edit_view.add"
|
|
)}"
|
|
>
|
|
<ha-svg-icon path=${mdiPlus}></ha-svg-icon>
|
|
</mwc-icon-button>
|
|
`
|
|
: ""}
|
|
</paper-tabs>
|
|
</div>
|
|
`
|
|
: ""}
|
|
</app-header>
|
|
<div
|
|
id="view"
|
|
class="${classMap({
|
|
"tabs-hidden":
|
|
!this._editMode && this.lovelace!.config.views.length < 2,
|
|
})}"
|
|
@ll-rebuild="${this._debouncedConfigChanged}"
|
|
></div>
|
|
</ha-app-layout>
|
|
`;
|
|
}
|
|
|
|
private _isVisible = (view: LovelaceViewConfig) =>
|
|
Boolean(
|
|
this._editMode ||
|
|
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);
|
|
|
|
const view = this._viewRoot;
|
|
const huiView = view.lastChild as HUIView | HUIPanelView;
|
|
|
|
if (
|
|
changedProperties.has("columns") &&
|
|
huiView &&
|
|
huiView instanceof HUIView
|
|
) {
|
|
huiView.columns = this.columns;
|
|
}
|
|
|
|
if (changedProperties.has("hass") && huiView) {
|
|
huiView.hass = this.hass;
|
|
}
|
|
|
|
let newSelectView;
|
|
let force = false;
|
|
|
|
const viewPath = this.route!.path.split("/")[1];
|
|
|
|
if (changedProperties.has("route")) {
|
|
const views = this.config.views;
|
|
|
|
if (!viewPath && views.length) {
|
|
newSelectView = views.findIndex(this._isVisible);
|
|
navigate(
|
|
this,
|
|
`${this.route!.prefix}/${views[newSelectView].path || newSelectView}`,
|
|
true
|
|
);
|
|
} else if (viewPath === "hass-unused-entities") {
|
|
newSelectView = "hass-unused-entities";
|
|
} else if (viewPath) {
|
|
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;
|
|
}
|
|
}
|
|
newSelectView = index;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (!oldLovelace || oldLovelace.editMode !== this.lovelace!.editMode) {
|
|
const views = this.config && this.config.views;
|
|
|
|
// Adjust for higher header
|
|
if (!views || views.length < 2) {
|
|
fireEvent(this, "iron-resize");
|
|
}
|
|
|
|
// Leave unused entities when leaving edit mode
|
|
if (
|
|
this.lovelace!.mode === "storage" &&
|
|
viewPath === "hass-unused-entities"
|
|
) {
|
|
newSelectView = views.findIndex(this._isVisible);
|
|
navigate(
|
|
this,
|
|
`${this.route!.prefix}/${
|
|
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(() => this._selectView(newSelectView, force));
|
|
}
|
|
}
|
|
|
|
private get config(): LovelaceConfig {
|
|
return this.lovelace!.config;
|
|
}
|
|
|
|
private get _yamlMode(): boolean {
|
|
return this.lovelace!.mode === "yaml";
|
|
}
|
|
|
|
private get _editMode() {
|
|
return this.lovelace!.editMode;
|
|
}
|
|
|
|
private get _layout(): any {
|
|
return this.shadowRoot!.getElementById("layout");
|
|
}
|
|
|
|
private get _viewRoot(): HTMLDivElement {
|
|
return this.shadowRoot!.getElementById("view") as HTMLDivElement;
|
|
}
|
|
|
|
private _handleRefresh(ev: CustomEvent<RequestSelectedDetail>): void {
|
|
if (!shouldHandleRequestSelectedEvent(ev)) {
|
|
return;
|
|
}
|
|
fireEvent(this, "config-refresh");
|
|
}
|
|
|
|
private _handleReloadResources(ev: CustomEvent<RequestSelectedDetail>): void {
|
|
if (!shouldHandleRequestSelectedEvent(ev)) {
|
|
return;
|
|
}
|
|
this.hass.callService("lovelace", "reload_resources");
|
|
showConfirmationDialog(this, {
|
|
title: this.hass!.localize(
|
|
"ui.panel.lovelace.reload_resources.refresh_header"
|
|
),
|
|
text: this.hass!.localize(
|
|
"ui.panel.lovelace.reload_resources.refresh_body"
|
|
),
|
|
confirm: () => location.reload(),
|
|
});
|
|
}
|
|
|
|
private _handleRawEditor(ev: CustomEvent<RequestSelectedDetail>): void {
|
|
if (!shouldHandleRequestSelectedEvent(ev)) {
|
|
return;
|
|
}
|
|
this.lovelace!.enableFullEditMode();
|
|
}
|
|
|
|
private _handleUnusedEntities(ev: CustomEvent<RequestSelectedDetail>): void {
|
|
if (!shouldHandleRequestSelectedEvent(ev)) {
|
|
return;
|
|
}
|
|
navigate(this, `${this.route?.prefix}/hass-unused-entities`);
|
|
}
|
|
|
|
private _showVoiceCommandDialog(): void {
|
|
showVoiceCommandDialog(this);
|
|
}
|
|
|
|
private _handleEnableEditMode(ev: CustomEvent<RequestSelectedDetail>): void {
|
|
if (!shouldHandleRequestSelectedEvent(ev)) {
|
|
return;
|
|
}
|
|
if (this._yamlMode) {
|
|
showAlertDialog(this, {
|
|
text: "The edit UI is not available when in YAML mode.",
|
|
});
|
|
return;
|
|
}
|
|
this._enableEditMode();
|
|
}
|
|
|
|
private _enableEditMode(): void {
|
|
this.lovelace!.setEditMode(true);
|
|
}
|
|
|
|
private _editModeDisable(): void {
|
|
this.lovelace!.setEditMode(false);
|
|
}
|
|
|
|
private _editLovelace() {
|
|
showEditLovelaceDialog(this, this.lovelace!);
|
|
}
|
|
|
|
private _editView() {
|
|
showEditViewDialog(this, {
|
|
lovelace: this.lovelace!,
|
|
viewIndex: this._curView as number,
|
|
});
|
|
}
|
|
|
|
private _moveViewLeft() {
|
|
if (this._curView === 0) {
|
|
return;
|
|
}
|
|
const lovelace = this.lovelace!;
|
|
const oldIndex = this._curView as number;
|
|
const newIndex = (this._curView as number) - 1;
|
|
this._curView = newIndex;
|
|
lovelace.saveConfig(swapView(lovelace.config, oldIndex, newIndex));
|
|
}
|
|
|
|
private _moveViewRight() {
|
|
if ((this._curView! as number) + 1 === this.lovelace!.config.views.length) {
|
|
return;
|
|
}
|
|
const lovelace = this.lovelace!;
|
|
const oldIndex = this._curView as number;
|
|
const newIndex = (this._curView as number) + 1;
|
|
this._curView = newIndex;
|
|
lovelace.saveConfig(swapView(lovelace.config, oldIndex, newIndex));
|
|
}
|
|
|
|
private _addView() {
|
|
showEditViewDialog(this, {
|
|
lovelace: this.lovelace!,
|
|
saveCallback: (viewIndex: number, viewConfig: LovelaceViewConfig) => {
|
|
const path = viewConfig.path || viewIndex;
|
|
navigate(this, `${this.route?.prefix}/${path}`);
|
|
},
|
|
});
|
|
}
|
|
|
|
private _handleViewSelected(ev) {
|
|
const viewIndex = ev.detail.selected as number;
|
|
|
|
if (viewIndex !== this._curView) {
|
|
const path = this.config.views[viewIndex].path || viewIndex;
|
|
navigate(this, `${this.route?.prefix}/${path}`);
|
|
}
|
|
scrollToTarget(this, this._layout.header.scrollTarget);
|
|
}
|
|
|
|
private _selectView(viewIndex: HUIRoot["_curView"], force: boolean): void {
|
|
if (!force && this._curView === viewIndex) {
|
|
return;
|
|
}
|
|
|
|
viewIndex = viewIndex === undefined ? 0 : viewIndex;
|
|
|
|
this._curView = viewIndex;
|
|
|
|
if (force) {
|
|
this._viewCache = {};
|
|
}
|
|
|
|
// 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(
|
|
/* webpackChunkName: "hui-unused-entities" */ "./editor/unused-entities/hui-unused-entities"
|
|
).then(() => {
|
|
unusedEntities.hass = this.hass!;
|
|
unusedEntities.lovelace = this.lovelace!;
|
|
unusedEntities.narrow = this.narrow;
|
|
});
|
|
if (this.config.background) {
|
|
unusedEntities.style.setProperty(
|
|
"--lovelace-background",
|
|
this.config.background
|
|
);
|
|
}
|
|
root.appendChild(unusedEntities);
|
|
return;
|
|
}
|
|
|
|
let view;
|
|
const viewConfig = this.config.views[viewIndex];
|
|
|
|
if (!viewConfig) {
|
|
this._enableEditMode();
|
|
return;
|
|
}
|
|
|
|
if (!force && this._viewCache![viewIndex]) {
|
|
view = this._viewCache![viewIndex];
|
|
} else {
|
|
if (viewConfig.panel && viewConfig.cards && viewConfig.cards.length > 0) {
|
|
view = document.createElement("hui-panel-view");
|
|
view.config = viewConfig;
|
|
view.index = viewIndex;
|
|
} else {
|
|
view = document.createElement("hui-view");
|
|
view.columns = this.columns;
|
|
view.index = viewIndex;
|
|
}
|
|
this._viewCache![viewIndex] = view;
|
|
}
|
|
|
|
view.lovelace = this.lovelace;
|
|
view.hass = this.hass;
|
|
|
|
const configBackground = viewConfig.background || this.config.background;
|
|
|
|
if (configBackground) {
|
|
view.style.setProperty("--lovelace-background", configBackground);
|
|
}
|
|
|
|
root.appendChild(view);
|
|
// Recalculate to see if we need to adjust content area for tab bar
|
|
fireEvent(this, "iron-resize");
|
|
}
|
|
|
|
static get styles(): CSSResult[] {
|
|
return [
|
|
haStyle,
|
|
css`
|
|
:host {
|
|
--dark-color: #455a64;
|
|
--text-dark-color: #fff;
|
|
-ms-user-select: none;
|
|
-webkit-user-select: none;
|
|
-moz-user-select: none;
|
|
}
|
|
|
|
ha-app-layout {
|
|
min-height: 100%;
|
|
}
|
|
paper-tabs {
|
|
margin-left: max(env(safe-area-inset-left), 12px);
|
|
margin-right: env(safe-area-inset-right);
|
|
--paper-tabs-selection-bar-color: var(--text-primary-color, #fff);
|
|
text-transform: uppercase;
|
|
}
|
|
.edit-mode {
|
|
background-color: var(--dark-color, #455a64);
|
|
color: var(--text-dark-color);
|
|
}
|
|
.edit-mode div[main-title] {
|
|
pointer-events: auto;
|
|
}
|
|
paper-tab.iron-selected .edit-icon {
|
|
display: inline-flex;
|
|
}
|
|
.edit-icon {
|
|
color: var(--accent-color);
|
|
padding-left: 8px;
|
|
vertical-align: middle;
|
|
--mdc-theme-text-disabled-on-light: var(--disabled-text-color);
|
|
}
|
|
.edit-icon.view {
|
|
display: none;
|
|
}
|
|
#add-view {
|
|
position: absolute;
|
|
height: 44px;
|
|
}
|
|
#add-view ha-svg-icon {
|
|
background-color: var(--accent-color);
|
|
border-radius: 4px;
|
|
}
|
|
app-toolbar a {
|
|
color: var(--text-primary-color, white);
|
|
}
|
|
mwc-button.warning:not([disabled]) {
|
|
color: var(--error-color);
|
|
}
|
|
#view {
|
|
min-height: calc(100vh - 112px);
|
|
/**
|
|
* Since we only set min-height, if child nodes need percentage
|
|
* heights they must use absolute positioning so we need relative
|
|
* positioning here.
|
|
*
|
|
* https://www.w3.org/TR/CSS2/visudet.html#the-height-property
|
|
*/
|
|
position: relative;
|
|
display: flex;
|
|
}
|
|
#view > * {
|
|
/**
|
|
* The view could get larger than the window in Firefox
|
|
* to prevent that we set the max-width to 100%
|
|
* flex-grow: 1 and flex-basis: 100% should make sure the view
|
|
* stays full width.
|
|
*
|
|
* https://github.com/home-assistant/home-assistant-polymer/pull/3806
|
|
*/
|
|
flex: 1 1 100%;
|
|
max-width: 100%;
|
|
}
|
|
#view.tabs-hidden {
|
|
min-height: calc(100vh - 64px);
|
|
}
|
|
.hide-tab {
|
|
display: none;
|
|
}
|
|
.menu-link {
|
|
text-decoration: none;
|
|
}
|
|
`,
|
|
];
|
|
}
|
|
}
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
"hui-root": HUIRoot;
|
|
}
|
|
}
|
|
|
|
customElements.define("hui-root", HUIRoot);
|