mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-26 02:36:37 +00:00
Various changes to card editor. (#3265)
* Various changes to card editor. * Avoid crashing on bad yaml when creating a new card * Address review comments * Revert interface change * Avoid config loops. Nicer error behavior.
This commit is contained in:
parent
ef3892de92
commit
c15629b81b
281
src/panels/lovelace/editor/card-editor/hui-card-editor.ts
Normal file
281
src/panels/lovelace/editor/card-editor/hui-card-editor.ts
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
import {
|
||||||
|
html,
|
||||||
|
css,
|
||||||
|
LitElement,
|
||||||
|
TemplateResult,
|
||||||
|
CSSResult,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
|
} from "lit-element";
|
||||||
|
|
||||||
|
import yaml from "js-yaml";
|
||||||
|
|
||||||
|
import "@material/mwc-button";
|
||||||
|
import { HomeAssistant } from "../../../../types";
|
||||||
|
import { LovelaceCardConfig } from "../../../../data/lovelace";
|
||||||
|
import { LovelaceCardEditor } from "../../types";
|
||||||
|
import { getCardElementTag } from "../../common/get-card-element-tag";
|
||||||
|
|
||||||
|
import "../../components/hui-yaml-editor";
|
||||||
|
// This is not a duplicate import, one is for types, one is for element.
|
||||||
|
// tslint:disable-next-line
|
||||||
|
import { HuiYamlEditor } from "../../components/hui-yaml-editor";
|
||||||
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
import { EntityConfig } from "../../entity-rows/types";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HASSDomEvents {
|
||||||
|
"entities-changed": {
|
||||||
|
entities: EntityConfig[];
|
||||||
|
};
|
||||||
|
"config-changed": {
|
||||||
|
config: LovelaceCardConfig;
|
||||||
|
error?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UIConfigChangedEvent extends Event {
|
||||||
|
detail: {
|
||||||
|
config: LovelaceCardConfig;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement("hui-card-editor")
|
||||||
|
export class HuiCardEditor extends LitElement {
|
||||||
|
@property() public hass?: HomeAssistant;
|
||||||
|
|
||||||
|
@property() private _yaml?: string;
|
||||||
|
@property() private _config?: LovelaceCardConfig;
|
||||||
|
@property() private _configElement?: LovelaceCardEditor;
|
||||||
|
@property() private _configElType?: string;
|
||||||
|
@property() private _GUImode: boolean = true;
|
||||||
|
// Error: Configuration broken - do not save
|
||||||
|
@property() private _error?: string;
|
||||||
|
// Warning: GUI editor can't handle configuration - ok to save
|
||||||
|
@property() private _warning?: string;
|
||||||
|
@property() private _loading: boolean = false;
|
||||||
|
|
||||||
|
public get yaml(): string {
|
||||||
|
return this._yaml || "";
|
||||||
|
}
|
||||||
|
public set yaml(_yaml: string) {
|
||||||
|
this._yaml = _yaml;
|
||||||
|
try {
|
||||||
|
this._config = yaml.safeLoad(this.yaml);
|
||||||
|
this._updateConfigElement();
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this._yamlEditor) {
|
||||||
|
this._yamlEditor.codemirror.refresh();
|
||||||
|
}
|
||||||
|
}, 1);
|
||||||
|
this._error = undefined;
|
||||||
|
} catch (err) {
|
||||||
|
this._error = err.message;
|
||||||
|
}
|
||||||
|
fireEvent(this, "config-changed", {
|
||||||
|
config: this.value!,
|
||||||
|
error: this._error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public get value(): LovelaceCardConfig | undefined {
|
||||||
|
return this._config;
|
||||||
|
}
|
||||||
|
public set value(config: LovelaceCardConfig | undefined) {
|
||||||
|
if (JSON.stringify(config) !== JSON.stringify(this._config || {})) {
|
||||||
|
this.yaml = yaml.safeDump(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get hasError(): boolean {
|
||||||
|
return this._error !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _yamlEditor(): HuiYamlEditor {
|
||||||
|
return this.shadowRoot!.querySelector("hui-yaml-editor")!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public toggleMode() {
|
||||||
|
this._GUImode = !this._GUImode;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<div class="wrapper">
|
||||||
|
${this._GUImode
|
||||||
|
? html`
|
||||||
|
<div class="gui-editor">
|
||||||
|
${this._loading
|
||||||
|
? html`
|
||||||
|
<paper-spinner
|
||||||
|
active
|
||||||
|
alt="Loading"
|
||||||
|
class="center margin-bot"
|
||||||
|
></paper-spinner>
|
||||||
|
`
|
||||||
|
: this._configElement}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<div class="yaml-editor">
|
||||||
|
<hui-yaml-editor
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${this.yaml}
|
||||||
|
@yaml-changed=${this._handleYAMLChanged}
|
||||||
|
></hui-yaml-editor>
|
||||||
|
</div>
|
||||||
|
`}
|
||||||
|
${this._error
|
||||||
|
? html`
|
||||||
|
<div class="error">
|
||||||
|
${this._error}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${this._warning
|
||||||
|
? html`
|
||||||
|
<div class="warning">
|
||||||
|
${this._warning}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
<div class="buttons">
|
||||||
|
<mwc-button
|
||||||
|
@click=${this.toggleMode}
|
||||||
|
?disabled=${this._warning || this._error}
|
||||||
|
?unelevated=${this._GUImode === false}
|
||||||
|
>
|
||||||
|
<ha-icon icon="mdi:code-braces"></ha-icon>
|
||||||
|
</mwc-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updated(changedProperties) {
|
||||||
|
super.updated(changedProperties);
|
||||||
|
|
||||||
|
if (changedProperties.has("_GUImode")) {
|
||||||
|
if (this._GUImode === false) {
|
||||||
|
// Refresh code editor when switching to yaml mode
|
||||||
|
this._yamlEditor.codemirror.refresh();
|
||||||
|
this._yamlEditor.codemirror.focus();
|
||||||
|
}
|
||||||
|
fireEvent(this as HTMLElement, "iron-resize");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleUIConfigChanged(ev: UIConfigChangedEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const config = ev.detail.config;
|
||||||
|
this.value = config;
|
||||||
|
}
|
||||||
|
private _handleYAMLChanged(ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const newYaml = ev.detail.value;
|
||||||
|
if (newYaml !== this.yaml) {
|
||||||
|
this.yaml = newYaml;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _updateConfigElement(): Promise<void> {
|
||||||
|
if (!this.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cardType = this.value.type;
|
||||||
|
let configElement = this._configElement;
|
||||||
|
try {
|
||||||
|
this._error = undefined;
|
||||||
|
this._warning = undefined;
|
||||||
|
|
||||||
|
if (this._configElType !== cardType) {
|
||||||
|
// If the card type has changed, we need to load a new GUI editor
|
||||||
|
if (!this.value.type) {
|
||||||
|
throw new Error("No card type defined");
|
||||||
|
}
|
||||||
|
|
||||||
|
const tag = getCardElementTag(cardType);
|
||||||
|
|
||||||
|
// Check if the card type exists
|
||||||
|
const elClass = customElements.get(tag);
|
||||||
|
if (!elClass) {
|
||||||
|
throw new Error(`Unknown card type encountered: ${cardType}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._loading = true;
|
||||||
|
// Check if a GUI editor exists
|
||||||
|
if (elClass && elClass.getConfigElement) {
|
||||||
|
configElement = await elClass.getConfigElement();
|
||||||
|
} else {
|
||||||
|
configElement = undefined;
|
||||||
|
throw Error(`WARNING: No GUI editor available for: ${cardType}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._configElement = configElement;
|
||||||
|
this._configElType = cardType;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup GUI editor and check that it can handle the current config
|
||||||
|
try {
|
||||||
|
this._configElement!.setConfig(this.value);
|
||||||
|
} catch (err) {
|
||||||
|
throw Error(`WARNING: ${err.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform final setup
|
||||||
|
this._configElement!.hass = this.hass;
|
||||||
|
this._configElement!.addEventListener("config-changed", (ev) =>
|
||||||
|
this._handleUIConfigChanged(ev as UIConfigChangedEvent)
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
} catch (err) {
|
||||||
|
if (err.message.startsWith("WARNING:")) {
|
||||||
|
this._warning = err.message.substr(8);
|
||||||
|
} else {
|
||||||
|
this._error = err;
|
||||||
|
}
|
||||||
|
this._GUImode = false;
|
||||||
|
} finally {
|
||||||
|
this._loading = false;
|
||||||
|
fireEvent(this, "iron-resize");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.wrapper {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.gui-editor,
|
||||||
|
.yaml-editor {
|
||||||
|
padding: 8px 0px;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
color: #ef5350;
|
||||||
|
}
|
||||||
|
.warning {
|
||||||
|
color: #ffa726;
|
||||||
|
}
|
||||||
|
.buttons {
|
||||||
|
text-align: right;
|
||||||
|
padding: 8px 0px;
|
||||||
|
}
|
||||||
|
paper-spinner {
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"hui-card-editor": HuiCardEditor;
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,7 @@ import { HomeAssistant } from "../../../../types";
|
|||||||
import { LovelaceCardConfig } from "../../../../data/lovelace";
|
import { LovelaceCardConfig } from "../../../../data/lovelace";
|
||||||
import { getCardElementTag } from "../../common/get-card-element-tag";
|
import { getCardElementTag } from "../../common/get-card-element-tag";
|
||||||
import { CardPickTarget } from "../types";
|
import { CardPickTarget } from "../types";
|
||||||
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
|
||||||
const cards = [
|
const cards = [
|
||||||
{ name: "Alarm panel", type: "alarm-panel" },
|
{ name: "Alarm panel", type: "alarm-panel" },
|
||||||
@ -60,6 +61,9 @@ export class HuiCardPicker extends LitElement {
|
|||||||
`;
|
`;
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="cards-container">
|
||||||
|
<mwc-button @click="${this._manualPicked}">MANUAL CARD</mwc-button>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,6 +89,12 @@ export class HuiCardPicker extends LitElement {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _manualPicked(): void {
|
||||||
|
fireEvent(this, "config-changed", {
|
||||||
|
config: { type: "" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private _cardPicked(ev: Event): void {
|
private _cardPicked(ev: Event): void {
|
||||||
const type = (ev.currentTarget! as CardPickTarget).type;
|
const type = (ev.currentTarget! as CardPickTarget).type;
|
||||||
const tag = getCardElementTag(type);
|
const tag = getCardElementTag(type);
|
||||||
@ -97,7 +107,7 @@ export class HuiCardPicker extends LitElement {
|
|||||||
config = { ...config, ...cardConfig };
|
config = { ...config, ...cardConfig };
|
||||||
}
|
}
|
||||||
|
|
||||||
this.cardPicked!(config);
|
fireEvent(this, "config-changed", { config });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
|
css,
|
||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
|
CSSResultArray,
|
||||||
customElement,
|
customElement,
|
||||||
property,
|
property,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
@ -9,11 +11,17 @@ import {
|
|||||||
import { HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../../../types";
|
||||||
import { HASSDomEvent } from "../../../../common/dom/fire_event";
|
import { HASSDomEvent } from "../../../../common/dom/fire_event";
|
||||||
import { LovelaceCardConfig } from "../../../../data/lovelace";
|
import { LovelaceCardConfig } from "../../../../data/lovelace";
|
||||||
import "./hui-edit-card";
|
import "./hui-card-editor";
|
||||||
import "./hui-dialog-pick-card";
|
// tslint:disable-next-line
|
||||||
|
import { HuiCardEditor } from "./hui-card-editor";
|
||||||
|
import "./hui-card-preview";
|
||||||
|
import "./hui-card-picker";
|
||||||
import { EditCardDialogParams } from "./show-edit-card-dialog";
|
import { EditCardDialogParams } from "./show-edit-card-dialog";
|
||||||
import { addCard, replaceCard } from "../config-util";
|
import { addCard, replaceCard } from "../config-util";
|
||||||
|
|
||||||
|
import "../../../../components/dialog/ha-paper-dialog";
|
||||||
|
import { haStyleDialog } from "../../../../resources/styles";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// for fire event
|
// for fire event
|
||||||
interface HASSDomEvents {
|
interface HASSDomEvents {
|
||||||
@ -33,72 +41,227 @@ export class HuiDialogEditCard extends LitElement {
|
|||||||
|
|
||||||
@property() private _cardConfig?: LovelaceCardConfig;
|
@property() private _cardConfig?: LovelaceCardConfig;
|
||||||
|
|
||||||
@property() private _newCard?: boolean;
|
@property() private _saving: boolean = false;
|
||||||
|
@property() private _error?: string;
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this._cardPicked = this._cardPicked.bind(this);
|
|
||||||
this._cancel = this._cancel.bind(this);
|
|
||||||
this._save = this._save.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async showDialog(params: EditCardDialogParams): Promise<void> {
|
public async showDialog(params: EditCardDialogParams): Promise<void> {
|
||||||
this._params = params;
|
this._params = params;
|
||||||
const [view, card] = params.path;
|
const [view, card] = params.path;
|
||||||
this._newCard = card !== undefined ? false : true;
|
|
||||||
this._cardConfig =
|
this._cardConfig =
|
||||||
card !== undefined
|
card !== undefined
|
||||||
? params.lovelace.config.views[view].cards![card]
|
? params.lovelace.config.views[view].cards![card]
|
||||||
: undefined;
|
: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private get _cardEditorEl(): HuiCardEditor | null {
|
||||||
|
return this.shadowRoot!.querySelector("hui-card-editor");
|
||||||
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
if (!this._params) {
|
if (!this._params) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
if (!this._cardConfig) {
|
|
||||||
// Card picker
|
|
||||||
return html`
|
|
||||||
<hui-dialog-pick-card
|
|
||||||
.hass="${this.hass}"
|
|
||||||
.cardPicked="${this._cardPicked}"
|
|
||||||
.closeDialog="${this._cancel}"
|
|
||||||
></hui-dialog-pick-card>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
return html`
|
return html`
|
||||||
<hui-edit-card
|
<ha-paper-dialog with-backdrop opened modal>
|
||||||
.hass="${this.hass}"
|
<h2>
|
||||||
.lovelace="${this._params.lovelace}"
|
${this.hass!.localize("ui.panel.lovelace.editor.edit_card.header")}
|
||||||
.cardConfig="${this._cardConfig}"
|
</h2>
|
||||||
.closeDialog="${this._cancel}"
|
<paper-dialog-scrollable>
|
||||||
.saveCard="${this._save}"
|
${this._cardConfig === undefined
|
||||||
.newCard="${this._newCard}"
|
? html`
|
||||||
>
|
<hui-card-picker
|
||||||
</hui-edit-card>
|
.hass="${this.hass}"
|
||||||
|
@config-changed="${this._handleConfigChanged}"
|
||||||
|
></hui-card-picker>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<div class="content">
|
||||||
|
<div class="element-editor">
|
||||||
|
<hui-card-editor
|
||||||
|
.hass="${this.hass}"
|
||||||
|
.value="${this._cardConfig}"
|
||||||
|
@config-changed="${this._handleConfigChanged}"
|
||||||
|
></hui-card-editor>
|
||||||
|
</div>
|
||||||
|
<div class="element-preview">
|
||||||
|
<hui-card-preview
|
||||||
|
.hass="${this.hass}"
|
||||||
|
.config="${this._cardConfig}"
|
||||||
|
class=${this._error ? "blur" : ""}
|
||||||
|
></hui-card-preview>
|
||||||
|
${this._error
|
||||||
|
? html`
|
||||||
|
<paper-spinner
|
||||||
|
active
|
||||||
|
alt="Can't update card"
|
||||||
|
></paper-spinner>
|
||||||
|
`
|
||||||
|
: ``}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`}
|
||||||
|
</paper-dialog-scrollable>
|
||||||
|
<div class="paper-dialog-buttons">
|
||||||
|
<mwc-button @click="${this._close}">
|
||||||
|
${this.hass!.localize("ui.common.cancel")}
|
||||||
|
</mwc-button>
|
||||||
|
<mwc-button
|
||||||
|
?disabled="${!this._canSave || this._saving}"
|
||||||
|
@click="${this._save}"
|
||||||
|
>
|
||||||
|
${this._saving
|
||||||
|
? html`
|
||||||
|
<paper-spinner active alt="Saving"></paper-spinner>
|
||||||
|
`
|
||||||
|
: this.hass!.localize("ui.common.save")}
|
||||||
|
</mwc-button>
|
||||||
|
</div>
|
||||||
|
</ha-paper-dialog>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _cardPicked(cardConf: LovelaceCardConfig): void {
|
static get styles(): CSSResultArray {
|
||||||
this._cardConfig = cardConf;
|
return [
|
||||||
|
haStyleDialog,
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
--code-mirror-max-height: calc(100vh - 176px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||||
|
/* overrule the ha-style-dialog max-height on small screens */
|
||||||
|
ha-paper-dialog {
|
||||||
|
max-height: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media all and (min-width: 660px) {
|
||||||
|
ha-paper-dialog {
|
||||||
|
width: 845px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-paper-dialog {
|
||||||
|
max-width: 845px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 0 -10px;
|
||||||
|
}
|
||||||
|
.content hui-card-preview {
|
||||||
|
margin: 4px auto;
|
||||||
|
max-width: 390px;
|
||||||
|
}
|
||||||
|
.content .element-editor {
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
ha-paper-dialog {
|
||||||
|
max-width: none;
|
||||||
|
width: 1000px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
.content > * {
|
||||||
|
flex-basis: 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.content hui-card-preview {
|
||||||
|
padding: 8px 0;
|
||||||
|
margin: auto 10px;
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mwc-button paper-spinner {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.element-editor {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.blur {
|
||||||
|
filter: blur(2px) grayscale(100%);
|
||||||
|
}
|
||||||
|
.element-preview {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.element-preview paper-spinner {
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
hui-card-preview {
|
||||||
|
padding-top: 8px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private _cancel(): void {
|
private _handleConfigChanged(ev) {
|
||||||
|
this._cardConfig = ev.detail.config;
|
||||||
|
this._error = ev.detail.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _close(): void {
|
||||||
this._params = undefined;
|
this._params = undefined;
|
||||||
this._cardConfig = undefined;
|
this._cardConfig = undefined;
|
||||||
|
this._error = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _save(cardConf: LovelaceCardConfig): Promise<void> {
|
private get _canSave(): boolean {
|
||||||
|
if (this._saving) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this._cardConfig === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this._cardEditorEl && this._cardEditorEl.hasError) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _save(): Promise<void> {
|
||||||
const lovelace = this._params!.lovelace;
|
const lovelace = this._params!.lovelace;
|
||||||
|
this._saving = true;
|
||||||
await lovelace.saveConfig(
|
await lovelace.saveConfig(
|
||||||
this._params!.path.length === 1
|
this._params!.path.length === 1
|
||||||
? addCard(lovelace.config, this._params!.path as [number], cardConf)
|
? addCard(
|
||||||
|
lovelace.config,
|
||||||
|
this._params!.path as [number],
|
||||||
|
this._cardConfig!
|
||||||
|
)
|
||||||
: replaceCard(
|
: replaceCard(
|
||||||
lovelace.config,
|
lovelace.config,
|
||||||
this._params!.path as [number, number],
|
this._params!.path as [number, number],
|
||||||
cardConf
|
this._cardConfig!
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
this._saving = false;
|
||||||
|
this._close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,88 +0,0 @@
|
|||||||
import {
|
|
||||||
html,
|
|
||||||
css,
|
|
||||||
LitElement,
|
|
||||||
TemplateResult,
|
|
||||||
CSSResult,
|
|
||||||
customElement,
|
|
||||||
} from "lit-element";
|
|
||||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
|
||||||
|
|
||||||
import "../../../../components/dialog/ha-paper-dialog";
|
|
||||||
|
|
||||||
import { haStyleDialog } from "../../../../resources/styles";
|
|
||||||
|
|
||||||
import "./hui-card-picker";
|
|
||||||
import { HomeAssistant } from "../../../../types";
|
|
||||||
import { LovelaceCardConfig } from "../../../../data/lovelace";
|
|
||||||
|
|
||||||
@customElement("hui-dialog-pick-card")
|
|
||||||
export class HuiDialogPickCard extends LitElement {
|
|
||||||
public hass?: HomeAssistant;
|
|
||||||
public cardPicked?: (cardConf: LovelaceCardConfig) => void;
|
|
||||||
public closeDialog?: () => void;
|
|
||||||
|
|
||||||
protected render(): TemplateResult | void {
|
|
||||||
return html`
|
|
||||||
<ha-paper-dialog
|
|
||||||
with-backdrop
|
|
||||||
opened
|
|
||||||
@opened-changed="${this._openedChanged}"
|
|
||||||
>
|
|
||||||
<h2>
|
|
||||||
${this.hass!.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">
|
|
||||||
<mwc-button @click="${this._skipPick}">MANUAL CARD</mwc-button>
|
|
||||||
</div>
|
|
||||||
</ha-paper-dialog>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _openedChanged(ev): void {
|
|
||||||
if (!ev.detail.value) {
|
|
||||||
this.closeDialog!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _skipPick() {
|
|
||||||
this.cardPicked!({ type: "" });
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
|
||||||
return [
|
|
||||||
haStyleDialog,
|
|
||||||
css`
|
|
||||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
|
||||||
/* overrule the ha-style-dialog max-height on small screens */
|
|
||||||
ha-paper-dialog {
|
|
||||||
max-height: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media all and (min-width: 660px) {
|
|
||||||
ha-paper-dialog {
|
|
||||||
width: 650px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-paper-dialog {
|
|
||||||
max-width: 650px;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"hui-dialog-pick-card": HuiDialogPickCard;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,493 +0,0 @@
|
|||||||
import {
|
|
||||||
html,
|
|
||||||
css,
|
|
||||||
LitElement,
|
|
||||||
PropertyValues,
|
|
||||||
TemplateResult,
|
|
||||||
CSSResult,
|
|
||||||
customElement,
|
|
||||||
property,
|
|
||||||
} from "lit-element";
|
|
||||||
import { classMap } from "lit-html/directives/class-map";
|
|
||||||
import yaml from "js-yaml";
|
|
||||||
|
|
||||||
import { haStyleDialog } from "../../../../resources/styles";
|
|
||||||
|
|
||||||
import "@polymer/paper-spinner/paper-spinner";
|
|
||||||
import "@polymer/paper-dialog/paper-dialog";
|
|
||||||
import "../../../../components/dialog/ha-paper-dialog";
|
|
||||||
// This is not a duplicate import, one is for types, one is for element.
|
|
||||||
// tslint:disable-next-line
|
|
||||||
import { HaPaperDialog } from "../../../../components/dialog/ha-paper-dialog";
|
|
||||||
import "@material/mwc-button";
|
|
||||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
|
||||||
import { HomeAssistant } from "../../../../types";
|
|
||||||
import { LovelaceCardConfig } from "../../../../data/lovelace";
|
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
|
||||||
|
|
||||||
import "../../components/hui-yaml-editor";
|
|
||||||
// This is not a duplicate import, one is for types, one is for element.
|
|
||||||
// tslint:disable-next-line
|
|
||||||
import { HuiYamlEditor } from "../../components/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, Lovelace } from "../../types";
|
|
||||||
import { ConfigError } from "../types";
|
|
||||||
import { EntityConfig } from "../../entity-rows/types";
|
|
||||||
import { getCardElementTag } from "../../common/get-card-element-tag";
|
|
||||||
import { afterNextRender } from "../../../../common/util/render-status";
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HASSDomEvents {
|
|
||||||
"entities-changed": {
|
|
||||||
entities: EntityConfig[];
|
|
||||||
};
|
|
||||||
"config-changed": {
|
|
||||||
config: LovelaceCardConfig;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@customElement("hui-edit-card")
|
|
||||||
export class HuiEditCard extends LitElement {
|
|
||||||
@property() public hass?: HomeAssistant;
|
|
||||||
|
|
||||||
@property() public cardConfig?: LovelaceCardConfig;
|
|
||||||
|
|
||||||
public lovelace?: Lovelace;
|
|
||||||
|
|
||||||
public closeDialog?: () => void;
|
|
||||||
|
|
||||||
public saveCard?: (cardConf: LovelaceCardConfig) => Promise<void>;
|
|
||||||
|
|
||||||
public newCard?: boolean;
|
|
||||||
|
|
||||||
@property() private _configElement?: LovelaceCardEditor | null;
|
|
||||||
|
|
||||||
@property() private _uiEditor?: boolean;
|
|
||||||
|
|
||||||
@property() private _cardConfig?: LovelaceCardConfig;
|
|
||||||
|
|
||||||
@property() private _configState?: string;
|
|
||||||
|
|
||||||
@property() private _loading?: boolean;
|
|
||||||
|
|
||||||
@property() private _saving: boolean;
|
|
||||||
|
|
||||||
@property() private _errorMsg?: TemplateResult;
|
|
||||||
|
|
||||||
private get _dialog(): HaPaperDialog {
|
|
||||||
return this.shadowRoot!.querySelector("ha-paper-dialog")!;
|
|
||||||
}
|
|
||||||
|
|
||||||
private get _previewEl(): HuiCardPreview {
|
|
||||||
return this.shadowRoot!.querySelector("hui-card-preview")!;
|
|
||||||
}
|
|
||||||
|
|
||||||
// tslint:disable-next-line
|
|
||||||
private __cardYaml: string | undefined;
|
|
||||||
|
|
||||||
private get _cardYaml(): string | undefined {
|
|
||||||
if (!this.__cardYaml) {
|
|
||||||
this.__cardYaml = yaml.safeDump(this._cardConfig);
|
|
||||||
}
|
|
||||||
return this.__cardYaml;
|
|
||||||
}
|
|
||||||
|
|
||||||
private set _cardYaml(yml: string | undefined) {
|
|
||||||
this.__cardYaml = yml;
|
|
||||||
}
|
|
||||||
|
|
||||||
public constructor() {
|
|
||||||
super();
|
|
||||||
this._saving = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected updated(changedProperties: PropertyValues): void {
|
|
||||||
super.updated(changedProperties);
|
|
||||||
|
|
||||||
if (!changedProperties.has("cardConfig")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._cardConfig = undefined;
|
|
||||||
this._cardYaml = undefined;
|
|
||||||
this._configState = "OK";
|
|
||||||
this._uiEditor = true;
|
|
||||||
this._errorMsg = undefined;
|
|
||||||
this._configElement = undefined;
|
|
||||||
|
|
||||||
this._loading = true;
|
|
||||||
this._loadConfigElement(this.cardConfig!);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult | void {
|
|
||||||
let content;
|
|
||||||
let preview;
|
|
||||||
if (this._configElement !== undefined) {
|
|
||||||
content = html`
|
|
||||||
<div class="element-editor">
|
|
||||||
${this._uiEditor
|
|
||||||
? this._configElement
|
|
||||||
: html`
|
|
||||||
<hui-yaml-editor
|
|
||||||
.hass="${this.hass}"
|
|
||||||
.value="${this._cardYaml}"
|
|
||||||
@yaml-changed="${this._handleYamlChanged}"
|
|
||||||
@yaml-save="${this._save}"
|
|
||||||
></hui-yaml-editor>
|
|
||||||
`}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
preview = html`
|
|
||||||
<hui-card-preview .hass="${this.hass}"> </hui-card-preview>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<ha-paper-dialog
|
|
||||||
with-backdrop
|
|
||||||
opened
|
|
||||||
modal
|
|
||||||
@opened-changed="${this._openedChanged}"
|
|
||||||
>
|
|
||||||
<h2>
|
|
||||||
${this.hass!.localize("ui.panel.lovelace.editor.edit_card.header")}
|
|
||||||
</h2>
|
|
||||||
<paper-spinner
|
|
||||||
?active="${this._loading}"
|
|
||||||
alt="Loading"
|
|
||||||
class="center margin-bot"
|
|
||||||
></paper-spinner>
|
|
||||||
<paper-dialog-scrollable
|
|
||||||
class="${classMap({ hidden: this._loading! })}"
|
|
||||||
>
|
|
||||||
${this._errorMsg
|
|
||||||
? html`
|
|
||||||
<div class="error">${this._errorMsg}</div>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
<div class="content">${content}${preview}</div>
|
|
||||||
</paper-dialog-scrollable>
|
|
||||||
${!this._loading
|
|
||||||
? html`
|
|
||||||
<div class="paper-dialog-buttons">
|
|
||||||
<mwc-button
|
|
||||||
class="toggle-button"
|
|
||||||
?disabled="${this._configElement === null ||
|
|
||||||
this._configState !== "OK"}"
|
|
||||||
@click="${this._toggleEditor}"
|
|
||||||
>${this.hass!.localize(
|
|
||||||
"ui.panel.lovelace.editor.edit_card.toggle_editor"
|
|
||||||
)}</mwc-button
|
|
||||||
>
|
|
||||||
<mwc-button @click="${this.closeDialog}"
|
|
||||||
>${this.hass!.localize("ui.common.cancel")}</mwc-button
|
|
||||||
>
|
|
||||||
<mwc-button
|
|
||||||
?disabled="${this._saving || this._configState !== "OK"}"
|
|
||||||
@click="${this._save}"
|
|
||||||
>
|
|
||||||
<paper-spinner
|
|
||||||
?active="${this._saving}"
|
|
||||||
alt="Saving"
|
|
||||||
></paper-spinner>
|
|
||||||
${this.hass!.localize("ui.common.save")}
|
|
||||||
</mwc-button>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
</ha-paper-dialog>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _loadedDialog(): Promise<void> {
|
|
||||||
await this.updateComplete;
|
|
||||||
this._loading = false;
|
|
||||||
this._resizeDialog();
|
|
||||||
if (!this._uiEditor) {
|
|
||||||
afterNextRender(() => {
|
|
||||||
this.yamlEditor.codemirror.refresh();
|
|
||||||
this._resizeDialog();
|
|
||||||
this.yamlEditor.codemirror.focus();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _resizeDialog(): Promise<void> {
|
|
||||||
await this.updateComplete;
|
|
||||||
fireEvent(this._dialog as HTMLElement, "iron-resize");
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _save(): Promise<void> {
|
|
||||||
if (!this._isConfigValid()) {
|
|
||||||
alert("Your config is not valid, please fix your config before saving.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this._isConfigChanged()) {
|
|
||||||
this.closeDialog!();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._saving = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.saveCard!(this._cardConfig!);
|
|
||||||
this._cardYaml = undefined;
|
|
||||||
this.closeDialog!();
|
|
||||||
} catch (err) {
|
|
||||||
alert(`Saving failed: ${err.message}`);
|
|
||||||
} finally {
|
|
||||||
this._saving = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _handleYamlChanged(ev: CustomEvent): void {
|
|
||||||
try {
|
|
||||||
this._cardConfig = yaml.safeLoad(ev.detail.value);
|
|
||||||
this._updatePreview(this._cardConfig!);
|
|
||||||
this._configState = "OK";
|
|
||||||
} catch (err) {
|
|
||||||
this._configState = "YAML_ERROR";
|
|
||||||
this._setPreviewError({
|
|
||||||
type: "YAML Error",
|
|
||||||
message: err,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _handleUIConfigChanged(value: LovelaceCardConfig): void {
|
|
||||||
this._cardConfig = value;
|
|
||||||
this._updatePreview(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _updatePreview(config: LovelaceCardConfig): Promise<void> {
|
|
||||||
await this.updateComplete;
|
|
||||||
|
|
||||||
if (!this._previewEl) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._previewEl.config = config;
|
|
||||||
|
|
||||||
if (this._loading) {
|
|
||||||
this._loadedDialog();
|
|
||||||
} else {
|
|
||||||
this._resizeDialog();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _setPreviewError(error: ConfigError): void {
|
|
||||||
if (!this._previewEl) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._previewEl.error = error;
|
|
||||||
|
|
||||||
this._resizeDialog();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _toggleEditor(): Promise<void> {
|
|
||||||
this._cardYaml = undefined;
|
|
||||||
if (this._uiEditor) {
|
|
||||||
this._uiEditor = false;
|
|
||||||
} else if (this._configElement) {
|
|
||||||
const success = await this._loadConfigElement(this._cardConfig!);
|
|
||||||
if (!success) {
|
|
||||||
this._loadedDialog();
|
|
||||||
} else {
|
|
||||||
this._uiEditor = true;
|
|
||||||
this._configElement.setConfig(this._cardConfig!);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this._resizeDialog();
|
|
||||||
}
|
|
||||||
|
|
||||||
private _isConfigValid(): boolean {
|
|
||||||
if (!this._cardConfig) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (this._configState === "OK") {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _isConfigChanged(): boolean {
|
|
||||||
if (this.newCard) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return JSON.stringify(this._cardConfig) !== JSON.stringify(this.cardConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _loadConfigElement(conf: LovelaceCardConfig): Promise<boolean> {
|
|
||||||
if (!conf) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._errorMsg = undefined;
|
|
||||||
this._loading = true;
|
|
||||||
this._configElement = undefined;
|
|
||||||
|
|
||||||
const tag = getCardElementTag(conf.type);
|
|
||||||
|
|
||||||
const elClass = customElements.get(tag);
|
|
||||||
let configElement;
|
|
||||||
|
|
||||||
this._cardConfig = conf;
|
|
||||||
|
|
||||||
if (elClass && elClass.getConfigElement) {
|
|
||||||
configElement = await elClass.getConfigElement();
|
|
||||||
} else {
|
|
||||||
this._updatePreview(conf);
|
|
||||||
this._uiEditor = false;
|
|
||||||
this._configElement = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
configElement.setConfig(conf);
|
|
||||||
} catch (err) {
|
|
||||||
this._errorMsg = html`
|
|
||||||
Your config is not supported by the UI editor:<br /><b>${err.message}</b
|
|
||||||
><br />Falling back to YAML editor.
|
|
||||||
`;
|
|
||||||
this._updatePreview(conf);
|
|
||||||
this._uiEditor = false;
|
|
||||||
this._configElement = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
configElement.hass = this.hass;
|
|
||||||
configElement.addEventListener("config-changed", (ev) =>
|
|
||||||
this._handleUIConfigChanged(ev.detail.config)
|
|
||||||
);
|
|
||||||
this._configElement = configElement;
|
|
||||||
await this.updateComplete;
|
|
||||||
this._updatePreview(conf);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _openedChanged(ev): void {
|
|
||||||
if (!ev.detail.value) {
|
|
||||||
this.closeDialog!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private get yamlEditor(): HuiYamlEditor {
|
|
||||||
return this.shadowRoot!.querySelector("hui-yaml-editor")!;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
|
||||||
return [
|
|
||||||
haStyleDialog,
|
|
||||||
css`
|
|
||||||
:host {
|
|
||||||
--code-mirror-max-height: calc(100vh - 176px);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
|
||||||
/* overrule the ha-style-dialog max-height on small screens */
|
|
||||||
ha-paper-dialog {
|
|
||||||
max-height: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media all and (min-width: 660px) {
|
|
||||||
ha-paper-dialog {
|
|
||||||
width: 845px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-paper-dialog {
|
|
||||||
max-width: 845px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.center {
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
margin: 0 -10px;
|
|
||||||
}
|
|
||||||
.content hui-card-preview {
|
|
||||||
margin-top: 16px;
|
|
||||||
margin: 0 auto;
|
|
||||||
max-width: 390px;
|
|
||||||
}
|
|
||||||
.content .element-editor {
|
|
||||||
margin: 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1200px) {
|
|
||||||
ha-paper-dialog {
|
|
||||||
max-width: none;
|
|
||||||
width: 1000px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
.content > * {
|
|
||||||
flex-basis: 0;
|
|
||||||
flex-grow: 1;
|
|
||||||
flex-shrink: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
.content hui-card-preview {
|
|
||||||
padding-top: 0;
|
|
||||||
margin: 0 10px;
|
|
||||||
max-width: 490px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.margin-bot {
|
|
||||||
margin-bottom: 24px;
|
|
||||||
}
|
|
||||||
mwc-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: 8px;
|
|
||||||
}
|
|
||||||
.error {
|
|
||||||
color: #ef5350;
|
|
||||||
border-bottom: 1px solid #ef5350;
|
|
||||||
}
|
|
||||||
hui-card-preview {
|
|
||||||
padding-top: 8px;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.toggle-button {
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"hui-edit-card": HuiEditCard;
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user