mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-25 10:16:46 +00:00
Changes for new storage (#2228)
* Changes for new storage * Fix lint * Use indexes for editing * Use lovelace object * Use lovelace object * Lit conversion panel lovelace * Lovelace obj * Fix edit cards * Fix some bugs * Fix last bugs * Lint * Don't drop view content * Move file * Add skip button to card picker * Correctly set lovelace mode
This commit is contained in:
parent
2a23487163
commit
4f0a965573
@ -1,22 +1,23 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface LovelaceConfig {
|
||||
_frontendAuto: boolean;
|
||||
title?: string;
|
||||
views: LovelaceViewConfig[];
|
||||
}
|
||||
|
||||
export interface LovelaceViewConfig {
|
||||
index?: number;
|
||||
title?: string;
|
||||
badges?: string[];
|
||||
cards?: LovelaceCardConfig[];
|
||||
id?: string;
|
||||
path?: string;
|
||||
icon?: string;
|
||||
theme?: string;
|
||||
}
|
||||
|
||||
export interface LovelaceCardConfig {
|
||||
id?: string;
|
||||
index?: number;
|
||||
view_index?: number;
|
||||
type: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
@ -60,95 +61,11 @@ export const fetchConfig = (
|
||||
force,
|
||||
});
|
||||
|
||||
export const migrateConfig = (hass: HomeAssistant): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "lovelace/config/migrate",
|
||||
});
|
||||
|
||||
export const saveConfig = (
|
||||
hass: HomeAssistant,
|
||||
config: LovelaceConfig | string,
|
||||
format: "json" | "yaml"
|
||||
config: LovelaceConfig
|
||||
): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "lovelace/config/save",
|
||||
config,
|
||||
format,
|
||||
});
|
||||
|
||||
export const getCardConfig = (
|
||||
hass: HomeAssistant,
|
||||
cardId: string
|
||||
): Promise<string> =>
|
||||
hass.callWS({
|
||||
type: "lovelace/config/card/get",
|
||||
card_id: cardId,
|
||||
});
|
||||
|
||||
export const updateCardConfig = (
|
||||
hass: HomeAssistant,
|
||||
cardId: string,
|
||||
config: LovelaceCardConfig | string,
|
||||
format: "json" | "yaml"
|
||||
): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "lovelace/config/card/update",
|
||||
card_id: cardId,
|
||||
card_config: config,
|
||||
format,
|
||||
});
|
||||
|
||||
export const deleteCard = (
|
||||
hass: HomeAssistant,
|
||||
cardId: string
|
||||
): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "lovelace/config/card/delete",
|
||||
card_id: cardId,
|
||||
});
|
||||
|
||||
export const addCard = (
|
||||
hass: HomeAssistant,
|
||||
viewId: string,
|
||||
config: LovelaceCardConfig | string,
|
||||
format: "json" | "yaml"
|
||||
): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "lovelace/config/card/add",
|
||||
view_id: viewId,
|
||||
card_config: config,
|
||||
format,
|
||||
});
|
||||
|
||||
export const updateViewConfig = (
|
||||
hass: HomeAssistant,
|
||||
viewId: string,
|
||||
config: LovelaceViewConfig | string,
|
||||
format: "json" | "yaml"
|
||||
): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "lovelace/config/view/update",
|
||||
view_id: viewId,
|
||||
view_config: config,
|
||||
format,
|
||||
});
|
||||
|
||||
export const deleteView = (
|
||||
hass: HomeAssistant,
|
||||
viewId: string
|
||||
): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "lovelace/config/view/delete",
|
||||
view_id: viewId,
|
||||
});
|
||||
|
||||
export const addView = (
|
||||
hass: HomeAssistant,
|
||||
config: LovelaceViewConfig | string,
|
||||
format: "json" | "yaml"
|
||||
): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "lovelace/config/view/add",
|
||||
view_config: config,
|
||||
format,
|
||||
});
|
||||
|
@ -134,8 +134,14 @@ export class HuiLightCard extends hassLocalizeLitMixin(LitElement)
|
||||
this._roundSliderStyle = loaded.roundSliderStyle;
|
||||
this._jQuery = loaded.jQuery;
|
||||
|
||||
const brightness = this.hass!.states[this._config!.entity].attributes
|
||||
.brightness;
|
||||
const stateObj = this.hass!.states[this._config!.entity] as LightEntity;
|
||||
|
||||
if (!stateObj) {
|
||||
return;
|
||||
}
|
||||
|
||||
const brightness = stateObj.attributes.brightness || 0;
|
||||
|
||||
this._jQuery("#light", this.shadowRoot).roundSlider({
|
||||
...lightConfig,
|
||||
change: (value) => this._setBrightness(value),
|
||||
@ -152,7 +158,13 @@ export class HuiLightCard extends hassLocalizeLitMixin(LitElement)
|
||||
return;
|
||||
}
|
||||
|
||||
const attrs = this.hass!.states[this._config!.entity].attributes;
|
||||
const stateObj = this.hass!.states[this._config!.entity];
|
||||
|
||||
if (!stateObj) {
|
||||
return;
|
||||
}
|
||||
|
||||
const attrs = stateObj.attributes;
|
||||
|
||||
this._jQuery("#light", this.shadowRoot).roundSlider({
|
||||
value: Math.round((attrs.brightness / 254) * 100) || 0,
|
||||
|
@ -94,7 +94,7 @@ const computeDefaultViewStates = (hass: HomeAssistant): HassEntities => {
|
||||
|
||||
const generateViewConfig = (
|
||||
localize: LocalizeFunc,
|
||||
id: string,
|
||||
path: string,
|
||||
title: string | undefined,
|
||||
icon: string | undefined,
|
||||
entities: HassEntities,
|
||||
@ -158,7 +158,7 @@ const generateViewConfig = (
|
||||
});
|
||||
|
||||
return {
|
||||
id,
|
||||
path,
|
||||
title,
|
||||
icon,
|
||||
badges,
|
||||
@ -228,7 +228,6 @@ export const generateLovelaceConfig = (
|
||||
}
|
||||
|
||||
return {
|
||||
_frontendAuto: true,
|
||||
title,
|
||||
views,
|
||||
};
|
||||
|
@ -1,32 +1,22 @@
|
||||
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
|
||||
import "@polymer/paper-button/paper-button";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { showEditCardDialog } from "../editor/show-edit-card-dialog";
|
||||
import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog";
|
||||
|
||||
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
|
||||
import { confDeleteCard } from "../editor/delete-card";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { LovelaceCardConfig } from "../../../data/lovelace";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"show-edit-card": {
|
||||
cardConfig?: LovelaceCardConfig;
|
||||
viewId?: string | number;
|
||||
add: boolean;
|
||||
reloadLovelace: () => void;
|
||||
};
|
||||
}
|
||||
}
|
||||
import { Lovelace } from "../types";
|
||||
|
||||
export class HuiCardOptions extends hassLocalizeLitMixin(LitElement) {
|
||||
public cardConfig?: LovelaceCardConfig;
|
||||
protected hass?: HomeAssistant;
|
||||
protected lovelace?: Lovelace;
|
||||
protected path?: [number, number];
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return { hass: {} };
|
||||
return { hass: {}, lovelace: {}, path: {} };
|
||||
}
|
||||
|
||||
protected render() {
|
||||
@ -66,26 +56,13 @@ export class HuiCardOptions extends hassLocalizeLitMixin(LitElement) {
|
||||
`;
|
||||
}
|
||||
private _editCard(): void {
|
||||
if (!this.cardConfig) {
|
||||
return;
|
||||
}
|
||||
showEditCardDialog(this, {
|
||||
cardConfig: this.cardConfig,
|
||||
add: false,
|
||||
reloadLovelace: () => fireEvent(this, "config-refresh"),
|
||||
lovelace: this.lovelace!,
|
||||
path: this.path!,
|
||||
});
|
||||
}
|
||||
private _deleteCard(): void {
|
||||
if (!this.cardConfig) {
|
||||
return;
|
||||
}
|
||||
if (!this.cardConfig.id) {
|
||||
this._editCard();
|
||||
return;
|
||||
}
|
||||
confDeleteCard(this.hass!, this.cardConfig.id, () =>
|
||||
fireEvent(this, "config-refresh")
|
||||
);
|
||||
confDeleteCard(this.lovelace!, this.path!);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,22 +2,11 @@ import { html, LitElement } from "@polymer/lit-element";
|
||||
import { TemplateResult } from "lit-html";
|
||||
import "@polymer/paper-button/paper-button";
|
||||
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { LovelaceCardConfig } from "../../../data/lovelace";
|
||||
import { getCardElementTag } from "../common/get-card-element-tag";
|
||||
import { CardPickTarget } from "./types";
|
||||
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
|
||||
|
||||
import { uid } from "../../../common/util/uid";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"card-picked": {
|
||||
config: LovelaceCardConfig;
|
||||
};
|
||||
}
|
||||
}
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { LovelaceCardConfig } from "../../../../data/lovelace";
|
||||
import { getCardElementTag } from "../../common/get-card-element-tag";
|
||||
import { CardPickTarget } from "../types";
|
||||
import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin";
|
||||
|
||||
const cards = [
|
||||
{ name: "Alarm panel", type: "alarm-panel" },
|
||||
@ -47,7 +36,8 @@ const cards = [
|
||||
];
|
||||
|
||||
export class HuiCardPicker extends hassLocalizeLitMixin(LitElement) {
|
||||
protected hass?: HomeAssistant;
|
||||
public hass?: HomeAssistant;
|
||||
public cardPicked?: (cardConf: LovelaceCardConfig) => void;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
@ -90,16 +80,14 @@ export class HuiCardPicker extends hassLocalizeLitMixin(LitElement) {
|
||||
const tag = getCardElementTag(type);
|
||||
|
||||
const elClass = customElements.get(tag);
|
||||
let config: LovelaceCardConfig = { type, id: uid() };
|
||||
let config: LovelaceCardConfig = { type };
|
||||
|
||||
if (elClass && elClass.getStubConfig) {
|
||||
const cardConfig = elClass.getStubConfig(this.hass);
|
||||
config = { ...config, ...cardConfig };
|
||||
}
|
||||
|
||||
fireEvent(this, "card-picked", {
|
||||
config,
|
||||
});
|
||||
this.cardPicked!(config);
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
|
||||
import createCardElement from "../common/create-card-element";
|
||||
import createErrorCardConfig from "../common/create-error-card-config";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { LovelaceCardConfig } from "../../../data/lovelace";
|
||||
import { LovelaceCard } from "../types";
|
||||
import { ConfigError } from "./types";
|
||||
import { getCardElementTag } from "../common/get-card-element-tag";
|
||||
import createCardElement from "../../common/create-card-element";
|
||||
import createErrorCardConfig from "../../common/create-error-card-config";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { LovelaceCardConfig } from "../../../../data/lovelace";
|
||||
import { LovelaceCard } from "../../types";
|
||||
import { ConfigError } from "../types";
|
||||
import { getCardElementTag } from "../../common/get-card-element-tag";
|
||||
|
||||
export class HuiCardPreview extends HTMLElement {
|
||||
private _hass?: HomeAssistant;
|
@ -1,11 +1,12 @@
|
||||
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
|
||||
import { TemplateResult } from "lit-html";
|
||||
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { LovelaceCardConfig } from "../../../data/lovelace";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { HASSDomEvent } from "../../../../common/dom/fire_event";
|
||||
import { LovelaceCardConfig } from "../../../../data/lovelace";
|
||||
import "./hui-edit-card";
|
||||
import "./hui-migrate-config";
|
||||
import "./hui-dialog-pick-card";
|
||||
import { EditCardDialogParams } from "./show-edit-card-dialog";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
@ -18,66 +19,67 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
export interface EditCardDialogParams {
|
||||
cardConfig?: LovelaceCardConfig;
|
||||
viewId?: string | number;
|
||||
add: boolean;
|
||||
reloadLovelace: () => void;
|
||||
}
|
||||
|
||||
export class HuiDialogEditCard extends LitElement {
|
||||
protected hass?: HomeAssistant;
|
||||
private _params?: EditCardDialogParams;
|
||||
private _cardConfig?: LovelaceCardConfig;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
hass: {},
|
||||
_params: {},
|
||||
_cardConfig: {},
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._cardPicked = this._cardPicked.bind(this);
|
||||
this._cancel = this._cancel.bind(this);
|
||||
}
|
||||
|
||||
public async showDialog(params: EditCardDialogParams): Promise<void> {
|
||||
this._params = params;
|
||||
await this.updateComplete;
|
||||
(this.shadowRoot!.children[0] as any).showDialog();
|
||||
this._cardConfig =
|
||||
params.path.length === 2
|
||||
? (this._cardConfig = params.lovelace.config.views[
|
||||
params.path[0]
|
||||
].cards![params.path[1]])
|
||||
: undefined;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._params) {
|
||||
return html``;
|
||||
}
|
||||
if (
|
||||
(!this._params.add &&
|
||||
this._params.cardConfig &&
|
||||
!("id" in this._params.cardConfig)) ||
|
||||
(this._params.add && !this._params.viewId)
|
||||
) {
|
||||
if (!this._cardConfig) {
|
||||
// Card picker
|
||||
return html`
|
||||
<hui-migrate-config
|
||||
<hui-dialog-pick-card
|
||||
.hass="${this.hass}"
|
||||
@reload-lovelace="${this._params.reloadLovelace}"
|
||||
></hui-migrate-config>
|
||||
.cardPicked="${this._cardPicked}"
|
||||
></hui-dialog-pick-card>
|
||||
`;
|
||||
}
|
||||
return html`
|
||||
<hui-edit-card
|
||||
.hass="${this.hass}"
|
||||
.viewId="${this._params.viewId}"
|
||||
.cardConfig="${this._params.cardConfig}"
|
||||
@reload-lovelace="${this._params.reloadLovelace}"
|
||||
@cancel-edit-card="${this._cancel}"
|
||||
.lovelace="${this._params.lovelace}"
|
||||
.path="${this._params.path}"
|
||||
.cardConfig="${this._cardConfig}"
|
||||
.closeDialog="${this._cancel}"
|
||||
>
|
||||
</hui-edit-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private _cardPicked(cardConf: LovelaceCardConfig) {
|
||||
this._cardConfig = cardConf;
|
||||
}
|
||||
|
||||
private _cancel() {
|
||||
this._params = {
|
||||
add: false,
|
||||
reloadLovelace: () => {
|
||||
return;
|
||||
},
|
||||
};
|
||||
this._params = undefined;
|
||||
this._cardConfig = undefined;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,47 @@
|
||||
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
|
||||
import { TemplateResult } from "lit-html";
|
||||
import "@polymer/paper-dialog/paper-dialog";
|
||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
|
||||
import "./hui-card-picker";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin";
|
||||
import { LovelaceCardConfig } from "../../../../data/lovelace";
|
||||
|
||||
export class HuiDialogPickCard extends hassLocalizeLitMixin(LitElement) {
|
||||
public hass?: HomeAssistant;
|
||||
public cardPicked?: (cardConf: LovelaceCardConfig) => void;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {};
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<paper-dialog with-backdrop opened>
|
||||
<h2>${this.localize("ui.panel.lovelace.editor.edit_card.header")}</h2>
|
||||
<paper-dialog-scrollable>
|
||||
<hui-card-picker
|
||||
.hass="${this.hass}"
|
||||
.cardPicked="${this.cardPicked}"
|
||||
></hui-card-picker>
|
||||
</paper-dialog-scrollable>
|
||||
<div class="paper-dialog-buttons">
|
||||
<paper-button @click="${this._skipPick}">SKIP</paper-button>
|
||||
</div>
|
||||
</paper-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _skipPick() {
|
||||
this.cardPicked!({ type: "" });
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-dialog-pick-card": HuiDialogPickCard;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-dialog-pick-card", HuiDialogPickCard);
|
@ -15,31 +15,22 @@ import "@polymer/paper-dialog/paper-dialog";
|
||||
import { PaperDialogElement } from "@polymer/paper-dialog/paper-dialog";
|
||||
import "@polymer/paper-button/paper-button";
|
||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import {
|
||||
addCard,
|
||||
updateCardConfig,
|
||||
LovelaceCardConfig,
|
||||
} from "../../../data/lovelace";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { LovelaceCardConfig } from "../../../../data/lovelace";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin";
|
||||
|
||||
import "./hui-yaml-editor";
|
||||
import "./hui-card-picker";
|
||||
import "./hui-card-preview";
|
||||
// This is not a duplicate import, one is for types, one is for element.
|
||||
// tslint:disable-next-line
|
||||
import { HuiCardPreview } from "./hui-card-preview";
|
||||
import { LovelaceCardEditor } from "../types";
|
||||
import {
|
||||
YamlChangedEvent,
|
||||
CardPickedEvent,
|
||||
ConfigValue,
|
||||
ConfigError,
|
||||
} from "./types";
|
||||
import { extYamlSchema } from "./yaml-ext-schema";
|
||||
import { EntityConfig } from "../entity-rows/types";
|
||||
import { getCardElementTag } from "../common/get-card-element-tag";
|
||||
import { LovelaceCardEditor, Lovelace } from "../../types";
|
||||
import { YamlChangedEvent, ConfigValue, ConfigError } from "../types";
|
||||
import { extYamlSchema } from "../yaml-ext-schema";
|
||||
import { EntityConfig } from "../../entity-rows/types";
|
||||
import { getCardElementTag } from "../../common/get-card-element-tag";
|
||||
import { addCard, replaceCard } from "../config-util";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
@ -52,17 +43,30 @@ declare global {
|
||||
"config-changed": {
|
||||
config: LovelaceCardConfig;
|
||||
};
|
||||
"cancel-edit-card": {};
|
||||
}
|
||||
}
|
||||
|
||||
export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
|
||||
public hass?: HomeAssistant;
|
||||
public lovelace?: Lovelace;
|
||||
public path?: [number] | [number, number];
|
||||
public cardConfig?: LovelaceCardConfig;
|
||||
public closeDialog?: () => void;
|
||||
private _configElement?: LovelaceCardEditor | null;
|
||||
private _uiEditor?: boolean;
|
||||
private _configValue?: ConfigValue;
|
||||
private _configState?: string;
|
||||
private _loading?: boolean;
|
||||
private _saving: boolean;
|
||||
private _errorMsg?: TemplateResult;
|
||||
private _cardType?: string;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
hass: {},
|
||||
cardConfig: {},
|
||||
viewId: {},
|
||||
_cardId: {},
|
||||
viewIndex: {},
|
||||
_cardIndex: {},
|
||||
_configElement: {},
|
||||
_configValue: {},
|
||||
_configState: {},
|
||||
@ -81,38 +85,15 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
|
||||
return this.shadowRoot!.querySelector("hui-card-preview")!;
|
||||
}
|
||||
|
||||
public cardConfig?: LovelaceCardConfig;
|
||||
public viewId?: string | number;
|
||||
protected hass?: HomeAssistant;
|
||||
private _cardId?: string;
|
||||
private _configElement?: LovelaceCardEditor | null;
|
||||
private _uiEditor?: boolean;
|
||||
private _configValue?: ConfigValue;
|
||||
private _configState?: string;
|
||||
private _loading?: boolean;
|
||||
private _saving: boolean;
|
||||
private _errorMsg?: TemplateResult;
|
||||
private _cardType?: string;
|
||||
|
||||
protected constructor() {
|
||||
super();
|
||||
this._saving = false;
|
||||
}
|
||||
|
||||
public async showDialog(): Promise<void> {
|
||||
// Wait till dialog is rendered.
|
||||
if (this._dialog == null) {
|
||||
await this.updateComplete;
|
||||
}
|
||||
this._dialog.open();
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
super.updated(changedProperties);
|
||||
if (
|
||||
!changedProperties.has("cardConfig") &&
|
||||
!changedProperties.has("viewId")
|
||||
) {
|
||||
|
||||
if (!changedProperties.has("cardConfig")) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -122,17 +103,8 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
|
||||
this._errorMsg = undefined;
|
||||
this._configElement = undefined;
|
||||
|
||||
if (this.cardConfig && String(this.cardConfig.id) !== this._cardId) {
|
||||
this._loading = true;
|
||||
this._cardId = String(this.cardConfig.id);
|
||||
this._loadConfigElement(this.cardConfig);
|
||||
} else {
|
||||
this._cardId = undefined;
|
||||
}
|
||||
|
||||
if (this.viewId && !this.cardConfig) {
|
||||
this._resizeDialog();
|
||||
}
|
||||
this._loading = true;
|
||||
this._loadConfigElement(this.cardConfig!);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
@ -147,7 +119,6 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
|
||||
content = html`
|
||||
<hui-yaml-editor
|
||||
.hass="${this.hass}"
|
||||
.cardId="${this._cardId}"
|
||||
.yaml="${this._configValue!.value}"
|
||||
@yaml-changed="${this._handleYamlChanged}"
|
||||
></hui-yaml-editor>
|
||||
@ -157,18 +128,11 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
|
||||
<hr />
|
||||
<hui-card-preview .hass="${this.hass}"> </hui-card-preview>
|
||||
`;
|
||||
} else if (this.viewId && !this.cardConfig) {
|
||||
content = html`
|
||||
<hui-card-picker
|
||||
.hass="${this.hass}"
|
||||
@card-picked="${this._handleCardPicked}"
|
||||
></hui-card-picker>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
<paper-dialog with-backdrop>
|
||||
<paper-dialog with-backdrop opened>
|
||||
<h2>${this.localize("ui.panel.lovelace.editor.edit_card.header")}</h2>
|
||||
<paper-spinner
|
||||
?active="${this._loading}"
|
||||
@ -203,7 +167,7 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
|
||||
)
|
||||
}</paper-button
|
||||
>
|
||||
<paper-button @click="${this._closeDialog}"
|
||||
<paper-button @click="${this.closeDialog}"
|
||||
>${this.localize("ui.common.cancel")}</paper-button
|
||||
>
|
||||
<paper-button
|
||||
@ -272,15 +236,6 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
|
||||
`;
|
||||
}
|
||||
|
||||
private _save(): void {
|
||||
this._saving = true;
|
||||
this._updateConfigInBackend();
|
||||
}
|
||||
|
||||
private _saveDone(): void {
|
||||
this._saving = false;
|
||||
}
|
||||
|
||||
private async _loadedDialog(): Promise<void> {
|
||||
await this.updateComplete;
|
||||
this._loading = false;
|
||||
@ -292,58 +247,42 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
|
||||
fireEvent(this._dialog, "iron-resize");
|
||||
}
|
||||
|
||||
private _closeDialog(): void {
|
||||
this.cardConfig = undefined;
|
||||
this.viewId = undefined;
|
||||
fireEvent(this, "cancel-edit-card");
|
||||
this._dialog.close();
|
||||
}
|
||||
|
||||
private async _updateConfigInBackend(): Promise<void> {
|
||||
private async _save(): Promise<void> {
|
||||
if (!this._isConfigValid()) {
|
||||
alert("Your config is not valid, please fix your config before saving.");
|
||||
this._saveDone();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._isConfigChanged()) {
|
||||
this._closeDialog();
|
||||
this._saveDone();
|
||||
this.closeDialog!();
|
||||
return;
|
||||
}
|
||||
|
||||
this._saving = true;
|
||||
|
||||
const cardConf: LovelaceCardConfig =
|
||||
this._configValue!.format === "yaml"
|
||||
? yaml.safeLoad(this._configValue!.value!, {
|
||||
schema: extYamlSchema,
|
||||
})
|
||||
: this._configValue!.value!;
|
||||
|
||||
try {
|
||||
if (this.viewId) {
|
||||
await addCard(
|
||||
this.hass!,
|
||||
String(this.viewId),
|
||||
this._configValue!.value!,
|
||||
this._configValue!.format
|
||||
);
|
||||
} else {
|
||||
await updateCardConfig(
|
||||
this.hass!,
|
||||
this._cardId!,
|
||||
this._configValue!.value!,
|
||||
this._configValue!.format
|
||||
);
|
||||
}
|
||||
fireEvent(this, "reload-lovelace");
|
||||
this._closeDialog();
|
||||
this._saveDone();
|
||||
const lovelace = this.lovelace!;
|
||||
await lovelace.saveConfig(
|
||||
this._creatingCard
|
||||
? addCard(lovelace.config, this.path as [number], cardConf)
|
||||
: replaceCard(
|
||||
lovelace.config,
|
||||
this.path as [number, number],
|
||||
cardConf
|
||||
)
|
||||
);
|
||||
this.closeDialog!();
|
||||
} catch (err) {
|
||||
alert(`Saving failed: ${err.message}`);
|
||||
this._saveDone();
|
||||
}
|
||||
}
|
||||
|
||||
private async _handleCardPicked(ev: CardPickedEvent): Promise<void> {
|
||||
const succes = await this._loadConfigElement(ev.detail.config);
|
||||
if (!succes) {
|
||||
this._configValue = {
|
||||
format: "yaml",
|
||||
value: yaml.safeDump(ev.detail.config),
|
||||
};
|
||||
} finally {
|
||||
this._saving = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -394,14 +333,10 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
|
||||
|
||||
private async _toggleEditor(): Promise<void> {
|
||||
if (this._uiEditor && this._configValue!.format === "json") {
|
||||
if (this._isConfigChanged()) {
|
||||
this._configValue = {
|
||||
format: "yaml",
|
||||
value: yaml.safeDump(this._configValue!.value),
|
||||
};
|
||||
} else {
|
||||
this._configValue = { format: "yaml", value: undefined };
|
||||
}
|
||||
this._configValue = {
|
||||
format: "yaml",
|
||||
value: yaml.safeDump(this._configValue!.value),
|
||||
};
|
||||
this._uiEditor = !this._uiEditor;
|
||||
} else if (this._configElement && this._configValue!.format === "yaml") {
|
||||
const yamlConfig = this._configValue!.value;
|
||||
@ -438,12 +373,12 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
|
||||
}
|
||||
|
||||
private _isConfigChanged(): boolean {
|
||||
if (this.viewId) {
|
||||
if (this._creatingCard) {
|
||||
return true;
|
||||
}
|
||||
const configValue =
|
||||
this._configValue!.format === "yaml"
|
||||
? yaml.safeDump(this._configValue!.value)
|
||||
? yaml.safeLoad(this._configValue!.value)
|
||||
: this._configValue!.value;
|
||||
return JSON.stringify(configValue) !== JSON.stringify(this.cardConfig);
|
||||
}
|
||||
@ -465,6 +400,7 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
|
||||
if (elClass && elClass.getConfigElement) {
|
||||
configElement = await elClass.getConfigElement();
|
||||
} else {
|
||||
this._configValue = { format: "yaml", value: yaml.safeDump(conf) };
|
||||
this._uiEditor = false;
|
||||
this._configElement = null;
|
||||
return false;
|
||||
@ -477,6 +413,10 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
|
||||
Your config is not supported by the UI editor:<br /><b>${err.message}</b
|
||||
><br />Falling back to YAML editor.
|
||||
`;
|
||||
this._configValue = {
|
||||
format: "yaml",
|
||||
value: yaml.safeDump(conf),
|
||||
};
|
||||
this._uiEditor = false;
|
||||
this._configElement = null;
|
||||
return false;
|
||||
@ -492,6 +432,10 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
|
||||
this._updatePreview(conf);
|
||||
return true;
|
||||
}
|
||||
|
||||
private get _creatingCard(): boolean {
|
||||
return this.path!.length === 1;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
@ -1,42 +1,29 @@
|
||||
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
|
||||
import { TemplateResult } from "lit-html";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import "@polymer/paper-spinner/paper-spinner";
|
||||
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { getCardConfig } from "../../../data/lovelace";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
|
||||
export class HuiYAMLEditor extends LitElement {
|
||||
public cardId?: string;
|
||||
protected hass?: HomeAssistant;
|
||||
private _yaml?: string;
|
||||
private _loading?: boolean;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return { _yaml: {}, cardId: {} };
|
||||
return { _yaml: {} };
|
||||
}
|
||||
|
||||
set yaml(yaml: string) {
|
||||
if (yaml === undefined) {
|
||||
this._loading = true;
|
||||
this._loadConfig();
|
||||
return;
|
||||
} else {
|
||||
this._yaml = yaml;
|
||||
if (this._loading) {
|
||||
this._loading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
<paper-spinner
|
||||
?active="${this._loading}"
|
||||
alt="Loading"
|
||||
class="center"
|
||||
></paper-spinner>
|
||||
<paper-textarea
|
||||
max-rows="10"
|
||||
.value="${this._yaml}"
|
||||
@ -51,31 +38,10 @@ export class HuiYAMLEditor extends LitElement {
|
||||
paper-textarea {
|
||||
--paper-input-container-shared-input-style_-_font-family: monospace;
|
||||
}
|
||||
.center {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
paper-spinner {
|
||||
display: none;
|
||||
}
|
||||
paper-spinner[active] {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _loadConfig(): Promise<void> {
|
||||
if (!this.hass || !this.cardId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._yaml = await getCardConfig(this.hass, this.cardId);
|
||||
if (this._loading) {
|
||||
this._loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private _valueChanged(ev: Event): void {
|
||||
const target = ev.target! as any;
|
||||
this._yaml = target.value;
|
@ -1,5 +1,5 @@
|
||||
import { LovelaceCardConfig } from "../../../data/lovelace";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { Lovelace } from "../../types";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
@ -13,10 +13,8 @@ const dialogShowEvent = "show-edit-card";
|
||||
const dialogTag = "hui-dialog-edit-card";
|
||||
|
||||
export interface EditCardDialogParams {
|
||||
cardConfig?: LovelaceCardConfig;
|
||||
viewId?: string | number;
|
||||
add: boolean;
|
||||
reloadLovelace: () => void;
|
||||
lovelace: Lovelace;
|
||||
path: [number] | [number, number];
|
||||
}
|
||||
|
||||
const registerEditCardDialog = (element: HTMLElement) =>
|
@ -33,7 +33,6 @@ const entitiesConfigStruct = struct.union([
|
||||
|
||||
const cardConfigStruct = struct({
|
||||
type: "string",
|
||||
id: "string|number",
|
||||
title: "string|number?",
|
||||
theme: "string?",
|
||||
show_header_toggle: "boolean?",
|
||||
|
@ -32,7 +32,6 @@ const entitiesConfigStruct = struct.union([
|
||||
|
||||
const cardConfigStruct = struct({
|
||||
type: "string",
|
||||
id: "string|number",
|
||||
title: "string|number?",
|
||||
theme: "string?",
|
||||
columns: "number?",
|
||||
|
117
src/panels/lovelace/editor/config-util.ts
Normal file
117
src/panels/lovelace/editor/config-util.ts
Normal file
@ -0,0 +1,117 @@
|
||||
import {
|
||||
LovelaceConfig,
|
||||
LovelaceCardConfig,
|
||||
LovelaceViewConfig,
|
||||
} from "../../../data/lovelace";
|
||||
|
||||
export const addCard = (
|
||||
config: LovelaceConfig,
|
||||
path: [number],
|
||||
cardConfig: LovelaceCardConfig
|
||||
): LovelaceConfig => {
|
||||
const [viewIndex] = path;
|
||||
const views: LovelaceViewConfig[] = [];
|
||||
|
||||
config.views.forEach((viewConf, index) => {
|
||||
if (index !== viewIndex) {
|
||||
views.push(config.views[index]);
|
||||
return;
|
||||
}
|
||||
|
||||
const cards = viewConf.cards
|
||||
? [...viewConf.cards, cardConfig]
|
||||
: [cardConfig];
|
||||
|
||||
views.push({
|
||||
...viewConf,
|
||||
cards,
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
...config,
|
||||
views,
|
||||
};
|
||||
};
|
||||
|
||||
export const replaceCard = (
|
||||
config: LovelaceConfig,
|
||||
path: [number, number],
|
||||
cardConfig: LovelaceCardConfig
|
||||
): LovelaceConfig => {
|
||||
const [viewIndex, cardIndex] = path;
|
||||
const views: LovelaceViewConfig[] = [];
|
||||
|
||||
config.views.forEach((viewConf, index) => {
|
||||
if (index !== viewIndex) {
|
||||
views.push(config.views[index]);
|
||||
return;
|
||||
}
|
||||
|
||||
views.push({
|
||||
...viewConf,
|
||||
cards: (viewConf.cards || []).map((origConf, ind) =>
|
||||
ind === cardIndex ? cardConfig : origConf
|
||||
),
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
...config,
|
||||
views,
|
||||
};
|
||||
};
|
||||
|
||||
export const deleteCard = (
|
||||
config: LovelaceConfig,
|
||||
path: [number, number]
|
||||
): LovelaceConfig => {
|
||||
const [viewIndex, cardIndex] = path;
|
||||
const views: LovelaceViewConfig[] = [];
|
||||
|
||||
config.views.forEach((viewConf, index) => {
|
||||
if (index !== viewIndex) {
|
||||
views.push(config.views[index]);
|
||||
return;
|
||||
}
|
||||
|
||||
views.push({
|
||||
...viewConf,
|
||||
cards: (viewConf.cards || []).filter(
|
||||
(_origConf, ind) => ind !== cardIndex
|
||||
),
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
...config,
|
||||
views,
|
||||
};
|
||||
};
|
||||
|
||||
export const addView = (
|
||||
config: LovelaceConfig,
|
||||
viewConfig: LovelaceViewConfig
|
||||
): LovelaceConfig => ({
|
||||
...config,
|
||||
views: config.views.concat(viewConfig),
|
||||
});
|
||||
|
||||
export const replaceView = (
|
||||
config: LovelaceConfig,
|
||||
viewIndex: number,
|
||||
viewConfig: LovelaceViewConfig
|
||||
): LovelaceConfig => ({
|
||||
...config,
|
||||
views: config.views.map((origView, index) =>
|
||||
index === viewIndex ? viewConfig : origView
|
||||
),
|
||||
});
|
||||
|
||||
export const deleteView = (
|
||||
config: LovelaceConfig,
|
||||
viewIndex: number
|
||||
): LovelaceConfig => ({
|
||||
...config,
|
||||
views: config.views.filter((_origView, index) => index !== viewIndex),
|
||||
});
|
@ -1,17 +1,15 @@
|
||||
import { deleteCard } from "../../../data/lovelace";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { Lovelace } from "../types";
|
||||
import { deleteCard } from "./config-util";
|
||||
|
||||
export async function confDeleteCard(
|
||||
hass: HomeAssistant,
|
||||
cardId: string,
|
||||
reloadLovelace: () => void
|
||||
lovelace: Lovelace,
|
||||
path: [number, number]
|
||||
): Promise<void> {
|
||||
if (!confirm("Are you sure you want to delete this card?")) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await deleteCard(hass, String(cardId));
|
||||
reloadLovelace();
|
||||
await lovelace.saveConfig(deleteCard(lovelace.config, path));
|
||||
} catch (err) {
|
||||
alert(`Deleting failed: ${err.message}`);
|
||||
}
|
||||
|
@ -1,18 +0,0 @@
|
||||
import { deleteView } from "../../../data/lovelace";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
|
||||
export async function confDeleteView(
|
||||
hass: HomeAssistant,
|
||||
viewId: string,
|
||||
reloadLovelace: () => void
|
||||
): Promise<void> {
|
||||
if (!confirm("Are you sure you want to delete this view?")) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await deleteView(hass, String(viewId));
|
||||
reloadLovelace();
|
||||
} catch (err) {
|
||||
alert(`Deleting failed: ${err.message}`);
|
||||
}
|
||||
}
|
@ -10,40 +10,8 @@ import "@polymer/paper-button/paper-button";
|
||||
|
||||
import { HomeAssistant } from "../../../types";
|
||||
|
||||
import {
|
||||
saveConfig,
|
||||
migrateConfig,
|
||||
LovelaceConfig,
|
||||
} from "../../../data/lovelace";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"show-save-config": SaveDialogParams;
|
||||
}
|
||||
}
|
||||
|
||||
const dialogShowEvent = "show-save-config";
|
||||
const dialogTag = "hui-dialog-save-config";
|
||||
|
||||
export interface SaveDialogParams {
|
||||
config: LovelaceConfig;
|
||||
reloadLovelace: () => void;
|
||||
}
|
||||
|
||||
export const registerSaveDialog = (element: HTMLElement) =>
|
||||
fireEvent(element, "register-dialog", {
|
||||
dialogShowEvent,
|
||||
dialogTag,
|
||||
dialogImport: () => import("./hui-dialog-save-config"),
|
||||
});
|
||||
|
||||
export const showSaveDialog = (
|
||||
element: HTMLElement,
|
||||
saveDialogParams: SaveDialogParams
|
||||
) => fireEvent(element, dialogShowEvent, saveDialogParams);
|
||||
import { SaveDialogParams } from "./show-save-config-dialog";
|
||||
|
||||
export class HuiSaveConfig extends hassLocalizeLitMixin(LitElement) {
|
||||
protected hass?: HomeAssistant;
|
||||
@ -137,13 +105,11 @@ export class HuiSaveConfig extends hassLocalizeLitMixin(LitElement) {
|
||||
return;
|
||||
}
|
||||
this._saving = true;
|
||||
delete this._params.config._frontendAuto;
|
||||
try {
|
||||
await saveConfig(this.hass, this._params.config, "json");
|
||||
await migrateConfig(this.hass);
|
||||
const lovelace = this._params!.lovelace;
|
||||
await lovelace.saveConfig(lovelace.config);
|
||||
this._saving = false;
|
||||
this._closeDialog();
|
||||
this._params.reloadLovelace!();
|
||||
} catch (err) {
|
||||
alert(`Saving failed: ${err.message}`);
|
||||
this._saving = false;
|
||||
@ -157,4 +123,4 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(dialogTag, HuiSaveConfig);
|
||||
customElements.define("hui-dialog-save-config", HuiSaveConfig);
|
||||
|
@ -1,113 +0,0 @@
|
||||
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
|
||||
import { TemplateResult } from "lit-html";
|
||||
|
||||
import "@polymer/paper-spinner/paper-spinner";
|
||||
import "@polymer/paper-dialog/paper-dialog";
|
||||
// This is not a duplicate import, one is for types, one is for element.
|
||||
// tslint:disable-next-line
|
||||
import { PaperDialogElement } from "@polymer/paper-dialog/paper-dialog";
|
||||
import "@polymer/paper-button/paper-button";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
|
||||
import { migrateConfig } from "../../../data/lovelace";
|
||||
|
||||
export class HuiMigrateConfig extends hassLocalizeLitMixin(LitElement) {
|
||||
protected hass?: HomeAssistant;
|
||||
private _migrating?: boolean;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return { _hass: {}, _migrating: {} };
|
||||
}
|
||||
|
||||
private get _dialog(): PaperDialogElement {
|
||||
return this.shadowRoot!.querySelector("paper-dialog")!;
|
||||
}
|
||||
|
||||
public async showDialog(): Promise<void> {
|
||||
// Wait till dialog is rendered.
|
||||
if (this._dialog == null) {
|
||||
await this.updateComplete;
|
||||
}
|
||||
this._dialog.open();
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
<paper-dialog with-backdrop>
|
||||
<h2>${this.localize("ui.panel.lovelace.editor.migrate.header")}</h2>
|
||||
<paper-dialog-scrollable>
|
||||
<p>${this.localize("ui.panel.lovelace.editor.migrate.para_no_id")}</p>
|
||||
<p>
|
||||
${this.localize("ui.panel.lovelace.editor.migrate.para_migrate")}
|
||||
</p>
|
||||
</paper-dialog-scrollable>
|
||||
<div class="paper-dialog-buttons">
|
||||
<paper-button @click="${this._closeDialog}"
|
||||
>${this.localize("ui.common.cancel")}</paper-button
|
||||
>
|
||||
<paper-button
|
||||
?disabled="${this._migrating}"
|
||||
@click="${this._migrateConfig}"
|
||||
>
|
||||
<paper-spinner
|
||||
?active="${this._migrating}"
|
||||
alt="Saving"
|
||||
></paper-spinner>
|
||||
${
|
||||
this.localize("ui.panel.lovelace.editor.migrate.migrate")
|
||||
}</paper-button
|
||||
>
|
||||
</div>
|
||||
</paper-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderStyle(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
paper-dialog {
|
||||
width: 650px;
|
||||
}
|
||||
paper-spinner {
|
||||
display: none;
|
||||
}
|
||||
paper-spinner[active] {
|
||||
display: block;
|
||||
}
|
||||
paper-button paper-spinner {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
}
|
||||
|
||||
private _closeDialog(): void {
|
||||
this._dialog.close();
|
||||
}
|
||||
|
||||
private async _migrateConfig(): Promise<void> {
|
||||
this._migrating = true;
|
||||
try {
|
||||
await migrateConfig(this.hass!);
|
||||
this._closeDialog();
|
||||
this._migrating = false;
|
||||
fireEvent(this, "reload-lovelace");
|
||||
} catch (err) {
|
||||
alert(`Migration failed: ${err.message}`);
|
||||
this._migrating = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-migrate-config": HuiMigrateConfig;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-migrate-config", HuiMigrateConfig);
|
33
src/panels/lovelace/editor/show-save-config-dialog.ts
Normal file
33
src/panels/lovelace/editor/show-save-config-dialog.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { Lovelace } from "../types";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"show-save-config": SaveDialogParams;
|
||||
}
|
||||
}
|
||||
|
||||
const dialogShowEvent = "show-save-config";
|
||||
const dialogTag = "hui-dialog-save-config";
|
||||
|
||||
export interface SaveDialogParams {
|
||||
lovelace: Lovelace;
|
||||
}
|
||||
|
||||
let registeredDialog = false;
|
||||
|
||||
export const showSaveDialog = (
|
||||
element: HTMLElement,
|
||||
saveDialogParams: SaveDialogParams
|
||||
) => {
|
||||
if (!registeredDialog) {
|
||||
registeredDialog = true;
|
||||
fireEvent(element, "register-dialog", {
|
||||
dialogShowEvent,
|
||||
dialogTag,
|
||||
dialogImport: () => import("./hui-dialog-save-config"),
|
||||
});
|
||||
}
|
||||
fireEvent(element, dialogShowEvent, saveDialogParams);
|
||||
};
|
@ -8,12 +8,6 @@ export interface YamlChangedEvent extends Event {
|
||||
};
|
||||
}
|
||||
|
||||
export interface CardPickedEvent extends Event {
|
||||
detail: {
|
||||
config: LovelaceCardConfig;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ViewEditEvent extends Event {
|
||||
detail: {
|
||||
config: LovelaceViewConfig;
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
|
||||
import { TemplateResult } from "lit-html";
|
||||
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { HASSDomEvent } from "../../../../common/dom/fire_event";
|
||||
import "./hui-edit-view";
|
||||
import "./hui-migrate-config";
|
||||
import { EditViewDialogParams } from "./show-edit-view-dialog";
|
||||
|
||||
declare global {
|
||||
@ -39,24 +38,11 @@ export class HuiDialogEditView extends LitElement {
|
||||
if (!this._params) {
|
||||
return html``;
|
||||
}
|
||||
if (
|
||||
!this._params.add &&
|
||||
this._params.viewConfig &&
|
||||
!("id" in this._params.viewConfig)
|
||||
) {
|
||||
return html`
|
||||
<hui-migrate-config
|
||||
.hass="${this.hass}"
|
||||
@reload-lovelace="${this._params.reloadLovelace}"
|
||||
></hui-migrate-config>
|
||||
`;
|
||||
}
|
||||
return html`
|
||||
<hui-edit-view
|
||||
.hass="${this.hass}"
|
||||
.viewConfig="${this._params.viewConfig}"
|
||||
.add="${this._params.add}"
|
||||
.reloadLovelace="${this._params.reloadLovelace}"
|
||||
.lovelace="${this._params.lovelace}"
|
||||
.viewIndex="${this._params.viewIndex}"
|
||||
>
|
||||
</hui-edit-view>
|
||||
`;
|
@ -1,9 +1,4 @@
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
PropertyDeclarations,
|
||||
PropertyValues,
|
||||
} from "@polymer/lit-element";
|
||||
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
|
||||
import { TemplateResult } from "lit-html";
|
||||
|
||||
import "@polymer/paper-spinner/paper-spinner";
|
||||
@ -16,40 +11,25 @@ import "@polymer/paper-icon-button/paper-icon-button.js";
|
||||
import { PaperDialogElement } from "@polymer/paper-dialog/paper-dialog";
|
||||
import "@polymer/paper-button/paper-button";
|
||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
import "../components/hui-entity-editor";
|
||||
import "./config-elements/hui-view-editor";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import "../../components/hui-entity-editor";
|
||||
import "./hui-view-editor";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import {
|
||||
addView,
|
||||
updateViewConfig,
|
||||
LovelaceViewConfig,
|
||||
LovelaceCardConfig,
|
||||
} from "../../../data/lovelace";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
|
||||
import { EntitiesEditorEvent, ViewEditEvent } from "./types";
|
||||
import { processEditorEntities } from "./process-editor-entities";
|
||||
import { EntityConfig } from "../entity-rows/types";
|
||||
import { confDeleteView } from "./delete-view";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
} from "../../../../data/lovelace";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin";
|
||||
import { EntitiesEditorEvent, ViewEditEvent } from "../types";
|
||||
import { processEditorEntities } from "../process-editor-entities";
|
||||
import { EntityConfig } from "../../entity-rows/types";
|
||||
import { navigate } from "../../../../common/navigate";
|
||||
import { Lovelace } from "../../types";
|
||||
import { deleteView, addView, replaceView } from "../config-util";
|
||||
|
||||
export class HuiEditView extends hassLocalizeLitMixin(LitElement) {
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
hass: {},
|
||||
viewConfig: {},
|
||||
add: {},
|
||||
_config: {},
|
||||
_badges: {},
|
||||
_cards: {},
|
||||
_saving: {},
|
||||
_curTab: {},
|
||||
};
|
||||
}
|
||||
|
||||
public viewConfig?: LovelaceViewConfig;
|
||||
public add?: boolean;
|
||||
public reloadLovelace?: () => {};
|
||||
public lovelace?: Lovelace;
|
||||
public viewIndex?: number;
|
||||
protected hass?: HomeAssistant;
|
||||
private _config?: LovelaceViewConfig;
|
||||
private _badges?: EntityConfig[];
|
||||
@ -58,6 +38,19 @@ export class HuiEditView extends hassLocalizeLitMixin(LitElement) {
|
||||
private _curTabIndex: number;
|
||||
private _curTab?: string;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
hass: {},
|
||||
lovelace: {},
|
||||
viewIndex: {},
|
||||
_config: {},
|
||||
_badges: {},
|
||||
_cards: {},
|
||||
_saving: {},
|
||||
_curTab: {},
|
||||
};
|
||||
}
|
||||
|
||||
protected constructor() {
|
||||
super();
|
||||
this._saving = false;
|
||||
@ -69,30 +62,21 @@ export class HuiEditView extends hassLocalizeLitMixin(LitElement) {
|
||||
if (this._dialog == null) {
|
||||
await this.updateComplete;
|
||||
}
|
||||
this._dialog.open();
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
super.updated(changedProperties);
|
||||
if (!changedProperties.has("viewConfig") && !changedProperties.has("add")) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
this.viewConfig &&
|
||||
(!changedProperties.get("viewConfig") ||
|
||||
this.viewConfig.id !==
|
||||
(changedProperties.get("viewConfig") as LovelaceViewConfig).id)
|
||||
) {
|
||||
const { cards, badges, ...viewConfig } = this.viewConfig;
|
||||
this._config = viewConfig;
|
||||
this._badges = badges ? processEditorEntities(badges) : [];
|
||||
this._cards = cards;
|
||||
} else if (changedProperties.has("add")) {
|
||||
if (this.viewIndex === undefined) {
|
||||
this._config = {};
|
||||
this._badges = [];
|
||||
this._cards = [];
|
||||
} else {
|
||||
const { cards, badges, ...viewConfig } = this.lovelace!.config.views[
|
||||
this.viewIndex
|
||||
];
|
||||
this._config = viewConfig;
|
||||
this._badges = badges ? processEditorEntities(badges) : [];
|
||||
this._cards = cards;
|
||||
}
|
||||
this._resizeDialog();
|
||||
|
||||
this._dialog.open();
|
||||
}
|
||||
|
||||
private get _dialog(): PaperDialogElement {
|
||||
@ -142,7 +126,7 @@ export class HuiEditView extends hassLocalizeLitMixin(LitElement) {
|
||||
<paper-dialog-scrollable> ${content} </paper-dialog-scrollable>
|
||||
<div class="paper-dialog-buttons">
|
||||
${
|
||||
!this.add
|
||||
this.viewIndex !== undefined
|
||||
? html`
|
||||
<paper-icon-button
|
||||
class="delete"
|
||||
@ -208,23 +192,27 @@ export class HuiEditView extends hassLocalizeLitMixin(LitElement) {
|
||||
`;
|
||||
}
|
||||
|
||||
private _save(): void {
|
||||
this._saving = true;
|
||||
this._updateConfigInBackend();
|
||||
}
|
||||
|
||||
private _delete() {
|
||||
private async _delete() {
|
||||
if (this._cards && this._cards.length > 0) {
|
||||
alert(
|
||||
"You can't delete a view that has cards in it. Remove the cards first."
|
||||
);
|
||||
return;
|
||||
}
|
||||
confDeleteView(this.hass!, String(this.viewConfig!.id!), () => {
|
||||
|
||||
if (!confirm("Are you sure you want to delete this view?")) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.lovelace!.saveConfig(
|
||||
deleteView(this.lovelace!.config, this.viewIndex!)
|
||||
);
|
||||
this._closeDialog();
|
||||
this.reloadLovelace!();
|
||||
navigate(this, `/lovelace/0`);
|
||||
});
|
||||
} catch (err) {
|
||||
alert(`Deleting failed: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async _resizeDialog(): Promise<void> {
|
||||
@ -234,9 +222,9 @@ export class HuiEditView extends hassLocalizeLitMixin(LitElement) {
|
||||
|
||||
private _closeDialog(): void {
|
||||
this._curTabIndex = 0;
|
||||
this.lovelace = undefined;
|
||||
this._config = {};
|
||||
this._badges = [];
|
||||
this.viewConfig = undefined;
|
||||
this._dialog.close();
|
||||
}
|
||||
|
||||
@ -248,39 +236,35 @@ export class HuiEditView extends hassLocalizeLitMixin(LitElement) {
|
||||
this._resizeDialog();
|
||||
}
|
||||
|
||||
private async _updateConfigInBackend(): Promise<void> {
|
||||
private async _save(): Promise<void> {
|
||||
if (!this._config) {
|
||||
return;
|
||||
}
|
||||
if (!this._isConfigChanged()) {
|
||||
this._closeDialog();
|
||||
this._saving = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._badges) {
|
||||
this._config.badges = this._badges.map((entityConf) => {
|
||||
return entityConf.entity;
|
||||
});
|
||||
}
|
||||
this._saving = true;
|
||||
|
||||
const viewConf: LovelaceViewConfig = {
|
||||
cards: this._cards,
|
||||
badges: this._badges!.map((entityConf) => entityConf.entity),
|
||||
...this._config,
|
||||
};
|
||||
|
||||
const lovelace = this.lovelace!;
|
||||
|
||||
try {
|
||||
if (this.add) {
|
||||
this._config.cards = [];
|
||||
await addView(this.hass!, this._config, "json");
|
||||
} else {
|
||||
await updateViewConfig(
|
||||
this.hass!,
|
||||
String(this.viewConfig!.id!),
|
||||
this._config,
|
||||
"json"
|
||||
);
|
||||
}
|
||||
this.reloadLovelace!();
|
||||
await lovelace.saveConfig(
|
||||
this._creatingView
|
||||
? addView(lovelace.config, viewConf)
|
||||
: replaceView(lovelace.config, this.viewIndex!, viewConf)
|
||||
);
|
||||
this._closeDialog();
|
||||
this._saving = false;
|
||||
} catch (err) {
|
||||
alert(`Saving failed: ${err.message}`);
|
||||
} finally {
|
||||
this._saving = false;
|
||||
}
|
||||
}
|
||||
@ -299,10 +283,15 @@ export class HuiEditView extends hassLocalizeLitMixin(LitElement) {
|
||||
}
|
||||
|
||||
private _isConfigChanged(): boolean {
|
||||
if (!this.add) {
|
||||
return true;
|
||||
}
|
||||
return JSON.stringify(this._config) !== JSON.stringify(this.viewConfig);
|
||||
return (
|
||||
this._creatingView ||
|
||||
JSON.stringify(this._config) !==
|
||||
JSON.stringify(this.lovelace!.config.views[this.viewIndex!])
|
||||
);
|
||||
}
|
||||
|
||||
private get _creatingView(): boolean {
|
||||
return this.viewIndex === undefined;
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import { EditorTarget } from "../types";
|
||||
import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { configElementStyle } from "./config-elements-style";
|
||||
import { configElementStyle } from "../config-elements/config-elements-style";
|
||||
|
||||
import "../../components/hui-theme-select-editor";
|
||||
import { LovelaceViewConfig } from "../../../../data/lovelace";
|
||||
@ -24,12 +24,11 @@ export class HuiViewEditor extends hassLocalizeLitMixin(LitElement) {
|
||||
return { hass: {}, _config: {} };
|
||||
}
|
||||
|
||||
get _id(): string {
|
||||
get _path(): string {
|
||||
if (!this._config) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return "id" in this._config ? this._config.id! : "";
|
||||
return this._config.path || "";
|
||||
}
|
||||
|
||||
get _title(): string {
|
||||
@ -68,12 +67,6 @@ export class HuiViewEditor extends hassLocalizeLitMixin(LitElement) {
|
||||
return html`
|
||||
${configElementStyle}
|
||||
<div class="card-config">
|
||||
<paper-input
|
||||
label="ID"
|
||||
value="${this._id}"
|
||||
.configValue="${"id"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></paper-input>
|
||||
<paper-input
|
||||
label="Title"
|
||||
value="${this._title}"
|
||||
@ -86,6 +79,12 @@ export class HuiViewEditor extends hassLocalizeLitMixin(LitElement) {
|
||||
.configValue="${"icon"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></paper-input>
|
||||
<paper-input
|
||||
label="URL Path"
|
||||
value="${this._path}"
|
||||
.configValue="${"path"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></paper-input>
|
||||
<hui-theme-select-editor
|
||||
.hass="${this.hass}"
|
||||
.value="${this._theme}"
|
@ -1,5 +1,5 @@
|
||||
import { HASSDomEvent, fireEvent } from "../../../common/dom/fire_event";
|
||||
import { LovelaceViewConfig } from "../../../data/lovelace";
|
||||
import { HASSDomEvent, fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { Lovelace } from "../../types";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
@ -18,9 +18,8 @@ const dialogShowEvent = "show-edit-view";
|
||||
const dialogTag = "hui-dialog-edit-view";
|
||||
|
||||
export interface EditViewDialogParams {
|
||||
viewConfig?: LovelaceViewConfig;
|
||||
add?: boolean;
|
||||
reloadLovelace: () => void;
|
||||
lovelace: Lovelace;
|
||||
viewIndex?: number;
|
||||
}
|
||||
|
||||
const registerEditViewDialog = (element: HTMLElement) =>
|
@ -1,154 +0,0 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "@polymer/paper-button/paper-button";
|
||||
|
||||
import { registerSaveDialog } from "./editor/hui-dialog-save-config";
|
||||
import { fetchConfig } from "../../data/lovelace";
|
||||
import "../../layouts/hass-loading-screen";
|
||||
import "../../layouts/hass-error-screen";
|
||||
import "./hui-root";
|
||||
import localizeMixin from "../../mixins/localize-mixin";
|
||||
|
||||
let registeredDialog = false;
|
||||
|
||||
class Lovelace extends localizeMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
paper-button {
|
||||
color: var(--primary-color);
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
<template is="dom-if" if="[[_equal(_state, "loaded")]]" restamp>
|
||||
<hui-root
|
||||
narrow="[[narrow]]"
|
||||
show-menu="[[showMenu]]"
|
||||
hass="[[hass]]"
|
||||
route="[[route]]"
|
||||
config="[[_config]]"
|
||||
columns="[[_columns]]"
|
||||
on-config-refresh="_forceFetchConfig"
|
||||
></hui-root>
|
||||
</template>
|
||||
<template
|
||||
is="dom-if"
|
||||
if="[[_equal(_state, "loading")]]"
|
||||
restamp
|
||||
>
|
||||
<hass-loading-screen
|
||||
narrow="[[narrow]]"
|
||||
show-menu="[[showMenu]]"
|
||||
></hass-loading-screen>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_equal(_state, "error")]]" restamp>
|
||||
<hass-error-screen
|
||||
title="Lovelace"
|
||||
error="[[_errorMsg]]"
|
||||
narrow="[[narrow]]"
|
||||
show-menu="[[showMenu]]"
|
||||
>
|
||||
<paper-button on-click="_forceFetchConfig"
|
||||
>Reload ui-lovelace.yaml</paper-button
|
||||
>
|
||||
</hass-error-screen>
|
||||
</template>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
|
||||
narrow: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
showMenu: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
route: Object,
|
||||
|
||||
_columns: {
|
||||
type: Number,
|
||||
value: 1,
|
||||
},
|
||||
|
||||
_state: {
|
||||
type: String,
|
||||
value: "loading",
|
||||
},
|
||||
|
||||
_errorMsg: String,
|
||||
|
||||
_config: {
|
||||
type: Object,
|
||||
value: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static get observers() {
|
||||
return ["_updateColumns(narrow, showMenu)"];
|
||||
}
|
||||
|
||||
ready() {
|
||||
this._fetchConfig(false);
|
||||
this._updateColumns = this._updateColumns.bind(this);
|
||||
this.mqls = [300, 600, 900, 1200].map((width) => {
|
||||
const mql = matchMedia(`(min-width: ${width}px)`);
|
||||
mql.addListener(this._updateColumns);
|
||||
return mql;
|
||||
});
|
||||
this._updateColumns();
|
||||
super.ready();
|
||||
}
|
||||
|
||||
_updateColumns() {
|
||||
const matchColumns = this.mqls.reduce((cols, mql) => cols + mql.matches, 0);
|
||||
// Do -1 column if the menu is docked and open
|
||||
this._columns = Math.max(1, matchColumns - (!this.narrow && this.showMenu));
|
||||
}
|
||||
|
||||
_forceFetchConfig() {
|
||||
this._fetchConfig(true);
|
||||
}
|
||||
|
||||
async _fetchConfig(force) {
|
||||
try {
|
||||
const conf = await fetchConfig(this.hass, force);
|
||||
this.setProperties({
|
||||
_config: conf,
|
||||
_state: "loaded",
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.code === "file_not_found") {
|
||||
const {
|
||||
generateLovelaceConfig,
|
||||
} = await import("./common/generate-lovelace-config");
|
||||
this.setProperties({
|
||||
_config: generateLovelaceConfig(this.hass, this.localize),
|
||||
_state: "loaded",
|
||||
});
|
||||
if (!registeredDialog) {
|
||||
registeredDialog = true;
|
||||
registerSaveDialog(this);
|
||||
}
|
||||
} else {
|
||||
this.setProperties({
|
||||
_state: "error",
|
||||
_errorMsg: err.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_equal(a, b) {
|
||||
return a === b;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-panel-lovelace", Lovelace);
|
185
src/panels/lovelace/ha-panel-lovelace.ts
Normal file
185
src/panels/lovelace/ha-panel-lovelace.ts
Normal file
@ -0,0 +1,185 @@
|
||||
import "@polymer/paper-button/paper-button";
|
||||
|
||||
import { fetchConfig, LovelaceConfig, saveConfig } from "../../data/lovelace";
|
||||
import "../../layouts/hass-loading-screen";
|
||||
import "../../layouts/hass-error-screen";
|
||||
import "./hui-root";
|
||||
import { HomeAssistant, PanelInfo } from "../../types";
|
||||
import { Lovelace } from "./types";
|
||||
import { LitElement, html, PropertyValues } from "@polymer/lit-element";
|
||||
import { hassLocalizeLitMixin } from "../../mixins/lit-localize-mixin";
|
||||
import { TemplateResult } from "lit-html";
|
||||
import { showSaveDialog } from "./editor/show-save-config-dialog";
|
||||
|
||||
interface LovelacePanelConfig {
|
||||
mode: "yaml" | "storage";
|
||||
}
|
||||
|
||||
class LovelacePanel extends hassLocalizeLitMixin(LitElement) {
|
||||
public panel?: PanelInfo<LovelacePanelConfig>;
|
||||
public hass?: HomeAssistant;
|
||||
public narrow?: boolean;
|
||||
public showMenu?: boolean;
|
||||
public route?: object;
|
||||
private _columns?: number;
|
||||
private _state?: "loading" | "loaded" | "error";
|
||||
private _errorMsg?: string;
|
||||
private lovelace?: Lovelace;
|
||||
private mqls?: MediaQueryList[];
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: {},
|
||||
lovelace: {},
|
||||
narrow: { type: Boolean, value: false },
|
||||
showMenu: { type: Boolean, value: false },
|
||||
route: {},
|
||||
_columns: { type: Number, value: 1 },
|
||||
_state: { type: String, value: "loading" },
|
||||
_errorMsg: String,
|
||||
_config: { type: {}, value: null },
|
||||
};
|
||||
}
|
||||
|
||||
public render(): TemplateResult {
|
||||
const state = this._state!;
|
||||
|
||||
if (state === "loaded") {
|
||||
return html`
|
||||
<hui-root
|
||||
.narrow="${this.narrow}"
|
||||
.showMenu="${this.showMenu}"
|
||||
.hass="${this.hass}"
|
||||
.lovelace="${this.lovelace}"
|
||||
.route="${this.route}"
|
||||
.columns="${this._columns}"
|
||||
@config-refresh="${this._forceFetchConfig}"
|
||||
></hui-root>
|
||||
`;
|
||||
}
|
||||
|
||||
if (state === "error") {
|
||||
return html`
|
||||
<style>
|
||||
paper-button {
|
||||
color: var(--primary-color);
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
<hass-error-screen
|
||||
title="Lovelace"
|
||||
.error="${this._errorMsg}"
|
||||
.narrow="${this.narrow}"
|
||||
.showMenu="${this.showMenu}"
|
||||
>
|
||||
<paper-button on-click="_forceFetchConfig"
|
||||
>Reload ui-lovelace.yaml</paper-button
|
||||
>
|
||||
</hass-error-screen>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<hass-loading-screen
|
||||
.narrow="${this.narrow}"
|
||||
.showMenu="${this.showMenu}"
|
||||
></hass-loading-screen>
|
||||
`;
|
||||
}
|
||||
|
||||
public updated(changedProps: PropertyValues): void {
|
||||
if (changedProps.has("narrow") || changedProps.has("showMenu")) {
|
||||
this._updateColumns();
|
||||
}
|
||||
}
|
||||
|
||||
public firstUpdated() {
|
||||
this._fetchConfig(false);
|
||||
this._updateColumns = this._updateColumns.bind(this);
|
||||
this.mqls = [300, 600, 900, 1200].map((width) => {
|
||||
const mql = matchMedia(`(min-width: ${width}px)`);
|
||||
mql.addListener(this._updateColumns);
|
||||
return mql;
|
||||
});
|
||||
this._updateColumns();
|
||||
}
|
||||
|
||||
private _updateColumns() {
|
||||
const matchColumns = this.mqls!.reduce(
|
||||
(cols, mql) => cols + Number(mql.matches),
|
||||
0
|
||||
);
|
||||
// Do -1 column if the menu is docked and open
|
||||
this._columns = Math.max(
|
||||
1,
|
||||
matchColumns - Number(!this.narrow && this.showMenu)
|
||||
);
|
||||
}
|
||||
|
||||
private _forceFetchConfig() {
|
||||
this._fetchConfig(true);
|
||||
}
|
||||
|
||||
private async _fetchConfig(force) {
|
||||
let conf;
|
||||
let gen: boolean;
|
||||
|
||||
try {
|
||||
conf = await fetchConfig(this.hass!, force);
|
||||
gen = false;
|
||||
} catch (err) {
|
||||
if (err.code !== "config_not_found") {
|
||||
// tslint:disable-next-line
|
||||
console.log(err);
|
||||
this._state = "error";
|
||||
this._errorMsg = err.message;
|
||||
return;
|
||||
}
|
||||
const {
|
||||
generateLovelaceConfig,
|
||||
} = await import("./common/generate-lovelace-config");
|
||||
conf = generateLovelaceConfig(this.hass!, this.localize);
|
||||
gen = true;
|
||||
}
|
||||
|
||||
this._state = "loaded";
|
||||
this.lovelace = {
|
||||
config: conf,
|
||||
autoGen: gen,
|
||||
editMode: this.lovelace ? this.lovelace.editMode : false,
|
||||
mode: this.panel!.config.mode,
|
||||
setEditMode: (editMode: boolean) => {
|
||||
if (!editMode || !this.lovelace!.autoGen) {
|
||||
this._updateLovelace({ editMode });
|
||||
return;
|
||||
}
|
||||
showSaveDialog(this, {
|
||||
lovelace: this.lovelace!,
|
||||
});
|
||||
},
|
||||
saveConfig: async (newConfig: LovelaceConfig): Promise<void> => {
|
||||
const { config, autoGen } = this.lovelace!;
|
||||
try {
|
||||
// Optimistic update
|
||||
this._updateLovelace({ config: newConfig, autoGen: false });
|
||||
await saveConfig(this.hass!, newConfig);
|
||||
} catch (err) {
|
||||
// tslint:disable-next-line
|
||||
console.error(err);
|
||||
// Rollback the optimistic update
|
||||
this._updateLovelace({ config, autoGen });
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private _updateLovelace(props: Partial<Lovelace>) {
|
||||
this.lovelace = {
|
||||
...this.lovelace!,
|
||||
...props,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-panel-lovelace", LovelacePanel);
|
@ -32,8 +32,7 @@ import "./hui-unused-entities";
|
||||
import "./hui-view";
|
||||
import debounce from "../../common/util/debounce";
|
||||
import createCardElement from "./common/create-card-element";
|
||||
import { showSaveDialog } from "./editor/hui-dialog-save-config";
|
||||
import { showEditViewDialog } from "./editor/show-edit-view-dialog";
|
||||
import { showEditViewDialog } from "./editor/view-editor/show-edit-view-dialog";
|
||||
|
||||
// CSS and JS should only be imported once. Modules and HTML are safe.
|
||||
const CSS_CACHE = {};
|
||||
@ -181,8 +180,12 @@ class HUIRoot extends NavigateMixin(
|
||||
},
|
||||
config: {
|
||||
type: Object,
|
||||
computed: "_computeConfig(lovelace)",
|
||||
observer: "_configChanged",
|
||||
},
|
||||
lovelace: {
|
||||
type: Object,
|
||||
},
|
||||
columns: {
|
||||
type: Number,
|
||||
observer: "_columnsChanged",
|
||||
@ -216,6 +219,7 @@ class HUIRoot extends NavigateMixin(
|
||||
_editMode: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
computed: "_computeEditMode(lovelace)",
|
||||
observer: "_editModeChanged",
|
||||
},
|
||||
|
||||
@ -258,12 +262,12 @@ class HUIRoot extends NavigateMixin(
|
||||
_routeChanged(route) {
|
||||
const views = this.config && this.config.views;
|
||||
if (route.path === "" && route.prefix === "/lovelace" && views) {
|
||||
this.navigate(`/lovelace/${views[0].id || 0}`, true);
|
||||
this.navigate(`/lovelace/${views[0].path || 0}`, true);
|
||||
} else if (this.routeData.view) {
|
||||
const view = this.routeData.view;
|
||||
let index = 0;
|
||||
for (let i = 0; i < views.length; i++) {
|
||||
if (views[i].id === view || i === parseInt(view)) {
|
||||
if (views[i].path === view || i === parseInt(view)) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
@ -272,8 +276,8 @@ class HUIRoot extends NavigateMixin(
|
||||
}
|
||||
}
|
||||
|
||||
_computeViewId(id, index) {
|
||||
return id || index;
|
||||
_computeViewPath(path, index) {
|
||||
return path || index;
|
||||
}
|
||||
|
||||
_computeTitle(config) {
|
||||
@ -305,17 +309,7 @@ class HUIRoot extends NavigateMixin(
|
||||
}
|
||||
|
||||
_editModeEnable() {
|
||||
if (this.config._frontendAuto) {
|
||||
showSaveDialog(this, {
|
||||
config: this.config,
|
||||
reloadLovelace: () => {
|
||||
this.fire("config-refresh");
|
||||
this._editMode = true;
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
this._editMode = true;
|
||||
this.lovelace.setEditMode(true);
|
||||
if (this.config.views.length < 2) {
|
||||
this.$.view.classList.remove("tabs-hidden");
|
||||
this.fire("iron-resize");
|
||||
@ -323,7 +317,7 @@ class HUIRoot extends NavigateMixin(
|
||||
}
|
||||
|
||||
_editModeDisable() {
|
||||
this._editMode = false;
|
||||
this.lovelace.setEditMode(false);
|
||||
if (this.config.views.length < 2) {
|
||||
this.$.view.classList.add("tabs-hidden");
|
||||
this.fire("iron-resize");
|
||||
@ -336,20 +330,14 @@ class HUIRoot extends NavigateMixin(
|
||||
|
||||
_editView() {
|
||||
showEditViewDialog(this, {
|
||||
viewConfig: this.config.views[this._curView],
|
||||
add: false,
|
||||
reloadLovelace: () => {
|
||||
this.fire("config-refresh");
|
||||
},
|
||||
lovelace: this.lovelace,
|
||||
viewIndex: this._curView,
|
||||
});
|
||||
}
|
||||
|
||||
_addView() {
|
||||
showEditViewDialog(this, {
|
||||
add: true,
|
||||
reloadLovelace: () => {
|
||||
this.fire("config-refresh");
|
||||
},
|
||||
lovelace: this.lovelace,
|
||||
});
|
||||
}
|
||||
|
||||
@ -360,8 +348,8 @@ class HUIRoot extends NavigateMixin(
|
||||
|
||||
_navigateView(viewIndex) {
|
||||
if (viewIndex !== this._curView) {
|
||||
const id = this.config.views[viewIndex].id || viewIndex;
|
||||
this.navigate(`/lovelace/${id}`);
|
||||
const path = this.config.views[viewIndex].path || viewIndex;
|
||||
this.navigate(`/lovelace/${path}`);
|
||||
}
|
||||
scrollToTarget(this, this.$.layout.header.scrollTarget);
|
||||
}
|
||||
@ -390,12 +378,12 @@ class HUIRoot extends NavigateMixin(
|
||||
if (viewConfig.panel) {
|
||||
view = createCardElement(viewConfig.cards[0]);
|
||||
view.isPanel = true;
|
||||
view.editMode = this._editMode;
|
||||
} else {
|
||||
view = document.createElement("hui-view");
|
||||
view.lovelace = this.lovelace;
|
||||
view.config = viewConfig;
|
||||
view.columns = this.columns;
|
||||
view.editMode = this._editMode;
|
||||
view.index = viewIndex;
|
||||
}
|
||||
if (viewConfig.background) background = viewConfig.background;
|
||||
}
|
||||
@ -452,5 +440,13 @@ class HUIRoot extends NavigateMixin(
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_computeConfig(lovelace) {
|
||||
return lovelace ? lovelace.config : null;
|
||||
}
|
||||
|
||||
_computeEditMode(lovelace) {
|
||||
return lovelace ? lovelace.editMode : false;
|
||||
}
|
||||
}
|
||||
customElements.define("hui-root", HUIRoot);
|
||||
|
@ -11,7 +11,7 @@ import EventsMixin from "../../mixins/events-mixin";
|
||||
import localizeMixin from "../../mixins/localize-mixin";
|
||||
import createCardElement from "./common/create-card-element";
|
||||
import { computeCardSize } from "./common/compute-card-size";
|
||||
import { showEditCardDialog } from "./editor/show-edit-card-dialog";
|
||||
import { showEditCardDialog } from "./editor/card-editor/show-edit-card-dialog";
|
||||
|
||||
class HUIView extends localizeMixin(EventsMixin(PolymerElement)) {
|
||||
static get template() {
|
||||
@ -82,7 +82,7 @@ class HUIView extends localizeMixin(EventsMixin(PolymerElement)) {
|
||||
<div id="badges"></div>
|
||||
<div id="columns"></div>
|
||||
<paper-fab
|
||||
hidden$="{{!editMode}}"
|
||||
hidden$="[[!lovelace.editMode]]"
|
||||
elevated="2"
|
||||
icon="hass:plus"
|
||||
title=[[localize("ui.panel.lovelace.editor.edit_card.add")]]
|
||||
@ -97,9 +97,11 @@ class HUIView extends localizeMixin(EventsMixin(PolymerElement)) {
|
||||
type: Object,
|
||||
observer: "_hassChanged",
|
||||
},
|
||||
lovelace: Object,
|
||||
config: Object,
|
||||
columns: Number,
|
||||
editMode: Boolean,
|
||||
index: Number,
|
||||
};
|
||||
}
|
||||
|
||||
@ -119,11 +121,8 @@ class HUIView extends localizeMixin(EventsMixin(PolymerElement)) {
|
||||
|
||||
_addCard() {
|
||||
showEditCardDialog(this, {
|
||||
viewId: "id" in this.config ? String(this.config.id) : undefined,
|
||||
add: true,
|
||||
reloadLovelace: () => {
|
||||
this.fire("config-refresh");
|
||||
},
|
||||
lovelace: this.lovelace,
|
||||
path: [this.index],
|
||||
});
|
||||
}
|
||||
|
||||
@ -169,23 +168,23 @@ class HUIView extends localizeMixin(EventsMixin(PolymerElement)) {
|
||||
|
||||
const elements = [];
|
||||
const elementsToAppend = [];
|
||||
for (const cardConfig of config.cards) {
|
||||
config.cards.forEach((cardConfig, cardIndex) => {
|
||||
const element = createCardElement(cardConfig);
|
||||
element.hass = this.hass;
|
||||
elements.push(element);
|
||||
|
||||
if (!this.editMode) {
|
||||
if (!this.lovelace.editMode) {
|
||||
elementsToAppend.push(element);
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
const wrapper = document.createElement("hui-card-options");
|
||||
wrapper.hass = this.hass;
|
||||
wrapper.cardConfig = cardConfig;
|
||||
wrapper.editMode = this.editMode;
|
||||
wrapper.lovelace = this.lovelace;
|
||||
wrapper.path = [this.index, cardIndex];
|
||||
wrapper.appendChild(element);
|
||||
elementsToAppend.push(wrapper);
|
||||
}
|
||||
});
|
||||
|
||||
let columns = [];
|
||||
const columnEntityCount = [];
|
||||
|
@ -1,5 +1,14 @@
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { LovelaceCardConfig } from "../../data/lovelace";
|
||||
import { LovelaceCardConfig, LovelaceConfig } from "../../data/lovelace";
|
||||
|
||||
export interface Lovelace {
|
||||
config: LovelaceConfig;
|
||||
editMode: boolean;
|
||||
autoGen: boolean;
|
||||
mode: "yaml" | "storage";
|
||||
setEditMode: (editMode: boolean) => void;
|
||||
saveConfig: (newConfig: LovelaceConfig) => Promise<void>;
|
||||
}
|
||||
|
||||
export interface LovelaceCard extends HTMLElement {
|
||||
hass?: HomeAssistant;
|
||||
|
@ -159,3 +159,11 @@ export type GroupEntity = HassEntityBase & {
|
||||
control?: "hidden";
|
||||
};
|
||||
};
|
||||
|
||||
export interface PanelInfo<T = unknown> {
|
||||
component_name: string;
|
||||
icon?: string;
|
||||
title?: string;
|
||||
url_path: string;
|
||||
config: T;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user