mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-25 22:07:20 +00:00
Added migrate dialog when card has no ID (#2008)
* Added migrate dialog when card has no ID * typos * Fix error messages * cardId should be a string * Add translation * Only load yaml in yaml editor * revert name change * Combine migrate and edit in one dialog * lint * fixes + inlude and secret yaml * resize after toggle preview -> value>config * add loading spinners * only create preview when type changes * loader on yaml editor * Fixed loading spinner not disappearing * moved dialog * disable toggle if not avail * address comments * cleanup showDialog
This commit is contained in:
parent
9a9986cf17
commit
0bb85bc895
@ -1,4 +1,5 @@
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { LovelaceConfig } from "../types";
|
||||
|
||||
export const getCardConfig = (
|
||||
hass: HomeAssistant,
|
||||
@ -12,10 +13,17 @@ export const getCardConfig = (
|
||||
export const updateCardConfig = (
|
||||
hass: HomeAssistant,
|
||||
cardId: string,
|
||||
config: any
|
||||
config: LovelaceConfig | string,
|
||||
configFormat: "json" | "yaml"
|
||||
): Promise<void> =>
|
||||
hass!.callWS({
|
||||
hass.callWS({
|
||||
type: "lovelace/config/card/update",
|
||||
card_id: cardId,
|
||||
card_config: config,
|
||||
format: configFormat,
|
||||
});
|
||||
|
||||
export const migrateConfig = (hass: HomeAssistant): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "lovelace/config/migrate",
|
||||
});
|
||||
|
@ -2,17 +2,16 @@ import "@polymer/paper-button/paper-button";
|
||||
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { LovelaceConfig } from "../types";
|
||||
|
||||
let registeredDialog = false;
|
||||
|
||||
export class HuiCardOptions extends LitElement {
|
||||
public cardId?: string;
|
||||
public cardConfig?: LovelaceConfig;
|
||||
protected hass?: HomeAssistant;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
hass: {},
|
||||
};
|
||||
return { hass: {} };
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
@ -51,7 +50,7 @@ export class HuiCardOptions extends LitElement {
|
||||
private _editCard() {
|
||||
fireEvent(this, "show-edit-card", {
|
||||
hass: this.hass,
|
||||
cardId: this.cardId,
|
||||
cardConfig: this.cardConfig,
|
||||
reloadLovelace: () => fireEvent(this, "config-refresh"),
|
||||
});
|
||||
}
|
||||
|
73
src/panels/lovelace/editor/hui-card-preview.ts
Normal file
73
src/panels/lovelace/editor/hui-card-preview.ts
Normal file
@ -0,0 +1,73 @@
|
||||
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 { LovelaceCard, LovelaceConfig } from "../types";
|
||||
import { ConfigError } from "./types";
|
||||
|
||||
const CUSTOM_TYPE_PREFIX = "custom:";
|
||||
|
||||
export class HuiCardPreview extends HTMLElement {
|
||||
private _hass?: HomeAssistant;
|
||||
private _element?: LovelaceCard;
|
||||
|
||||
set hass(value: HomeAssistant) {
|
||||
this._hass = value;
|
||||
if (this._element) {
|
||||
this._element.hass = value;
|
||||
}
|
||||
}
|
||||
|
||||
set error(error: ConfigError) {
|
||||
const configValue = createErrorCardConfig(
|
||||
`${error.type}: ${error.message}`,
|
||||
undefined
|
||||
);
|
||||
|
||||
this._createCard(configValue);
|
||||
}
|
||||
|
||||
set config(configValue: LovelaceConfig) {
|
||||
if (!configValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._element) {
|
||||
this._createCard(configValue);
|
||||
return;
|
||||
}
|
||||
|
||||
const tag = configValue.type.startsWith(CUSTOM_TYPE_PREFIX)
|
||||
? configValue.type.substr(CUSTOM_TYPE_PREFIX.length)
|
||||
: `hui-${configValue.type}-card`;
|
||||
|
||||
if (tag.toUpperCase() === this._element.tagName) {
|
||||
this._element.setConfig(configValue);
|
||||
} else {
|
||||
this._createCard(configValue);
|
||||
}
|
||||
}
|
||||
|
||||
private _createCard(configValue: LovelaceConfig): void {
|
||||
if (this._element) {
|
||||
this.removeChild(this._element);
|
||||
}
|
||||
|
||||
this._element = createCardElement(configValue);
|
||||
|
||||
if (this._hass) {
|
||||
this._element!.hass = this._hass;
|
||||
}
|
||||
|
||||
this.appendChild(this._element!);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-card-preview": HuiCardPreview;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-card-preview", HuiCardPreview);
|
@ -1,229 +1,52 @@
|
||||
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
|
||||
import yaml from "js-yaml";
|
||||
import { when } from "lit-html/directives/when";
|
||||
import { TemplateResult } from "lit-html";
|
||||
|
||||
import "@polymer/paper-button/paper-button";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
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 { HomeAssistant } from "../../../types";
|
||||
import { getCardConfig, updateCardConfig } from "../common/data";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
|
||||
import "./hui-yaml-editor";
|
||||
import "./hui-yaml-card-preview";
|
||||
// This is not a duplicate import, one is for types, one is for element.
|
||||
// tslint:disable-next-line
|
||||
import { HuiYAMLCardPreview } from "./hui-yaml-card-preview";
|
||||
import { LovelaceCardEditor, LovelaceConfig } from "../types";
|
||||
import { YamlChangedEvent, ConfigValue } from "./types";
|
||||
|
||||
const CUSTOM_TYPE_PREFIX = "custom:";
|
||||
import { LovelaceConfig } from "../types";
|
||||
import "./hui-edit-card";
|
||||
import "./hui-migrate-config";
|
||||
|
||||
export class HuiDialogEditCard extends LitElement {
|
||||
protected hass?: HomeAssistant;
|
||||
private _cardId?: string;
|
||||
private _originalConfigYaml?: string;
|
||||
private _configElement?: LovelaceCardEditor | null;
|
||||
protected _hass?: HomeAssistant;
|
||||
private _cardConfig?: LovelaceConfig;
|
||||
private _reloadLovelace?: () => void;
|
||||
private _editorToggle?: boolean;
|
||||
private _configValue?: ConfigValue;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
hass: {},
|
||||
cardId: {
|
||||
type: Number,
|
||||
},
|
||||
_dialogClosedCallback: {},
|
||||
_configElement: {},
|
||||
_editorToggle: {},
|
||||
_hass: {},
|
||||
_cardConfig: {},
|
||||
};
|
||||
}
|
||||
|
||||
public async showDialog({ hass, cardId, reloadLovelace }) {
|
||||
this.hass = hass;
|
||||
this._cardId = cardId;
|
||||
public async showDialog({ hass, cardConfig, reloadLovelace }): Promise<void> {
|
||||
this._hass = hass;
|
||||
this._cardConfig = cardConfig;
|
||||
this._reloadLovelace = reloadLovelace;
|
||||
this._editorToggle = true;
|
||||
this._configElement = undefined;
|
||||
this._configValue = { format: "yaml", value: "" };
|
||||
this._loadConfig().then(() => this._loadConfigElement());
|
||||
// Wait till dialog is rendered.
|
||||
await this.updateComplete;
|
||||
this._dialog.open();
|
||||
}
|
||||
|
||||
private get _dialog(): PaperDialogElement {
|
||||
return this.shadowRoot!.querySelector("paper-dialog")!;
|
||||
}
|
||||
|
||||
private get _previewEl(): HuiYAMLCardPreview {
|
||||
return this.shadowRoot!.querySelector("hui-yaml-card-preview")!;
|
||||
(this.shadowRoot!.children[0] as any).showDialog();
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
paper-dialog {
|
||||
width: 650px;
|
||||
}
|
||||
.element-editor {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
<paper-dialog with-backdrop>
|
||||
<h2>Card Configuration</h2>
|
||||
<paper-dialog-scrollable>
|
||||
${
|
||||
this._editorToggle && this._configElement !== null
|
||||
? html`
|
||||
<div class="element-editor">
|
||||
${
|
||||
when(
|
||||
this._configElement,
|
||||
() => this._configElement,
|
||||
() =>
|
||||
html`
|
||||
Loading...
|
||||
`
|
||||
)
|
||||
}
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<hui-yaml-editor
|
||||
.yaml="${this._configValue!.value}"
|
||||
@yaml-changed="${this._handleYamlChanged}"
|
||||
></hui-yaml-editor>
|
||||
`
|
||||
}
|
||||
<hui-yaml-card-preview
|
||||
.hass="${this.hass}"
|
||||
.value="${this._configValue}"
|
||||
></hui-yaml-card-preview>
|
||||
</paper-dialog-scrollable>
|
||||
<div class="paper-dialog-buttons">
|
||||
<paper-button @click="${this._toggleEditor}"
|
||||
>Toggle Editor</paper-button
|
||||
>
|
||||
<paper-button @click="${this._closeDialog}">Cancel</paper-button>
|
||||
<paper-button @click="${this._updateConfigInBackend}"
|
||||
>Save</paper-button
|
||||
>
|
||||
</div>
|
||||
</paper-dialog>
|
||||
${
|
||||
this._cardConfig!.id
|
||||
? html`
|
||||
<hui-edit-card
|
||||
.cardConfig="${this._cardConfig}"
|
||||
.hass="${this._hass}"
|
||||
@reload-lovelace="${this._reloadLovelace}"
|
||||
>
|
||||
</hui-edit-card>
|
||||
`
|
||||
: html`
|
||||
<hui-migrate-config
|
||||
.hass="${this._hass}"
|
||||
@reload-lovelace="${this._reloadLovelace}"
|
||||
></hui-migrate-config>
|
||||
`
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleYamlChanged(ev: YamlChangedEvent): void {
|
||||
this._configValue = { format: "yaml", value: ev.detail.yaml };
|
||||
this._updatePreview(this._configValue);
|
||||
}
|
||||
|
||||
private _handleJSConfigChanged(value: LovelaceConfig): void {
|
||||
this._configElement!.setConfig(value);
|
||||
this._configValue = { format: "js", value };
|
||||
this._updatePreview(this._configValue);
|
||||
}
|
||||
|
||||
private _updatePreview(value: ConfigValue) {
|
||||
if (!this._previewEl) {
|
||||
return;
|
||||
}
|
||||
this._previewEl.value = value;
|
||||
}
|
||||
|
||||
private _closeDialog(): void {
|
||||
this._dialog.close();
|
||||
}
|
||||
|
||||
private _toggleEditor(): void {
|
||||
if (this._editorToggle && this._configValue!.format === "js") {
|
||||
this._configValue = {
|
||||
format: "yaml",
|
||||
value: yaml.safeDump(this._configValue!.value),
|
||||
};
|
||||
} else if (this._configElement && this._configValue!.format === "yaml") {
|
||||
this._configValue = {
|
||||
format: "js",
|
||||
value: yaml.safeLoad(this._configValue!.value),
|
||||
};
|
||||
this._configElement.setConfig(this._configValue!.value as LovelaceConfig);
|
||||
}
|
||||
this._editorToggle = !this._editorToggle;
|
||||
}
|
||||
|
||||
private async _loadConfig(): Promise<void> {
|
||||
const cardConfig = await getCardConfig(this.hass!, this._cardId!);
|
||||
this._configValue = {
|
||||
format: "yaml",
|
||||
value: cardConfig,
|
||||
};
|
||||
this._originalConfigYaml = cardConfig;
|
||||
|
||||
// This will center the dialog with the updated config Element
|
||||
fireEvent(this._dialog, "iron-resize");
|
||||
}
|
||||
|
||||
private async _loadConfigElement(): Promise<void> {
|
||||
const conf = yaml.safeLoad(this._configValue!.value);
|
||||
|
||||
const tag = conf.type.startsWith(CUSTOM_TYPE_PREFIX)
|
||||
? conf.type.substr(CUSTOM_TYPE_PREFIX.length)
|
||||
: `hui-${conf.type}-card`;
|
||||
|
||||
const elClass = customElements.get(tag);
|
||||
let configElement;
|
||||
|
||||
try {
|
||||
configElement = await elClass.getConfigElement();
|
||||
} catch (err) {
|
||||
this._configElement = null;
|
||||
return;
|
||||
}
|
||||
|
||||
configElement.setConfig(conf);
|
||||
configElement.hass = this.hass;
|
||||
configElement.addEventListener("config-changed", (ev) =>
|
||||
this._handleJSConfigChanged(ev.detail.config)
|
||||
);
|
||||
this._configValue = { format: "js", value: conf };
|
||||
this._configElement = configElement;
|
||||
|
||||
// This will center the dialog with the updated config Element
|
||||
fireEvent(this._dialog, "iron-resize");
|
||||
}
|
||||
|
||||
private async _updateConfigInBackend(): Promise<void> {
|
||||
if (this._configValue!.format === "js") {
|
||||
this._configValue = {
|
||||
format: "yaml",
|
||||
value: yaml.safeDump(this._configValue!.value),
|
||||
};
|
||||
}
|
||||
|
||||
if (this._configValue!.value === this._originalConfigYaml) {
|
||||
this._dialog.close();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await updateCardConfig(
|
||||
this.hass!,
|
||||
this._cardId!,
|
||||
this._configValue!.value
|
||||
);
|
||||
this._dialog.close();
|
||||
this._reloadLovelace!();
|
||||
} catch (err) {
|
||||
alert(`Saving failed: ${err.reason}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
376
src/panels/lovelace/editor/hui-edit-card.ts
Normal file
376
src/panels/lovelace/editor/hui-edit-card.ts
Normal file
@ -0,0 +1,376 @@
|
||||
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
|
||||
import { classMap } from "lit-html/directives/classMap";
|
||||
import { TemplateResult } from "lit-html";
|
||||
import yaml from "js-yaml";
|
||||
|
||||
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 "@polymer/paper-input/paper-textarea";
|
||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { updateCardConfig } from "../common/data";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
|
||||
|
||||
import "./hui-yaml-editor";
|
||||
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, LovelaceConfig } from "../types";
|
||||
import { YamlChangedEvent, ConfigValue, ConfigError } from "./types";
|
||||
import { extYamlSchema } from "./yaml-ext-schema";
|
||||
|
||||
const CUSTOM_TYPE_PREFIX = "custom:";
|
||||
|
||||
export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
|
||||
protected hass?: HomeAssistant;
|
||||
private _cardId?: string;
|
||||
private _originalConfig?: LovelaceConfig;
|
||||
private _configElement?: LovelaceCardEditor | null;
|
||||
private _uiEditor?: boolean;
|
||||
private _configValue?: ConfigValue;
|
||||
private _configState?: string;
|
||||
private _loading?: boolean;
|
||||
private _isToggleAvailable?: boolean;
|
||||
private _saving: boolean;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
_hass: {},
|
||||
_cardId: {},
|
||||
_originalConfig: {},
|
||||
_configElement: {},
|
||||
_configValue: {},
|
||||
_configState: {},
|
||||
_uiEditor: {},
|
||||
_saving: {},
|
||||
_loading: {},
|
||||
_isToggleAvailable: {},
|
||||
};
|
||||
}
|
||||
|
||||
protected constructor() {
|
||||
super();
|
||||
this._saving = false;
|
||||
}
|
||||
|
||||
set cardConfig(cardConfig: LovelaceConfig) {
|
||||
this._originalConfig = cardConfig;
|
||||
if (String(cardConfig.id) !== this._cardId) {
|
||||
this._loading = true;
|
||||
this._uiEditor = true;
|
||||
this._configElement = undefined;
|
||||
this._configValue = { format: "yaml", value: undefined };
|
||||
this._configState = "OK";
|
||||
this._isToggleAvailable = false;
|
||||
this._cardId = String(cardConfig.id);
|
||||
this._loadConfigElement();
|
||||
}
|
||||
}
|
||||
|
||||
public async showDialog(): Promise<void> {
|
||||
// Wait till dialog is rendered.
|
||||
if (this._dialog == null) {
|
||||
await this.updateComplete;
|
||||
}
|
||||
this._dialog.open();
|
||||
}
|
||||
|
||||
private get _dialog(): PaperDialogElement {
|
||||
return this.shadowRoot!.querySelector("paper-dialog")!;
|
||||
}
|
||||
|
||||
private get _previewEl(): HuiCardPreview {
|
||||
return this.shadowRoot!.querySelector("hui-card-preview")!;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
<paper-dialog with-backdrop>
|
||||
<h2>${this.localize("ui.panel.lovelace.editor.edit.header")}</h2>
|
||||
<paper-spinner
|
||||
?active="${this._loading}"
|
||||
alt="Loading"
|
||||
class="center margin-bot"
|
||||
></paper-spinner>
|
||||
<paper-dialog-scrollable
|
||||
class="${classMap({ hidden: this._loading! })}"
|
||||
>
|
||||
${
|
||||
this._uiEditor && this._configElement !== null
|
||||
? html`
|
||||
<div class="element-editor">${this._configElement}</div>
|
||||
`
|
||||
: html`
|
||||
<hui-yaml-editor
|
||||
.hass="${this.hass}"
|
||||
.cardId="${this._cardId}"
|
||||
.yaml="${this._configValue!.value}"
|
||||
@yaml-changed="${this._handleYamlChanged}"
|
||||
></hui-yaml-editor>
|
||||
`
|
||||
}
|
||||
<hui-card-preview .hass="${this.hass}"></hui-card-preview>
|
||||
</paper-dialog-scrollable>
|
||||
${
|
||||
!this._loading
|
||||
? html`
|
||||
<div class="paper-dialog-buttons">
|
||||
<paper-button
|
||||
?disabled="${!this._isToggleAvailable}"
|
||||
@click="${this._toggleEditor}"
|
||||
>${
|
||||
this.localize(
|
||||
"ui.panel.lovelace.editor.edit.toggle_editor"
|
||||
)
|
||||
}</paper-button
|
||||
>
|
||||
<paper-button @click="${this._closeDialog}"
|
||||
>${this.localize("ui.common.cancel")}</paper-button
|
||||
>
|
||||
<paper-button
|
||||
?disabled="${this._saving}"
|
||||
@click="${this._save}"
|
||||
>
|
||||
<paper-spinner
|
||||
?active="${this._saving}"
|
||||
alt="Saving"
|
||||
></paper-spinner>
|
||||
${
|
||||
this.localize("ui.panel.lovelace.editor.edit.save")
|
||||
}</paper-button
|
||||
>
|
||||
</div>
|
||||
`
|
||||
: html``
|
||||
}
|
||||
</paper-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderStyle(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
paper-dialog {
|
||||
width: 650px;
|
||||
}
|
||||
.center {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
.margin-bot {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
paper-button paper-spinner {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
paper-spinner {
|
||||
display: none;
|
||||
}
|
||||
paper-spinner[active] {
|
||||
display: block;
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
.element-editor {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
}
|
||||
|
||||
private _toggleEditor(): void {
|
||||
if (!this._isToggleAvailable) {
|
||||
alert("You can't switch editor.");
|
||||
return;
|
||||
}
|
||||
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._uiEditor = !this._uiEditor;
|
||||
} else if (this._configElement && this._configValue!.format === "yaml") {
|
||||
this._configValue = {
|
||||
format: "json",
|
||||
value: yaml.safeLoad(this._configValue!.value, {
|
||||
schema: extYamlSchema,
|
||||
}),
|
||||
};
|
||||
this._configElement.setConfig(this._configValue!.value as LovelaceConfig);
|
||||
this._uiEditor = !this._uiEditor;
|
||||
}
|
||||
this._resizeDialog();
|
||||
}
|
||||
|
||||
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;
|
||||
this._resizeDialog();
|
||||
}
|
||||
|
||||
private async _resizeDialog(): Promise<void> {
|
||||
await this.updateComplete;
|
||||
fireEvent(this._dialog, "iron-resize");
|
||||
}
|
||||
|
||||
private _closeDialog(): void {
|
||||
this._dialog.close();
|
||||
}
|
||||
|
||||
private async _updateConfigInBackend(): 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();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await updateCardConfig(
|
||||
this.hass!,
|
||||
this._cardId!,
|
||||
this._configValue!.value!,
|
||||
this._configValue!.format
|
||||
);
|
||||
this._closeDialog();
|
||||
this._saveDone();
|
||||
fireEvent(this, "reload-lovelace");
|
||||
} catch (err) {
|
||||
alert(`Saving failed: ${err.message}`);
|
||||
this._saveDone();
|
||||
}
|
||||
}
|
||||
|
||||
private _handleYamlChanged(ev: YamlChangedEvent): void {
|
||||
this._configValue = { format: "yaml", value: ev.detail.yaml };
|
||||
try {
|
||||
const config = yaml.safeLoad(this._configValue.value, {
|
||||
schema: extYamlSchema,
|
||||
}) as LovelaceConfig;
|
||||
this._updatePreview(config);
|
||||
this._configState = "OK";
|
||||
if (!this._isToggleAvailable && this._configElement !== null) {
|
||||
this._isToggleAvailable = true;
|
||||
}
|
||||
} catch (err) {
|
||||
this._isToggleAvailable = false;
|
||||
this._configState = "YAML_ERROR";
|
||||
this._setPreviewError({
|
||||
type: "YAML Error",
|
||||
message: err,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _handleUIConfigChanged(value: LovelaceConfig): void {
|
||||
this._configElement!.setConfig(value);
|
||||
this._configValue = { format: "json", value };
|
||||
this._updatePreview(value);
|
||||
}
|
||||
|
||||
private _updatePreview(config: LovelaceConfig) {
|
||||
if (!this._previewEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._previewEl.config = config;
|
||||
|
||||
if (this._loading) {
|
||||
this._loadedDialog();
|
||||
} else {
|
||||
this._resizeDialog();
|
||||
}
|
||||
}
|
||||
|
||||
private _setPreviewError(error: ConfigError) {
|
||||
if (!this._previewEl) {
|
||||
return;
|
||||
}
|
||||
this._previewEl.error = error;
|
||||
|
||||
this._resizeDialog();
|
||||
}
|
||||
|
||||
private _isConfigValid() {
|
||||
if (!this._cardId || !this._configValue || !this._configValue.value) {
|
||||
return false;
|
||||
}
|
||||
if (this._configState === "OK") {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private _isConfigChanged(): boolean {
|
||||
const configValue =
|
||||
this._configValue!.format === "yaml"
|
||||
? yaml.safeDump(this._configValue!.value)
|
||||
: this._configValue!.value;
|
||||
return JSON.stringify(configValue) !== JSON.stringify(this._originalConfig);
|
||||
}
|
||||
|
||||
private async _loadConfigElement(): Promise<void> {
|
||||
const conf = this._originalConfig;
|
||||
const tag = conf!.type.startsWith(CUSTOM_TYPE_PREFIX)
|
||||
? conf!.type.substr(CUSTOM_TYPE_PREFIX.length)
|
||||
: `hui-${conf!.type}-card`;
|
||||
|
||||
const elClass = customElements.get(tag);
|
||||
let configElement;
|
||||
|
||||
try {
|
||||
configElement = await elClass.getConfigElement();
|
||||
} catch (err) {
|
||||
this._configElement = null;
|
||||
this._uiEditor = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this._isToggleAvailable = true;
|
||||
|
||||
configElement.setConfig(conf);
|
||||
configElement.hass = this.hass;
|
||||
configElement.addEventListener("config-changed", (ev) =>
|
||||
this._handleUIConfigChanged(ev.detail.config)
|
||||
);
|
||||
this._configValue = { format: "json", value: conf! };
|
||||
this._configElement = configElement;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-edit-card": HuiEditCard;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-edit-card", HuiEditCard);
|
112
src/panels/lovelace/editor/hui-migrate-config.ts
Normal file
112
src/panels/lovelace/editor/hui-migrate-config.ts
Normal file
@ -0,0 +1,112 @@
|
||||
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 "../common/data";
|
||||
|
||||
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();
|
||||
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);
|
@ -1,57 +0,0 @@
|
||||
import yaml from "js-yaml";
|
||||
|
||||
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 { LovelaceCard } from "../types";
|
||||
import { ConfigValue } from "./types";
|
||||
|
||||
export class HuiYAMLCardPreview extends HTMLElement {
|
||||
private _hass?: HomeAssistant;
|
||||
|
||||
set hass(value: HomeAssistant) {
|
||||
this._hass = value;
|
||||
if (this.lastChild) {
|
||||
(this.lastChild as LovelaceCard).hass = value;
|
||||
}
|
||||
}
|
||||
|
||||
set value(configValue: ConfigValue) {
|
||||
if (this.lastChild) {
|
||||
this.removeChild(this.lastChild);
|
||||
}
|
||||
|
||||
if (!configValue.value || configValue.value === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
let conf;
|
||||
if (configValue.format === "yaml") {
|
||||
try {
|
||||
conf = yaml.safeLoad(configValue.value);
|
||||
} catch (err) {
|
||||
conf = createErrorCardConfig(`Invalid YAML: ${err.message}`, undefined);
|
||||
}
|
||||
} else {
|
||||
conf = configValue.value;
|
||||
}
|
||||
|
||||
const element = createCardElement(conf);
|
||||
|
||||
if (this._hass) {
|
||||
element.hass = this._hass;
|
||||
}
|
||||
|
||||
this.appendChild(element);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-yaml-card-preview": HuiYAMLCardPreview;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-yaml-card-preview", HuiYAMLCardPreview);
|
@ -1,37 +1,87 @@
|
||||
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 "../common/data";
|
||||
|
||||
export class HuiYAMLEditor extends LitElement {
|
||||
public yaml?: string;
|
||||
public cardId?: string;
|
||||
protected hass?: HomeAssistant;
|
||||
private _yaml?: string;
|
||||
private _loading?: boolean;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
yaml: {},
|
||||
};
|
||||
return { _yaml: {}, cardId: {} };
|
||||
}
|
||||
|
||||
set yaml(yaml: string) {
|
||||
if (yaml === undefined) {
|
||||
this._loading = true;
|
||||
this._loadConfig();
|
||||
} else {
|
||||
this._yaml = yaml;
|
||||
if (this._loading) {
|
||||
this._loading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
paper-textarea {
|
||||
--paper-input-container-shared-input-style_-_font-family: monospace;
|
||||
}
|
||||
</style>
|
||||
${this.renderStyle()}
|
||||
<paper-spinner
|
||||
?active="${this._loading}"
|
||||
alt="Loading"
|
||||
class="center"
|
||||
></paper-spinner>
|
||||
<paper-textarea
|
||||
max-rows="10"
|
||||
value="${this.yaml}"
|
||||
.value="${this._yaml}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></paper-textarea>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: MouseEvent): void {
|
||||
private renderStyle(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
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;
|
||||
fireEvent(this, "yaml-changed", { yaml: target.value });
|
||||
this._yaml = target.value;
|
||||
fireEvent(this, "yaml-changed", {
|
||||
yaml: target.value,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,8 +8,13 @@ export interface YamlChangedEvent extends Event {
|
||||
}
|
||||
|
||||
export interface ConfigValue {
|
||||
format: "js" | "yaml";
|
||||
value: string | LovelaceConfig;
|
||||
format: "json" | "yaml";
|
||||
value?: string | LovelaceConfig;
|
||||
}
|
||||
|
||||
export interface ConfigError {
|
||||
type: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface EntitiesEditorEvent {
|
||||
|
22
src/panels/lovelace/editor/yaml-ext-schema.ts
Normal file
22
src/panels/lovelace/editor/yaml-ext-schema.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import yaml from "js-yaml";
|
||||
|
||||
const secretYamlType = new yaml.Type("!secret", {
|
||||
kind: "scalar",
|
||||
construct(data) {
|
||||
data = data || "";
|
||||
return "!secret " + data;
|
||||
},
|
||||
});
|
||||
|
||||
const includeYamlType = new yaml.Type("!include", {
|
||||
kind: "scalar",
|
||||
construct(data) {
|
||||
data = data || "";
|
||||
return "!include " + data;
|
||||
},
|
||||
});
|
||||
|
||||
export const extYamlSchema = yaml.Schema.create([
|
||||
secretYamlType,
|
||||
includeYamlType,
|
||||
]);
|
@ -147,7 +147,7 @@ class HUIView extends PolymerElement {
|
||||
|
||||
const wrapper = document.createElement("hui-card-options");
|
||||
wrapper.hass = this.hass;
|
||||
wrapper.cardId = cardConfig.id;
|
||||
wrapper.cardConfig = cardConfig;
|
||||
wrapper.editMode = this.editMode;
|
||||
wrapper.appendChild(element);
|
||||
elementsToAppend.push(wrapper);
|
||||
|
@ -2,6 +2,7 @@ import { HomeAssistant } from "../../types";
|
||||
|
||||
export interface LovelaceConfig {
|
||||
type: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface LovelaceCard extends HTMLElement {
|
||||
|
@ -765,6 +765,19 @@
|
||||
"clear_items": "Clear checked items",
|
||||
"add_item": "Add item"
|
||||
}
|
||||
},
|
||||
"editor": {
|
||||
"edit": {
|
||||
"header": "Card Configuration",
|
||||
"save": "Save",
|
||||
"toggle_editor": "Toggle Editor"
|
||||
},
|
||||
"migrate": {
|
||||
"header": "Configuration Incompatible",
|
||||
"para_no_id": "This element doesn't have an ID. Please add an ID to this element in 'ui-lovelace.yaml'.",
|
||||
"para_migrate": "Home Assistant can add ID's to all your cards and views automatically for you by pressing the 'Migrate config' button.",
|
||||
"migrate": "Migrate config"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mailbox": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user