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:
Bram Kragten 2018-11-19 22:19:50 +01:00 committed by GitHub
parent 9a9986cf17
commit 0bb85bc895
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 710 additions and 285 deletions

View File

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

View File

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

View 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);

View File

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

View 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);

View 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);

View File

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

View File

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

View File

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

View 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,
]);

View File

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

View File

@ -2,6 +2,7 @@ import { HomeAssistant } from "../../types";
export interface LovelaceConfig {
type: string;
id: string;
}
export interface LovelaceCard extends HTMLElement {

View File

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