mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 01:36:49 +00:00
Add UI to create and manage Lovelace dashboards and resources (#5012)
* Add UI to create and manage Lovelace dashboards and resources * update, comments, fixes * Align icons with seach icon and checkboxes * Fix * Remove js and html resource types * Allow it for existing ones
This commit is contained in:
parent
33d65bcefc
commit
5646045e9e
@ -571,6 +571,18 @@ export class HaDataTable extends BaseElement {
|
|||||||
width: 24px;
|
width: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mdc-data-table__header-cell--icon {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mdc-data-table__cell--icon:first-child ha-icon {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mdc-data-table__cell--icon:first-child state-badge {
|
||||||
|
margin-right: -8px;
|
||||||
|
}
|
||||||
|
|
||||||
.mdc-data-table__header-cell {
|
.mdc-data-table__header-cell {
|
||||||
font-family: Roboto, sans-serif;
|
font-family: Roboto, sans-serif;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
@ -598,10 +610,6 @@ export class HaDataTable extends BaseElement {
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mdc-data-table__header-cell--icon {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* custom from here */
|
/* custom from here */
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
@ -615,27 +623,39 @@ export class HaDataTable extends BaseElement {
|
|||||||
}
|
}
|
||||||
.mdc-data-table__header-cell {
|
.mdc-data-table__header-cell {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
.mdc-data-table__header-cell span {
|
||||||
|
position: relative;
|
||||||
|
left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.mdc-data-table__header-cell.sortable {
|
.mdc-data-table__header-cell.sortable {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.mdc-data-table__header-cell.not-sorted:not(.mdc-data-table__header-cell--numeric):not(.mdc-data-table__header-cell--icon)
|
.mdc-data-table__header-cell > * {
|
||||||
span {
|
|
||||||
position: relative;
|
|
||||||
left: -24px;
|
|
||||||
}
|
|
||||||
.mdc-data-table__header-cell.not-sorted > * {
|
|
||||||
transition: left 0.2s ease 0s;
|
transition: left 0.2s ease 0s;
|
||||||
}
|
}
|
||||||
|
.mdc-data-table__header-cell ha-icon {
|
||||||
|
top: 15px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
.mdc-data-table__header-cell.not-sorted ha-icon {
|
.mdc-data-table__header-cell.not-sorted ha-icon {
|
||||||
left: -36px;
|
left: -20px;
|
||||||
}
|
}
|
||||||
.mdc-data-table__header-cell.not-sorted:not(.mdc-data-table__header-cell--numeric):not(.mdc-data-table__header-cell--icon):hover
|
.mdc-data-table__header-cell:not(.not-sorted) span,
|
||||||
|
.mdc-data-table__header-cell.not-sorted:hover span {
|
||||||
|
left: 24px;
|
||||||
|
}
|
||||||
|
.mdc-data-table__header-cell.mdc-data-table__header-cell--numeric:not(.not-sorted)
|
||||||
|
span,
|
||||||
|
.mdc-data-table__header-cell.mdc-data-table__header-cell--numeric.not-sorted:hover
|
||||||
span {
|
span {
|
||||||
left: 0px;
|
left: 12px;
|
||||||
}
|
}
|
||||||
|
.mdc-data-table__header-cell:not(.not-sorted) ha-icon,
|
||||||
.mdc-data-table__header-cell:hover.not-sorted ha-icon {
|
.mdc-data-table__header-cell:hover.not-sorted ha-icon {
|
||||||
left: 0px;
|
left: 12px;
|
||||||
}
|
}
|
||||||
.table-header {
|
.table-header {
|
||||||
border-bottom: 1px solid rgba(var(--rgb-primary-text-color), 0.12);
|
border-bottom: 1px solid rgba(var(--rgb-primary-text-color), 0.12);
|
||||||
|
@ -1,12 +1,23 @@
|
|||||||
import { customElement, CSSResult, css } from "lit-element";
|
import { customElement, CSSResult, css, html } from "lit-element";
|
||||||
|
import "@polymer/paper-icon-button/paper-icon-button";
|
||||||
import "@material/mwc-dialog";
|
import "@material/mwc-dialog";
|
||||||
import { style } from "@material/mwc-dialog/mwc-dialog-css";
|
import { style } from "@material/mwc-dialog/mwc-dialog-css";
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
import { Dialog } from "@material/mwc-dialog";
|
import { Dialog } from "@material/mwc-dialog";
|
||||||
import { Constructor } from "../types";
|
import { Constructor, HomeAssistant } from "../types";
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
const MwcDialog = customElements.get("mwc-dialog") as Constructor<Dialog>;
|
const MwcDialog = customElements.get("mwc-dialog") as Constructor<Dialog>;
|
||||||
|
|
||||||
|
export const createCloseHeading = (hass: HomeAssistant, title: string) => html`
|
||||||
|
${title}
|
||||||
|
<paper-icon-button
|
||||||
|
aria-label=${hass.localize("ui.dialogs.generic.close")}
|
||||||
|
icon="hass:close"
|
||||||
|
dialogAction="close"
|
||||||
|
class="close_button"
|
||||||
|
></paper-icon-button>
|
||||||
|
`;
|
||||||
|
|
||||||
@customElement("ha-dialog")
|
@customElement("ha-dialog")
|
||||||
export class HaDialog extends MwcDialog {
|
export class HaDialog extends MwcDialog {
|
||||||
protected static get styles(): CSSResult[] {
|
protected static get styles(): CSSResult[] {
|
||||||
@ -19,6 +30,15 @@ export class HaDialog extends MwcDialog {
|
|||||||
.mdc-dialog__container {
|
.mdc-dialog__container {
|
||||||
align-items: var(--vertial-align-dialog, center);
|
align-items: var(--vertial-align-dialog, center);
|
||||||
}
|
}
|
||||||
|
.mdc-dialog__title::before {
|
||||||
|
display: block;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
.close_button {
|
||||||
|
position: absolute;
|
||||||
|
right: 16px;
|
||||||
|
top: 12px;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -12,10 +12,47 @@ export interface LovelaceConfig {
|
|||||||
background?: string;
|
background?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LovelaceResources = Array<{
|
export interface LovelaceResource {
|
||||||
|
id: string;
|
||||||
type: "css" | "js" | "module" | "html";
|
type: "css" | "js" | "module" | "html";
|
||||||
url: string;
|
url: string;
|
||||||
}>;
|
}
|
||||||
|
|
||||||
|
export interface LovelaceResourcesMutableParams {
|
||||||
|
res_type: "css" | "js" | "module" | "html";
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LovelaceDashboard =
|
||||||
|
| LovelaceYamlDashboard
|
||||||
|
| LovelaceStorageDashboard;
|
||||||
|
|
||||||
|
interface LovelaceGenericDashboard {
|
||||||
|
id: string;
|
||||||
|
url_path: string;
|
||||||
|
require_admin: boolean;
|
||||||
|
sidebar?: { icon: string; title: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LovelaceYamlDashboard extends LovelaceGenericDashboard {
|
||||||
|
mode: "yaml";
|
||||||
|
filename: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LovelaceStorageDashboard extends LovelaceGenericDashboard {
|
||||||
|
mode: "storage";
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LovelaceDashboardMutableParams {
|
||||||
|
require_admin: boolean;
|
||||||
|
sidebar: { icon: string; title: string } | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LovelaceDashboardCreateParams
|
||||||
|
extends LovelaceDashboardMutableParams {
|
||||||
|
url_path: string;
|
||||||
|
mode: "storage";
|
||||||
|
}
|
||||||
|
|
||||||
export interface LovelaceViewConfig {
|
export interface LovelaceViewConfig {
|
||||||
index?: number;
|
index?: number;
|
||||||
@ -111,10 +148,70 @@ type LovelaceUpdatedEvent = HassEventBase & {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchResources = (conn: Connection): Promise<LovelaceResources> =>
|
export const fetchResources = (conn: Connection): Promise<LovelaceResource[]> =>
|
||||||
conn.sendMessagePromise({
|
conn.sendMessagePromise({
|
||||||
type: "lovelace/resources",
|
type: "lovelace/resources",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const createResource = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
values: LovelaceResourcesMutableParams
|
||||||
|
) =>
|
||||||
|
hass.callWS<LovelaceResource>({
|
||||||
|
type: "lovelace/resources/create",
|
||||||
|
...values,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const updateResource = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
id: string,
|
||||||
|
updates: Partial<LovelaceResourcesMutableParams>
|
||||||
|
) =>
|
||||||
|
hass.callWS<LovelaceResource>({
|
||||||
|
type: "lovelace/resources/update",
|
||||||
|
resource_id: id,
|
||||||
|
...updates,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const deleteResource = (hass: HomeAssistant, id: string) =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "lovelace/resources/delete",
|
||||||
|
resource_id: id,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const fetchDashboards = (
|
||||||
|
hass: HomeAssistant
|
||||||
|
): Promise<LovelaceDashboard[]> =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "lovelace/dashboards/list",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const createDashboard = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
values: LovelaceDashboardCreateParams
|
||||||
|
) =>
|
||||||
|
hass.callWS<LovelaceDashboard>({
|
||||||
|
type: "lovelace/dashboards/create",
|
||||||
|
...values,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const updateDashboard = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
id: string,
|
||||||
|
updates: Partial<LovelaceDashboardMutableParams>
|
||||||
|
) =>
|
||||||
|
hass.callWS<LovelaceDashboard>({
|
||||||
|
type: "lovelace/dashboards/update",
|
||||||
|
dashboard_id: id,
|
||||||
|
...updates,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const deleteDashboard = (hass: HomeAssistant, id: string) =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "lovelace/dashboards/delete",
|
||||||
|
dashboard_id: id,
|
||||||
|
});
|
||||||
|
|
||||||
export const fetchConfig = (
|
export const fetchConfig = (
|
||||||
conn: Connection,
|
conn: Connection,
|
||||||
urlPath: string | null,
|
urlPath: string | null,
|
||||||
@ -125,6 +222,7 @@ export const fetchConfig = (
|
|||||||
url_path: urlPath,
|
url_path: urlPath,
|
||||||
force,
|
force,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const saveConfig = (
|
export const saveConfig = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
urlPath: string | null,
|
urlPath: string | null,
|
||||||
@ -174,7 +272,7 @@ export const getLovelaceCollection = (
|
|||||||
|
|
||||||
export interface WindowWithLovelaceProm extends Window {
|
export interface WindowWithLovelaceProm extends Window {
|
||||||
llConfProm?: Promise<LovelaceConfig>;
|
llConfProm?: Promise<LovelaceConfig>;
|
||||||
llResProm?: Promise<LovelaceResources>;
|
llResProm?: Promise<LovelaceResource[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ActionHandlerOptions {
|
export interface ActionHandlerOptions {
|
||||||
|
@ -15,6 +15,7 @@ import { Route, HomeAssistant } from "../types";
|
|||||||
import { navigate } from "../common/navigate";
|
import { navigate } from "../common/navigate";
|
||||||
import "@material/mwc-ripple";
|
import "@material/mwc-ripple";
|
||||||
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
|
||||||
export interface PageNavigation {
|
export interface PageNavigation {
|
||||||
path: string;
|
path: string;
|
||||||
@ -22,7 +23,7 @@ export interface PageNavigation {
|
|||||||
component?: string;
|
component?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
core?: boolean;
|
core?: boolean;
|
||||||
exportOnly?: boolean;
|
advancedOnly?: boolean;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
info?: any;
|
info?: any;
|
||||||
}
|
}
|
||||||
@ -33,12 +34,57 @@ class HassTabsSubpage extends LitElement {
|
|||||||
@property({ type: String, attribute: "back-path" }) public backPath?: string;
|
@property({ type: String, attribute: "back-path" }) public backPath?: string;
|
||||||
@property() public backCallback?: () => void;
|
@property() public backCallback?: () => void;
|
||||||
@property({ type: Boolean }) public hassio = false;
|
@property({ type: Boolean }) public hassio = false;
|
||||||
@property({ type: Boolean }) public showAdvanced = false;
|
|
||||||
@property() public route!: Route;
|
@property() public route!: Route;
|
||||||
@property() public tabs!: PageNavigation[];
|
@property() public tabs!: PageNavigation[];
|
||||||
@property({ type: Boolean, reflect: true }) public narrow = false;
|
@property({ type: Boolean, reflect: true }) public narrow = false;
|
||||||
@property() private _activeTab: number = -1;
|
@property() private _activeTab: number = -1;
|
||||||
|
|
||||||
|
private _getTabs = memoizeOne(
|
||||||
|
(
|
||||||
|
tabs: PageNavigation[],
|
||||||
|
activeTab: number,
|
||||||
|
showAdvanced: boolean | undefined,
|
||||||
|
_components,
|
||||||
|
_language
|
||||||
|
) => {
|
||||||
|
const shownTabs = tabs.filter(
|
||||||
|
(page) =>
|
||||||
|
(!page.component ||
|
||||||
|
page.core ||
|
||||||
|
isComponentLoaded(this.hass, page.component)) &&
|
||||||
|
(!page.advancedOnly || showAdvanced)
|
||||||
|
);
|
||||||
|
|
||||||
|
return shownTabs.map(
|
||||||
|
(page, index) => html`
|
||||||
|
<div
|
||||||
|
class="tab ${classMap({
|
||||||
|
active: index === activeTab,
|
||||||
|
})}"
|
||||||
|
@click=${this._tabTapped}
|
||||||
|
.path=${page.path}
|
||||||
|
>
|
||||||
|
${this.narrow
|
||||||
|
? html`
|
||||||
|
<ha-icon .icon=${page.icon}></ha-icon>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${!this.narrow || index === activeTab
|
||||||
|
? html`
|
||||||
|
<span class="name"
|
||||||
|
>${page.translationKey
|
||||||
|
? this.hass.localize(page.translationKey)
|
||||||
|
: name}</span
|
||||||
|
>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
<mwc-ripple></mwc-ripple>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
protected updated(changedProperties: PropertyValues) {
|
protected updated(changedProperties: PropertyValues) {
|
||||||
super.updated(changedProperties);
|
super.updated(changedProperties);
|
||||||
if (changedProperties.has("route")) {
|
if (changedProperties.has("route")) {
|
||||||
@ -49,6 +95,14 @@ class HassTabsSubpage extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
|
const tabs = this._getTabs(
|
||||||
|
this.tabs,
|
||||||
|
this._activeTab,
|
||||||
|
this.hass.userData?.showAdvanced,
|
||||||
|
this.hass.config.components,
|
||||||
|
this.hass.language
|
||||||
|
);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<ha-paper-icon-button-arrow-prev
|
<ha-paper-icon-button-arrow-prev
|
||||||
@ -61,41 +115,13 @@ class HassTabsSubpage extends LitElement {
|
|||||||
<div main-title><slot name="header"></slot></div>
|
<div main-title><slot name="header"></slot></div>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
<div id="tabbar" class=${classMap({ "bottom-bar": this.narrow })}>
|
${tabs.length > 1 || !this.narrow
|
||||||
${this.tabs.map((page, index) =>
|
? html`
|
||||||
(!page.component ||
|
<div id="tabbar" class=${classMap({ "bottom-bar": this.narrow })}>
|
||||||
page.core ||
|
${tabs}
|
||||||
isComponentLoaded(this.hass, page.component)) &&
|
</div>
|
||||||
(!page.exportOnly || this.showAdvanced)
|
`
|
||||||
? html`
|
: ""}
|
||||||
<div
|
|
||||||
class="tab ${classMap({
|
|
||||||
active: index === this._activeTab,
|
|
||||||
})}"
|
|
||||||
@click=${this._tabTapped}
|
|
||||||
.path=${page.path}
|
|
||||||
>
|
|
||||||
${this.narrow
|
|
||||||
? html`
|
|
||||||
<ha-icon .icon=${page.icon}></ha-icon>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
${!this.narrow || index === this._activeTab
|
|
||||||
? html`
|
|
||||||
<span class="name"
|
|
||||||
>${page.translationKey
|
|
||||||
? this.hass.localize(page.translationKey)
|
|
||||||
: name}</span
|
|
||||||
>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
<mwc-ripple></mwc-ripple>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: ""
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="toolbar-icon">
|
<div id="toolbar-icon">
|
||||||
<slot name="toolbar-icon"></slot>
|
<slot name="toolbar-icon"></slot>
|
||||||
</div>
|
</div>
|
||||||
|
@ -31,7 +31,7 @@ class HaConfigNavigation extends LitElement {
|
|||||||
(!page.component ||
|
(!page.component ||
|
||||||
page.core ||
|
page.core ||
|
||||||
isComponentLoaded(this.hass, page.component)) &&
|
isComponentLoaded(this.hass, page.component)) &&
|
||||||
(!page.exportOnly || this.showAdvanced)
|
(!page.advancedOnly || this.showAdvanced)
|
||||||
? html`
|
? html`
|
||||||
<a
|
<a
|
||||||
href=${`/config/${page.component}`}
|
href=${`/config/${page.component}`}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
LitElement,
|
LitElement,
|
||||||
html,
|
html,
|
||||||
css,
|
|
||||||
CSSResult,
|
CSSResult,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
property,
|
property,
|
||||||
@ -22,6 +21,7 @@ import {
|
|||||||
fetchDeviceConditions,
|
fetchDeviceConditions,
|
||||||
fetchDeviceActions,
|
fetchDeviceActions,
|
||||||
} from "../../../../data/device_automation";
|
} from "../../../../data/device_automation";
|
||||||
|
import { haStyleDialog } from "../../../../resources/styles";
|
||||||
|
|
||||||
@customElement("dialog-device-automation")
|
@customElement("dialog-device-automation")
|
||||||
export class DialogDeviceAutomation extends LitElement {
|
export class DialogDeviceAutomation extends LitElement {
|
||||||
@ -129,16 +129,7 @@ export class DialogDeviceAutomation extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResult {
|
static get styles(): CSSResult {
|
||||||
return css`
|
return haStyleDialog;
|
||||||
ha-dialog {
|
|
||||||
--mdc-dialog-title-ink-color: var(--primary-text-color);
|
|
||||||
}
|
|
||||||
@media only screen and (min-width: 600px) {
|
|
||||||
ha-dialog {
|
|
||||||
--mdc-dialog-min-width: 600px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import "@polymer/app-route/app-route";
|
|
||||||
|
|
||||||
import "./ha-config-devices-dashboard";
|
import "./ha-config-devices-dashboard";
|
||||||
import "./ha-config-device-page";
|
import "./ha-config-device-page";
|
||||||
import { compare } from "../../../common/string/compare";
|
import { compare } from "../../../common/string/compare";
|
||||||
|
@ -75,6 +75,14 @@ export const configSections: { [name: string]: PageNavigation[] } = {
|
|||||||
core: true,
|
core: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
lovelace: [
|
||||||
|
{
|
||||||
|
component: "lovelace",
|
||||||
|
path: "/config/lovelace/dashboards",
|
||||||
|
translationKey: "ui.panel.config.lovelace.caption",
|
||||||
|
icon: "hass:view-dashboard",
|
||||||
|
},
|
||||||
|
],
|
||||||
persons: [
|
persons: [
|
||||||
{
|
{
|
||||||
component: "person",
|
component: "person",
|
||||||
@ -117,7 +125,7 @@ export const configSections: { [name: string]: PageNavigation[] } = {
|
|||||||
translationKey: "ui.panel.config.customize.caption",
|
translationKey: "ui.panel.config.customize.caption",
|
||||||
icon: "hass:pencil",
|
icon: "hass:pencil",
|
||||||
core: true,
|
core: true,
|
||||||
exportOnly: true,
|
advancedOnly: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
other: [
|
other: [
|
||||||
@ -217,6 +225,13 @@ class HaPanelConfig extends HassRouterPage {
|
|||||||
/* webpackChunkName: "panel-config-integrations" */ "./integrations/ha-config-integrations"
|
/* webpackChunkName: "panel-config-integrations" */ "./integrations/ha-config-integrations"
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
lovelace: {
|
||||||
|
tag: "ha-config-lovelace",
|
||||||
|
load: () =>
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "panel-config-lovelace" */ "./lovelace/ha-config-lovelace"
|
||||||
|
),
|
||||||
|
},
|
||||||
person: {
|
person: {
|
||||||
tag: "ha-config-person",
|
tag: "ha-config-person",
|
||||||
load: () =>
|
load: () =>
|
||||||
|
@ -25,6 +25,7 @@ import "./forms/ha-input_select-form";
|
|||||||
import "./forms/ha-input_number-form";
|
import "./forms/ha-input_number-form";
|
||||||
import { domainIcon } from "../../../common/entity/domain_icon";
|
import { domainIcon } from "../../../common/entity/domain_icon";
|
||||||
import { classMap } from "lit-html/directives/class-map";
|
import { classMap } from "lit-html/directives/class-map";
|
||||||
|
import { haStyleDialog } from "../../../resources/styles";
|
||||||
|
|
||||||
const HELPERS = {
|
const HELPERS = {
|
||||||
input_boolean: createInputBoolean,
|
input_boolean: createInputBoolean,
|
||||||
@ -156,37 +157,18 @@ export class DialogHelperDetail extends LitElement {
|
|||||||
this._platform = undefined;
|
this._platform = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResult {
|
static get styles(): CSSResult[] {
|
||||||
return css`
|
return [
|
||||||
ha-dialog {
|
haStyleDialog,
|
||||||
--mdc-dialog-title-ink-color: var(--primary-text-color);
|
css`
|
||||||
--justify-action-buttons: space-between;
|
ha-dialog.button-left {
|
||||||
}
|
--justify-action-buttons: flex-start;
|
||||||
ha-dialog.button-left {
|
|
||||||
--justify-action-buttons: flex-start;
|
|
||||||
}
|
|
||||||
@media only screen and (min-width: 600px) {
|
|
||||||
ha-dialog {
|
|
||||||
--mdc-dialog-min-width: 600px;
|
|
||||||
}
|
}
|
||||||
}
|
paper-icon-item {
|
||||||
|
cursor: pointer;
|
||||||
/* make dialog fullscreen on small screens */
|
|
||||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
|
||||||
ha-dialog {
|
|
||||||
--mdc-dialog-min-width: 100vw;
|
|
||||||
--mdc-dialog-max-height: 100vh;
|
|
||||||
--mdc-dialog-shape-radius: 0px;
|
|
||||||
--vertial-align-dialog: flex-end;
|
|
||||||
}
|
}
|
||||||
}
|
`,
|
||||||
.error {
|
];
|
||||||
color: var(--google-red-500);
|
|
||||||
}
|
|
||||||
paper-icon-item {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,262 @@
|
|||||||
|
import {
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
|
customElement,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
property,
|
||||||
|
TemplateResult,
|
||||||
|
} from "lit-element";
|
||||||
|
import "../../../../components/ha-icon-input";
|
||||||
|
import { HomeAssistant } from "../../../../types";
|
||||||
|
import {
|
||||||
|
LovelaceDashboard,
|
||||||
|
LovelaceDashboardMutableParams,
|
||||||
|
LovelaceDashboardCreateParams,
|
||||||
|
} from "../../../../data/lovelace";
|
||||||
|
import { LovelaceDashboardDetailsDialogParams } from "./show-dialog-lovelace-dashboard-detail";
|
||||||
|
import { PolymerChangedEvent } from "../../../../polymer-types";
|
||||||
|
import { HaSwitch } from "../../../../components/ha-switch";
|
||||||
|
import { createCloseHeading } from "../../../../components/ha-dialog";
|
||||||
|
import { haStyleDialog } from "../../../../resources/styles";
|
||||||
|
|
||||||
|
@customElement("dialog-lovelace-dashboard-detail")
|
||||||
|
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 _requireAdmin!: LovelaceDashboard["require_admin"];
|
||||||
|
|
||||||
|
@property() private _error?: string;
|
||||||
|
@property() private _submitting = false;
|
||||||
|
|
||||||
|
public async showDialog(
|
||||||
|
params: LovelaceDashboardDetailsDialogParams
|
||||||
|
): Promise<void> {
|
||||||
|
this._params = params;
|
||||||
|
this._error = undefined;
|
||||||
|
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._requireAdmin = this._params.dashboard.require_admin || false;
|
||||||
|
} else {
|
||||||
|
this._urlPath = "";
|
||||||
|
this._showSidebar = true;
|
||||||
|
this._sidebarIcon = "";
|
||||||
|
this._sidebarTitle = "";
|
||||||
|
this._requireAdmin = false;
|
||||||
|
}
|
||||||
|
await this.updateComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
if (!this._params) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
const urlInvalid = !/^[a-zA-Z0-9_-]+$/.test(this._urlPath);
|
||||||
|
return html`
|
||||||
|
<ha-dialog
|
||||||
|
open
|
||||||
|
@closing="${this._close}"
|
||||||
|
scrimClickAction
|
||||||
|
escapeKeyAction
|
||||||
|
.heading=${createCloseHeading(
|
||||||
|
this.hass,
|
||||||
|
this._params.dashboard
|
||||||
|
? this._sidebarTitle ||
|
||||||
|
this.hass!.localize(
|
||||||
|
"ui.panel.config.lovelace.dashboards.detail.edit_dashboard"
|
||||||
|
)
|
||||||
|
: this.hass!.localize(
|
||||||
|
"ui.panel.config.lovelace.dashboards.detail.new_dashboard"
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
${this._error
|
||||||
|
? html`
|
||||||
|
<div class="error">${this._error}</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
<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>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${!this._params.dashboard
|
||||||
|
? html`
|
||||||
|
<paper-input
|
||||||
|
.value=${this._urlPath}
|
||||||
|
@value-changed=${this._urlChanged}
|
||||||
|
.label=${this.hass!.localize(
|
||||||
|
"ui.panel.config.lovelace.dashboards.detail.url"
|
||||||
|
)}
|
||||||
|
.errorMessage=${this.hass!.localize(
|
||||||
|
"ui.panel.config.lovelace.dashboards.detail.url_error_msg"
|
||||||
|
)}
|
||||||
|
.invalid=${urlInvalid}
|
||||||
|
></paper-input>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
<ha-switch
|
||||||
|
.checked=${this._requireAdmin}
|
||||||
|
@change=${this._requireAdminChanged}
|
||||||
|
>${this.hass!.localize(
|
||||||
|
"ui.panel.config.lovelace.dashboards.detail.require_admin"
|
||||||
|
)}</ha-switch
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
${this._params.dashboard
|
||||||
|
? html`
|
||||||
|
<mwc-button
|
||||||
|
slot="secondaryAction"
|
||||||
|
class="warning"
|
||||||
|
@click="${this._deleteDashboard}"
|
||||||
|
.disabled=${this._submitting}
|
||||||
|
>
|
||||||
|
${this.hass!.localize(
|
||||||
|
"ui.panel.config.lovelace.dashboards.detail.delete"
|
||||||
|
)}
|
||||||
|
</mwc-button>
|
||||||
|
`
|
||||||
|
: html``}
|
||||||
|
<mwc-button
|
||||||
|
slot="primaryAction"
|
||||||
|
@click="${this._updateDashboard}"
|
||||||
|
.disabled=${urlInvalid || this._submitting}
|
||||||
|
>
|
||||||
|
${this._params.dashboard
|
||||||
|
? this.hass!.localize(
|
||||||
|
"ui.panel.config.lovelace.dashboards.detail.update"
|
||||||
|
)
|
||||||
|
: this.hass!.localize(
|
||||||
|
"ui.panel.config.lovelace.dashboards.detail.create"
|
||||||
|
)}
|
||||||
|
</mwc-button>
|
||||||
|
</ha-dialog>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _urlChanged(ev: PolymerChangedEvent<string>) {
|
||||||
|
this._error = undefined;
|
||||||
|
this._urlPath = ev.detail.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _sidebarIconChanged(ev: PolymerChangedEvent<string>) {
|
||||||
|
this._error = undefined;
|
||||||
|
this._sidebarIcon = ev.detail.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _sidebarTitleChanged(ev: PolymerChangedEvent<string>) {
|
||||||
|
this._error = undefined;
|
||||||
|
this._sidebarTitle = ev.detail.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _fillUrlPath() {
|
||||||
|
if (this._urlPath) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const parts = this._sidebarTitle.split(" ");
|
||||||
|
|
||||||
|
if (parts.length) {
|
||||||
|
this._urlPath = parts[0].toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _showSidebarChanged(ev: Event) {
|
||||||
|
this._showSidebar = (ev.target as HaSwitch).checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _requireAdminChanged(ev: Event) {
|
||||||
|
this._requireAdmin = (ev.target as HaSwitch).checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _updateDashboard() {
|
||||||
|
this._submitting = true;
|
||||||
|
try {
|
||||||
|
const values: Partial<LovelaceDashboardMutableParams> = {
|
||||||
|
require_admin: this._requireAdmin,
|
||||||
|
sidebar: this._showSidebar
|
||||||
|
? { icon: this._sidebarIcon, title: this._sidebarTitle }
|
||||||
|
: null,
|
||||||
|
};
|
||||||
|
if (this._params!.dashboard) {
|
||||||
|
await this._params!.updateDashboard(values);
|
||||||
|
} else {
|
||||||
|
(values as LovelaceDashboardCreateParams).url_path = this._urlPath.trim();
|
||||||
|
(values as LovelaceDashboardCreateParams).mode = "storage";
|
||||||
|
await this._params!.createDashboard(
|
||||||
|
values as LovelaceDashboardCreateParams
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this._params = undefined;
|
||||||
|
} catch (err) {
|
||||||
|
this._error = err?.message || "Unknown error";
|
||||||
|
} finally {
|
||||||
|
this._submitting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _deleteDashboard() {
|
||||||
|
this._submitting = true;
|
||||||
|
try {
|
||||||
|
if (await this._params!.removeDashboard()) {
|
||||||
|
this._close();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this._submitting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _close(): void {
|
||||||
|
this._params = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult[] {
|
||||||
|
return [
|
||||||
|
haStyleDialog,
|
||||||
|
css`
|
||||||
|
.form {
|
||||||
|
padding-bottom: 24px;
|
||||||
|
}
|
||||||
|
ha-switch {
|
||||||
|
padding: 16px 0;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"dialog-lovelace-dashboard-detail": DialogLovelaceDashboardDetail;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,276 @@
|
|||||||
|
import {
|
||||||
|
customElement,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
property,
|
||||||
|
PropertyValues,
|
||||||
|
TemplateResult,
|
||||||
|
CSSResult,
|
||||||
|
css,
|
||||||
|
} from "lit-element";
|
||||||
|
import memoize from "memoize-one";
|
||||||
|
import {
|
||||||
|
DataTableColumnContainer,
|
||||||
|
RowClickedEvent,
|
||||||
|
} from "../../../../components/data-table/ha-data-table";
|
||||||
|
import "../../../../components/ha-icon";
|
||||||
|
import "../../../../layouts/hass-loading-screen";
|
||||||
|
import "../../../../layouts/hass-tabs-subpage-data-table";
|
||||||
|
import { HomeAssistant, Route } from "../../../../types";
|
||||||
|
import {
|
||||||
|
LovelaceDashboard,
|
||||||
|
fetchDashboards,
|
||||||
|
createDashboard,
|
||||||
|
updateDashboard,
|
||||||
|
deleteDashboard,
|
||||||
|
LovelaceDashboardCreateParams,
|
||||||
|
} from "../../../../data/lovelace";
|
||||||
|
import { showDashboardDetailDialog } from "./show-dialog-lovelace-dashboard-detail";
|
||||||
|
import { compare } from "../../../../common/string/compare";
|
||||||
|
import {
|
||||||
|
showConfirmationDialog,
|
||||||
|
showAlertDialog,
|
||||||
|
} from "../../../../dialogs/generic/show-dialog-box";
|
||||||
|
import { lovelaceTabs } from "../ha-config-lovelace";
|
||||||
|
import { navigate } from "../../../../common/navigate";
|
||||||
|
|
||||||
|
@customElement("ha-config-lovelace-dashboards")
|
||||||
|
export class HaConfigLovelaceDashboards extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public isWide!: boolean;
|
||||||
|
@property() public narrow!: boolean;
|
||||||
|
@property() public route!: Route;
|
||||||
|
@property() private _dashboards: LovelaceDashboard[] = [];
|
||||||
|
|
||||||
|
private _columns = memoize(
|
||||||
|
(_language, dashboards): DataTableColumnContainer => {
|
||||||
|
const columns: DataTableColumnContainer = {
|
||||||
|
icon: {
|
||||||
|
title: "",
|
||||||
|
type: "icon",
|
||||||
|
template: (icon) =>
|
||||||
|
icon
|
||||||
|
? html`
|
||||||
|
<ha-icon slot="item-icon" .icon=${icon}></ha-icon>
|
||||||
|
`
|
||||||
|
: html``,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
title: this.hass.localize(
|
||||||
|
"ui.panel.config.lovelace.dashboards.picker.headers.title"
|
||||||
|
),
|
||||||
|
sortable: true,
|
||||||
|
filterable: true,
|
||||||
|
direction: "asc",
|
||||||
|
},
|
||||||
|
mode: {
|
||||||
|
title: this.hass.localize(
|
||||||
|
"ui.panel.config.lovelace.dashboards.picker.headers.conf_mode"
|
||||||
|
),
|
||||||
|
sortable: true,
|
||||||
|
filterable: true,
|
||||||
|
template: (mode) =>
|
||||||
|
html`
|
||||||
|
${this.hass.localize(
|
||||||
|
`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: {
|
||||||
|
title: this.hass.localize(
|
||||||
|
"ui.panel.config.lovelace.dashboards.picker.headers.require_admin"
|
||||||
|
),
|
||||||
|
sortable: true,
|
||||||
|
type: "icon",
|
||||||
|
template: (requireAdmin: boolean) =>
|
||||||
|
requireAdmin
|
||||||
|
? html`
|
||||||
|
<ha-icon icon="hass:check-circle-outline"></ha-icon>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
-
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
sidebar: {
|
||||||
|
title: this.hass.localize(
|
||||||
|
"ui.panel.config.lovelace.dashboards.picker.headers.sidebar"
|
||||||
|
),
|
||||||
|
type: "icon",
|
||||||
|
template: (sidebar) =>
|
||||||
|
sidebar
|
||||||
|
? html`
|
||||||
|
<ha-icon icon="hass:check-circle-outline"></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
|
||||||
|
>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return { ...columns, ...columns2 };
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
private _getItems = memoize((dashboards: LovelaceDashboard[]) => {
|
||||||
|
return dashboards.map((dashboard) => {
|
||||||
|
return {
|
||||||
|
filename: "",
|
||||||
|
...dashboard,
|
||||||
|
icon: dashboard.sidebar?.icon,
|
||||||
|
title: dashboard.sidebar?.title || dashboard.url_path,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
if (!this.hass || this._dashboards === undefined) {
|
||||||
|
return html`
|
||||||
|
<hass-loading-screen></hass-loading-screen>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<hass-tabs-subpage-data-table
|
||||||
|
.hass=${this.hass}
|
||||||
|
.narrow=${this.narrow}
|
||||||
|
back-path="/config"
|
||||||
|
.route=${this.route}
|
||||||
|
.tabs=${lovelaceTabs}
|
||||||
|
.columns=${this._columns(this.hass.language, this._dashboards)}
|
||||||
|
.data=${this._getItems(this._dashboards)}
|
||||||
|
@row-click=${this._editDashboard}
|
||||||
|
>
|
||||||
|
</hass-tabs-subpage-data-table>
|
||||||
|
<ha-fab
|
||||||
|
?is-wide=${this.isWide}
|
||||||
|
?narrow=${this.narrow}
|
||||||
|
icon="hass:plus"
|
||||||
|
title="${this.hass.localize(
|
||||||
|
"ui.panel.config.lovelace.dashboards.picker.add_dashboard"
|
||||||
|
)}"
|
||||||
|
@click=${this._addDashboard}
|
||||||
|
></ha-fab>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
this._getDashboards();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _getDashboards() {
|
||||||
|
this._dashboards = await fetchDashboards(this.hass);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _navigate(ev: Event) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const url = `/${(ev.target as any).urlPath}`;
|
||||||
|
navigate(this, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _editDashboard(ev: CustomEvent) {
|
||||||
|
const id = (ev.detail as RowClickedEvent).id;
|
||||||
|
const dashboard = id
|
||||||
|
? this._dashboards.find((res) => res.id === id)
|
||||||
|
: undefined;
|
||||||
|
if (!dashboard) {
|
||||||
|
showAlertDialog(this, {
|
||||||
|
text: this.hass!.localize(
|
||||||
|
"ui.panel.config.lovelace.dashboards.cant_edit_yaml"
|
||||||
|
),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._openDialog(dashboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addDashboard() {
|
||||||
|
this._openDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _openDialog(dashboard?: LovelaceDashboard): Promise<void> {
|
||||||
|
showDashboardDetailDialog(this, {
|
||||||
|
dashboard,
|
||||||
|
createDashboard: async (values: LovelaceDashboardCreateParams) => {
|
||||||
|
const created = await createDashboard(this.hass!, values);
|
||||||
|
this._dashboards = this._dashboards!.concat(
|
||||||
|
created
|
||||||
|
).sort((res1, res2) => compare(res1.url_path, res2.url_path));
|
||||||
|
},
|
||||||
|
updateDashboard: async (values) => {
|
||||||
|
const updated = await updateDashboard(
|
||||||
|
this.hass!,
|
||||||
|
dashboard!.id,
|
||||||
|
values
|
||||||
|
);
|
||||||
|
this._dashboards = this._dashboards!.map((res) =>
|
||||||
|
res === dashboard ? updated : res
|
||||||
|
);
|
||||||
|
},
|
||||||
|
removeDashboard: async () => {
|
||||||
|
if (
|
||||||
|
!(await showConfirmationDialog(this, {
|
||||||
|
text: this.hass!.localize(
|
||||||
|
"ui.panel.config.lovelace.dashboards.confirm_delete"
|
||||||
|
),
|
||||||
|
}))
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await deleteDashboard(this.hass!, dashboard!.id);
|
||||||
|
this._dashboards = this._dashboards!.filter(
|
||||||
|
(res) => res !== dashboard
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
ha-fab {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 16px;
|
||||||
|
right: 16px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
ha-fab[is-wide] {
|
||||||
|
bottom: 24px;
|
||||||
|
right: 24px;
|
||||||
|
}
|
||||||
|
ha-fab[narrow] {
|
||||||
|
bottom: 84px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
import {
|
||||||
|
LovelaceDashboard,
|
||||||
|
LovelaceDashboardMutableParams,
|
||||||
|
LovelaceDashboardCreateParams,
|
||||||
|
} from "../../../../data/lovelace";
|
||||||
|
|
||||||
|
export interface LovelaceDashboardDetailsDialogParams {
|
||||||
|
dashboard?: LovelaceDashboard;
|
||||||
|
createDashboard: (values: LovelaceDashboardCreateParams) => Promise<unknown>;
|
||||||
|
updateDashboard: (
|
||||||
|
updates: Partial<LovelaceDashboardMutableParams>
|
||||||
|
) => Promise<unknown>;
|
||||||
|
removeDashboard: () => Promise<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const loadDashboardDetailDialog = () =>
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "lovelace-dashboard-detail-dialog" */ "./dialog-lovelace-dashboard-detail"
|
||||||
|
);
|
||||||
|
|
||||||
|
export const showDashboardDetailDialog = (
|
||||||
|
element: HTMLElement,
|
||||||
|
dialogParams: LovelaceDashboardDetailsDialogParams
|
||||||
|
) => {
|
||||||
|
fireEvent(element, "show-dialog", {
|
||||||
|
dialogTag: "dialog-lovelace-dashboard-detail",
|
||||||
|
dialogImport: loadDashboardDetailDialog,
|
||||||
|
dialogParams,
|
||||||
|
});
|
||||||
|
};
|
63
src/panels/config/lovelace/ha-config-lovelace.ts
Normal file
63
src/panels/config/lovelace/ha-config-lovelace.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import {
|
||||||
|
HassRouterPage,
|
||||||
|
RouterOptions,
|
||||||
|
} from "../../../layouts/hass-router-page";
|
||||||
|
import { property, customElement } from "lit-element";
|
||||||
|
import { HomeAssistant } from "../../../types";
|
||||||
|
|
||||||
|
export const lovelaceTabs = [
|
||||||
|
{
|
||||||
|
component: "lovelace",
|
||||||
|
path: "/config/lovelace/dashboards",
|
||||||
|
translationKey: "ui.panel.config.lovelace.dashboards.caption",
|
||||||
|
icon: "hass:view-dashboard",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: "lovelace",
|
||||||
|
path: "/config/lovelace/resources",
|
||||||
|
translationKey: "ui.panel.config.lovelace.resources.caption",
|
||||||
|
icon: "hass:file-multiple",
|
||||||
|
advancedOnly: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@customElement("ha-config-lovelace")
|
||||||
|
class HaConfigLovelace extends HassRouterPage {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public narrow!: boolean;
|
||||||
|
@property() public isWide!: boolean;
|
||||||
|
|
||||||
|
protected routerOptions: RouterOptions = {
|
||||||
|
defaultPage: "dashboards",
|
||||||
|
routes: {
|
||||||
|
dashboards: {
|
||||||
|
tag: "ha-config-lovelace-dashboards",
|
||||||
|
load: () =>
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "panel-config-lovelace-dashboards" */ "./dashboards/ha-config-lovelace-dashboards"
|
||||||
|
),
|
||||||
|
cache: true,
|
||||||
|
},
|
||||||
|
resources: {
|
||||||
|
tag: "ha-config-lovelace-resources",
|
||||||
|
load: () =>
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "panel-config-lovelace-resources" */ "./resources/ha-config-lovelace-resources"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
protected updatePageEl(pageEl) {
|
||||||
|
pageEl.hass = this.hass;
|
||||||
|
pageEl.narrow = this.narrow;
|
||||||
|
pageEl.isWide = this.isWide;
|
||||||
|
pageEl.route = this.routeTail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-config-lovelace": HaConfigLovelace;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,228 @@
|
|||||||
|
import {
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
|
customElement,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
property,
|
||||||
|
TemplateResult,
|
||||||
|
} from "lit-element";
|
||||||
|
import { HomeAssistant } from "../../../../types";
|
||||||
|
import {
|
||||||
|
LovelaceResource,
|
||||||
|
LovelaceResourcesMutableParams,
|
||||||
|
} from "../../../../data/lovelace";
|
||||||
|
import { LovelaceResourceDetailsDialogParams } from "./show-dialog-lovelace-resource-detail";
|
||||||
|
import { PolymerChangedEvent } from "../../../../polymer-types";
|
||||||
|
import { createCloseHeading } from "../../../../components/ha-dialog";
|
||||||
|
import { haStyleDialog } from "../../../../resources/styles";
|
||||||
|
|
||||||
|
@customElement("dialog-lovelace-resource-detail")
|
||||||
|
export class DialogLovelaceResourceDetail extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() private _params?: LovelaceResourceDetailsDialogParams;
|
||||||
|
@property() private _url!: LovelaceResource["url"];
|
||||||
|
@property() private _type!: LovelaceResource["type"];
|
||||||
|
@property() private _error?: string;
|
||||||
|
@property() private _submitting = false;
|
||||||
|
|
||||||
|
public async showDialog(
|
||||||
|
params: LovelaceResourceDetailsDialogParams
|
||||||
|
): Promise<void> {
|
||||||
|
this._params = params;
|
||||||
|
this._error = undefined;
|
||||||
|
if (this._params.resource) {
|
||||||
|
this._url = this._params.resource.url || "";
|
||||||
|
this._type = this._params.resource.type || "module";
|
||||||
|
} else {
|
||||||
|
this._url = "";
|
||||||
|
this._type = "module";
|
||||||
|
}
|
||||||
|
await this.updateComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
if (!this._params) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
const urlInvalid = this._url.trim() === "";
|
||||||
|
return html`
|
||||||
|
<ha-dialog
|
||||||
|
open
|
||||||
|
@closing=${this._close}
|
||||||
|
scrimClickAction
|
||||||
|
escapeKeyAction
|
||||||
|
.heading=${createCloseHeading(
|
||||||
|
this.hass,
|
||||||
|
this._params.resource
|
||||||
|
? this._params.resource.url
|
||||||
|
: this.hass!.localize(
|
||||||
|
"ui.panel.config.lovelace.resources.detail.new_resource"
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
${this._error
|
||||||
|
? html`
|
||||||
|
<div class="error">${this._error}</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
<div class="form">
|
||||||
|
<h3 class="warning">
|
||||||
|
${this.hass!.localize(
|
||||||
|
"ui.panel.config.lovelace.resources.detail.warning_header"
|
||||||
|
)}
|
||||||
|
</h3>
|
||||||
|
${this.hass!.localize(
|
||||||
|
"ui.panel.config.lovelace.resources.detail.warning_text"
|
||||||
|
)}
|
||||||
|
<paper-input
|
||||||
|
.value=${this._url}
|
||||||
|
@value-changed=${this._urlChanged}
|
||||||
|
.label=${this.hass!.localize(
|
||||||
|
"ui.panel.config.lovelace.resources.detail.url"
|
||||||
|
)}
|
||||||
|
.errorMessage=${this.hass!.localize(
|
||||||
|
"ui.panel.config.lovelace.resources.detail.url_error_msg"
|
||||||
|
)}
|
||||||
|
.invalid=${urlInvalid}
|
||||||
|
></paper-input>
|
||||||
|
<br />
|
||||||
|
<ha-paper-dropdown-menu
|
||||||
|
.label=${this.hass!.localize(
|
||||||
|
"ui.panel.config.lovelace.resources.detail.type"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<paper-listbox
|
||||||
|
slot="dropdown-content"
|
||||||
|
.selected=${this._type}
|
||||||
|
@iron-select=${this._typeChanged}
|
||||||
|
attr-for-selected="type"
|
||||||
|
>
|
||||||
|
<paper-item type="module">
|
||||||
|
${this.hass!.localize(
|
||||||
|
"ui.panel.config.lovelace.resources.types.module"
|
||||||
|
)}
|
||||||
|
</paper-item>
|
||||||
|
${this._type === "js"
|
||||||
|
? html`
|
||||||
|
<paper-item type="js">
|
||||||
|
${this.hass!.localize(
|
||||||
|
"ui.panel.config.lovelace.resources.types.js"
|
||||||
|
)}
|
||||||
|
</paper-item>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
<paper-item type="css">
|
||||||
|
${this.hass!.localize(
|
||||||
|
"ui.panel.config.lovelace.resources.types.css"
|
||||||
|
)}
|
||||||
|
</paper-item>
|
||||||
|
${this._type === "html"
|
||||||
|
? html`
|
||||||
|
<paper-item type="html">
|
||||||
|
${this.hass!.localize(
|
||||||
|
"ui.panel.config.lovelace.resources.types.html"
|
||||||
|
)}
|
||||||
|
</paper-item>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</paper-listbox>
|
||||||
|
</ha-paper-dropdown-menu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
${this._params.resource
|
||||||
|
? html`
|
||||||
|
<mwc-button
|
||||||
|
slot="secondaryAction"
|
||||||
|
class="warning"
|
||||||
|
@click="${this._deleteResource}"
|
||||||
|
.disabled=${this._submitting}
|
||||||
|
>
|
||||||
|
${this.hass!.localize(
|
||||||
|
"ui.panel.config.lovelace.resources.detail.delete"
|
||||||
|
)}
|
||||||
|
</mwc-button>
|
||||||
|
`
|
||||||
|
: html``}
|
||||||
|
<mwc-button
|
||||||
|
slot="primaryAction"
|
||||||
|
@click="${this._updateResource}"
|
||||||
|
.disabled=${urlInvalid || this._submitting}
|
||||||
|
>
|
||||||
|
${this._params.resource
|
||||||
|
? this.hass!.localize(
|
||||||
|
"ui.panel.config.lovelace.resources.detail.update"
|
||||||
|
)
|
||||||
|
: this.hass!.localize(
|
||||||
|
"ui.panel.config.lovelace.resources.detail.create"
|
||||||
|
)}
|
||||||
|
</mwc-button>
|
||||||
|
</ha-dialog>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _urlChanged(ev: PolymerChangedEvent<string>) {
|
||||||
|
this._error = undefined;
|
||||||
|
this._url = ev.detail.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _typeChanged(ev: CustomEvent) {
|
||||||
|
this._type = ev.detail.item.getAttribute("type");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _updateResource() {
|
||||||
|
this._submitting = true;
|
||||||
|
try {
|
||||||
|
const values: LovelaceResourcesMutableParams = {
|
||||||
|
url: this._url.trim(),
|
||||||
|
res_type: this._type,
|
||||||
|
};
|
||||||
|
if (this._params!.resource) {
|
||||||
|
await this._params!.updateResource(values);
|
||||||
|
} else {
|
||||||
|
await this._params!.createResource(values);
|
||||||
|
}
|
||||||
|
this._params = undefined;
|
||||||
|
} catch (err) {
|
||||||
|
this._error = err?.message || "Unknown error";
|
||||||
|
} finally {
|
||||||
|
this._submitting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _deleteResource() {
|
||||||
|
this._submitting = true;
|
||||||
|
try {
|
||||||
|
if (await this._params!.removeResource()) {
|
||||||
|
this._close();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this._submitting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _close(): void {
|
||||||
|
this._params = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult[] {
|
||||||
|
return [
|
||||||
|
haStyleDialog,
|
||||||
|
css`
|
||||||
|
.form {
|
||||||
|
padding-bottom: 24px;
|
||||||
|
}
|
||||||
|
.warning {
|
||||||
|
color: var(--error-color);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"dialog-lovelace-resource-detail": DialogLovelaceResourceDetail;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,209 @@
|
|||||||
|
import "@polymer/paper-checkbox/paper-checkbox";
|
||||||
|
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||||
|
import "@polymer/paper-item/paper-icon-item";
|
||||||
|
import "@polymer/paper-listbox/paper-listbox";
|
||||||
|
import "@polymer/paper-tooltip/paper-tooltip";
|
||||||
|
import {
|
||||||
|
customElement,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
property,
|
||||||
|
PropertyValues,
|
||||||
|
TemplateResult,
|
||||||
|
CSSResult,
|
||||||
|
css,
|
||||||
|
} from "lit-element";
|
||||||
|
import memoize from "memoize-one";
|
||||||
|
import "../../../../common/search/search-input";
|
||||||
|
import {
|
||||||
|
DataTableColumnContainer,
|
||||||
|
RowClickedEvent,
|
||||||
|
} from "../../../../components/data-table/ha-data-table";
|
||||||
|
import "../../../../components/ha-icon";
|
||||||
|
import "../../../../layouts/hass-loading-screen";
|
||||||
|
import "../../../../layouts/hass-tabs-subpage-data-table";
|
||||||
|
import { HomeAssistant, Route } from "../../../../types";
|
||||||
|
import {
|
||||||
|
LovelaceResource,
|
||||||
|
fetchResources,
|
||||||
|
createResource,
|
||||||
|
updateResource,
|
||||||
|
deleteResource,
|
||||||
|
} from "../../../../data/lovelace";
|
||||||
|
import { showResourceDetailDialog } from "./show-dialog-lovelace-resource-detail";
|
||||||
|
import { compare } from "../../../../common/string/compare";
|
||||||
|
import {
|
||||||
|
showConfirmationDialog,
|
||||||
|
showAlertDialog,
|
||||||
|
} from "../../../../dialogs/generic/show-dialog-box";
|
||||||
|
import { lovelaceTabs } from "../ha-config-lovelace";
|
||||||
|
import { loadLovelaceResources } from "../../../lovelace/common/load-resources";
|
||||||
|
|
||||||
|
@customElement("ha-config-lovelace-resources")
|
||||||
|
export class HaConfigLovelaceRescources extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public isWide!: boolean;
|
||||||
|
@property() public narrow!: boolean;
|
||||||
|
@property() public route!: Route;
|
||||||
|
@property() private _resources: LovelaceResource[] = [];
|
||||||
|
|
||||||
|
private _columns = memoize(
|
||||||
|
(_language): DataTableColumnContainer => {
|
||||||
|
return {
|
||||||
|
url: {
|
||||||
|
title: this.hass.localize(
|
||||||
|
"ui.panel.config.lovelace.resources.picker.headers.url"
|
||||||
|
),
|
||||||
|
sortable: true,
|
||||||
|
filterable: true,
|
||||||
|
direction: "asc",
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
title: this.hass.localize(
|
||||||
|
"ui.panel.config.lovelace.resources.picker.headers.type"
|
||||||
|
),
|
||||||
|
sortable: true,
|
||||||
|
filterable: true,
|
||||||
|
template: (type) =>
|
||||||
|
html`
|
||||||
|
${this.hass.localize(
|
||||||
|
`ui.panel.config.lovelace.resources.types.${type}`
|
||||||
|
) || type}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
if (!this.hass || this._resources === undefined) {
|
||||||
|
return html`
|
||||||
|
<hass-loading-screen></hass-loading-screen>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<hass-tabs-subpage-data-table
|
||||||
|
.hass=${this.hass}
|
||||||
|
.narrow=${this.narrow}
|
||||||
|
back-path="/config"
|
||||||
|
.route=${this.route}
|
||||||
|
.tabs=${lovelaceTabs}
|
||||||
|
.columns=${this._columns(this.hass.language)}
|
||||||
|
.data=${this._resources}
|
||||||
|
@row-click=${this._editResource}
|
||||||
|
>
|
||||||
|
</hass-tabs-subpage-data-table>
|
||||||
|
<ha-fab
|
||||||
|
?is-wide=${this.isWide}
|
||||||
|
?narrow=${this.narrow}
|
||||||
|
icon="hass:plus"
|
||||||
|
title="${this.hass.localize(
|
||||||
|
"ui.panel.config.lovelace.resources.picker.add_resource"
|
||||||
|
)}"
|
||||||
|
@click=${this._addResource}
|
||||||
|
></ha-fab>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
this._getResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _getResources() {
|
||||||
|
this._resources = await fetchResources(this.hass.connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _editResource(ev: CustomEvent) {
|
||||||
|
if ((this.hass.panels.lovelace?.config as any)?.mode !== "storage") {
|
||||||
|
showAlertDialog(this, {
|
||||||
|
text: this.hass!.localize(
|
||||||
|
"ui.panel.config.lovelace.resources.cant_edit_yaml"
|
||||||
|
),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const id = (ev.detail as RowClickedEvent).id;
|
||||||
|
const resource = this._resources.find((res) => res.id === id);
|
||||||
|
this._openDialog(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addResource() {
|
||||||
|
if ((this.hass.panels.lovelace?.config as any)?.mode !== "storage") {
|
||||||
|
showAlertDialog(this, {
|
||||||
|
text: this.hass!.localize(
|
||||||
|
"ui.panel.config.lovelace.resources.cant_edit_yaml"
|
||||||
|
),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._openDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _openDialog(resource?: LovelaceResource): Promise<void> {
|
||||||
|
showResourceDetailDialog(this, {
|
||||||
|
resource,
|
||||||
|
createResource: async (values) => {
|
||||||
|
const created = await createResource(this.hass!, values);
|
||||||
|
this._resources = this._resources!.concat(created).sort((res1, res2) =>
|
||||||
|
compare(res1.url, res2.url)
|
||||||
|
);
|
||||||
|
loadLovelaceResources(this._resources, this.hass!.auth.data.hassUrl);
|
||||||
|
},
|
||||||
|
updateResource: async (values) => {
|
||||||
|
const updated = await updateResource(this.hass!, resource!.id, values);
|
||||||
|
this._resources = this._resources!.map((res) =>
|
||||||
|
res === resource ? updated : res
|
||||||
|
);
|
||||||
|
loadLovelaceResources(this._resources, this.hass!.auth.data.hassUrl);
|
||||||
|
},
|
||||||
|
removeResource: async () => {
|
||||||
|
if (
|
||||||
|
!(await showConfirmationDialog(this, {
|
||||||
|
text: this.hass!.localize(
|
||||||
|
"ui.panel.config.lovelace.resources.confirm_delete"
|
||||||
|
),
|
||||||
|
}))
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await deleteResource(this.hass!, resource!.id);
|
||||||
|
this._resources = this._resources!.filter((res) => res !== resource);
|
||||||
|
showConfirmationDialog(this, {
|
||||||
|
title: this.hass!.localize(
|
||||||
|
"ui.panel.config.lovelace.resources.refresh_header"
|
||||||
|
),
|
||||||
|
text: this.hass!.localize(
|
||||||
|
"ui.panel.config.lovelace.resources.refresh_body"
|
||||||
|
),
|
||||||
|
confirm: () => location.reload(),
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
ha-fab {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 16px;
|
||||||
|
right: 16px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
ha-fab[is-wide] {
|
||||||
|
bottom: 24px;
|
||||||
|
right: 24px;
|
||||||
|
}
|
||||||
|
ha-fab[narrow] {
|
||||||
|
bottom: 84px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
import {
|
||||||
|
LovelaceResource,
|
||||||
|
LovelaceResourcesMutableParams,
|
||||||
|
} from "../../../../data/lovelace";
|
||||||
|
|
||||||
|
export interface LovelaceResourceDetailsDialogParams {
|
||||||
|
resource?: LovelaceResource;
|
||||||
|
createResource: (values: LovelaceResourcesMutableParams) => Promise<unknown>;
|
||||||
|
updateResource: (
|
||||||
|
updates: Partial<LovelaceResourcesMutableParams>
|
||||||
|
) => Promise<unknown>;
|
||||||
|
removeResource: () => Promise<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const loadResourceDetailDialog = () =>
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "lovelace-resource-detail-dialog" */ "./dialog-lovelace-resource-detail"
|
||||||
|
);
|
||||||
|
|
||||||
|
export const showResourceDetailDialog = (
|
||||||
|
element: HTMLElement,
|
||||||
|
dialogParams: LovelaceResourceDetailsDialogParams
|
||||||
|
) => {
|
||||||
|
fireEvent(element, "show-dialog", {
|
||||||
|
dialogTag: "dialog-lovelace-resource-detail",
|
||||||
|
dialogImport: loadResourceDetailDialog,
|
||||||
|
dialogParams,
|
||||||
|
});
|
||||||
|
};
|
@ -13,11 +13,12 @@ import "@material/mwc-button";
|
|||||||
|
|
||||||
import "../../../components/entity/ha-entities-picker";
|
import "../../../components/entity/ha-entities-picker";
|
||||||
import "../../../components/user/ha-user-picker";
|
import "../../../components/user/ha-user-picker";
|
||||||
import "../../../components/ha-dialog";
|
|
||||||
import { PersonDetailDialogParams } from "./show-dialog-person-detail";
|
import { PersonDetailDialogParams } from "./show-dialog-person-detail";
|
||||||
import { PolymerChangedEvent } from "../../../polymer-types";
|
import { PolymerChangedEvent } from "../../../polymer-types";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { PersonMutableParams } from "../../../data/person";
|
import { PersonMutableParams } from "../../../data/person";
|
||||||
|
import { createCloseHeading } from "../../../components/ha-dialog";
|
||||||
|
import { haStyleDialog } from "../../../resources/styles";
|
||||||
|
|
||||||
class DialogPersonDetail extends LitElement {
|
class DialogPersonDetail extends LitElement {
|
||||||
@property() public hass!: HomeAssistant;
|
@property() public hass!: HomeAssistant;
|
||||||
@ -55,26 +56,18 @@ class DialogPersonDetail extends LitElement {
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
const nameInvalid = this._name.trim() === "";
|
const nameInvalid = this._name.trim() === "";
|
||||||
const title = html`
|
|
||||||
${this._params.entry
|
|
||||||
? this._params.entry.name
|
|
||||||
: this.hass!.localize("ui.panel.config.person.detail.new_person")}
|
|
||||||
<paper-icon-button
|
|
||||||
aria-label=${this.hass.localize(
|
|
||||||
"ui.panel.config.integrations.config_flow.dismiss"
|
|
||||||
)}
|
|
||||||
icon="hass:close"
|
|
||||||
dialogAction="close"
|
|
||||||
style="position: absolute; right: 16px; top: 12px;"
|
|
||||||
></paper-icon-button>
|
|
||||||
`;
|
|
||||||
return html`
|
return html`
|
||||||
<ha-dialog
|
<ha-dialog
|
||||||
open
|
open
|
||||||
@closing="${this._close}"
|
@closing="${this._close}"
|
||||||
scrimClickAction=""
|
scrimClickAction=""
|
||||||
escapeKeyAction=""
|
escapeKeyAction=""
|
||||||
.heading=${title}
|
.heading=${createCloseHeading(
|
||||||
|
this.hass,
|
||||||
|
this._params.entry
|
||||||
|
? this._params.entry.name
|
||||||
|
: this.hass!.localize("ui.panel.config.person.detail.new_person")
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
${this._error
|
${this._error
|
||||||
@ -236,34 +229,14 @@ class DialogPersonDetail extends LitElement {
|
|||||||
|
|
||||||
static get styles(): CSSResult[] {
|
static get styles(): CSSResult[] {
|
||||||
return [
|
return [
|
||||||
|
haStyleDialog,
|
||||||
css`
|
css`
|
||||||
ha-dialog {
|
|
||||||
--mdc-dialog-min-width: 400px;
|
|
||||||
--mdc-dialog-max-width: 600px;
|
|
||||||
--mdc-dialog-title-ink-color: var(--primary-text-color);
|
|
||||||
--justify-action-buttons: space-between;
|
|
||||||
}
|
|
||||||
/* make dialog fullscreen on small screens */
|
|
||||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
|
||||||
ha-dialog {
|
|
||||||
--mdc-dialog-min-width: 100vw;
|
|
||||||
--mdc-dialog-max-height: 100vh;
|
|
||||||
--mdc-dialog-shape-radius: 0px;
|
|
||||||
--vertial-align-dialog: flex-end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.form {
|
.form {
|
||||||
padding-bottom: 24px;
|
padding-bottom: 24px;
|
||||||
}
|
}
|
||||||
ha-user-picker {
|
ha-user-picker {
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
mwc-button.warning {
|
|
||||||
--mdc-theme-primary: var(--google-red-500);
|
|
||||||
}
|
|
||||||
.error {
|
|
||||||
color: var(--google-red-500);
|
|
||||||
}
|
|
||||||
a {
|
a {
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ import "@material/mwc-button";
|
|||||||
|
|
||||||
import "../../../components/map/ha-location-editor";
|
import "../../../components/map/ha-location-editor";
|
||||||
import "../../../components/ha-switch";
|
import "../../../components/ha-switch";
|
||||||
import "../../../components/ha-dialog";
|
|
||||||
|
|
||||||
import { ZoneDetailDialogParams } from "./show-dialog-zone-detail";
|
import { ZoneDetailDialogParams } from "./show-dialog-zone-detail";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
@ -23,6 +22,8 @@ import {
|
|||||||
getZoneEditorInitData,
|
getZoneEditorInitData,
|
||||||
} from "../../../data/zone";
|
} from "../../../data/zone";
|
||||||
import { addDistanceToCoord } from "../../../common/location/add_distance_to_coord";
|
import { addDistanceToCoord } from "../../../common/location/add_distance_to_coord";
|
||||||
|
import { createCloseHeading } from "../../../components/ha-dialog";
|
||||||
|
import { haStyleDialog } from "../../../resources/styles";
|
||||||
|
|
||||||
class DialogZoneDetail extends LitElement {
|
class DialogZoneDetail extends LitElement {
|
||||||
@property() public hass!: HomeAssistant;
|
@property() public hass!: HomeAssistant;
|
||||||
@ -72,19 +73,6 @@ class DialogZoneDetail extends LitElement {
|
|||||||
if (!this._params) {
|
if (!this._params) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
const title = html`
|
|
||||||
${this._params.entry
|
|
||||||
? this._params.entry.name
|
|
||||||
: this.hass!.localize("ui.panel.config.zone.detail.new_zone")}
|
|
||||||
<paper-icon-button
|
|
||||||
aria-label=${this.hass.localize(
|
|
||||||
"ui.panel.config.integrations.config_flow.dismiss"
|
|
||||||
)}
|
|
||||||
icon="hass:close"
|
|
||||||
dialogAction="close"
|
|
||||||
style="position: absolute; right: 16px; top: 12px;"
|
|
||||||
></paper-icon-button>
|
|
||||||
`;
|
|
||||||
const nameValid = this._name.trim() === "";
|
const nameValid = this._name.trim() === "";
|
||||||
const iconValid = !this._icon.trim().includes(":");
|
const iconValid = !this._icon.trim().includes(":");
|
||||||
const latValid = String(this._latitude) === "";
|
const latValid = String(this._latitude) === "";
|
||||||
@ -100,7 +88,12 @@ class DialogZoneDetail extends LitElement {
|
|||||||
@closing="${this._close}"
|
@closing="${this._close}"
|
||||||
scrimClickAction=""
|
scrimClickAction=""
|
||||||
escapeKeyAction=""
|
escapeKeyAction=""
|
||||||
.heading=${title}
|
.heading=${createCloseHeading(
|
||||||
|
this.hass,
|
||||||
|
this._params.entry
|
||||||
|
? this._params.entry.name
|
||||||
|
: this.hass!.localize("ui.panel.config.zone.detail.new_zone")
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
${this._error
|
${this._error
|
||||||
@ -277,26 +270,8 @@ class DialogZoneDetail extends LitElement {
|
|||||||
|
|
||||||
static get styles(): CSSResult[] {
|
static get styles(): CSSResult[] {
|
||||||
return [
|
return [
|
||||||
|
haStyleDialog,
|
||||||
css`
|
css`
|
||||||
ha-dialog {
|
|
||||||
--mdc-dialog-title-ink-color: var(--primary-text-color);
|
|
||||||
--justify-action-buttons: space-between;
|
|
||||||
}
|
|
||||||
@media only screen and (min-width: 600px) {
|
|
||||||
ha-dialog {
|
|
||||||
--mdc-dialog-min-width: 600px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* make dialog fullscreen on small screens */
|
|
||||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
|
||||||
ha-dialog {
|
|
||||||
--mdc-dialog-min-width: 100vw;
|
|
||||||
--mdc-dialog-max-height: 100vh;
|
|
||||||
--mdc-dialog-shape-radius: 0px;
|
|
||||||
--vertial-align-dialog: flex-end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.form {
|
.form {
|
||||||
padding-bottom: 24px;
|
padding-bottom: 24px;
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
@ -320,12 +295,6 @@ class DialogZoneDetail extends LitElement {
|
|||||||
ha-user-picker {
|
ha-user-picker {
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
mwc-button.warning {
|
|
||||||
--mdc-theme-primary: var(--google-red-500);
|
|
||||||
}
|
|
||||||
.error {
|
|
||||||
color: var(--google-red-500);
|
|
||||||
}
|
|
||||||
a {
|
a {
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { loadModule, loadCSS, loadJS } from "../../../common/dom/load_resource";
|
import { loadModule, loadCSS, loadJS } from "../../../common/dom/load_resource";
|
||||||
|
|
||||||
import { LovelaceResources } from "../../../data/lovelace";
|
import { LovelaceResource } from "../../../data/lovelace";
|
||||||
|
|
||||||
// CSS and JS should only be imported once. Modules and HTML are safe.
|
// CSS and JS should only be imported once. Modules and HTML are safe.
|
||||||
const CSS_CACHE = {};
|
const CSS_CACHE = {};
|
||||||
const JS_CACHE = {};
|
const JS_CACHE = {};
|
||||||
|
|
||||||
export const loadLovelaceResources = (
|
export const loadLovelaceResources = (
|
||||||
resources: NonNullable<LovelaceResources>,
|
resources: NonNullable<LovelaceResource[]>,
|
||||||
hassUrl: string
|
hassUrl: string
|
||||||
) =>
|
) =>
|
||||||
resources.forEach((resource) => {
|
resources.forEach((resource) => {
|
||||||
|
@ -180,4 +180,27 @@ export const haStyleDialog = css`
|
|||||||
border-bottom-right-radius: 0px;
|
border-bottom-right-radius: 0px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* mwc-dialog (ha-dialog) styles */
|
||||||
|
ha-dialog {
|
||||||
|
--mdc-dialog-min-width: 400px;
|
||||||
|
--mdc-dialog-max-width: 600px;
|
||||||
|
--mdc-dialog-title-ink-color: var(--primary-text-color);
|
||||||
|
--justify-action-buttons: space-between;
|
||||||
|
}
|
||||||
|
/* make dialog fullscreen on small screens */
|
||||||
|
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||||
|
ha-dialog {
|
||||||
|
--mdc-dialog-min-width: 100vw;
|
||||||
|
--mdc-dialog-max-height: 100vh;
|
||||||
|
--mdc-dialog-shape-radius: 0px;
|
||||||
|
--vertial-align-dialog: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mwc-button.warning {
|
||||||
|
--mdc-theme-primary: var(--google-red-500);
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
color: var(--google-red-500);
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -595,7 +595,8 @@
|
|||||||
"generic": {
|
"generic": {
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"default_confirmation_title": "Are you sure?"
|
"default_confirmation_title": "Are you sure?",
|
||||||
|
"close": "close"
|
||||||
},
|
},
|
||||||
"more_info_control": {
|
"more_info_control": {
|
||||||
"dismiss": "Dismiss dialog",
|
"dismiss": "Dismiss dialog",
|
||||||
@ -847,6 +848,76 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"lovelace": {
|
||||||
|
"caption": "Lovelace Dashboards",
|
||||||
|
"description": "Configure your Lovelace Dashboards",
|
||||||
|
"dashboards": {
|
||||||
|
"caption": "Dashboards",
|
||||||
|
"conf_mode": {
|
||||||
|
"yaml": "YAML file",
|
||||||
|
"storage": "UI controlled"
|
||||||
|
},
|
||||||
|
"picker": {
|
||||||
|
"headers": {
|
||||||
|
"title": "Title",
|
||||||
|
"conf_mode": "Configuration method",
|
||||||
|
"require_admin": "Admin only",
|
||||||
|
"sidebar": "Show in sidebar",
|
||||||
|
"filename": "Filename"
|
||||||
|
},
|
||||||
|
"open": "Open dashboard",
|
||||||
|
"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.",
|
||||||
|
"detail": {
|
||||||
|
"edit_dashboard": "Edit dashboard",
|
||||||
|
"new_dashboard": "Add new dashboard",
|
||||||
|
"dismiss": "Close",
|
||||||
|
"show_sidebar": "Show in sidebar",
|
||||||
|
"icon": "Sidebar icon",
|
||||||
|
"title": "Sidebar title",
|
||||||
|
"url": "Url",
|
||||||
|
"url_error_msg": "The url can not contain spaces or special characters, except for _ and -",
|
||||||
|
"require_admin": "Admin only",
|
||||||
|
"delete": "Delete",
|
||||||
|
"update": "Update",
|
||||||
|
"create": "Create"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resources": {
|
||||||
|
"caption": "Resources",
|
||||||
|
"types": {
|
||||||
|
"css": "Stylesheet",
|
||||||
|
"html": "HTML (deprecated)",
|
||||||
|
"js": "JavaScript File (deprecated)",
|
||||||
|
"module": "JavaScript Module"
|
||||||
|
},
|
||||||
|
"picker": {
|
||||||
|
"headers": {
|
||||||
|
"url": "Url",
|
||||||
|
"type": "Type"
|
||||||
|
},
|
||||||
|
"add_resource": "Add resource"
|
||||||
|
},
|
||||||
|
"confirm_delete": "Are you sure you want to delete this resource?",
|
||||||
|
"refresh_header": "Do you want to refresh?",
|
||||||
|
"refresh_body": "You have to refresh the page to complete the removal, do you want to refresh now?",
|
||||||
|
"cant_edit_yaml": "You are using Lovelace in YAML mode, therefore you can not manage your resources through the UI. Manage them in configuration.yaml.",
|
||||||
|
"detail": {
|
||||||
|
"new_resource": "Add new resource",
|
||||||
|
"dismiss": "Close",
|
||||||
|
"warning_header": "Be cautious!",
|
||||||
|
"warning_text": "Adding resources can be dangerous, make sure you know the source of the resource and trust them. Bad resources could seriously harm your system.",
|
||||||
|
"url": "Url",
|
||||||
|
"url_error_msg": "Url is a required field",
|
||||||
|
"type": "Resource type",
|
||||||
|
"delete": "Delete",
|
||||||
|
"update": "Update",
|
||||||
|
"create": "Create"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"server_control": {
|
"server_control": {
|
||||||
"caption": "Server Controls",
|
"caption": "Server Controls",
|
||||||
"description": "Restart and stop the Home Assistant server",
|
"description": "Restart and stop the Home Assistant server",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user