diff --git a/src/data/lovelace.ts b/src/data/lovelace.ts index 054a79ce26..d3ea76c430 100644 --- a/src/data/lovelace.ts +++ b/src/data/lovelace.ts @@ -1,22 +1,23 @@ import { HomeAssistant } from "../types"; export interface LovelaceConfig { - _frontendAuto: boolean; title?: string; views: LovelaceViewConfig[]; } export interface LovelaceViewConfig { + index?: number; title?: string; badges?: string[]; cards?: LovelaceCardConfig[]; - id?: string; + path?: string; icon?: string; theme?: string; } export interface LovelaceCardConfig { - id?: string; + index?: number; + view_index?: number; type: string; [key: string]: any; } @@ -60,95 +61,11 @@ export const fetchConfig = ( force, }); -export const migrateConfig = (hass: HomeAssistant): Promise => - hass.callWS({ - type: "lovelace/config/migrate", - }); - export const saveConfig = ( hass: HomeAssistant, - config: LovelaceConfig | string, - format: "json" | "yaml" + config: LovelaceConfig ): Promise => hass.callWS({ type: "lovelace/config/save", config, - format, - }); - -export const getCardConfig = ( - hass: HomeAssistant, - cardId: string -): Promise => - hass.callWS({ - type: "lovelace/config/card/get", - card_id: cardId, - }); - -export const updateCardConfig = ( - hass: HomeAssistant, - cardId: string, - config: LovelaceCardConfig | string, - format: "json" | "yaml" -): Promise => - hass.callWS({ - type: "lovelace/config/card/update", - card_id: cardId, - card_config: config, - format, - }); - -export const deleteCard = ( - hass: HomeAssistant, - cardId: string -): Promise => - hass.callWS({ - type: "lovelace/config/card/delete", - card_id: cardId, - }); - -export const addCard = ( - hass: HomeAssistant, - viewId: string, - config: LovelaceCardConfig | string, - format: "json" | "yaml" -): Promise => - hass.callWS({ - type: "lovelace/config/card/add", - view_id: viewId, - card_config: config, - format, - }); - -export const updateViewConfig = ( - hass: HomeAssistant, - viewId: string, - config: LovelaceViewConfig | string, - format: "json" | "yaml" -): Promise => - hass.callWS({ - type: "lovelace/config/view/update", - view_id: viewId, - view_config: config, - format, - }); - -export const deleteView = ( - hass: HomeAssistant, - viewId: string -): Promise => - hass.callWS({ - type: "lovelace/config/view/delete", - view_id: viewId, - }); - -export const addView = ( - hass: HomeAssistant, - config: LovelaceViewConfig | string, - format: "json" | "yaml" -): Promise => - hass.callWS({ - type: "lovelace/config/view/add", - view_config: config, - format, }); diff --git a/src/panels/lovelace/cards/hui-light-card.ts b/src/panels/lovelace/cards/hui-light-card.ts index b161d8bb59..4dbdbc40c1 100644 --- a/src/panels/lovelace/cards/hui-light-card.ts +++ b/src/panels/lovelace/cards/hui-light-card.ts @@ -134,8 +134,14 @@ export class HuiLightCard extends hassLocalizeLitMixin(LitElement) this._roundSliderStyle = loaded.roundSliderStyle; this._jQuery = loaded.jQuery; - const brightness = this.hass!.states[this._config!.entity].attributes - .brightness; + const stateObj = this.hass!.states[this._config!.entity] as LightEntity; + + if (!stateObj) { + return; + } + + const brightness = stateObj.attributes.brightness || 0; + this._jQuery("#light", this.shadowRoot).roundSlider({ ...lightConfig, change: (value) => this._setBrightness(value), @@ -152,7 +158,13 @@ export class HuiLightCard extends hassLocalizeLitMixin(LitElement) return; } - const attrs = this.hass!.states[this._config!.entity].attributes; + const stateObj = this.hass!.states[this._config!.entity]; + + if (!stateObj) { + return; + } + + const attrs = stateObj.attributes; this._jQuery("#light", this.shadowRoot).roundSlider({ value: Math.round((attrs.brightness / 254) * 100) || 0, diff --git a/src/panels/lovelace/common/generate-lovelace-config.ts b/src/panels/lovelace/common/generate-lovelace-config.ts index 1be6e0bfba..448f4507f3 100644 --- a/src/panels/lovelace/common/generate-lovelace-config.ts +++ b/src/panels/lovelace/common/generate-lovelace-config.ts @@ -94,7 +94,7 @@ const computeDefaultViewStates = (hass: HomeAssistant): HassEntities => { const generateViewConfig = ( localize: LocalizeFunc, - id: string, + path: string, title: string | undefined, icon: string | undefined, entities: HassEntities, @@ -158,7 +158,7 @@ const generateViewConfig = ( }); return { - id, + path, title, icon, badges, @@ -228,7 +228,6 @@ export const generateLovelaceConfig = ( } return { - _frontendAuto: true, title, views, }; diff --git a/src/panels/lovelace/components/hui-card-options.ts b/src/panels/lovelace/components/hui-card-options.ts index 13a5ed290a..a82af564df 100644 --- a/src/panels/lovelace/components/hui-card-options.ts +++ b/src/panels/lovelace/components/hui-card-options.ts @@ -1,32 +1,22 @@ import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import "@polymer/paper-button/paper-button"; import "@polymer/paper-icon-button/paper-icon-button"; -import { fireEvent } from "../../../common/dom/fire_event"; -import { showEditCardDialog } from "../editor/show-edit-card-dialog"; +import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog"; import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; import { confDeleteCard } from "../editor/delete-card"; import { HomeAssistant } from "../../../types"; import { LovelaceCardConfig } from "../../../data/lovelace"; - -declare global { - // for fire event - interface HASSDomEvents { - "show-edit-card": { - cardConfig?: LovelaceCardConfig; - viewId?: string | number; - add: boolean; - reloadLovelace: () => void; - }; - } -} +import { Lovelace } from "../types"; export class HuiCardOptions extends hassLocalizeLitMixin(LitElement) { public cardConfig?: LovelaceCardConfig; protected hass?: HomeAssistant; + protected lovelace?: Lovelace; + protected path?: [number, number]; static get properties(): PropertyDeclarations { - return { hass: {} }; + return { hass: {}, lovelace: {}, path: {} }; } protected render() { @@ -66,26 +56,13 @@ export class HuiCardOptions extends hassLocalizeLitMixin(LitElement) { `; } private _editCard(): void { - if (!this.cardConfig) { - return; - } showEditCardDialog(this, { - cardConfig: this.cardConfig, - add: false, - reloadLovelace: () => fireEvent(this, "config-refresh"), + lovelace: this.lovelace!, + path: this.path!, }); } private _deleteCard(): void { - if (!this.cardConfig) { - return; - } - if (!this.cardConfig.id) { - this._editCard(); - return; - } - confDeleteCard(this.hass!, this.cardConfig.id, () => - fireEvent(this, "config-refresh") - ); + confDeleteCard(this.lovelace!, this.path!); } } diff --git a/src/panels/lovelace/editor/hui-card-picker.ts b/src/panels/lovelace/editor/card-editor/hui-card-picker.ts similarity index 80% rename from src/panels/lovelace/editor/hui-card-picker.ts rename to src/panels/lovelace/editor/card-editor/hui-card-picker.ts index c2e339aa45..c9f06aaac6 100644 --- a/src/panels/lovelace/editor/hui-card-picker.ts +++ b/src/panels/lovelace/editor/card-editor/hui-card-picker.ts @@ -2,22 +2,11 @@ import { html, LitElement } from "@polymer/lit-element"; import { TemplateResult } from "lit-html"; import "@polymer/paper-button/paper-button"; -import { HomeAssistant } from "../../../types"; -import { fireEvent } from "../../../common/dom/fire_event"; -import { LovelaceCardConfig } from "../../../data/lovelace"; -import { getCardElementTag } from "../common/get-card-element-tag"; -import { CardPickTarget } from "./types"; -import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; - -import { uid } from "../../../common/util/uid"; - -declare global { - interface HASSDomEvents { - "card-picked": { - config: LovelaceCardConfig; - }; - } -} +import { HomeAssistant } from "../../../../types"; +import { LovelaceCardConfig } from "../../../../data/lovelace"; +import { getCardElementTag } from "../../common/get-card-element-tag"; +import { CardPickTarget } from "../types"; +import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin"; const cards = [ { name: "Alarm panel", type: "alarm-panel" }, @@ -47,7 +36,8 @@ const cards = [ ]; export class HuiCardPicker extends hassLocalizeLitMixin(LitElement) { - protected hass?: HomeAssistant; + public hass?: HomeAssistant; + public cardPicked?: (cardConf: LovelaceCardConfig) => void; protected render(): TemplateResult { return html` @@ -90,16 +80,14 @@ export class HuiCardPicker extends hassLocalizeLitMixin(LitElement) { const tag = getCardElementTag(type); const elClass = customElements.get(tag); - let config: LovelaceCardConfig = { type, id: uid() }; + let config: LovelaceCardConfig = { type }; if (elClass && elClass.getStubConfig) { const cardConfig = elClass.getStubConfig(this.hass); config = { ...config, ...cardConfig }; } - fireEvent(this, "card-picked", { - config, - }); + this.cardPicked!(config); } } diff --git a/src/panels/lovelace/editor/hui-card-preview.ts b/src/panels/lovelace/editor/card-editor/hui-card-preview.ts similarity index 77% rename from src/panels/lovelace/editor/hui-card-preview.ts rename to src/panels/lovelace/editor/card-editor/hui-card-preview.ts index 08933f84f5..26552fc705 100644 --- a/src/panels/lovelace/editor/hui-card-preview.ts +++ b/src/panels/lovelace/editor/card-editor/hui-card-preview.ts @@ -1,12 +1,12 @@ import "@polymer/paper-input/paper-textarea"; -import createCardElement from "../common/create-card-element"; -import createErrorCardConfig from "../common/create-error-card-config"; -import { HomeAssistant } from "../../../types"; -import { LovelaceCardConfig } from "../../../data/lovelace"; -import { LovelaceCard } from "../types"; -import { ConfigError } from "./types"; -import { getCardElementTag } from "../common/get-card-element-tag"; +import createCardElement from "../../common/create-card-element"; +import createErrorCardConfig from "../../common/create-error-card-config"; +import { HomeAssistant } from "../../../../types"; +import { LovelaceCardConfig } from "../../../../data/lovelace"; +import { LovelaceCard } from "../../types"; +import { ConfigError } from "../types"; +import { getCardElementTag } from "../../common/get-card-element-tag"; export class HuiCardPreview extends HTMLElement { private _hass?: HomeAssistant; diff --git a/src/panels/lovelace/editor/hui-dialog-edit-card.ts b/src/panels/lovelace/editor/card-editor/hui-dialog-edit-card.ts similarity index 50% rename from src/panels/lovelace/editor/hui-dialog-edit-card.ts rename to src/panels/lovelace/editor/card-editor/hui-dialog-edit-card.ts index bdad7178d9..3046a4d5f4 100644 --- a/src/panels/lovelace/editor/hui-dialog-edit-card.ts +++ b/src/panels/lovelace/editor/card-editor/hui-dialog-edit-card.ts @@ -1,11 +1,12 @@ import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import { TemplateResult } from "lit-html"; -import { HomeAssistant } from "../../../types"; -import { HASSDomEvent } from "../../../common/dom/fire_event"; -import { LovelaceCardConfig } from "../../../data/lovelace"; +import { HomeAssistant } from "../../../../types"; +import { HASSDomEvent } from "../../../../common/dom/fire_event"; +import { LovelaceCardConfig } from "../../../../data/lovelace"; import "./hui-edit-card"; -import "./hui-migrate-config"; +import "./hui-dialog-pick-card"; +import { EditCardDialogParams } from "./show-edit-card-dialog"; declare global { // for fire event @@ -18,66 +19,67 @@ declare global { } } -export interface EditCardDialogParams { - cardConfig?: LovelaceCardConfig; - viewId?: string | number; - add: boolean; - reloadLovelace: () => void; -} - export class HuiDialogEditCard extends LitElement { protected hass?: HomeAssistant; private _params?: EditCardDialogParams; + private _cardConfig?: LovelaceCardConfig; static get properties(): PropertyDeclarations { return { hass: {}, _params: {}, + _cardConfig: {}, }; } + constructor() { + super(); + this._cardPicked = this._cardPicked.bind(this); + this._cancel = this._cancel.bind(this); + } + public async showDialog(params: EditCardDialogParams): Promise { this._params = params; - await this.updateComplete; - (this.shadowRoot!.children[0] as any).showDialog(); + this._cardConfig = + params.path.length === 2 + ? (this._cardConfig = params.lovelace.config.views[ + params.path[0] + ].cards![params.path[1]]) + : undefined; } protected render(): TemplateResult { if (!this._params) { return html``; } - if ( - (!this._params.add && - this._params.cardConfig && - !("id" in this._params.cardConfig)) || - (this._params.add && !this._params.viewId) - ) { + if (!this._cardConfig) { + // Card picker return html` - + .cardPicked="${this._cardPicked}" + > `; } return html` `; } + private _cardPicked(cardConf: LovelaceCardConfig) { + this._cardConfig = cardConf; + } + private _cancel() { - this._params = { - add: false, - reloadLovelace: () => { - return; - }, - }; + this._params = undefined; + this._cardConfig = undefined; } } diff --git a/src/panels/lovelace/editor/card-editor/hui-dialog-pick-card.ts b/src/panels/lovelace/editor/card-editor/hui-dialog-pick-card.ts new file mode 100644 index 0000000000..75553235ad --- /dev/null +++ b/src/panels/lovelace/editor/card-editor/hui-dialog-pick-card.ts @@ -0,0 +1,47 @@ +import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; +import "@polymer/paper-dialog/paper-dialog"; +import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable"; + +import "./hui-card-picker"; +import { HomeAssistant } from "../../../../types"; +import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin"; +import { LovelaceCardConfig } from "../../../../data/lovelace"; + +export class HuiDialogPickCard extends hassLocalizeLitMixin(LitElement) { + public hass?: HomeAssistant; + public cardPicked?: (cardConf: LovelaceCardConfig) => void; + + static get properties(): PropertyDeclarations { + return {}; + } + + protected render(): TemplateResult { + return html` + +

${this.localize("ui.panel.lovelace.editor.edit_card.header")}

+ + + +
+ SKIP +
+
+ `; + } + + private _skipPick() { + this.cardPicked!({ type: "" }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-dialog-pick-card": HuiDialogPickCard; + } +} + +customElements.define("hui-dialog-pick-card", HuiDialogPickCard); diff --git a/src/panels/lovelace/editor/hui-edit-card.ts b/src/panels/lovelace/editor/card-editor/hui-edit-card.ts similarity index 76% rename from src/panels/lovelace/editor/hui-edit-card.ts rename to src/panels/lovelace/editor/card-editor/hui-edit-card.ts index 5a523617c9..ef346233d3 100644 --- a/src/panels/lovelace/editor/hui-edit-card.ts +++ b/src/panels/lovelace/editor/card-editor/hui-edit-card.ts @@ -15,31 +15,22 @@ import "@polymer/paper-dialog/paper-dialog"; import { PaperDialogElement } from "@polymer/paper-dialog/paper-dialog"; import "@polymer/paper-button/paper-button"; import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable"; -import { HomeAssistant } from "../../../types"; -import { - addCard, - updateCardConfig, - LovelaceCardConfig, -} from "../../../data/lovelace"; -import { fireEvent } from "../../../common/dom/fire_event"; -import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; +import { HomeAssistant } from "../../../../types"; +import { LovelaceCardConfig } from "../../../../data/lovelace"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin"; import "./hui-yaml-editor"; -import "./hui-card-picker"; import "./hui-card-preview"; // This is not a duplicate import, one is for types, one is for element. // tslint:disable-next-line import { HuiCardPreview } from "./hui-card-preview"; -import { LovelaceCardEditor } from "../types"; -import { - YamlChangedEvent, - CardPickedEvent, - ConfigValue, - ConfigError, -} from "./types"; -import { extYamlSchema } from "./yaml-ext-schema"; -import { EntityConfig } from "../entity-rows/types"; -import { getCardElementTag } from "../common/get-card-element-tag"; +import { LovelaceCardEditor, Lovelace } from "../../types"; +import { YamlChangedEvent, ConfigValue, ConfigError } from "../types"; +import { extYamlSchema } from "../yaml-ext-schema"; +import { EntityConfig } from "../../entity-rows/types"; +import { getCardElementTag } from "../../common/get-card-element-tag"; +import { addCard, replaceCard } from "../config-util"; declare global { interface HASSDomEvents { @@ -52,17 +43,30 @@ declare global { "config-changed": { config: LovelaceCardConfig; }; - "cancel-edit-card": {}; } } export class HuiEditCard extends hassLocalizeLitMixin(LitElement) { + public hass?: HomeAssistant; + public lovelace?: Lovelace; + public path?: [number] | [number, number]; + public cardConfig?: LovelaceCardConfig; + public closeDialog?: () => void; + private _configElement?: LovelaceCardEditor | null; + private _uiEditor?: boolean; + private _configValue?: ConfigValue; + private _configState?: string; + private _loading?: boolean; + private _saving: boolean; + private _errorMsg?: TemplateResult; + private _cardType?: string; + static get properties(): PropertyDeclarations { return { hass: {}, cardConfig: {}, - viewId: {}, - _cardId: {}, + viewIndex: {}, + _cardIndex: {}, _configElement: {}, _configValue: {}, _configState: {}, @@ -81,38 +85,15 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) { return this.shadowRoot!.querySelector("hui-card-preview")!; } - public cardConfig?: LovelaceCardConfig; - public viewId?: string | number; - protected hass?: HomeAssistant; - private _cardId?: string; - private _configElement?: LovelaceCardEditor | null; - private _uiEditor?: boolean; - private _configValue?: ConfigValue; - private _configState?: string; - private _loading?: boolean; - private _saving: boolean; - private _errorMsg?: TemplateResult; - private _cardType?: string; - protected constructor() { super(); this._saving = false; } - public async showDialog(): Promise { - // Wait till dialog is rendered. - if (this._dialog == null) { - await this.updateComplete; - } - this._dialog.open(); - } - protected updated(changedProperties: PropertyValues): void { super.updated(changedProperties); - if ( - !changedProperties.has("cardConfig") && - !changedProperties.has("viewId") - ) { + + if (!changedProperties.has("cardConfig")) { return; } @@ -122,17 +103,8 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) { this._errorMsg = undefined; this._configElement = undefined; - if (this.cardConfig && String(this.cardConfig.id) !== this._cardId) { - this._loading = true; - this._cardId = String(this.cardConfig.id); - this._loadConfigElement(this.cardConfig); - } else { - this._cardId = undefined; - } - - if (this.viewId && !this.cardConfig) { - this._resizeDialog(); - } + this._loading = true; + this._loadConfigElement(this.cardConfig!); } protected render(): TemplateResult { @@ -147,7 +119,6 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) { content = html` @@ -157,18 +128,11 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
`; - } else if (this.viewId && !this.cardConfig) { - content = html` - - `; } return html` ${this.renderStyle()} - +

${this.localize("ui.panel.lovelace.editor.edit_card.header")}

- ${this.localize("ui.common.cancel")} { await this.updateComplete; this._loading = false; @@ -292,58 +247,42 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) { fireEvent(this._dialog, "iron-resize"); } - private _closeDialog(): void { - this.cardConfig = undefined; - this.viewId = undefined; - fireEvent(this, "cancel-edit-card"); - this._dialog.close(); - } - - private async _updateConfigInBackend(): Promise { + private async _save(): Promise { if (!this._isConfigValid()) { alert("Your config is not valid, please fix your config before saving."); - this._saveDone(); return; } if (!this._isConfigChanged()) { - this._closeDialog(); - this._saveDone(); + this.closeDialog!(); return; } + this._saving = true; + + const cardConf: LovelaceCardConfig = + this._configValue!.format === "yaml" + ? yaml.safeLoad(this._configValue!.value!, { + schema: extYamlSchema, + }) + : this._configValue!.value!; + try { - if (this.viewId) { - await addCard( - this.hass!, - String(this.viewId), - this._configValue!.value!, - this._configValue!.format - ); - } else { - await updateCardConfig( - this.hass!, - this._cardId!, - this._configValue!.value!, - this._configValue!.format - ); - } - fireEvent(this, "reload-lovelace"); - this._closeDialog(); - this._saveDone(); + const lovelace = this.lovelace!; + await lovelace.saveConfig( + this._creatingCard + ? addCard(lovelace.config, this.path as [number], cardConf) + : replaceCard( + lovelace.config, + this.path as [number, number], + cardConf + ) + ); + this.closeDialog!(); } catch (err) { alert(`Saving failed: ${err.message}`); - this._saveDone(); - } - } - - private async _handleCardPicked(ev: CardPickedEvent): Promise { - const succes = await this._loadConfigElement(ev.detail.config); - if (!succes) { - this._configValue = { - format: "yaml", - value: yaml.safeDump(ev.detail.config), - }; + } finally { + this._saving = false; } } @@ -394,14 +333,10 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) { private async _toggleEditor(): Promise { if (this._uiEditor && this._configValue!.format === "json") { - if (this._isConfigChanged()) { - this._configValue = { - format: "yaml", - value: yaml.safeDump(this._configValue!.value), - }; - } else { - this._configValue = { format: "yaml", value: undefined }; - } + this._configValue = { + format: "yaml", + value: yaml.safeDump(this._configValue!.value), + }; this._uiEditor = !this._uiEditor; } else if (this._configElement && this._configValue!.format === "yaml") { const yamlConfig = this._configValue!.value; @@ -438,12 +373,12 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) { } private _isConfigChanged(): boolean { - if (this.viewId) { + if (this._creatingCard) { return true; } const configValue = this._configValue!.format === "yaml" - ? yaml.safeDump(this._configValue!.value) + ? yaml.safeLoad(this._configValue!.value) : this._configValue!.value; return JSON.stringify(configValue) !== JSON.stringify(this.cardConfig); } @@ -465,6 +400,7 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) { if (elClass && elClass.getConfigElement) { configElement = await elClass.getConfigElement(); } else { + this._configValue = { format: "yaml", value: yaml.safeDump(conf) }; this._uiEditor = false; this._configElement = null; return false; @@ -477,6 +413,10 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) { Your config is not supported by the UI editor:
${err.message}
Falling back to YAML editor. `; + this._configValue = { + format: "yaml", + value: yaml.safeDump(conf), + }; this._uiEditor = false; this._configElement = null; return false; @@ -492,6 +432,10 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) { this._updatePreview(conf); return true; } + + private get _creatingCard(): boolean { + return this.path!.length === 1; + } } declare global { diff --git a/src/panels/lovelace/editor/hui-yaml-editor.ts b/src/panels/lovelace/editor/card-editor/hui-yaml-editor.ts similarity index 56% rename from src/panels/lovelace/editor/hui-yaml-editor.ts rename to src/panels/lovelace/editor/card-editor/hui-yaml-editor.ts index 6dcb47b70c..906df6f121 100644 --- a/src/panels/lovelace/editor/hui-yaml-editor.ts +++ b/src/panels/lovelace/editor/card-editor/hui-yaml-editor.ts @@ -1,42 +1,29 @@ import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import { TemplateResult } from "lit-html"; import "@polymer/paper-input/paper-textarea"; -import "@polymer/paper-spinner/paper-spinner"; -import { HomeAssistant } from "../../../types"; -import { fireEvent } from "../../../common/dom/fire_event"; -import { getCardConfig } from "../../../data/lovelace"; +import { HomeAssistant } from "../../../../types"; +import { fireEvent } from "../../../../common/dom/fire_event"; export class HuiYAMLEditor extends LitElement { - public cardId?: string; protected hass?: HomeAssistant; private _yaml?: string; - private _loading?: boolean; static get properties(): PropertyDeclarations { - return { _yaml: {}, cardId: {} }; + return { _yaml: {} }; } set yaml(yaml: string) { if (yaml === undefined) { - this._loading = true; - this._loadConfig(); + return; } else { this._yaml = yaml; - if (this._loading) { - this._loading = false; - } } } protected render(): TemplateResult { return html` ${this.renderStyle()} - `; } - private async _loadConfig(): Promise { - 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; diff --git a/src/panels/lovelace/editor/show-edit-card-dialog.ts b/src/panels/lovelace/editor/card-editor/show-edit-card-dialog.ts similarity index 76% rename from src/panels/lovelace/editor/show-edit-card-dialog.ts rename to src/panels/lovelace/editor/card-editor/show-edit-card-dialog.ts index dfd8ee379a..8fb12d0f01 100644 --- a/src/panels/lovelace/editor/show-edit-card-dialog.ts +++ b/src/panels/lovelace/editor/card-editor/show-edit-card-dialog.ts @@ -1,5 +1,5 @@ -import { LovelaceCardConfig } from "../../../data/lovelace"; -import { fireEvent } from "../../../common/dom/fire_event"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { Lovelace } from "../../types"; declare global { // for fire event @@ -13,10 +13,8 @@ const dialogShowEvent = "show-edit-card"; const dialogTag = "hui-dialog-edit-card"; export interface EditCardDialogParams { - cardConfig?: LovelaceCardConfig; - viewId?: string | number; - add: boolean; - reloadLovelace: () => void; + lovelace: Lovelace; + path: [number] | [number, number]; } const registerEditCardDialog = (element: HTMLElement) => diff --git a/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts index 16614a117f..233c9e3ba5 100644 --- a/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts @@ -33,7 +33,6 @@ const entitiesConfigStruct = struct.union([ const cardConfigStruct = struct({ type: "string", - id: "string|number", title: "string|number?", theme: "string?", show_header_toggle: "boolean?", diff --git a/src/panels/lovelace/editor/config-elements/hui-glance-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-glance-card-editor.ts index b93bc827d2..dadb82f702 100644 --- a/src/panels/lovelace/editor/config-elements/hui-glance-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-glance-card-editor.ts @@ -32,7 +32,6 @@ const entitiesConfigStruct = struct.union([ const cardConfigStruct = struct({ type: "string", - id: "string|number", title: "string|number?", theme: "string?", columns: "number?", diff --git a/src/panels/lovelace/editor/config-util.ts b/src/panels/lovelace/editor/config-util.ts new file mode 100644 index 0000000000..991aec1f7c --- /dev/null +++ b/src/panels/lovelace/editor/config-util.ts @@ -0,0 +1,117 @@ +import { + LovelaceConfig, + LovelaceCardConfig, + LovelaceViewConfig, +} from "../../../data/lovelace"; + +export const addCard = ( + config: LovelaceConfig, + path: [number], + cardConfig: LovelaceCardConfig +): LovelaceConfig => { + const [viewIndex] = path; + const views: LovelaceViewConfig[] = []; + + config.views.forEach((viewConf, index) => { + if (index !== viewIndex) { + views.push(config.views[index]); + return; + } + + const cards = viewConf.cards + ? [...viewConf.cards, cardConfig] + : [cardConfig]; + + views.push({ + ...viewConf, + cards, + }); + }); + + return { + ...config, + views, + }; +}; + +export const replaceCard = ( + config: LovelaceConfig, + path: [number, number], + cardConfig: LovelaceCardConfig +): LovelaceConfig => { + const [viewIndex, cardIndex] = path; + const views: LovelaceViewConfig[] = []; + + config.views.forEach((viewConf, index) => { + if (index !== viewIndex) { + views.push(config.views[index]); + return; + } + + views.push({ + ...viewConf, + cards: (viewConf.cards || []).map((origConf, ind) => + ind === cardIndex ? cardConfig : origConf + ), + }); + }); + + return { + ...config, + views, + }; +}; + +export const deleteCard = ( + config: LovelaceConfig, + path: [number, number] +): LovelaceConfig => { + const [viewIndex, cardIndex] = path; + const views: LovelaceViewConfig[] = []; + + config.views.forEach((viewConf, index) => { + if (index !== viewIndex) { + views.push(config.views[index]); + return; + } + + views.push({ + ...viewConf, + cards: (viewConf.cards || []).filter( + (_origConf, ind) => ind !== cardIndex + ), + }); + }); + + return { + ...config, + views, + }; +}; + +export const addView = ( + config: LovelaceConfig, + viewConfig: LovelaceViewConfig +): LovelaceConfig => ({ + ...config, + views: config.views.concat(viewConfig), +}); + +export const replaceView = ( + config: LovelaceConfig, + viewIndex: number, + viewConfig: LovelaceViewConfig +): LovelaceConfig => ({ + ...config, + views: config.views.map((origView, index) => + index === viewIndex ? viewConfig : origView + ), +}); + +export const deleteView = ( + config: LovelaceConfig, + viewIndex: number +): LovelaceConfig => ({ + ...config, + views: config.views.filter((_origView, index) => index !== viewIndex), +}); diff --git a/src/panels/lovelace/editor/delete-card.ts b/src/panels/lovelace/editor/delete-card.ts index 11646605cc..c4afc7e863 100644 --- a/src/panels/lovelace/editor/delete-card.ts +++ b/src/panels/lovelace/editor/delete-card.ts @@ -1,17 +1,15 @@ -import { deleteCard } from "../../../data/lovelace"; -import { HomeAssistant } from "../../../types"; +import { Lovelace } from "../types"; +import { deleteCard } from "./config-util"; export async function confDeleteCard( - hass: HomeAssistant, - cardId: string, - reloadLovelace: () => void + lovelace: Lovelace, + path: [number, number] ): Promise { if (!confirm("Are you sure you want to delete this card?")) { return; } try { - await deleteCard(hass, String(cardId)); - reloadLovelace(); + await lovelace.saveConfig(deleteCard(lovelace.config, path)); } catch (err) { alert(`Deleting failed: ${err.message}`); } diff --git a/src/panels/lovelace/editor/delete-view.ts b/src/panels/lovelace/editor/delete-view.ts deleted file mode 100644 index 740c00ede6..0000000000 --- a/src/panels/lovelace/editor/delete-view.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { deleteView } from "../../../data/lovelace"; -import { HomeAssistant } from "../../../types"; - -export async function confDeleteView( - hass: HomeAssistant, - viewId: string, - reloadLovelace: () => void -): Promise { - if (!confirm("Are you sure you want to delete this view?")) { - return; - } - try { - await deleteView(hass, String(viewId)); - reloadLovelace(); - } catch (err) { - alert(`Deleting failed: ${err.message}`); - } -} diff --git a/src/panels/lovelace/editor/hui-dialog-save-config.ts b/src/panels/lovelace/editor/hui-dialog-save-config.ts index 9b7bc343ff..7e00454cfa 100644 --- a/src/panels/lovelace/editor/hui-dialog-save-config.ts +++ b/src/panels/lovelace/editor/hui-dialog-save-config.ts @@ -10,40 +10,8 @@ import "@polymer/paper-button/paper-button"; import { HomeAssistant } from "../../../types"; -import { - saveConfig, - migrateConfig, - LovelaceConfig, -} from "../../../data/lovelace"; -import { fireEvent } from "../../../common/dom/fire_event"; import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; - -declare global { - // for fire event - interface HASSDomEvents { - "show-save-config": SaveDialogParams; - } -} - -const dialogShowEvent = "show-save-config"; -const dialogTag = "hui-dialog-save-config"; - -export interface SaveDialogParams { - config: LovelaceConfig; - reloadLovelace: () => void; -} - -export const registerSaveDialog = (element: HTMLElement) => - fireEvent(element, "register-dialog", { - dialogShowEvent, - dialogTag, - dialogImport: () => import("./hui-dialog-save-config"), - }); - -export const showSaveDialog = ( - element: HTMLElement, - saveDialogParams: SaveDialogParams -) => fireEvent(element, dialogShowEvent, saveDialogParams); +import { SaveDialogParams } from "./show-save-config-dialog"; export class HuiSaveConfig extends hassLocalizeLitMixin(LitElement) { protected hass?: HomeAssistant; @@ -137,13 +105,11 @@ export class HuiSaveConfig extends hassLocalizeLitMixin(LitElement) { return; } this._saving = true; - delete this._params.config._frontendAuto; try { - await saveConfig(this.hass, this._params.config, "json"); - await migrateConfig(this.hass); + const lovelace = this._params!.lovelace; + await lovelace.saveConfig(lovelace.config); this._saving = false; this._closeDialog(); - this._params.reloadLovelace!(); } catch (err) { alert(`Saving failed: ${err.message}`); this._saving = false; @@ -157,4 +123,4 @@ declare global { } } -customElements.define(dialogTag, HuiSaveConfig); +customElements.define("hui-dialog-save-config", HuiSaveConfig); diff --git a/src/panels/lovelace/editor/hui-migrate-config.ts b/src/panels/lovelace/editor/hui-migrate-config.ts deleted file mode 100644 index e9f32f52e1..0000000000 --- a/src/panels/lovelace/editor/hui-migrate-config.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; -import { TemplateResult } from "lit-html"; - -import "@polymer/paper-spinner/paper-spinner"; -import "@polymer/paper-dialog/paper-dialog"; -// This is not a duplicate import, one is for types, one is for element. -// tslint:disable-next-line -import { PaperDialogElement } from "@polymer/paper-dialog/paper-dialog"; -import "@polymer/paper-button/paper-button"; -import { fireEvent } from "../../../common/dom/fire_event"; - -import { HomeAssistant } from "../../../types"; -import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; -import { migrateConfig } from "../../../data/lovelace"; - -export class HuiMigrateConfig extends hassLocalizeLitMixin(LitElement) { - protected hass?: HomeAssistant; - private _migrating?: boolean; - - static get properties(): PropertyDeclarations { - return { _hass: {}, _migrating: {} }; - } - - private get _dialog(): PaperDialogElement { - return this.shadowRoot!.querySelector("paper-dialog")!; - } - - public async showDialog(): Promise { - // Wait till dialog is rendered. - if (this._dialog == null) { - await this.updateComplete; - } - this._dialog.open(); - } - - protected render(): TemplateResult { - return html` - ${this.renderStyle()} - -

${this.localize("ui.panel.lovelace.editor.migrate.header")}

- -

${this.localize("ui.panel.lovelace.editor.migrate.para_no_id")}

-

- ${this.localize("ui.panel.lovelace.editor.migrate.para_migrate")} -

-
-
- ${this.localize("ui.common.cancel")} - - - ${ - this.localize("ui.panel.lovelace.editor.migrate.migrate") - } -
-
- `; - } - - private renderStyle(): TemplateResult { - return html` - - `; - } - - private _closeDialog(): void { - this._dialog.close(); - } - - private async _migrateConfig(): Promise { - this._migrating = true; - try { - await migrateConfig(this.hass!); - this._closeDialog(); - this._migrating = false; - fireEvent(this, "reload-lovelace"); - } catch (err) { - alert(`Migration failed: ${err.message}`); - this._migrating = false; - } - } -} - -declare global { - interface HTMLElementTagNameMap { - "hui-migrate-config": HuiMigrateConfig; - } -} - -customElements.define("hui-migrate-config", HuiMigrateConfig); diff --git a/src/panels/lovelace/editor/show-save-config-dialog.ts b/src/panels/lovelace/editor/show-save-config-dialog.ts new file mode 100644 index 0000000000..f5207c8241 --- /dev/null +++ b/src/panels/lovelace/editor/show-save-config-dialog.ts @@ -0,0 +1,33 @@ +import { fireEvent } from "../../../common/dom/fire_event"; +import { Lovelace } from "../types"; + +declare global { + // for fire event + interface HASSDomEvents { + "show-save-config": SaveDialogParams; + } +} + +const dialogShowEvent = "show-save-config"; +const dialogTag = "hui-dialog-save-config"; + +export interface SaveDialogParams { + lovelace: Lovelace; +} + +let registeredDialog = false; + +export const showSaveDialog = ( + element: HTMLElement, + saveDialogParams: SaveDialogParams +) => { + if (!registeredDialog) { + registeredDialog = true; + fireEvent(element, "register-dialog", { + dialogShowEvent, + dialogTag, + dialogImport: () => import("./hui-dialog-save-config"), + }); + } + fireEvent(element, dialogShowEvent, saveDialogParams); +}; diff --git a/src/panels/lovelace/editor/types.ts b/src/panels/lovelace/editor/types.ts index 93f66220c6..b91e381632 100644 --- a/src/panels/lovelace/editor/types.ts +++ b/src/panels/lovelace/editor/types.ts @@ -8,12 +8,6 @@ export interface YamlChangedEvent extends Event { }; } -export interface CardPickedEvent extends Event { - detail: { - config: LovelaceCardConfig; - }; -} - export interface ViewEditEvent extends Event { detail: { config: LovelaceViewConfig; diff --git a/src/panels/lovelace/editor/hui-dialog-edit-view.ts b/src/panels/lovelace/editor/view-editor/hui-dialog-edit-view.ts similarity index 67% rename from src/panels/lovelace/editor/hui-dialog-edit-view.ts rename to src/panels/lovelace/editor/view-editor/hui-dialog-edit-view.ts index 3f4d13129f..087ed20ce2 100644 --- a/src/panels/lovelace/editor/hui-dialog-edit-view.ts +++ b/src/panels/lovelace/editor/view-editor/hui-dialog-edit-view.ts @@ -1,10 +1,9 @@ import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import { TemplateResult } from "lit-html"; -import { HomeAssistant } from "../../../types"; -import { HASSDomEvent } from "../../../common/dom/fire_event"; +import { HomeAssistant } from "../../../../types"; +import { HASSDomEvent } from "../../../../common/dom/fire_event"; import "./hui-edit-view"; -import "./hui-migrate-config"; import { EditViewDialogParams } from "./show-edit-view-dialog"; declare global { @@ -39,24 +38,11 @@ export class HuiDialogEditView extends LitElement { if (!this._params) { return html``; } - if ( - !this._params.add && - this._params.viewConfig && - !("id" in this._params.viewConfig) - ) { - return html` - - `; - } return html` `; diff --git a/src/panels/lovelace/editor/hui-edit-view.ts b/src/panels/lovelace/editor/view-editor/hui-edit-view.ts similarity index 73% rename from src/panels/lovelace/editor/hui-edit-view.ts rename to src/panels/lovelace/editor/view-editor/hui-edit-view.ts index fc7c4b9f6b..733ec46a39 100644 --- a/src/panels/lovelace/editor/hui-edit-view.ts +++ b/src/panels/lovelace/editor/view-editor/hui-edit-view.ts @@ -1,9 +1,4 @@ -import { - html, - LitElement, - PropertyDeclarations, - PropertyValues, -} from "@polymer/lit-element"; +import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import { TemplateResult } from "lit-html"; import "@polymer/paper-spinner/paper-spinner"; @@ -16,40 +11,25 @@ import "@polymer/paper-icon-button/paper-icon-button.js"; import { PaperDialogElement } from "@polymer/paper-dialog/paper-dialog"; import "@polymer/paper-button/paper-button"; import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable"; -import "../components/hui-entity-editor"; -import "./config-elements/hui-view-editor"; -import { HomeAssistant } from "../../../types"; +import "../../components/hui-entity-editor"; +import "./hui-view-editor"; +import { HomeAssistant } from "../../../../types"; import { - addView, - updateViewConfig, LovelaceViewConfig, LovelaceCardConfig, -} from "../../../data/lovelace"; -import { fireEvent } from "../../../common/dom/fire_event"; -import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; -import { EntitiesEditorEvent, ViewEditEvent } from "./types"; -import { processEditorEntities } from "./process-editor-entities"; -import { EntityConfig } from "../entity-rows/types"; -import { confDeleteView } from "./delete-view"; -import { navigate } from "../../../common/navigate"; +} from "../../../../data/lovelace"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin"; +import { EntitiesEditorEvent, ViewEditEvent } from "../types"; +import { processEditorEntities } from "../process-editor-entities"; +import { EntityConfig } from "../../entity-rows/types"; +import { navigate } from "../../../../common/navigate"; +import { Lovelace } from "../../types"; +import { deleteView, addView, replaceView } from "../config-util"; export class HuiEditView extends hassLocalizeLitMixin(LitElement) { - static get properties(): PropertyDeclarations { - return { - hass: {}, - viewConfig: {}, - add: {}, - _config: {}, - _badges: {}, - _cards: {}, - _saving: {}, - _curTab: {}, - }; - } - - public viewConfig?: LovelaceViewConfig; - public add?: boolean; - public reloadLovelace?: () => {}; + public lovelace?: Lovelace; + public viewIndex?: number; protected hass?: HomeAssistant; private _config?: LovelaceViewConfig; private _badges?: EntityConfig[]; @@ -58,6 +38,19 @@ export class HuiEditView extends hassLocalizeLitMixin(LitElement) { private _curTabIndex: number; private _curTab?: string; + static get properties(): PropertyDeclarations { + return { + hass: {}, + lovelace: {}, + viewIndex: {}, + _config: {}, + _badges: {}, + _cards: {}, + _saving: {}, + _curTab: {}, + }; + } + protected constructor() { super(); this._saving = false; @@ -69,30 +62,21 @@ export class HuiEditView extends hassLocalizeLitMixin(LitElement) { if (this._dialog == null) { await this.updateComplete; } - this._dialog.open(); - } - protected updated(changedProperties: PropertyValues): void { - super.updated(changedProperties); - if (!changedProperties.has("viewConfig") && !changedProperties.has("add")) { - return; - } - if ( - this.viewConfig && - (!changedProperties.get("viewConfig") || - this.viewConfig.id !== - (changedProperties.get("viewConfig") as LovelaceViewConfig).id) - ) { - const { cards, badges, ...viewConfig } = this.viewConfig; - this._config = viewConfig; - this._badges = badges ? processEditorEntities(badges) : []; - this._cards = cards; - } else if (changedProperties.has("add")) { + if (this.viewIndex === undefined) { this._config = {}; this._badges = []; this._cards = []; + } else { + const { cards, badges, ...viewConfig } = this.lovelace!.config.views[ + this.viewIndex + ]; + this._config = viewConfig; + this._badges = badges ? processEditorEntities(badges) : []; + this._cards = cards; } - this._resizeDialog(); + + this._dialog.open(); } private get _dialog(): PaperDialogElement { @@ -142,7 +126,7 @@ export class HuiEditView extends hassLocalizeLitMixin(LitElement) { ${content}
${ - !this.add + this.viewIndex !== undefined ? html` 0) { alert( "You can't delete a view that has cards in it. Remove the cards first." ); return; } - confDeleteView(this.hass!, String(this.viewConfig!.id!), () => { + + if (!confirm("Are you sure you want to delete this view?")) { + return; + } + + try { + await this.lovelace!.saveConfig( + deleteView(this.lovelace!.config, this.viewIndex!) + ); this._closeDialog(); - this.reloadLovelace!(); navigate(this, `/lovelace/0`); - }); + } catch (err) { + alert(`Deleting failed: ${err.message}`); + } } private async _resizeDialog(): Promise { @@ -234,9 +222,9 @@ export class HuiEditView extends hassLocalizeLitMixin(LitElement) { private _closeDialog(): void { this._curTabIndex = 0; + this.lovelace = undefined; this._config = {}; this._badges = []; - this.viewConfig = undefined; this._dialog.close(); } @@ -248,39 +236,35 @@ export class HuiEditView extends hassLocalizeLitMixin(LitElement) { this._resizeDialog(); } - private async _updateConfigInBackend(): Promise { + private async _save(): Promise { if (!this._config) { return; } if (!this._isConfigChanged()) { this._closeDialog(); - this._saving = false; return; } - if (this._badges) { - this._config.badges = this._badges.map((entityConf) => { - return entityConf.entity; - }); - } + this._saving = true; + + const viewConf: LovelaceViewConfig = { + cards: this._cards, + badges: this._badges!.map((entityConf) => entityConf.entity), + ...this._config, + }; + + const lovelace = this.lovelace!; try { - if (this.add) { - this._config.cards = []; - await addView(this.hass!, this._config, "json"); - } else { - await updateViewConfig( - this.hass!, - String(this.viewConfig!.id!), - this._config, - "json" - ); - } - this.reloadLovelace!(); + await lovelace.saveConfig( + this._creatingView + ? addView(lovelace.config, viewConf) + : replaceView(lovelace.config, this.viewIndex!, viewConf) + ); this._closeDialog(); - this._saving = false; } catch (err) { alert(`Saving failed: ${err.message}`); + } finally { this._saving = false; } } @@ -299,10 +283,15 @@ export class HuiEditView extends hassLocalizeLitMixin(LitElement) { } private _isConfigChanged(): boolean { - if (!this.add) { - return true; - } - return JSON.stringify(this._config) !== JSON.stringify(this.viewConfig); + return ( + this._creatingView || + JSON.stringify(this._config) !== + JSON.stringify(this.lovelace!.config.views[this.viewIndex!]) + ); + } + + private get _creatingView(): boolean { + return this.viewIndex === undefined; } } diff --git a/src/panels/lovelace/editor/config-elements/hui-view-editor.ts b/src/panels/lovelace/editor/view-editor/hui-view-editor.ts similarity index 92% rename from src/panels/lovelace/editor/config-elements/hui-view-editor.ts rename to src/panels/lovelace/editor/view-editor/hui-view-editor.ts index 64ee490639..f9f95d7ad2 100644 --- a/src/panels/lovelace/editor/config-elements/hui-view-editor.ts +++ b/src/panels/lovelace/editor/view-editor/hui-view-editor.ts @@ -6,7 +6,7 @@ import { EditorTarget } from "../types"; import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin"; import { HomeAssistant } from "../../../../types"; import { fireEvent } from "../../../../common/dom/fire_event"; -import { configElementStyle } from "./config-elements-style"; +import { configElementStyle } from "../config-elements/config-elements-style"; import "../../components/hui-theme-select-editor"; import { LovelaceViewConfig } from "../../../../data/lovelace"; @@ -24,12 +24,11 @@ export class HuiViewEditor extends hassLocalizeLitMixin(LitElement) { return { hass: {}, _config: {} }; } - get _id(): string { + get _path(): string { if (!this._config) { return ""; } - - return "id" in this._config ? this._config.id! : ""; + return this._config.path || ""; } get _title(): string { @@ -68,12 +67,6 @@ export class HuiViewEditor extends hassLocalizeLitMixin(LitElement) { return html` ${configElementStyle}
- + void; + lovelace: Lovelace; + viewIndex?: number; } const registerEditViewDialog = (element: HTMLElement) => diff --git a/src/panels/lovelace/ha-panel-lovelace.js b/src/panels/lovelace/ha-panel-lovelace.js deleted file mode 100644 index 936b0e5d1a..0000000000 --- a/src/panels/lovelace/ha-panel-lovelace.js +++ /dev/null @@ -1,154 +0,0 @@ -import { html } from "@polymer/polymer/lib/utils/html-tag"; -import { PolymerElement } from "@polymer/polymer/polymer-element"; -import "@polymer/paper-button/paper-button"; - -import { registerSaveDialog } from "./editor/hui-dialog-save-config"; -import { fetchConfig } from "../../data/lovelace"; -import "../../layouts/hass-loading-screen"; -import "../../layouts/hass-error-screen"; -import "./hui-root"; -import localizeMixin from "../../mixins/localize-mixin"; - -let registeredDialog = false; - -class Lovelace extends localizeMixin(PolymerElement) { - static get template() { - return html` - - - - - `; - } - - static get properties() { - return { - hass: Object, - - narrow: { - type: Boolean, - value: false, - }, - - showMenu: { - type: Boolean, - value: false, - }, - - route: Object, - - _columns: { - type: Number, - value: 1, - }, - - _state: { - type: String, - value: "loading", - }, - - _errorMsg: String, - - _config: { - type: Object, - value: null, - }, - }; - } - - static get observers() { - return ["_updateColumns(narrow, showMenu)"]; - } - - ready() { - this._fetchConfig(false); - this._updateColumns = this._updateColumns.bind(this); - this.mqls = [300, 600, 900, 1200].map((width) => { - const mql = matchMedia(`(min-width: ${width}px)`); - mql.addListener(this._updateColumns); - return mql; - }); - this._updateColumns(); - super.ready(); - } - - _updateColumns() { - const matchColumns = this.mqls.reduce((cols, mql) => cols + mql.matches, 0); - // Do -1 column if the menu is docked and open - this._columns = Math.max(1, matchColumns - (!this.narrow && this.showMenu)); - } - - _forceFetchConfig() { - this._fetchConfig(true); - } - - async _fetchConfig(force) { - try { - const conf = await fetchConfig(this.hass, force); - this.setProperties({ - _config: conf, - _state: "loaded", - }); - } catch (err) { - if (err.code === "file_not_found") { - const { - generateLovelaceConfig, - } = await import("./common/generate-lovelace-config"); - this.setProperties({ - _config: generateLovelaceConfig(this.hass, this.localize), - _state: "loaded", - }); - if (!registeredDialog) { - registeredDialog = true; - registerSaveDialog(this); - } - } else { - this.setProperties({ - _state: "error", - _errorMsg: err.message, - }); - } - } - } - - _equal(a, b) { - return a === b; - } -} - -customElements.define("ha-panel-lovelace", Lovelace); diff --git a/src/panels/lovelace/ha-panel-lovelace.ts b/src/panels/lovelace/ha-panel-lovelace.ts new file mode 100644 index 0000000000..6bc94a67c0 --- /dev/null +++ b/src/panels/lovelace/ha-panel-lovelace.ts @@ -0,0 +1,185 @@ +import "@polymer/paper-button/paper-button"; + +import { fetchConfig, LovelaceConfig, saveConfig } from "../../data/lovelace"; +import "../../layouts/hass-loading-screen"; +import "../../layouts/hass-error-screen"; +import "./hui-root"; +import { HomeAssistant, PanelInfo } from "../../types"; +import { Lovelace } from "./types"; +import { LitElement, html, PropertyValues } from "@polymer/lit-element"; +import { hassLocalizeLitMixin } from "../../mixins/lit-localize-mixin"; +import { TemplateResult } from "lit-html"; +import { showSaveDialog } from "./editor/show-save-config-dialog"; + +interface LovelacePanelConfig { + mode: "yaml" | "storage"; +} + +class LovelacePanel extends hassLocalizeLitMixin(LitElement) { + public panel?: PanelInfo; + public hass?: HomeAssistant; + public narrow?: boolean; + public showMenu?: boolean; + public route?: object; + private _columns?: number; + private _state?: "loading" | "loaded" | "error"; + private _errorMsg?: string; + private lovelace?: Lovelace; + private mqls?: MediaQueryList[]; + + static get properties() { + return { + hass: {}, + lovelace: {}, + narrow: { type: Boolean, value: false }, + showMenu: { type: Boolean, value: false }, + route: {}, + _columns: { type: Number, value: 1 }, + _state: { type: String, value: "loading" }, + _errorMsg: String, + _config: { type: {}, value: null }, + }; + } + + public render(): TemplateResult { + const state = this._state!; + + if (state === "loaded") { + return html` + + `; + } + + if (state === "error") { + return html` + + + Reload ui-lovelace.yaml + + `; + } + + return html` + + `; + } + + public updated(changedProps: PropertyValues): void { + if (changedProps.has("narrow") || changedProps.has("showMenu")) { + this._updateColumns(); + } + } + + public firstUpdated() { + this._fetchConfig(false); + this._updateColumns = this._updateColumns.bind(this); + this.mqls = [300, 600, 900, 1200].map((width) => { + const mql = matchMedia(`(min-width: ${width}px)`); + mql.addListener(this._updateColumns); + return mql; + }); + this._updateColumns(); + } + + private _updateColumns() { + const matchColumns = this.mqls!.reduce( + (cols, mql) => cols + Number(mql.matches), + 0 + ); + // Do -1 column if the menu is docked and open + this._columns = Math.max( + 1, + matchColumns - Number(!this.narrow && this.showMenu) + ); + } + + private _forceFetchConfig() { + this._fetchConfig(true); + } + + private async _fetchConfig(force) { + let conf; + let gen: boolean; + + try { + conf = await fetchConfig(this.hass!, force); + gen = false; + } catch (err) { + if (err.code !== "config_not_found") { + // tslint:disable-next-line + console.log(err); + this._state = "error"; + this._errorMsg = err.message; + return; + } + const { + generateLovelaceConfig, + } = await import("./common/generate-lovelace-config"); + conf = generateLovelaceConfig(this.hass!, this.localize); + gen = true; + } + + this._state = "loaded"; + this.lovelace = { + config: conf, + autoGen: gen, + editMode: this.lovelace ? this.lovelace.editMode : false, + mode: this.panel!.config.mode, + setEditMode: (editMode: boolean) => { + if (!editMode || !this.lovelace!.autoGen) { + this._updateLovelace({ editMode }); + return; + } + showSaveDialog(this, { + lovelace: this.lovelace!, + }); + }, + saveConfig: async (newConfig: LovelaceConfig): Promise => { + const { config, autoGen } = this.lovelace!; + try { + // Optimistic update + this._updateLovelace({ config: newConfig, autoGen: false }); + await saveConfig(this.hass!, newConfig); + } catch (err) { + // tslint:disable-next-line + console.error(err); + // Rollback the optimistic update + this._updateLovelace({ config, autoGen }); + throw err; + } + }, + }; + } + + private _updateLovelace(props: Partial) { + this.lovelace = { + ...this.lovelace!, + ...props, + }; + } +} + +customElements.define("ha-panel-lovelace", LovelacePanel); diff --git a/src/panels/lovelace/hui-root.js b/src/panels/lovelace/hui-root.js index 05da5b2e30..614e833de0 100644 --- a/src/panels/lovelace/hui-root.js +++ b/src/panels/lovelace/hui-root.js @@ -32,8 +32,7 @@ import "./hui-unused-entities"; import "./hui-view"; import debounce from "../../common/util/debounce"; import createCardElement from "./common/create-card-element"; -import { showSaveDialog } from "./editor/hui-dialog-save-config"; -import { showEditViewDialog } from "./editor/show-edit-view-dialog"; +import { showEditViewDialog } from "./editor/view-editor/show-edit-view-dialog"; // CSS and JS should only be imported once. Modules and HTML are safe. const CSS_CACHE = {}; @@ -181,8 +180,12 @@ class HUIRoot extends NavigateMixin( }, config: { type: Object, + computed: "_computeConfig(lovelace)", observer: "_configChanged", }, + lovelace: { + type: Object, + }, columns: { type: Number, observer: "_columnsChanged", @@ -216,6 +219,7 @@ class HUIRoot extends NavigateMixin( _editMode: { type: Boolean, value: false, + computed: "_computeEditMode(lovelace)", observer: "_editModeChanged", }, @@ -258,12 +262,12 @@ class HUIRoot extends NavigateMixin( _routeChanged(route) { const views = this.config && this.config.views; if (route.path === "" && route.prefix === "/lovelace" && views) { - this.navigate(`/lovelace/${views[0].id || 0}`, true); + this.navigate(`/lovelace/${views[0].path || 0}`, true); } else if (this.routeData.view) { const view = this.routeData.view; let index = 0; for (let i = 0; i < views.length; i++) { - if (views[i].id === view || i === parseInt(view)) { + if (views[i].path === view || i === parseInt(view)) { index = i; break; } @@ -272,8 +276,8 @@ class HUIRoot extends NavigateMixin( } } - _computeViewId(id, index) { - return id || index; + _computeViewPath(path, index) { + return path || index; } _computeTitle(config) { @@ -305,17 +309,7 @@ class HUIRoot extends NavigateMixin( } _editModeEnable() { - if (this.config._frontendAuto) { - showSaveDialog(this, { - config: this.config, - reloadLovelace: () => { - this.fire("config-refresh"); - this._editMode = true; - }, - }); - return; - } - this._editMode = true; + this.lovelace.setEditMode(true); if (this.config.views.length < 2) { this.$.view.classList.remove("tabs-hidden"); this.fire("iron-resize"); @@ -323,7 +317,7 @@ class HUIRoot extends NavigateMixin( } _editModeDisable() { - this._editMode = false; + this.lovelace.setEditMode(false); if (this.config.views.length < 2) { this.$.view.classList.add("tabs-hidden"); this.fire("iron-resize"); @@ -336,20 +330,14 @@ class HUIRoot extends NavigateMixin( _editView() { showEditViewDialog(this, { - viewConfig: this.config.views[this._curView], - add: false, - reloadLovelace: () => { - this.fire("config-refresh"); - }, + lovelace: this.lovelace, + viewIndex: this._curView, }); } _addView() { showEditViewDialog(this, { - add: true, - reloadLovelace: () => { - this.fire("config-refresh"); - }, + lovelace: this.lovelace, }); } @@ -360,8 +348,8 @@ class HUIRoot extends NavigateMixin( _navigateView(viewIndex) { if (viewIndex !== this._curView) { - const id = this.config.views[viewIndex].id || viewIndex; - this.navigate(`/lovelace/${id}`); + const path = this.config.views[viewIndex].path || viewIndex; + this.navigate(`/lovelace/${path}`); } scrollToTarget(this, this.$.layout.header.scrollTarget); } @@ -390,12 +378,12 @@ class HUIRoot extends NavigateMixin( if (viewConfig.panel) { view = createCardElement(viewConfig.cards[0]); view.isPanel = true; - view.editMode = this._editMode; } else { view = document.createElement("hui-view"); + view.lovelace = this.lovelace; view.config = viewConfig; view.columns = this.columns; - view.editMode = this._editMode; + view.index = viewIndex; } if (viewConfig.background) background = viewConfig.background; } @@ -452,5 +440,13 @@ class HUIRoot extends NavigateMixin( } }); } + + _computeConfig(lovelace) { + return lovelace ? lovelace.config : null; + } + + _computeEditMode(lovelace) { + return lovelace ? lovelace.editMode : false; + } } customElements.define("hui-root", HUIRoot); diff --git a/src/panels/lovelace/hui-view.js b/src/panels/lovelace/hui-view.js index 88e7fe0793..369dd3fb1c 100644 --- a/src/panels/lovelace/hui-view.js +++ b/src/panels/lovelace/hui-view.js @@ -11,7 +11,7 @@ import EventsMixin from "../../mixins/events-mixin"; import localizeMixin from "../../mixins/localize-mixin"; import createCardElement from "./common/create-card-element"; import { computeCardSize } from "./common/compute-card-size"; -import { showEditCardDialog } from "./editor/show-edit-card-dialog"; +import { showEditCardDialog } from "./editor/card-editor/show-edit-card-dialog"; class HUIView extends localizeMixin(EventsMixin(PolymerElement)) { static get template() { @@ -82,7 +82,7 @@ class HUIView extends localizeMixin(EventsMixin(PolymerElement)) {
{ - this.fire("config-refresh"); - }, + lovelace: this.lovelace, + path: [this.index], }); } @@ -169,23 +168,23 @@ class HUIView extends localizeMixin(EventsMixin(PolymerElement)) { const elements = []; const elementsToAppend = []; - for (const cardConfig of config.cards) { + config.cards.forEach((cardConfig, cardIndex) => { const element = createCardElement(cardConfig); element.hass = this.hass; elements.push(element); - if (!this.editMode) { + if (!this.lovelace.editMode) { elementsToAppend.push(element); - continue; + return; } const wrapper = document.createElement("hui-card-options"); wrapper.hass = this.hass; - wrapper.cardConfig = cardConfig; - wrapper.editMode = this.editMode; + wrapper.lovelace = this.lovelace; + wrapper.path = [this.index, cardIndex]; wrapper.appendChild(element); elementsToAppend.push(wrapper); - } + }); let columns = []; const columnEntityCount = []; diff --git a/src/panels/lovelace/types.ts b/src/panels/lovelace/types.ts index 141f12c86f..52a7a6bb31 100644 --- a/src/panels/lovelace/types.ts +++ b/src/panels/lovelace/types.ts @@ -1,5 +1,14 @@ import { HomeAssistant } from "../../types"; -import { LovelaceCardConfig } from "../../data/lovelace"; +import { LovelaceCardConfig, LovelaceConfig } from "../../data/lovelace"; + +export interface Lovelace { + config: LovelaceConfig; + editMode: boolean; + autoGen: boolean; + mode: "yaml" | "storage"; + setEditMode: (editMode: boolean) => void; + saveConfig: (newConfig: LovelaceConfig) => Promise; +} export interface LovelaceCard extends HTMLElement { hass?: HomeAssistant; diff --git a/src/types.ts b/src/types.ts index 9fc4b9db09..1531918016 100644 --- a/src/types.ts +++ b/src/types.ts @@ -159,3 +159,11 @@ export type GroupEntity = HassEntityBase & { control?: "hidden"; }; }; + +export interface PanelInfo { + component_name: string; + icon?: string; + title?: string; + url_path: string; + config: T; +}