Style fixes to lovelace config dashboard (#5055)

* Style fixes to lovelace config dashboard

* Add standard panel and show which is default

* Update dialog-lovelace-dashboard-detail.ts

* Updated lovelace icon

* Comments and adapt to backend changes
This commit is contained in:
Paulus Schoutsen 2020-03-05 11:52:16 -08:00 committed by GitHub
parent 802db71400
commit 1a3b747d17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 221 additions and 129 deletions

View File

@ -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);
}
`;
}
}

View File

@ -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;

View File

@ -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

View File

@ -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 {

View File

@ -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<void> {
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`
<ha-dialog
open
@ -67,21 +67,25 @@ export class DialogLovelaceDashboardDetail extends LitElement {
escapeKeyAction
.heading=${createCloseHeading(
this.hass,
this._params.dashboard
? this._sidebarTitle ||
this.hass!.localize(
this._params.urlPath
? this._title ||
this.hass.localize(
"ui.panel.config.lovelace.dashboards.detail.edit_dashboard"
)
: this.hass!.localize(
: this.hass.localize(
"ui.panel.config.lovelace.dashboards.detail.new_dashboard"
)
)}
>
<div>
${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 {
`
: ""}
<div class="form">
<ha-switch
.checked=${this._showSidebar}
@change=${this._showSidebarChanged}
>${this.hass!.localize(
"ui.panel.config.lovelace.dashboards.detail.show_sidebar"
)}</ha-switch
>
${this._showSidebar
? html`
<ha-icon-input
.value=${this._sidebarIcon}
@value-changed=${this._sidebarIconChanged}
.label=${this.hass!.localize(
"ui.panel.config.lovelace.dashboards.detail.icon"
)}
></ha-icon-input>
<paper-input
.value=${this._sidebarTitle}
@value-changed=${this._sidebarTitleChanged}
.label=${this.hass!.localize(
"ui.panel.config.lovelace.dashboards.detail.title"
)}
@blur=${this._fillUrlPath}
></paper-input>
`
: ""}
<paper-input
.value=${this._title}
@value-changed=${this._titleChanged}
.label=${this.hass.localize(
"ui.panel.config.lovelace.dashboards.detail.title"
)}
@blur=${this._fillUrlPath}
.invalid=${titleInvalid}
.errorMessage=${this.hass.localize(
"ui.panel.config.lovelace.dashboards.detail.title_required"
)}
></paper-input>
<ha-icon-input
.value=${this._icon}
@value-changed=${this._iconChanged}
.label=${this.hass.localize(
"ui.panel.config.lovelace.dashboards.detail.icon"
)}
></ha-icon-input>
${!this._params.dashboard
? html`
<paper-input
.value=${this._urlPath}
@value-changed=${this._urlChanged}
.label=${this.hass!.localize(
.label=${this.hass.localize(
"ui.panel.config.lovelace.dashboards.detail.url"
)}
.errorMessage=${this.hass!.localize(
.errorMessage=${this.hass.localize(
"ui.panel.config.lovelace.dashboards.detail.url_error_msg"
)}
.invalid=${urlInvalid}
></paper-input>
`
: ""}
<ha-switch
.checked=${this._showInSidebar}
@change=${this._showSidebarChanged}
>${this.hass.localize(
"ui.panel.config.lovelace.dashboards.detail.show_sidebar"
)}
</ha-switch>
<ha-switch
.checked=${this._requireAdmin}
@change=${this._requireAdminChanged}
>${this.hass!.localize(
>${this.hass.localize(
"ui.panel.config.lovelace.dashboards.detail.require_admin"
)}</ha-switch
>
)}
</ha-switch>
</div>
`}
</div>
${this._params.dashboard
${this._params.urlPath
? html`
${this._params.dashboard.id
${this._params.dashboard?.id
? html`
<mwc-button
slot="secondaryAction"
@ -150,18 +154,26 @@ export class DialogLovelaceDashboardDetail extends LitElement {
@click=${this._deleteDashboard}
.disabled=${this._submitting}
>
${this.hass!.localize(
${this.hass.localize(
"ui.panel.config.lovelace.dashboards.detail.delete"
)}
</mwc-button>
`
: ""}
<mwc-button slot="secondaryAction" @click=${this._toggleDefault}>
${this._params.dashboard.url_path === localStorage.defaultPage
? this.hass!.localize(
<mwc-button
slot="secondaryAction"
@click=${this._toggleDefault}
.disabled=${this._params.urlPath === "lovelace" &&
(!localStorage.defaultPage ||
localStorage.defaultPage === "lovelace")}
>
${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"
)}
</mwc-button>
@ -170,15 +182,15 @@ export class DialogLovelaceDashboardDetail extends LitElement {
<mwc-button
slot="primaryAction"
@click="${this._updateDashboard}"
.disabled=${urlInvalid || this._submitting}
.disabled=${urlInvalid || titleInvalid || this._submitting}
>
${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"
)}
</mwc-button>
@ -191,29 +203,29 @@ export class DialogLovelaceDashboardDetail extends LitElement {
this._urlPath = ev.detail.value;
}
private _sidebarIconChanged(ev: PolymerChangedEvent<string>) {
private _iconChanged(ev: PolymerChangedEvent<string>) {
this._error = undefined;
this._sidebarIcon = ev.detail.value;
this._icon = ev.detail.value;
}
private _sidebarTitleChanged(ev: PolymerChangedEvent<string>) {
private _titleChanged(ev: PolymerChangedEvent<string>) {
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<LovelaceDashboardMutableParams> = {
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 {

View File

@ -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`
<ha-icon
style="padding-left: 10px;"
icon="hass:check-circle-outline"
></ha-icon>
<paper-tooltip>
This is the default dashdoard.
</paper-tooltip>
`
: ""}
`;
return narrow
? html`
${titleTemplate}
<div class="secondary">
${this.hass.localize(
`ui.panel.config.lovelace.dashboards.conf_mode.${dashboard.mode}`
)}${dashboard.filename
? html`
- ${dashboard.filename}
`
: ""}
</div>
`
: 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`
<ha-icon icon="hass:check-circle-outline"></ha-icon>
<ha-icon icon="hass:check"></ha-icon>
`
: 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`
<ha-icon icon="hass:check-circle-outline"></ha-icon>
<ha-icon icon="hass:check"></ha-icon>
`
: html`
-
`,
},
url_path: {
title: "",
type: "icon",
filterable: true,
template: (urlPath) =>
html`
<mwc-button .urlPath=${urlPath} @click=${this._navigate}
>${this.hass.localize(
"ui.panel.config.lovelace.dashboards.picker.open"
)}</mwc-button
>
`,
},
};
}
columns.url_path = {
title: "",
filterable: true,
template: (urlPath) =>
narrow
? html`
<paper-icon-button
icon="hass:open-in-new"
.urlPath=${urlPath}
@click=${this._navigate}
></paper-icon-button>
`
: html`
<mwc-button .urlPath=${urlPath} @click=${this._navigate}
>${this.hass.localize(
"ui.panel.config.lovelace.dashboards.picker.open"
)}</mwc-button
>
`,
};
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<void> {
private async _openDialog(
dashboard?: LovelaceDashboard,
urlPath?: string
): Promise<void> {
showDashboardDetailDialog(this, {
dashboard,
urlPath,
createDashboard: async (values: LovelaceDashboardCreateParams) => {
const created = await createDashboard(this.hass!, values);
this._dashboards = this._dashboards!.concat(

View File

@ -7,6 +7,7 @@ import {
export interface LovelaceDashboardDetailsDialogParams {
dashboard?: LovelaceDashboard;
urlPath?: string;
createDashboard: (values: LovelaceDashboardCreateParams) => Promise<unknown>;
updateDashboard: (
updates: Partial<LovelaceDashboardMutableParams>

View File

@ -215,6 +215,7 @@ export class HuiUnusedEntities extends LitElement {
:host {
background: var(--lovelace-background);
padding: 16px;
box-sizing: border-box;
}
ha-fab {
position: sticky;

View File

@ -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",