Edit + hide header

This commit is contained in:
Zack 2022-05-10 10:28:35 -05:00
parent 552c474feb
commit f6d92c4b0a
8 changed files with 633 additions and 454 deletions

View File

@ -53,6 +53,7 @@ interface LovelaceGenericDashboard {
show_in_sidebar: boolean;
icon?: string;
title: string;
hide_header?: boolean;
}
export interface LovelaceYamlDashboard extends LovelaceGenericDashboard {
@ -69,6 +70,7 @@ export interface LovelaceDashboardMutableParams {
show_in_sidebar: boolean;
icon?: string;
title: string;
hide_header?: boolean;
}
export interface LovelaceDashboardCreateParams

View File

@ -45,6 +45,7 @@ export class DialogLovelaceDashboardDetail extends LitElement {
title: "",
require_admin: false,
mode: "storage",
hide_header: false,
};
}
}
@ -194,6 +195,13 @@ export class DialogLovelaceDashboardDetail extends LitElement {
boolean: {},
},
},
{
name: "hide_header",
required: true,
selector: {
boolean: {},
},
},
].filter(Boolean)
);
@ -270,6 +278,7 @@ export class DialogLovelaceDashboardDetail extends LitElement {
show_in_sidebar: this._data!.show_in_sidebar,
icon: this._data!.icon || undefined,
title: this._data!.title,
hide_header: this._data!.hide_header || false,
};
await this._params!.updateDashboard(values);
} else {

View File

@ -3,6 +3,7 @@ import {
mdiCheckCircleOutline,
mdiDotsVertical,
mdiOpenInNew,
mdiPencil,
mdiPlus,
} from "@mdi/js";
import "@polymer/paper-tooltip/paper-tooltip";
@ -13,10 +14,8 @@ import memoize from "memoize-one";
import { isComponentLoaded } from "../../../../common/config/is_component_loaded";
import { navigate } from "../../../../common/navigate";
import { stringCompare } from "../../../../common/string/compare";
import {
DataTableColumnContainer,
RowClickedEvent,
} from "../../../../components/data-table/ha-data-table";
import { addSearchParam } from "../../../../common/url/search-params";
import { DataTableColumnContainer } from "../../../../components/data-table/ha-data-table";
import "../../../../components/ha-clickable-list-item";
import "../../../../components/ha-fab";
import "../../../../components/ha-icon";
@ -31,7 +30,10 @@ import {
LovelacePanelConfig,
updateDashboard,
} from "../../../../data/lovelace";
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
import {
showAlertDialog,
showConfirmationDialog,
} from "../../../../dialogs/generic/show-dialog-box";
import "../../../../layouts/hass-loading-screen";
import "../../../../layouts/hass-tabs-subpage-data-table";
import { HomeAssistant, Route } from "../../../../types";
@ -42,11 +44,11 @@ import { showDashboardDetailDialog } from "./show-dialog-lovelace-dashboard-deta
export class HaConfigLovelaceDashboards extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public isWide!: boolean;
@property({ type: Boolean }) public isWide!: boolean;
@property() public narrow!: boolean;
@property({ type: Boolean }) public narrow!: boolean;
@property() public route!: Route;
@property({ attribute: false }) public route!: Route;
@state() private _dashboards: LovelaceDashboard[] = [];
@ -192,6 +194,37 @@ export class HaConfigLovelaceDashboards extends LitElement {
`,
};
columns.edit_path = {
title: "",
label: this.hass.localize(
"ui.panel.config.lovelace.dashboards.picker.headers.edit"
),
width: "100px",
template: (edit_path, dashboard) =>
narrow
? html`
<ha-icon-button
.path=${mdiPencil}
.urlPath=${edit_path}
.mode=${dashboard.mode}
@click=${this._editDashboard}
.label=${this.hass.localize(
"ui.panel.config.lovelace.dashboards.picker.edit"
)}
></ha-icon-button>
`
: html`
<mwc-button
.urlPath=${edit_path}
.mode=${dashboard.mode}
@click=${this._editDashboard}
>${this.hass.localize(
"ui.panel.config.lovelace.dashboards.picker.edit"
)}</mwc-button
>
`,
};
return columns;
}
);
@ -210,6 +243,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
show_in_sidebar: isDefault,
require_admin: false,
url_path: "lovelace",
edit_path: "lovelace?edit=1",
mode: defaultMode,
filename: defaultMode === "yaml" ? "ui-lovelace.yaml" : "",
iconColor: "var(--primary-color)",
@ -222,6 +256,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
show_in_sidebar: true,
mode: "storage",
url_path: "energy",
edit_path: "config/energy",
filename: "",
iconColor: "var(--label-badge-yellow)",
});
@ -232,6 +267,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
.sort((a, b) => stringCompare(a.title, b.title))
.map((dashboard) => ({
filename: "",
edit_path: `${dashboard.url_path}?${addSearchParam({ edit: "1" })}`,
...dashboard,
default: defaultUrlPath === dashboard.url_path,
}))
@ -257,10 +293,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
this._dashboards
)}
.data=${this._getItems(this._dashboards)}
@row-click=${this._editDashboard}
id="url_path"
hasFab
clickable
>
${this.hass.userData?.showAdvanced
? html`
@ -317,14 +350,13 @@ export class HaConfigLovelaceDashboards extends LitElement {
}
private _editDashboard(ev: CustomEvent) {
const urlPath = (ev.detail as RowClickedEvent).id;
if (urlPath === "energy") {
navigate("/config/energy");
if ((ev.detail as any).mode === "yaml") {
showAlertDialog(this, {
text: "The edit UI is not available when in YAML mode.",
});
return;
}
const dashboard = this._dashboards.find((res) => res.url_path === urlPath);
this._openDialog(dashboard, urlPath);
navigate(`/${(ev.target as any).urlPath}`);
}
private _addDashboard() {
@ -387,3 +419,9 @@ export class HaConfigLovelaceDashboards extends LitElement {
ev.currentTarget.blur();
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-config-lovelace-dashboards": HaConfigLovelaceDashboards;
}
}

View File

@ -1,14 +1,25 @@
import "@material/mwc-button";
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../common/dom/fire_event";
import { slugify } from "../../../../common/string/slugify";
import "../../../../components/ha-circular-progress";
import type { LovelaceConfig } from "../../../../data/lovelace";
import "../../../../components/ha-dialog";
import "../../../../components/ha-form/ha-form";
import { HaFormSchema } from "../../../../components/ha-form/types";
import { CoreFrontendUserData } from "../../../../data/frontend";
import {
LovelaceConfig,
LovelaceDashboard,
LovelaceDashboardMutableParams,
updateDashboard,
} from "../../../../data/lovelace";
import { haStyleDialog } from "../../../../resources/styles";
import type { HomeAssistant } from "../../../../types";
import { LovelaceDashboardDetailsDialogParams } from "../../../config/lovelace/dashboards/show-dialog-lovelace-dashboard-detail";
import type { Lovelace } from "../../types";
import "./hui-lovelace-editor";
import "../../../../components/ha-dialog";
@customElement("hui-dialog-edit-lovelace")
export class HuiDialogEditLovelace extends LitElement {
@ -18,7 +29,15 @@ export class HuiDialogEditLovelace extends LitElement {
@state() private _config?: LovelaceConfig;
private _saving = false;
@state() private _params?: LovelaceDashboardDetailsDialogParams;
@state() private _urlPathChanged = false;
@state() private _data?: Partial<LovelaceDashboard>;
@state() private _error?: Record<string, string>;
@state() private _submitting = false;
public showDialog(lovelace: Lovelace): void {
this._lovelace = lovelace;
@ -32,9 +51,10 @@ export class HuiDialogEditLovelace extends LitElement {
}
protected render(): TemplateResult {
if (!this._config) {
if (!this._config || !this.hass) {
return html``;
}
return html`
<ha-dialog
open
@ -52,16 +72,25 @@ export class HuiDialogEditLovelace extends LitElement {
@lovelace-config-changed=${this._ConfigChanged}
dialogInitialFocus
></hui-lovelace-editor>
<ha-form
.schema=${this._schema(this._params, this.hass!.userData)}
.data=${this._data}
.hass=${this.hass}
.error=${this._error}
.computeLabel=${this._computeLabel}
@value-changed=${this._valueChanged}
></ha-form>
</div>
<mwc-button @click=${this.closeDialog} slot="secondaryAction">
${this.hass!.localize("ui.common.cancel")}
</mwc-button>
<mwc-button
.disabled=${!this._config || this._saving}
.disabled=${!this._config || this._submitting}
@click=${this._save}
slot="primaryAction"
>
${this._saving
${this._submitting
? html`<ha-circular-progress
active
size="small"
@ -83,7 +112,7 @@ export class HuiDialogEditLovelace extends LitElement {
return;
}
this._saving = true;
this._submitting = true;
const lovelace = this._lovelace!;
const config: LovelaceConfig = {
@ -97,7 +126,7 @@ export class HuiDialogEditLovelace extends LitElement {
} catch (err: any) {
alert(`Saving failed: ${err.message}`);
} finally {
this._saving = false;
this._submitting = false;
}
}
@ -112,9 +141,130 @@ export class HuiDialogEditLovelace extends LitElement {
return JSON.stringify(this._config) !== JSON.stringify(lovelaceConfig);
}
static get styles(): CSSResultGroup {
return haStyleDialog;
private _schema = memoizeOne(
(
params: LovelaceDashboardDetailsDialogParams,
userData: CoreFrontendUserData | null | undefined
) =>
[
{
name: "title",
required: true,
selector: {
text: {},
},
},
{
name: "icon",
required: true,
selector: {
icon: {},
},
},
!params.dashboard &&
userData?.showAdvanced && {
name: "url_path",
required: true,
selector: { text: {} },
},
{
name: "require_admin",
required: true,
selector: {
boolean: {},
},
},
{
name: "show_in_sidebar",
required: true,
selector: {
boolean: {},
},
},
{
name: "hide_header",
required: true,
selector: {
boolean: {},
},
},
].filter(Boolean)
);
private _computeLabel = (entry: HaFormSchema): string =>
this.hass!.localize(
`ui.panel.config.lovelace.dashboards.detail.${
entry.name === "show_in_sidebar"
? "show_sidebar"
: entry.name === "url_path"
? "url"
: entry.name
}`
);
private _valueChanged(ev: CustomEvent) {
this._error = undefined;
const value = ev.detail.value;
if (value.url_path !== this._data?.url_path) {
this._urlPathChanged = true;
if (
!value.url_path ||
value.url_path === "lovelace" ||
!/^[a-zA-Z0-9_-]+-[a-zA-Z0-9_-]+$/.test(value.url_path)
) {
this._error = {
url_path: this.hass!.localize(
"ui.panel.config.lovelace.dashboards.detail.url_error_msg"
),
};
}
}
if (value.title !== this._data?.title) {
this._data = value;
this._fillUrlPath(value.title);
} else {
this._data = value;
}
}
private _fillUrlPath(title: string) {
if ((this.hass!.userData?.showAdvanced && this._urlPathChanged) || !title) {
return;
}
const slugifyTitle = slugify(title, "-");
this._data = {
...this._data,
url_path: slugifyTitle.includes("-")
? slugifyTitle
: `lovelace-${slugifyTitle}`,
};
}
private async _updateDashboard() {
if (this._params?.urlPath && !this._params.dashboard?.id) {
this.closeDialog();
}
this._submitting = true;
try {
const values: Partial<LovelaceDashboardMutableParams> = {
require_admin: this._data!.require_admin,
show_in_sidebar: this._data!.show_in_sidebar,
icon: this._data!.icon || undefined,
title: this._data!.title,
hide_header: this._data!.hide_header || false,
};
await updateDashboard(this.hass!, "", values);
this.closeDialog();
} catch (err: any) {
this._error = { base: err?.message || "Unknown error" };
} finally {
this._submitting = false;
}
}
static styles: CSSResultGroup = haStyleDialog;
}
declare global {

View File

@ -1,7 +1,7 @@
import "../../../../components/ha-textfield";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-textfield";
import { LovelaceConfig } from "../../../../data/lovelace";
import { HomeAssistant } from "../../../../types";
import { EditorTarget } from "../types";
@ -18,7 +18,7 @@ declare global {
export class HuiLovelaceEditor extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public config?: LovelaceConfig;
@property({ attribute: false }) public config?: LovelaceConfig;
get _title(): string {
if (!this.config) {

View File

@ -57,10 +57,7 @@ import type {
LovelacePanelConfig,
LovelaceViewConfig,
} from "../../data/lovelace";
import {
showAlertDialog,
showConfirmationDialog,
} from "../../dialogs/generic/show-dialog-box";
import { showConfirmationDialog } from "../../dialogs/generic/show-dialog-box";
import { showQuickBar } from "../../dialogs/quick-bar/show-dialog-quick-bar";
import { showVoiceCommandDialog } from "../../dialogs/voice-command-dialog/show-ha-voice-command-dialog";
import "../../layouts/ha-app-layout";
@ -108,6 +105,8 @@ class HUIRoot extends LitElement {
}
protected render(): TemplateResult {
const currentPanel = this.hass.panels[this.route!.prefix.substring(1)]!;
return html`
<ha-app-layout
class=${classMap({
@ -115,179 +114,47 @@ class HUIRoot extends LitElement {
})}
id="layout"
>
<app-header slot="header" effects="waterfall" fixed condenses>
${this._editMode
? html`
<app-toolbar class="edit-mode">
<div main-title>
${this.config.title ||
this.hass!.localize("ui.panel.lovelace.editor.header")}
<ha-icon-button
.label=${this.hass!.localize(
"ui.panel.lovelace.editor.edit_lovelace.edit_title"
)}
.path=${mdiPencil}
class="edit-icon"
@click=${this._editLovelace}
></ha-icon-button>
</div>
<mwc-button
outlined
class="exit-edit-mode"
.label=${this.hass!.localize(
"ui.panel.lovelace.menu.exit_edit_mode"
)}
@click=${this._editModeDisable}
></mwc-button>
<a
href=${documentationUrl(this.hass, "/lovelace/")}
rel="noreferrer"
class="menu-link"
target="_blank"
>
<ha-icon-button
.label=${this.hass!.localize(
"ui.panel.lovelace.menu.help"
)}
.path=${mdiHelpCircle}
></ha-icon-button>
</a>
<ha-button-menu corner="BOTTOM_START">
<ha-icon-button
slot="trigger"
.label=${this.hass!.localize(
"ui.panel.lovelace.editor.menu.open"
)}
.path=${mdiDotsVertical}
></ha-icon-button>
${__DEMO__ /* No unused entities available in the demo */
? ""
: html`
<mwc-list-item
graphic="icon"
aria-label=${this.hass!.localize(
"ui.panel.lovelace.unused_entities.title"
)}
@request-selected=${this._handleUnusedEntities}
>
<ha-svg-icon
slot="graphic"
.path=${mdiFormatListBulletedTriangle}
>
</ha-svg-icon>
${this.hass!.localize(
"ui.panel.lovelace.unused_entities.title"
)}
</mwc-list-item>
`}
<mwc-list-item
graphic="icon"
@request-selected=${this._handleRawEditor}
>
<ha-svg-icon
slot="graphic"
.path=${mdiCodeBraces}
></ha-svg-icon>
${this.hass!.localize(
"ui.panel.lovelace.editor.menu.raw_editor"
)}
</mwc-list-item>
${__DEMO__ /* No config available in the demo */
? ""
: html`<mwc-list-item
graphic="icon"
@request-selected=${this._handleManageDashboards}
>
<ha-svg-icon
slot="graphic"
.path=${mdiViewDashboard}
></ha-svg-icon>
${this.hass!.localize(
"ui.panel.lovelace.editor.menu.manage_dashboards"
)}
</mwc-list-item>
${this.hass.userData?.showAdvanced
? html`<mwc-list-item
graphic="icon"
@request-selected=${this._handleManageResources}
>
<ha-svg-icon
slot="graphic"
.path=${mdiFileMultiple}
></ha-svg-icon>
${this.hass!.localize(
"ui.panel.lovelace.editor.menu.manage_resources"
)}
</mwc-list-item>`
: ""} `}
</ha-button-menu>
</app-toolbar>
`
: html`
<app-toolbar>
<ha-menu-button
.hass=${this.hass}
.narrow=${this.narrow}
></ha-menu-button>
${this.lovelace!.config.views.length > 1
? html`
<ha-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(
view.visible !== undefined &&
((Array.isArray(view.visible) &&
!view.visible.some(
(e) => e.user === this.hass!.user!.id
)) ||
view.visible === false)
),
})}
>
${view.icon
? html`
<ha-icon
title=${view.title}
.icon=${view.icon}
></ha-icon>
`
: view.title || "Unnamed view"}
</paper-tab>
`
${this._editMode || !currentPanel.hide_header
? html`
<app-header slot="header" effects="waterfall" fixed condenses>
${this._editMode
? html`
<app-toolbar class="edit-mode">
<div main-title>
${this.config.title ||
this.hass!.localize(
"ui.panel.lovelace.editor.header"
)}
</ha-tabs>
`
: html`<div main-title>${this.config.title}</div>`}
${!this.narrow
? html`
<ha-icon-button
.path=${mdiMagnify}
@click=${this._showQuickBar}
></ha-icon-button>
`
: ""}
${!this.narrow &&
this._conversation(this.hass.config.components)
? html`
<ha-icon-button
<ha-icon-button
.label=${this.hass!.localize(
"ui.panel.lovelace.editor.edit_lovelace.edit_title"
)}
.path=${mdiPencil}
class="edit-icon"
@click=${this._editLovelace}
></ha-icon-button>
</div>
<mwc-button
outlined
class="exit-edit-mode"
.label=${this.hass!.localize(
"ui.panel.lovelace.menu.start_conversation"
"ui.panel.lovelace.menu.exit_edit_mode"
)}
.path=${mdiMicrophone}
@click=${this._showVoiceCommandDialog}
></ha-icon-button>
`
: ""}
${this._showButtonMenu
? html`
@click=${this._editModeDisable}
></mwc-button>
<a
href=${documentationUrl(this.hass, "/lovelace/")}
rel="noreferrer"
class="menu-link"
target="_blank"
>
<ha-icon-button
.label=${this.hass!.localize(
"ui.panel.lovelace.menu.help"
)}
.path=${mdiHelpCircle}
></ha-icon-button>
</a>
<ha-button-menu corner="BOTTOM_START">
<ha-icon-button
slot="trigger"
@ -296,255 +163,378 @@ class HUIRoot extends LitElement {
)}
.path=${mdiDotsVertical}
></ha-icon-button>
${this.narrow
? html`
${__DEMO__ /* No unused entities available in the demo */
? ""
: html`
<mwc-list-item
.label=${this.hass!.localize(
"ui.panel.lovelace.menu.search"
)}
graphic="icon"
@request-selected=${this._showQuickBar}
>
<span
>${this.hass!.localize(
"ui.panel.lovelace.menu.search"
)}</span
>
<ha-svg-icon
slot="graphic"
.path=${mdiMagnify}
></ha-svg-icon>
</mwc-list-item>
`
: ""}
${this.narrow &&
this._conversation(this.hass.config.components)
? html`
<mwc-list-item
.label=${this.hass!.localize(
"ui.panel.lovelace.menu.start_conversation"
)}
graphic="icon"
@request-selected=${this
._showVoiceCommandDialog}
>
<span
>${this.hass!.localize(
"ui.panel.lovelace.menu.start_conversation"
)}</span
>
<ha-svg-icon
slot="graphic"
.path=${mdiMicrophone}
></ha-svg-icon>
</mwc-list-item>
`
: ""}
${this._yamlMode
? html`
<mwc-list-item
aria-label=${this.hass!.localize(
"ui.common.refresh"
)}
graphic="icon"
@request-selected=${this._handleRefresh}
>
<span
>${this.hass!.localize(
"ui.common.refresh"
)}</span
>
<ha-svg-icon
slot="graphic"
.path=${mdiRefresh}
></ha-svg-icon>
</mwc-list-item>
<mwc-list-item
aria-label=${this.hass!.localize(
"ui.panel.lovelace.unused_entities.title"
)}
graphic="icon"
@request-selected=${this
._handleUnusedEntities}
>
<span
>${this.hass!.localize(
"ui.panel.lovelace.unused_entities.title"
)}</span
<ha-svg-icon
slot="graphic"
.path=${mdiFormatListBulletedTriangle}
>
<ha-svg-icon
slot="graphic"
.path=${mdiShape}
></ha-svg-icon>
</mwc-list-item>
`
: ""}
${(
this.hass.panels.lovelace
?.config as LovelacePanelConfig
)?.mode === "yaml"
? html`
<mwc-list-item
graphic="icon"
aria-label=${this.hass!.localize(
"ui.panel.lovelace.menu.reload_resources"
)}
@request-selected=${this
._handleReloadResources}
>
</ha-svg-icon>
${this.hass!.localize(
"ui.panel.lovelace.menu.reload_resources"
"ui.panel.lovelace.unused_entities.title"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiRefresh}
></ha-svg-icon>
</mwc-list-item>
`
: ""}
${this.hass!.user?.is_admin &&
!this.hass!.config.safe_mode
? html`
<mwc-list-item
graphic="icon"
aria-label=${this.hass!.localize(
"ui.panel.lovelace.menu.configure_ui"
)}
@request-selected=${this
._handleEnableEditMode}
>
${this.hass!.localize(
"ui.panel.lovelace.menu.configure_ui"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiPencil}
></ha-svg-icon>
</mwc-list-item>
`
: ""}
${this._editMode
? html`
<a
href=${documentationUrl(
this.hass,
"/lovelace/"
)}
rel="noreferrer"
class="menu-link"
target="_blank"
>
<mwc-list-item
graphic="icon"
aria-label=${this.hass!.localize(
"ui.panel.lovelace.menu.help"
)}
>
${this.hass!.localize(
"ui.panel.lovelace.menu.help"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiHelp}
></ha-svg-icon>
</mwc-list-item>
</a>
`
: ""}
</ha-button-menu>
`
: ""}
</app-toolbar>
`}
${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
.hass=${this.hass}
.label=${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
.hass=${this.hass}
.label=${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`
<ha-icon-button
id="add-view"
@click=${this._addView}
.label=${this.hass!.localize(
"ui.panel.lovelace.editor.edit_view.add"
`}
<mwc-list-item
graphic="icon"
@request-selected=${this._handleRawEditor}
>
<ha-svg-icon
slot="graphic"
.path=${mdiCodeBraces}
></ha-svg-icon>
${this.hass!.localize(
"ui.panel.lovelace.editor.menu.raw_editor"
)}
.path=${mdiPlus}
></ha-icon-button>
`
: ""}
</paper-tabs>
</div>
`
: ""}
</app-header>
</mwc-list-item>
${__DEMO__ /* No config available in the demo */
? ""
: html`<mwc-list-item
graphic="icon"
@request-selected=${this
._handleManageDashboards}
>
<ha-svg-icon
slot="graphic"
.path=${mdiViewDashboard}
></ha-svg-icon>
${this.hass!.localize(
"ui.panel.lovelace.editor.menu.manage_dashboards"
)}
</mwc-list-item>
${this.hass.userData?.showAdvanced
? html`<mwc-list-item
graphic="icon"
@request-selected=${this
._handleManageResources}
>
<ha-svg-icon
slot="graphic"
.path=${mdiFileMultiple}
></ha-svg-icon>
${this.hass!.localize(
"ui.panel.lovelace.editor.menu.manage_resources"
)}
</mwc-list-item>`
: ""} `}
</ha-button-menu>
</app-toolbar>
`
: html`
<app-toolbar>
<ha-menu-button
.hass=${this.hass}
.narrow=${this.narrow}
></ha-menu-button>
${this.lovelace!.config.views.length > 1
? html`
<ha-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(
view.visible !== undefined &&
((Array.isArray(view.visible) &&
!view.visible.some(
(e) =>
e.user === this.hass!.user!.id
)) ||
view.visible === false)
),
})}
>
${view.icon
? html`
<ha-icon
.title=${view.title}
.icon=${view.icon}
></ha-icon>
`
: view.title || "Unnamed view"}
</paper-tab>
`
)}
</ha-tabs>
`
: html`<div main-title>${this.config.title}</div>`}
${!this.narrow
? html`
<ha-icon-button
.path=${mdiMagnify}
@click=${this._showQuickBar}
></ha-icon-button>
`
: ""}
${!this.narrow &&
this._conversation(this.hass.config.components)
? html`
<ha-icon-button
.label=${this.hass!.localize(
"ui.panel.lovelace.menu.start_conversation"
)}
.path=${mdiMicrophone}
@click=${this._showVoiceCommandDialog}
></ha-icon-button>
`
: ""}
${this._showButtonMenu
? html`
<ha-button-menu corner="BOTTOM_START">
<ha-icon-button
slot="trigger"
.label=${this.hass!.localize(
"ui.panel.lovelace.editor.menu.open"
)}
.path=${mdiDotsVertical}
></ha-icon-button>
${this.narrow
? html`
<mwc-list-item
.label=${this.hass!.localize(
"ui.panel.lovelace.menu.search"
)}
graphic="icon"
@request-selected=${this._showQuickBar}
>
<span
>${this.hass!.localize(
"ui.panel.lovelace.menu.search"
)}</span
>
<ha-svg-icon
slot="graphic"
.path=${mdiMagnify}
></ha-svg-icon>
</mwc-list-item>
`
: ""}
${this.narrow &&
this._conversation(this.hass.config.components)
? html`
<mwc-list-item
.label=${this.hass!.localize(
"ui.panel.lovelace.menu.start_conversation"
)}
graphic="icon"
@request-selected=${this
._showVoiceCommandDialog}
>
<span
>${this.hass!.localize(
"ui.panel.lovelace.menu.start_conversation"
)}</span
>
<ha-svg-icon
slot="graphic"
.path=${mdiMicrophone}
></ha-svg-icon>
</mwc-list-item>
`
: ""}
${this._yamlMode
? html`
<mwc-list-item
aria-label=${this.hass!.localize(
"ui.common.refresh"
)}
graphic="icon"
@request-selected=${this._handleRefresh}
>
<span
>${this.hass!.localize(
"ui.common.refresh"
)}</span
>
<ha-svg-icon
slot="graphic"
.path=${mdiRefresh}
></ha-svg-icon>
</mwc-list-item>
<mwc-list-item
aria-label=${this.hass!.localize(
"ui.panel.lovelace.unused_entities.title"
)}
graphic="icon"
@request-selected=${this
._handleUnusedEntities}
>
<span
>${this.hass!.localize(
"ui.panel.lovelace.unused_entities.title"
)}</span
>
<ha-svg-icon
slot="graphic"
.path=${mdiShape}
></ha-svg-icon>
</mwc-list-item>
`
: ""}
${(
this.hass.panels.lovelace
?.config as LovelacePanelConfig
)?.mode === "yaml"
? html`
<mwc-list-item
graphic="icon"
aria-label=${this.hass!.localize(
"ui.panel.lovelace.menu.reload_resources"
)}
@request-selected=${this
._handleReloadResources}
>
${this.hass!.localize(
"ui.panel.lovelace.menu.reload_resources"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiRefresh}
></ha-svg-icon>
</mwc-list-item>
`
: ""}
${this._editMode
? html`
<a
href=${documentationUrl(
this.hass,
"/lovelace/"
)}
rel="noreferrer"
class="menu-link"
target="_blank"
>
<mwc-list-item
graphic="icon"
aria-label=${this.hass!.localize(
"ui.panel.lovelace.menu.help"
)}
>
${this.hass!.localize(
"ui.panel.lovelace.menu.help"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiHelp}
></ha-svg-icon>
</mwc-list-item>
</a>
`
: ""}
</ha-button-menu>
`
: ""}
</app-toolbar>
`}
${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
.hass=${this.hass}
.label=${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
.hass=${this.hass}
.label=${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`
<ha-icon-button
id="add-view"
@click=${this._addView}
.label=${this.hass!.localize(
"ui.panel.lovelace.editor.edit_view.add"
)}
.path=${mdiPlus}
></ha-icon-button>
`
: ""}
</paper-tabs>
</div>
`
: ""}
</app-header>
`
: ""}
<div id="view" @ll-rebuild=${this._debouncedConfigChanged}></div>
</ha-app-layout>
`;
@ -674,7 +664,6 @@ class HUIRoot extends LitElement {
return (
(this.narrow && this._conversation(this.hass.config.components)) ||
this._editMode ||
(this.hass!.user?.is_admin && !this.hass!.config.safe_mode) ||
(this.hass.panels.lovelace?.config as LovelacePanelConfig)?.mode ===
"yaml" ||
this._yamlMode
@ -747,19 +736,6 @@ class HUIRoot extends LitElement {
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.lovelace!.setEditMode(true);
}
private _editModeDisable(): void {
this.lovelace!.setEditMode(false);
}

View File

@ -1621,9 +1621,11 @@
"require_admin": "Admin only",
"sidebar": "Show in sidebar",
"filename": "Filename",
"url": "Open"
"url": "Open",
"edit": "Edit"
},
"open": "Open",
"edit": "Edit",
"add_dashboard": "Add dashboard"
},
"confirm_delete_title": "Delete {dashboard_title}?",
@ -1644,6 +1646,7 @@
"delete": "Delete",
"update": "Update",
"create": "Create",
"hide_header": "Hide header",
"set_default": "Set as default on this device",
"remove_default": "Remove as default on this device"
}

View File

@ -103,6 +103,7 @@ export interface PanelInfo<T = Record<string, any> | null> {
icon: string | null;
title: string | null;
url_path: string;
hide_header: boolean;
}
export interface Panels {