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; show_in_sidebar: boolean;
icon?: string; icon?: string;
title: string; title: string;
hide_header?: boolean;
} }
export interface LovelaceYamlDashboard extends LovelaceGenericDashboard { export interface LovelaceYamlDashboard extends LovelaceGenericDashboard {
@ -69,6 +70,7 @@ export interface LovelaceDashboardMutableParams {
show_in_sidebar: boolean; show_in_sidebar: boolean;
icon?: string; icon?: string;
title: string; title: string;
hide_header?: boolean;
} }
export interface LovelaceDashboardCreateParams export interface LovelaceDashboardCreateParams

View File

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

View File

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

View File

@ -1,14 +1,25 @@
import "@material/mwc-button"; import "@material/mwc-button";
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import { slugify } from "../../../../common/string/slugify";
import "../../../../components/ha-circular-progress"; 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 { haStyleDialog } from "../../../../resources/styles";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../types";
import { LovelaceDashboardDetailsDialogParams } from "../../../config/lovelace/dashboards/show-dialog-lovelace-dashboard-detail";
import type { Lovelace } from "../../types"; import type { Lovelace } from "../../types";
import "./hui-lovelace-editor"; import "./hui-lovelace-editor";
import "../../../../components/ha-dialog";
@customElement("hui-dialog-edit-lovelace") @customElement("hui-dialog-edit-lovelace")
export class HuiDialogEditLovelace extends LitElement { export class HuiDialogEditLovelace extends LitElement {
@ -18,7 +29,15 @@ export class HuiDialogEditLovelace extends LitElement {
@state() private _config?: LovelaceConfig; @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 { public showDialog(lovelace: Lovelace): void {
this._lovelace = lovelace; this._lovelace = lovelace;
@ -32,9 +51,10 @@ export class HuiDialogEditLovelace extends LitElement {
} }
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this._config) { if (!this._config || !this.hass) {
return html``; return html``;
} }
return html` return html`
<ha-dialog <ha-dialog
open open
@ -52,16 +72,25 @@ export class HuiDialogEditLovelace extends LitElement {
@lovelace-config-changed=${this._ConfigChanged} @lovelace-config-changed=${this._ConfigChanged}
dialogInitialFocus dialogInitialFocus
></hui-lovelace-editor> ></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> </div>
<mwc-button @click=${this.closeDialog} slot="secondaryAction"> <mwc-button @click=${this.closeDialog} slot="secondaryAction">
${this.hass!.localize("ui.common.cancel")} ${this.hass!.localize("ui.common.cancel")}
</mwc-button> </mwc-button>
<mwc-button <mwc-button
.disabled=${!this._config || this._saving} .disabled=${!this._config || this._submitting}
@click=${this._save} @click=${this._save}
slot="primaryAction" slot="primaryAction"
> >
${this._saving ${this._submitting
? html`<ha-circular-progress ? html`<ha-circular-progress
active active
size="small" size="small"
@ -83,7 +112,7 @@ export class HuiDialogEditLovelace extends LitElement {
return; return;
} }
this._saving = true; this._submitting = true;
const lovelace = this._lovelace!; const lovelace = this._lovelace!;
const config: LovelaceConfig = { const config: LovelaceConfig = {
@ -97,7 +126,7 @@ export class HuiDialogEditLovelace extends LitElement {
} catch (err: any) { } catch (err: any) {
alert(`Saving failed: ${err.message}`); alert(`Saving failed: ${err.message}`);
} finally { } finally {
this._saving = false; this._submitting = false;
} }
} }
@ -112,9 +141,130 @@ export class HuiDialogEditLovelace extends LitElement {
return JSON.stringify(this._config) !== JSON.stringify(lovelaceConfig); return JSON.stringify(this._config) !== JSON.stringify(lovelaceConfig);
} }
static get styles(): CSSResultGroup { private _schema = memoizeOne(
return haStyleDialog; (
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 { declare global {

View File

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

View File

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

View File

@ -1621,9 +1621,11 @@
"require_admin": "Admin only", "require_admin": "Admin only",
"sidebar": "Show in sidebar", "sidebar": "Show in sidebar",
"filename": "Filename", "filename": "Filename",
"url": "Open" "url": "Open",
"edit": "Edit"
}, },
"open": "Open", "open": "Open",
"edit": "Edit",
"add_dashboard": "Add dashboard" "add_dashboard": "Add dashboard"
}, },
"confirm_delete_title": "Delete {dashboard_title}?", "confirm_delete_title": "Delete {dashboard_title}?",
@ -1644,6 +1646,7 @@
"delete": "Delete", "delete": "Delete",
"update": "Update", "update": "Update",
"create": "Create", "create": "Create",
"hide_header": "Hide header",
"set_default": "Set as default on this device", "set_default": "Set as default on this device",
"remove_default": "Remove 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; icon: string | null;
title: string | null; title: string | null;
url_path: string; url_path: string;
hide_header: boolean;
} }
export interface Panels { export interface Panels {