diff --git a/src/components/data-table/ha-data-table.ts b/src/components/data-table/ha-data-table.ts index 73188403e6..73a51914b4 100644 --- a/src/components/data-table/ha-data-table.ts +++ b/src/components/data-table/ha-data-table.ts @@ -449,7 +449,7 @@ export class HaDataTable extends BaseElement { private async _calcScrollHeight() { await this.updateComplete; - this._scroller.style.maxHeight = `calc(100% - ${this._header.clientHeight}px)`; + this._scroller.style.height = `calc(100% - ${this._header.clientHeight}px)`; } static get styles(): CSSResult { @@ -670,6 +670,9 @@ export class HaDataTable extends BaseElement { slot[name="header"] { display: block; } + .secondary { + color: var(--secondary-text-color); + } `; } } diff --git a/src/components/ha-sidebar.ts b/src/components/ha-sidebar.ts index 54ffd28100..9e0eca09fe 100644 --- a/src/components/ha-sidebar.ts +++ b/src/components/ha-sidebar.ts @@ -32,6 +32,7 @@ import { classMap } from "lit-html/directives/class-map"; // tslint:disable-next-line: no-duplicate-imports import { PaperIconItemElement } from "@polymer/paper-item/paper-icon-item"; import { computeRTL } from "../common/util/compute_rtl"; +import { compare } from "../common/string/compare"; const SHOW_AFTER_SPACER = ["config", "developer-tools", "hassio"]; @@ -51,6 +52,9 @@ const panelSorter = (a: PanelInfo, b: PanelInfo) => { const aLovelace = a.component_name === "lovelace"; const bLovelace = b.component_name === "lovelace"; + if (aLovelace && bLovelace) { + return compare(a.title!, b.title!); + } if (aLovelace && !bLovelace) { return -1; } @@ -71,13 +75,7 @@ const panelSorter = (a: PanelInfo, b: PanelInfo) => { return 1; } // both not built in, sort by title - if (a.title! < b.title!) { - return -1; - } - if (a.title! > b.title!) { - return 1; - } - return 0; + return compare(a.title!, b.title!); }; const DEFAULT_PAGE = localStorage.defaultPage || DEFAULT_PANEL; diff --git a/src/data/lovelace.ts b/src/data/lovelace.ts index cde4dda192..a9c11f4cc8 100644 --- a/src/data/lovelace.ts +++ b/src/data/lovelace.ts @@ -6,6 +6,10 @@ import { } from "home-assistant-js-websocket"; import { HASSDomEvent } from "../common/dom/fire_event"; +export interface LovelacePanelConfig { + mode: "yaml" | "storage"; +} + export interface LovelaceConfig { title?: string; views: LovelaceViewConfig[]; @@ -31,7 +35,9 @@ interface LovelaceGenericDashboard { id: string; url_path: string; require_admin: boolean; - sidebar?: { icon: string; title: string }; + show_in_sidebar: boolean; + icon?: string; + title: string; } export interface LovelaceYamlDashboard extends LovelaceGenericDashboard { @@ -45,7 +51,9 @@ export interface LovelaceStorageDashboard extends LovelaceGenericDashboard { export interface LovelaceDashboardMutableParams { require_admin: boolean; - sidebar: { icon: string; title: string } | null; + show_in_sidebar: boolean; + icon?: string; + title: string; } export interface LovelaceDashboardCreateParams diff --git a/src/layouts/hass-tabs-subpage-data-table.ts b/src/layouts/hass-tabs-subpage-data-table.ts index c24cdfccf1..ea4f411fb3 100644 --- a/src/layouts/hass-tabs-subpage-data-table.ts +++ b/src/layouts/hass-tabs-subpage-data-table.ts @@ -135,6 +135,7 @@ export class HaTabsSubpageDataTable extends LitElement { return css` ha-data-table { width: 100%; + height: 100%; --data-table-border-width: 0; } :host(:not([narrow])) ha-data-table { diff --git a/src/panels/config/lovelace/dashboards/dialog-lovelace-dashboard-detail.ts b/src/panels/config/lovelace/dashboards/dialog-lovelace-dashboard-detail.ts index 30ad367c0f..491266998d 100644 --- a/src/panels/config/lovelace/dashboards/dialog-lovelace-dashboard-detail.ts +++ b/src/panels/config/lovelace/dashboards/dialog-lovelace-dashboard-detail.ts @@ -25,9 +25,9 @@ export class DialogLovelaceDashboardDetail extends LitElement { @property() public hass!: HomeAssistant; @property() private _params?: LovelaceDashboardDetailsDialogParams; @property() private _urlPath!: LovelaceDashboard["url_path"]; - @property() private _showSidebar!: boolean; - @property() private _sidebarIcon!: string; - @property() private _sidebarTitle!: string; + @property() private _showInSidebar!: boolean; + @property() private _icon!: string; + @property() private _title!: string; @property() private _requireAdmin!: LovelaceDashboard["require_admin"]; @property() private _error?: string; @@ -38,17 +38,16 @@ export class DialogLovelaceDashboardDetail extends LitElement { ): Promise { this._params = params; this._error = undefined; + this._urlPath = this._params.urlPath || ""; if (this._params.dashboard) { - this._urlPath = this._params.dashboard.url_path || ""; - this._showSidebar = !!this._params.dashboard.sidebar; - this._sidebarIcon = this._params.dashboard.sidebar?.icon || ""; - this._sidebarTitle = this._params.dashboard.sidebar?.title || ""; + this._showInSidebar = !!this._params.dashboard.show_in_sidebar; + this._icon = this._params.dashboard.icon || ""; + this._title = this._params.dashboard.title || ""; this._requireAdmin = this._params.dashboard.require_admin || false; } else { - this._urlPath = ""; - this._showSidebar = true; - this._sidebarIcon = ""; - this._sidebarTitle = ""; + this._showInSidebar = true; + this._icon = ""; + this._title = ""; this._requireAdmin = false; } await this.updateComplete; @@ -59,6 +58,7 @@ export class DialogLovelaceDashboardDetail extends LitElement { return html``; } const urlInvalid = !/^[a-zA-Z0-9_-]+$/.test(this._urlPath); + const titleInvalid = !this._urlPath.trim(); return html`
${this._params.dashboard && !this._params.dashboard.id - ? this.hass!.localize( + ? this.hass.localize( "ui.panel.config.lovelace.dashboards.cant_edit_yaml" ) + : this._params.urlPath === "lovelace" + ? this.hass.localize( + "ui.panel.config.lovelace.dashboards.cant_edit_default" + ) : html` ${this._error ? html` @@ -89,60 +93,60 @@ export class DialogLovelaceDashboardDetail extends LitElement { ` : ""}
- ${this.hass!.localize( - "ui.panel.config.lovelace.dashboards.detail.show_sidebar" - )} - ${this._showSidebar - ? html` - - - ` - : ""} + + ${!this._params.dashboard ? html` ` : ""} + ${this.hass.localize( + "ui.panel.config.lovelace.dashboards.detail.show_sidebar" + )} + ${this.hass!.localize( + >${this.hass.localize( "ui.panel.config.lovelace.dashboards.detail.require_admin" - )} + )} +
`}
- ${this._params.dashboard + ${this._params.urlPath ? html` - ${this._params.dashboard.id + ${this._params.dashboard?.id ? html` - ${this.hass!.localize( + ${this.hass.localize( "ui.panel.config.lovelace.dashboards.detail.delete" )} ` : ""} - - ${this._params.dashboard.url_path === localStorage.defaultPage - ? this.hass!.localize( + + ${this._params.urlPath === localStorage.defaultPage || + (this._params.urlPath === "lovelace" && + !localStorage.defaultPage) + ? this.hass.localize( "ui.panel.config.lovelace.dashboards.detail.remove_default" ) - : this.hass!.localize( + : this.hass.localize( "ui.panel.config.lovelace.dashboards.detail.set_default" )} @@ -170,15 +182,15 @@ export class DialogLovelaceDashboardDetail extends LitElement { - ${this._params.dashboard - ? this._params.dashboard.id - ? this.hass!.localize( + ${this._params.urlPath + ? this._params.dashboard?.id + ? this.hass.localize( "ui.panel.config.lovelace.dashboards.detail.update" ) - : this.hass!.localize("ui.common.close") - : this.hass!.localize( + : this.hass.localize("ui.common.close") + : this.hass.localize( "ui.panel.config.lovelace.dashboards.detail.create" )} @@ -191,29 +203,29 @@ export class DialogLovelaceDashboardDetail extends LitElement { this._urlPath = ev.detail.value; } - private _sidebarIconChanged(ev: PolymerChangedEvent) { + private _iconChanged(ev: PolymerChangedEvent) { this._error = undefined; - this._sidebarIcon = ev.detail.value; + this._icon = ev.detail.value; } - private _sidebarTitleChanged(ev: PolymerChangedEvent) { + private _titleChanged(ev: PolymerChangedEvent) { this._error = undefined; - this._sidebarTitle = ev.detail.value; + this._title = ev.detail.value; } private _fillUrlPath() { if (this._urlPath) { return; } - const parts = this._sidebarTitle.split(" "); + const parts = this._title.split(" "); if (parts.length) { - this._urlPath = parts[0].toLowerCase(); + this._urlPath = parts.join("_").toLowerCase(); } } private _showSidebarChanged(ev: Event) { - this._showSidebar = (ev.target as HaSwitch).checked; + this._showInSidebar = (ev.target as HaSwitch).checked; } private _requireAdminChanged(ev: Event) { @@ -221,7 +233,7 @@ export class DialogLovelaceDashboardDetail extends LitElement { } private _toggleDefault() { - const urlPath = this._params?.dashboard?.url_path; + const urlPath = this._params?.urlPath; if (!urlPath) { return; } @@ -234,13 +246,16 @@ export class DialogLovelaceDashboardDetail extends LitElement { } private async _updateDashboard() { + if (this._params?.urlPath && !this._params.dashboard?.id) { + this._close(); + } this._submitting = true; try { const values: Partial = { require_admin: this._requireAdmin, - sidebar: this._showSidebar - ? { icon: this._sidebarIcon, title: this._sidebarTitle } - : null, + show_in_sidebar: this._showInSidebar, + icon: this._icon || undefined, + title: this._title, }; if (this._params!.dashboard) { await this._params!.updateDashboard(values); @@ -251,7 +266,7 @@ export class DialogLovelaceDashboardDetail extends LitElement { values as LovelaceDashboardCreateParams ); } - this._params = undefined; + this._close(); } catch (err) { this._error = err?.message || "Unknown error"; } finally { diff --git a/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts b/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts index de7548cd7e..2404323893 100644 --- a/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts +++ b/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts @@ -9,6 +9,7 @@ import { css, } from "lit-element"; import memoize from "memoize-one"; +import "@polymer/paper-tooltip/paper-tooltip"; import { DataTableColumnContainer, RowClickedEvent, @@ -24,6 +25,7 @@ import { updateDashboard, deleteDashboard, LovelaceDashboardCreateParams, + LovelacePanelConfig, } from "../../../../data/lovelace"; import { showDashboardDetailDialog } from "./show-dialog-lovelace-dashboard-detail"; import { compare } from "../../../../common/string/compare"; @@ -40,7 +42,7 @@ export class HaConfigLovelaceDashboards extends LitElement { @property() private _dashboards: LovelaceDashboard[] = []; private _columns = memoize( - (_language, dashboards): DataTableColumnContainer => { + (narrow: boolean, _language, dashboards): DataTableColumnContainer => { const columns: DataTableColumnContainer = { icon: { title: "", @@ -59,8 +61,41 @@ export class HaConfigLovelaceDashboards extends LitElement { sortable: true, filterable: true, direction: "asc", + template: (title, dashboard: any) => { + const titleTemplate = html` + ${title} + ${dashboard.default + ? html` + + + This is the default dashdoard. + + ` + : ""} + `; + return narrow + ? html` + ${titleTemplate} +
+ ${this.hass.localize( + `ui.panel.config.lovelace.dashboards.conf_mode.${dashboard.mode}` + )}${dashboard.filename + ? html` + - ${dashboard.filename} + ` + : ""} +
+ ` + : titleTemplate; + }, }, - mode: { + }; + + if (!narrow) { + columns.mode = { title: this.hass.localize( "ui.panel.config.lovelace.dashboards.picker.headers.conf_mode" ), @@ -72,21 +107,17 @@ export class HaConfigLovelaceDashboards extends LitElement { `ui.panel.config.lovelace.dashboards.conf_mode.${mode}` ) || mode} `, - }, - }; - - if (dashboards.some((dashboard) => dashboard.mode === "yaml")) { - columns.filename = { - title: this.hass.localize( - "ui.panel.config.lovelace.dashboards.picker.headers.filename" - ), - sortable: true, - filterable: true, }; - } - - const columns2: DataTableColumnContainer = { - require_admin: { + if (dashboards.some((dashboard) => dashboard.filename)) { + columns.filename = { + title: this.hass.localize( + "ui.panel.config.lovelace.dashboards.picker.headers.filename" + ), + sortable: true, + filterable: true, + }; + } + columns.require_admin = { title: this.hass.localize( "ui.panel.config.lovelace.dashboards.picker.headers.require_admin" ), @@ -95,13 +126,13 @@ export class HaConfigLovelaceDashboards extends LitElement { template: (requireAdmin: boolean) => requireAdmin ? html` - + ` : html` - `, - }, - sidebar: { + }; + columns.show_in_sidebar = { title: this.hass.localize( "ui.panel.config.lovelace.dashboards.picker.headers.sidebar" ), @@ -109,39 +140,62 @@ export class HaConfigLovelaceDashboards extends LitElement { template: (sidebar) => sidebar ? html` - + ` : html` - `, - }, - url_path: { - title: "", - type: "icon", - filterable: true, - template: (urlPath) => - html` - ${this.hass.localize( - "ui.panel.config.lovelace.dashboards.picker.open" - )} - `, - }, + }; + } + + columns.url_path = { + title: "", + filterable: true, + template: (urlPath) => + narrow + ? html` + + ` + : html` + ${this.hass.localize( + "ui.panel.config.lovelace.dashboards.picker.open" + )} + `, }; - return { ...columns, ...columns2 }; + + return columns; } ); private _getItems = memoize((dashboards: LovelaceDashboard[]) => { - return dashboards.map((dashboard) => { - return { - filename: "", - ...dashboard, - icon: dashboard.sidebar?.icon, - title: dashboard.sidebar?.title || dashboard.url_path, - }; - }); + const defaultMode = (this.hass.panels?.lovelace + ?.config as LovelacePanelConfig).mode; + const isDefault = + !localStorage.defaultPage || localStorage.defaultPage === "lovelace"; + return [ + { + icon: "hass:view-dashboard", + title: this.hass.localize("panel.states"), + default: isDefault, + sidebar: isDefault, + require_admin: false, + url_path: "lovelace", + mode: defaultMode, + filename: defaultMode === "yaml" ? "ui-lovelace.yaml" : "", + }, + ...dashboards.map((dashboard) => { + return { + ...dashboard, + default: localStorage.defaultPage === dashboard.url_path, + }; + }), + ]; }); protected render(): TemplateResult { @@ -158,7 +212,11 @@ export class HaConfigLovelaceDashboards extends LitElement { back-path="/config" .route=${this.route} .tabs=${lovelaceTabs} - .columns=${this._columns(this.hass.language, this._dashboards)} + .columns=${this._columns( + this.narrow, + this.hass.language, + this._dashboards + )} .data=${this._getItems(this._dashboards)} @row-click=${this._editDashboard} id="url_path" @@ -194,16 +252,20 @@ export class HaConfigLovelaceDashboards extends LitElement { private _editDashboard(ev: CustomEvent) { const urlPath = (ev.detail as RowClickedEvent).id; const dashboard = this._dashboards.find((res) => res.url_path === urlPath); - this._openDialog(dashboard); + this._openDialog(dashboard, urlPath); } private _addDashboard() { this._openDialog(); } - private async _openDialog(dashboard?: LovelaceDashboard): Promise { + private async _openDialog( + dashboard?: LovelaceDashboard, + urlPath?: string + ): Promise { showDashboardDetailDialog(this, { dashboard, + urlPath, createDashboard: async (values: LovelaceDashboardCreateParams) => { const created = await createDashboard(this.hass!, values); this._dashboards = this._dashboards!.concat( diff --git a/src/panels/config/lovelace/dashboards/show-dialog-lovelace-dashboard-detail.ts b/src/panels/config/lovelace/dashboards/show-dialog-lovelace-dashboard-detail.ts index 84c72d7176..b0dedf4966 100644 --- a/src/panels/config/lovelace/dashboards/show-dialog-lovelace-dashboard-detail.ts +++ b/src/panels/config/lovelace/dashboards/show-dialog-lovelace-dashboard-detail.ts @@ -7,6 +7,7 @@ import { export interface LovelaceDashboardDetailsDialogParams { dashboard?: LovelaceDashboard; + urlPath?: string; createDashboard: (values: LovelaceDashboardCreateParams) => Promise; updateDashboard: ( updates: Partial diff --git a/src/panels/lovelace/editor/unused-entities/hui-unused-entities.ts b/src/panels/lovelace/editor/unused-entities/hui-unused-entities.ts index 3fb242b616..486af15178 100644 --- a/src/panels/lovelace/editor/unused-entities/hui-unused-entities.ts +++ b/src/panels/lovelace/editor/unused-entities/hui-unused-entities.ts @@ -215,6 +215,7 @@ export class HuiUnusedEntities extends LitElement { :host { background: var(--lovelace-background); padding: 16px; + box-sizing: border-box; } ha-fab { position: sticky; diff --git a/src/translations/en.json b/src/translations/en.json index d2d2f5dbae..c739c3d6aa 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -861,15 +861,17 @@ "headers": { "title": "Title", "conf_mode": "Configuration method", + "default": "Default", "require_admin": "Admin only", "sidebar": "Show in sidebar", "filename": "Filename" }, - "open": "Open dashboard", + "open": "Open", "add_dashboard": "Add dashboard" }, "confirm_delete": "Are you sure you want to delete this dashboard?", "cant_edit_yaml": "Dashboards defined in YAML can not be edited from the UI. Change them in configuration.yaml.", + "cant_edit_default": "The standard Lovelace dashboard can not be edited from the UI. You can hide it by setting another dashboard as default.", "detail": { "edit_dashboard": "Edit dashboard", "new_dashboard": "Add new dashboard", @@ -877,6 +879,7 @@ "show_sidebar": "Show in sidebar", "icon": "Sidebar icon", "title": "Sidebar title", + "title_required": "Title is required.", "url": "Url", "url_error_msg": "The url can not contain spaces or special characters, except for _ and -", "require_admin": "Admin only",