diff --git a/gallery/src/components/demo-card.js b/gallery/src/components/demo-card.js index 6cbbad8067..e63ad1eecd 100644 --- a/gallery/src/components/demo-card.js +++ b/gallery/src/components/demo-card.js @@ -7,7 +7,7 @@ import { demoConfig } from "../data/demo_config"; import { demoServices } from "../data/demo_services"; import demoResources from "../data/demo_resources"; import demoStates from "../data/demo_states"; -import createCardElement from "../../../src/panels/lovelace/common/create-card-element"; +import { createCardElement } from "../../../src/panels/lovelace/common/create-card-element"; class DemoCard extends PolymerElement { static get template() { @@ -78,6 +78,10 @@ class DemoCard extends PolymerElement { hass.resources = demoResources; hass.language = "en"; hass.states = demoStates; + hass.themes = { + default_theme: "default", + themes: {}, + }; el.hass = hass; } diff --git a/gallery/src/demos/demo-hui-map-card.ts b/gallery/src/demos/demo-hui-map-card.ts index a402d80024..da59ba6d00 100644 --- a/gallery/src/demos/demo-hui-map-card.ts +++ b/gallery/src/demos/demo-hui-map-card.ts @@ -29,6 +29,31 @@ const ENTITIES = [ friendly_name: "Home", icon: "mdi:home", }), + getEntity("zone", "bushfire", "zoning", { + latitude: -33.8611, + longitude: 151.203, + radius: 35000, + friendly_name: "Bushfire Zone", + icon: "mdi:home", + }), + getEntity("geo_location", "nelsons_creek", "15", { + source: "bushfire_demo", + latitude: -34.07792, + longitude: 151.03219, + friendly_name: "Nelsons Creek", + }), + getEntity("geo_location", "forest_rd_nowra_hill", "8", { + source: "bushfire_demo", + latitude: -33.69452, + longitude: 151.19577, + friendly_name: "Forest Rd, Nowra Hill", + }), + getEntity("geo_location", "stoney_ridge_rd_kremnos", "20", { + source: "bushfire_demo", + latitude: -33.66584, + longitude: 150.97209, + friendly_name: "Stoney Ridge Rd, Kremnos", + }), ]; const CONFIGS = [ @@ -116,6 +141,24 @@ const CONFIGS = [ - light.bed_light `, }, + { + heading: "Geo Location Entities", + config: ` +- type: map + geo_location_sources: + - bushfire_demo + `, + }, + { + heading: "Geo Location Entities with Home Zone", + config: ` +- type: map + geo_location_sources: + - bushfire_demo + entities: + - zone.bushfire + `, + }, ]; class DemoMap extends PolymerElement { diff --git a/gallery/src/demos/demo-hui-stack-card.ts b/gallery/src/demos/demo-hui-stack-card.ts index 257d753d4a..32c2e862d6 100644 --- a/gallery/src/demos/demo-hui-stack-card.ts +++ b/gallery/src/demos/demo-hui-stack-card.ts @@ -9,6 +9,9 @@ const ENTITIES = [ getEntity("light", "kitchen_lights", "on", { friendly_name: "Kitchen Lights", }), + getEntity("light", "bed_light", "on", { + friendly_name: "Bed Lights", + }), getEntity("device_tracker", "demo_paulus", "work", { source_type: "gps", latitude: 32.877105, diff --git a/public/icons/favicon-apple-180x180.png b/public/icons/favicon-apple-180x180.png index 20d29a5c5c..a65aacb29d 100644 Binary files a/public/icons/favicon-apple-180x180.png and b/public/icons/favicon-apple-180x180.png differ diff --git a/setup.py b/setup.py index b832c942f1..448d14ef3b 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup( name="home-assistant-frontend", - version="20181211.2", + version="2019109.0", description="The Home Assistant frontend", url="https://github.com/home-assistant/home-assistant-polymer", author="The Home Assistant Authors", diff --git a/src/cards/ha-weather-card.js b/src/cards/ha-weather-card.js index ce9ac0152d..4f27663ede 100644 --- a/src/cards/ha-weather-card.js +++ b/src/cards/ha-weather-card.js @@ -8,6 +8,7 @@ import "../components/ha-icon"; import EventsMixin from "../mixins/events-mixin"; import LocalizeMixin from "../mixins/localize-mixin"; +import { computeRTL } from "../common/util/compute_rtl"; /* * @appliesMixin LocalizeMixin @@ -30,12 +31,16 @@ class HaWeatherCard extends LocalizeMixin(EventsMixin(PolymerElement)) { .header { font-family: var(--paper-font-headline_-_font-family); - -webkit-font-smoothing: var(--paper-font-headline_-_-webkit-font-smoothing); + -webkit-font-smoothing: var( + --paper-font-headline_-_-webkit-font-smoothing + ); font-size: var(--paper-font-headline_-_font-size); font-weight: var(--paper-font-headline_-_font-weight); letter-spacing: var(--paper-font-headline_-_letter-spacing); line-height: var(--paper-font-headline_-_line-height); - text-rendering: var(--paper-font-common-expensive-kerning_-_text-rendering); + text-rendering: var( + --paper-font-common-expensive-kerning_-_text-rendering + ); opacity: var(--dark-primary-opacity); padding: 24px 16px 16px; display: flex; @@ -48,6 +53,11 @@ class HaWeatherCard extends LocalizeMixin(EventsMixin(PolymerElement)) { color: var(--secondary-text-color); } + :host([rtl]) .name { + margin-left: 0px; + margin-right: 16px; + } + .now { display: flex; justify-content: space-between; @@ -61,18 +71,31 @@ class HaWeatherCard extends LocalizeMixin(EventsMixin(PolymerElement)) { margin-right: 32px; } + :host([rtl]) .main { + margin-right: 0px; + } + .main ha-icon { --iron-icon-height: 72px; --iron-icon-width: 72px; margin-right: 8px; } + :host([rtl]) .main ha-icon { + margin-right: 0px; + } + .main .temp { font-size: 52px; line-height: 1em; position: relative; } + :host([rtl]) .main .temp { + direction: ltr; + margin-right: 28px; + } + .main .temp span { font-size: 24px; line-height: 1em; @@ -80,6 +103,14 @@ class HaWeatherCard extends LocalizeMixin(EventsMixin(PolymerElement)) { top: 4px; } + .measurand { + display: inline-block; + } + + :host([rtl]) .measurand { + direction: ltr; + } + .forecast { margin-top: 16px; display: flex; @@ -96,13 +127,17 @@ class HaWeatherCard extends LocalizeMixin(EventsMixin(PolymerElement)) { text-align: center; } + :host([rtl]) .forecast .temp { + direction: ltr; + } + .weekday { font-weight: bold; } .attributes, .templow, - .precipitation { { + .precipitation { color: var(--secondary-text-color); } @@ -130,7 +165,9 @@ class HaWeatherCard extends LocalizeMixin(EventsMixin(PolymerElement)) { >
[[localize('ui.card.weather.attributes.air_pressure')]]: - [[stateObj.attributes.pressure]] [[getUnit('air_pressure')]] + + [[stateObj.attributes.pressure]] [[getUnit('air_pressure')]] +
@@ -202,6 +243,11 @@ class HaWeatherCard extends LocalizeMixin(EventsMixin(PolymerElement)) { type: Array, computed: "computeForecast(stateObj.attributes.forecast)", }, + rtl: { + type: Boolean, + reflectToAttribute: true, + computed: "_computeRTL(hass)", + }, }; } @@ -295,14 +341,18 @@ class HaWeatherCard extends LocalizeMixin(EventsMixin(PolymerElement)) { return degree; } - getWind(speed, bearing, localize) { + getWindSpeed(speed) { + return `${speed} ${this.getUnit("length")}/h`; + } + + getWindBearing(bearing, localize) { if (bearing != null) { const cardinalDirection = this.windBearingToText(bearing); - return `${speed} ${this.getUnit("length")}/h (${localize( + return `(${localize( `ui.card.weather.cardinal_direction.${cardinalDirection.toLowerCase()}` ) || cardinalDirection})`; } - return `${speed} ${this.getUnit("length")}/h`; + return ``; } _showValue(item) { @@ -324,5 +374,9 @@ class HaWeatherCard extends LocalizeMixin(EventsMixin(PolymerElement)) { { hour: "numeric" } ); } + + _computeRTL(hass) { + return computeRTL(hass); + } } customElements.define("ha-weather-card", HaWeatherCard); diff --git a/src/common/auth/external_auth.ts b/src/common/auth/external_auth.ts index 4f5613af15..cbb4e573aa 100644 --- a/src/common/auth/external_auth.ts +++ b/src/common/auth/external_auth.ts @@ -18,8 +18,8 @@ interface RefreshTokenResponse { declare global { interface Window { externalApp?: { - getExternalAuth(payload: BasePayload); - revokeExternalAuth(payload: BasePayload); + getExternalAuth(payload: string); + revokeExternalAuth(payload: string); }; webkit?: { messageHandlers: { @@ -67,7 +67,7 @@ export default class ExternalAuth extends Auth { const callbackPayload = { callback: CALLBACK_SET_TOKEN }; if (window.externalApp) { - window.externalApp.getExternalAuth(callbackPayload); + window.externalApp.getExternalAuth(JSON.stringify(callbackPayload)); } else { window.webkit!.messageHandlers.getExternalAuth.postMessage( callbackPayload @@ -92,7 +92,7 @@ export default class ExternalAuth extends Auth { const callbackPayload = { callback: CALLBACK_REVOKE_TOKEN }; if (window.externalApp) { - window.externalApp.revokeExternalAuth(callbackPayload); + window.externalApp.revokeExternalAuth(JSON.stringify(callbackPayload)); } else { window.webkit!.messageHandlers.revokeExternalAuth.postMessage( callbackPayload diff --git a/src/common/entity/compute_state_name.ts b/src/common/entity/compute_state_name.ts index 487ea67dd9..57cf9bef45 100644 --- a/src/common/entity/compute_state_name.ts +++ b/src/common/entity/compute_state_name.ts @@ -2,5 +2,6 @@ import { HassEntity } from "home-assistant-js-websocket"; import computeObjectId from "./compute_object_id"; export default (stateObj: HassEntity): string => - stateObj.attributes.friendly_name || - computeObjectId(stateObj.entity_id).replace(/_/g, " "); + stateObj.attributes.friendly_name === undefined + ? computeObjectId(stateObj.entity_id).replace(/_/g, " ") + : stateObj.attributes.friendly_name || ""; diff --git a/src/common/util/render-status.ts b/src/common/util/render-status.ts new file mode 100644 index 0000000000..4564c29cd6 --- /dev/null +++ b/src/common/util/render-status.ts @@ -0,0 +1,3 @@ +export const afterNextRender = (cb: () => void): void => { + requestAnimationFrame(() => setTimeout(cb, 0)); +}; diff --git a/src/components/entity/ha-state-label-badge.js b/src/components/entity/ha-state-label-badge.ts similarity index 59% rename from src/components/entity/ha-state-label-badge.js rename to src/components/entity/ha-state-label-badge.ts index bbd6f4dcac..db59a82ea6 100644 --- a/src/components/entity/ha-state-label-badge.js +++ b/src/components/entity/ha-state-label-badge.ts @@ -1,25 +1,208 @@ -import { html } from "@polymer/polymer/lib/utils/html-tag"; -import { PolymerElement } from "@polymer/polymer/polymer-element"; - -import "../ha-label-badge"; +import { + LitElement, + html, + PropertyValues, + PropertyDeclarations, +} from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; +import { HassEntity } from "home-assistant-js-websocket"; +import { classMap } from "lit-html/directives/classMap"; import computeStateDomain from "../../common/entity/compute_state_domain"; import computeStateName from "../../common/entity/compute_state_name"; import domainIcon from "../../common/entity/domain_icon"; import stateIcon from "../../common/entity/state_icon"; import timerTimeRemaining from "../../common/entity/timer_time_remaining"; -import attributeClassNames from "../../common/entity/attribute_class_names"; import secondsToDuration from "../../common/datetime/seconds_to_duration"; +import { fireEvent } from "../../common/dom/fire_event"; +import { hassLocalizeLitMixin } from "../../mixins/lit-localize-mixin"; -import EventsMixin from "../../mixins/events-mixin"; -import LocalizeMixin from "../../mixins/localize-mixin"; +import "../ha-label-badge"; /* * @appliesMixin LocalizeMixin * @appliesMixin EventsMixin */ -class HaStateLabelBadge extends LocalizeMixin(EventsMixin(PolymerElement)) { - static get template() { +export class HaStateLabelBadge extends hassLocalizeLitMixin(LitElement) { + public state?: HassEntity; + private _connected?: boolean; + private _updateRemaining?: number; + private _timerTimeRemaining?: number; + + public connectedCallback(): void { + super.connectedCallback(); + this._connected = true; + this.startInterval(this.state); + } + + public disconnectedCallback(): void { + super.disconnectedCallback(); + this._connected = false; + this.clearInterval(); + } + + protected render(): TemplateResult { + const state = this.state; + + if (!state) { + return html` + ${this.renderStyle()} + + `; + } + + const domain = computeStateDomain(state); + + return html` + ${this.renderStyle()} + + `; + } + + static get properties(): PropertyDeclarations { + return { + hass: {}, + state: {}, + _timerTimeRemaining: {}, + }; + } + + protected firstUpdated(changedProperties: PropertyValues): void { + super.firstUpdated(changedProperties); + this.addEventListener("click", (ev) => { + ev.stopPropagation(); + if (this.state) { + fireEvent(this, "hass-more-info", { entityId: this.state.entity_id }); + } + }); + } + + protected updated(changedProperties: PropertyValues): void { + super.updated(changedProperties); + + if (this._connected && changedProperties.has("state")) { + this.startInterval(this.state); + } + } + + private _computeValue(domain: string, state: HassEntity) { + switch (domain) { + case "binary_sensor": + case "device_tracker": + case "updater": + case "sun": + case "alarm_control_panel": + case "timer": + return null; + case "sensor": + default: + return state.state === "unknown" + ? "-" + : this.localize(`component.${domain}.state.${state.state}`) || + state.state; + } + } + + private _computeIcon(domain: string, state: HassEntity) { + if (state.state === "unavailable") { + return null; + } + switch (domain) { + case "alarm_control_panel": + if (state.state === "pending") { + return "hass:clock-fast"; + } + if (state.state === "armed_away") { + return "hass:nature"; + } + if (state.state === "armed_home") { + return "hass:home-variant"; + } + if (state.state === "armed_night") { + return "hass:weather-night"; + } + if (state.state === "armed_custom_bypass") { + return "hass:security-home"; + } + if (state.state === "triggered") { + return "hass:alert-circle"; + } + // state == 'disarmed' + return domainIcon(domain, state.state); + case "binary_sensor": + case "device_tracker": + case "updater": + return stateIcon(state); + case "sun": + return state.state === "above_horizon" + ? domainIcon(domain) + : "hass:brightness-3"; + case "timer": + return state.state === "active" ? "hass:timer" : "hass:timer-off"; + default: + return null; + } + } + + private _computeLabel(domain, state, _timerTimeRemaining) { + if ( + state.state === "unavailable" || + ["device_tracker", "alarm_control_panel"].includes(domain) + ) { + // Localize the state with a special state_badge namespace, which has variations of + // the state translations that are truncated to fit within the badge label. Translations + // are only added for device_tracker and alarm_control_panel. + return ( + this.localize(`state_badge.${domain}.${state.state}`) || + this.localize(`state_badge.default.${state.state}`) || + state.state + ); + } + if (domain === "timer") { + return secondsToDuration(_timerTimeRemaining); + } + return state.attributes.unit_of_measurement || null; + } + + private clearInterval() { + if (this._updateRemaining) { + clearInterval(this._updateRemaining); + this._updateRemaining = undefined; + } + } + + private startInterval(stateObj) { + this.clearInterval(); + if (stateObj && computeStateDomain(stateObj) === "timer") { + this.calculateTimerRemaining(stateObj); + + if (stateObj.state === "active") { + this._updateRemaining = window.setInterval( + () => this.calculateTimerRemaining(this.state), + 1000 + ); + } + } + } + + private calculateTimerRemaining(stateObj) { + this._timerTimeRemaining = timerTimeRemaining(stateObj); + } + + private renderStyle(): TemplateResult { return html` - - `; } +} - static get properties() { - return { - hass: Object, - state: { - type: Object, - observer: "stateChanged", - }, - _timerTimeRemaining: { - type: Number, - value: 0, - }, - }; - } - - connectedCallback() { - super.connectedCallback(); - this.startInterval(this.state); - } - - disconnectedCallback() { - super.disconnectedCallback(); - this.clearInterval(); - } - - ready() { - super.ready(); - this.addEventListener("click", (ev) => this.badgeTap(ev)); - } - - badgeTap(ev) { - ev.stopPropagation(); - this.fire("hass-more-info", { entityId: this.state.entity_id }); - } - - computeClassNames(state) { - const classes = [computeStateDomain(state)]; - classes.push(attributeClassNames(state, ["unit_of_measurement"])); - return classes.join(" "); - } - - computeValue(localize, state) { - const domain = computeStateDomain(state); - switch (domain) { - case "binary_sensor": - case "device_tracker": - case "updater": - case "sun": - case "alarm_control_panel": - case "timer": - return null; - case "sensor": - default: - return state.state === "unknown" - ? "-" - : localize(`component.${domain}.state.${state.state}`) || state.state; - } - } - - computeIcon(state) { - if (state.state === "unavailable") { - return null; - } - const domain = computeStateDomain(state); - switch (domain) { - case "alarm_control_panel": - if (state.state === "pending") { - return "hass:clock-fast"; - } - if (state.state === "armed_away") { - return "hass:nature"; - } - if (state.state === "armed_home") { - return "hass:home-variant"; - } - if (state.state === "armed_night") { - return "hass:weather-night"; - } - if (state.state === "armed_custom_bypass") { - return "hass:security-home"; - } - if (state.state === "triggered") { - return "hass:alert-circle"; - } - // state == 'disarmed' - return domainIcon(domain, state.state); - case "binary_sensor": - case "device_tracker": - case "updater": - return stateIcon(state); - case "sun": - return state.state === "above_horizon" - ? domainIcon(domain) - : "hass:brightness-3"; - case "timer": - return state.state === "active" ? "hass:timer" : "hass:timer-off"; - default: - return null; - } - } - - computeImage(state) { - return state.attributes.entity_picture || null; - } - - computeLabel(localize, state, _timerTimeRemaining) { - const domain = computeStateDomain(state); - if ( - state.state === "unavailable" || - ["device_tracker", "alarm_control_panel"].includes(domain) - ) { - // Localize the state with a special state_badge namespace, which has variations of - // the state translations that are truncated to fit within the badge label. Translations - // are only added for device_tracker and alarm_control_panel. - return ( - localize(`state_badge.${domain}.${state.state}`) || - localize(`state_badge.default.${state.state}`) || - state.state - ); - } - if (domain === "timer") { - return secondsToDuration(_timerTimeRemaining); - } - return state.attributes.unit_of_measurement || null; - } - - computeDescription(state) { - return computeStateName(state); - } - - stateChanged(stateObj) { - this.updateStyles(); - this.startInterval(stateObj); - } - - clearInterval() { - if (this._updateRemaining) { - clearInterval(this._updateRemaining); - this._updateRemaining = null; - } - } - - startInterval(stateObj) { - this.clearInterval(); - if (computeStateDomain(stateObj) === "timer") { - this.calculateTimerRemaining(stateObj); - - if (stateObj.state === "active") { - this._updateRemaining = setInterval( - () => this.calculateTimerRemaining(this.state), - 1000 - ); - } - } - } - - calculateTimerRemaining(stateObj) { - this._timerTimeRemaining = timerTimeRemaining(stateObj); +declare global { + interface HTMLElementTagNameMap { + "ha-state-label-badge": HaStateLabelBadge; } } diff --git a/src/components/ha-climate-control.js b/src/components/ha-climate-control.js index 1ca3b874c7..20c7131373 100644 --- a/src/components/ha-climate-control.js +++ b/src/components/ha-climate-control.js @@ -81,8 +81,15 @@ class HaClimateControl extends EventsMixin(PolymerElement) { this.$.target_temperature.classList.toggle("in-flux", inFlux); } + _round(val) { + // round value to precision derived from step + // insired by https://github.com/soundar24/roundSlider/blob/master/src/roundslider.js + const s = this.step.toString().split("."); + return s[1] ? parseFloat(val.toFixed(s[1].length)) : Math.round(val); + } + incrementValue() { - const newval = this.value + this.step; + const newval = this._round(this.value + this.step); if (this.value < this.max) { this.last_changed = Date.now(); this.temperatureStateInFlux(true); @@ -102,7 +109,7 @@ class HaClimateControl extends EventsMixin(PolymerElement) { } decrementValue() { - const newval = this.value - this.step; + const newval = this._round(this.value - this.step); if (this.value > this.min) { this.last_changed = Date.now(); this.temperatureStateInFlux(true); diff --git a/src/components/ha-label-badge.js b/src/components/ha-label-badge.ts similarity index 58% rename from src/components/ha-label-badge.js rename to src/components/ha-label-badge.ts index 0aafbd8300..f29778a0f0 100644 --- a/src/components/ha-label-badge.js +++ b/src/components/ha-label-badge.ts @@ -1,9 +1,83 @@ -import { html } from "@polymer/polymer/lib/utils/html-tag"; -import { PolymerElement } from "@polymer/polymer/polymer-element"; +import { + LitElement, + PropertyDeclarations, + PropertyValues, +} from "@polymer/lit-element"; +import { TemplateResult, html } from "lit-html"; +import { classMap } from "lit-html/directives/classMap"; import "./ha-icon"; -class HaLabelBadge extends PolymerElement { - static get template() { +class HaLabelBadge extends LitElement { + public value?: string; + public icon?: string; + public label?: string; + public description?: string; + public image?: string; + + static get properties(): PropertyDeclarations { + return { + value: {}, + icon: {}, + label: {}, + description: {}, + image: {}, + }; + } + + protected render(): TemplateResult { + return html` + ${this.renderStyle()} +
+
+
+ ${ + this.icon && !this.value && !this.image + ? html` + + ` + : "" + } + ${ + this.value && !this.image + ? html` + ${this.value} + ` + : "" + } +
+ ${ + this.label + ? html` +
+ ${this.label} +
+ ` + : "" + } +
+ ${ + this.description + ? html` +
${this.description}
+ ` + : "" + } +
+ `; + } + + protected renderStyle(): TemplateResult { return html` - -
-
-
- - [[value]] -
-
- [[label]] -
-
-
[[description]]
-
`; } - static get properties() { - return { - value: String, - icon: String, - label: String, - description: String, - - image: { - type: String, - observer: "imageChanged", - }, - }; - } - - computeValueClasses(value) { - return value && value.length > 4 ? "value big" : "value"; - } - - computeLabelClasses(label) { - return label && label.length > 5 ? "label big" : "label"; - } - - computeHideLabel(label) { - return !label || !label.trim(); - } - - computeHideIcon(icon, value, image) { - return !icon || value || image; - } - - computeHideValue(value, image) { - return !value || image; - } - - imageChanged(newVal) { - this.$.badge.style.backgroundImage = newVal ? "url(" + newVal + ")" : ""; + protected updated(changedProperties: PropertyValues): void { + super.updated(changedProperties); + if (changedProperties.has("image")) { + this.shadowRoot!.getElementById("badge")!.style.backgroundImage = this + .image + ? `url(${this.image})` + : ""; + } } } + +declare global { + interface HTMLElementTagNameMap { + "ha-label-badge": HaLabelBadge; + } +} + customElements.define("ha-label-badge", HaLabelBadge); diff --git a/src/data/alarm_control_panel.ts b/src/data/alarm_control_panel.ts new file mode 100644 index 0000000000..58f33c601d --- /dev/null +++ b/src/data/alarm_control_panel.ts @@ -0,0 +1,18 @@ +import { HomeAssistant } from "../types"; + +export const callAlarmAction = ( + hass: HomeAssistant, + entity: string, + action: + | "arm_away" + | "arm_home" + | "arm_night" + | "arm_custom_bypass" + | "disarm", + code: string +) => { + hass!.callService("alarm_control_panel", "alarm_" + action, { + entity_id: entity, + code, + }); +}; diff --git a/src/data/lovelace.ts b/src/data/lovelace.ts index d3ea76c430..84edaa0472 100644 --- a/src/data/lovelace.ts +++ b/src/data/lovelace.ts @@ -3,6 +3,9 @@ import { HomeAssistant } from "../types"; export interface LovelaceConfig { title?: string; views: LovelaceViewConfig[]; + background?: string; + resources?: Array<{ type: "css" | "js" | "module" | "html"; url: string }>; + excluded_entities?: string[]; } export interface LovelaceViewConfig { @@ -13,6 +16,8 @@ export interface LovelaceViewConfig { path?: string; icon?: string; theme?: string; + panel?: boolean; + background?: string; } export interface LovelaceCardConfig { diff --git a/src/dialogs/more-info/controls/more-info-script.js b/src/dialogs/more-info/controls/more-info-script.js index bc8ef940c0..fec483e033 100644 --- a/src/dialogs/more-info/controls/more-info-script.js +++ b/src/dialogs/more-info/controls/more-info-script.js @@ -1,15 +1,18 @@ import "@polymer/iron-flex-layout/iron-flex-layout-classes"; import { html } from "@polymer/polymer/lib/utils/html-tag"; import { PolymerElement } from "@polymer/polymer/polymer-element"; +import LocalizeMixin from "../../../mixins/localize-mixin"; -class MoreInfoScript extends PolymerElement { +class MoreInfoScript extends LocalizeMixin(PolymerElement) { static get template() { return html`
-
Last Action
+
+ [[localize('ui.dialogs.more_info_control.script.last_action')]] +
[[stateObj.attributes.last_action]]
diff --git a/src/dialogs/more-info/controls/more-info-sun.js b/src/dialogs/more-info/controls/more-info-sun.js index dc95d62b3f..d806826f23 100644 --- a/src/dialogs/more-info/controls/more-info-sun.js +++ b/src/dialogs/more-info/controls/more-info-sun.js @@ -28,7 +28,9 @@ class MoreInfoSun extends LocalizeMixin(PolymerElement) {
-
Elevation
+
+ [[localize('ui.dialogs.more_info_control.sun.elevation')]] +
[[stateObj.attributes.elevation]]
`; @@ -63,7 +65,10 @@ class MoreInfoSun extends LocalizeMixin(PolymerElement) { } itemCaption(type) { - return type === "ris" ? "Rising " : "Setting "; + if (type === "ris") { + return this.localize("ui.dialogs.more_info_control.sun.rising"); + } + return this.localize("ui.dialogs.more_info_control.sun.setting"); } itemDate(type) { diff --git a/src/dialogs/more-info/controls/more-info-updater.js b/src/dialogs/more-info/controls/more-info-updater.js index 7bda369170..b3aed3b696 100644 --- a/src/dialogs/more-info/controls/more-info-updater.js +++ b/src/dialogs/more-info/controls/more-info-updater.js @@ -1,7 +1,8 @@ import { html } from "@polymer/polymer/lib/utils/html-tag"; import { PolymerElement } from "@polymer/polymer/polymer-element"; +import LocalizeMixin from "../../../mixins/localize-mixin"; -class MoreInfoUpdater extends PolymerElement { +class MoreInfoUpdater extends LocalizeMixin(PolymerElement) { static get template() { return html` + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "zha-network": ZHANetwork; + } +} + +customElements.define("zha-network", ZHANetwork); diff --git a/src/panels/config/zwave/zwave-groups.js b/src/panels/config/zwave/zwave-groups.js index 92246f440c..0be7b41663 100644 --- a/src/panels/config/zwave/zwave-groups.js +++ b/src/panels/config/zwave/zwave-groups.js @@ -108,6 +108,16 @@ class ZwaveGroups extends PolymerElement { Remove From Group + @@ -165,6 +175,16 @@ class ZwaveGroups extends PolymerElement { type: String, value: "", }, + + _removeBroadcastNodeServiceData: { + type: String, + value: "", + }, + + _isBroadcastNodeInGroup: { + type: Boolean, + value: false, + }, }; } @@ -201,6 +221,7 @@ class ZwaveGroups extends PolymerElement { _computeOtherGroupNodes(selectedGroup) { if (selectedGroup === -1) return -1; + this.setProperties({ _isBroadcastNodeInGroup: false }); const associations = Object.values( this.groups[selectedGroup].value.association_instances ); @@ -212,6 +233,17 @@ class ZwaveGroups extends PolymerElement { const id = assoc[0]; const instance = assoc[1]; const node = this.nodes.find((n) => n.attributes.node_id === id); + if (id === 255) { + this.setProperties({ + _isBroadcastNodeInGroup: true, + _removeBroadcastNodeServiceData: { + node_id: this.nodes[this.selectedNode].attributes.node_id, + association: "remove", + target_node_id: 255, + group: this.groups[selectedGroup].key, + }, + }); + } if (!node) { return `Unknown Node (${id}: (${instance} ? ${id}.${instance} : ${id}))`; } @@ -288,6 +320,7 @@ class ZwaveGroups extends PolymerElement { _otherGroupNodes: Object.values( groupData[this._selectedGroup].value.associations ), + _isBroadcastNodeInGroup: false, }); const oldGroup = this._selectedGroup; this.setProperties({ _selectedGroup: -1 }); diff --git a/src/panels/config/zwave/zwave-log.js b/src/panels/config/zwave/zwave-log.js index 866869ca41..3c3269e31c 100644 --- a/src/panels/config/zwave/zwave-log.js +++ b/src/panels/config/zwave/zwave-log.js @@ -133,7 +133,8 @@ class OzwLog extends EventsMixin(PolymerElement) { this.fire("register-dialog", { dialogShowEvent: "show-ozwlog-dialog", dialogTag: "zwave-log-dialog", - dialogImport: () => import("./zwave-log-dialog"), + dialogImport: () => + import(/* webpackChunkName: "zwave-log-dialog" */ "./zwave-log-dialog"), }); } } diff --git a/src/panels/dev-info/ha-panel-dev-info.js b/src/panels/dev-info/ha-panel-dev-info.js index 1252aaa84b..72945f4cb0 100644 --- a/src/panels/dev-info/ha-panel-dev-info.js +++ b/src/panels/dev-info/ha-panel-dev-info.js @@ -312,7 +312,8 @@ class HaPanelDevInfo extends EventsMixin(LocalizeMixin(PolymerElement)) { this.fire("register-dialog", { dialogShowEvent: "show-loaded-components", dialogTag: "ha-loaded-components", - dialogImport: () => import("./ha-loaded-components"), + dialogImport: () => + import(/* webpackChunkName: "ha-loaded-components" */ "./ha-loaded-components"), }); } diff --git a/src/panels/dev-state/ha-panel-dev-state.js b/src/panels/dev-state/ha-panel-dev-state.js index f309a2386e..c0d495f5a9 100644 --- a/src/panels/dev-state/ha-panel-dev-state.js +++ b/src/panels/dev-state/ha-panel-dev-state.js @@ -28,6 +28,7 @@ class HaPanelDevState extends EventsMixin(PolymerElement) { .content { padding: 16px; + direction: ltr; } ha-entity-picker, diff --git a/src/panels/lovelace/cards/hui-alarm-panel-card.js b/src/panels/lovelace/cards/hui-alarm-panel-card.js deleted file mode 100644 index 3d1e858f43..0000000000 --- a/src/panels/lovelace/cards/hui-alarm-panel-card.js +++ /dev/null @@ -1,258 +0,0 @@ -import { html } from "@polymer/polymer/lib/utils/html-tag"; -import { PolymerElement } from "@polymer/polymer/polymer-element"; - -import "../../../components/ha-card"; - -import EventsMixin from "../../../mixins/events-mixin"; -import LocalizeMixin from "../../../mixins/localize-mixin"; -import "../../../components/ha-label-badge"; - -/* - * @appliesMixin EventsMixin - */ - -const Icons = { - armed_away: "hass:security-lock", - armed_custom_bypass: "hass:security", - armed_home: "hass:security-home", - armed_night: "hass:security-home", - disarmed: "hass:verified", - pending: "hass:shield-outline", - triggered: "hass:bell-ring", -}; - -class HuiAlarmPanelCard extends LocalizeMixin(EventsMixin(PolymerElement)) { - static get template() { - return html` - - - - - - `; - } - - static get properties() { - return { - hass: { - type: Object, - }, - _config: Object, - _stateObj: { - type: Object, - computed: "_computeStateObj(hass.states, _config.entity)", - }, - _value: { - type: String, - value: "", - }, - }; - } - - getCardSize() { - return 4; - } - - setConfig(config) { - if ( - !config || - !config.entity || - config.entity.split(".")[0] !== "alarm_control_panel" - ) { - throw new Error("Invalid card configuration"); - } - - const defaults = { - states: ["arm_away", "arm_home"], - }; - - this._config = { ...defaults, ...config }; - this._icons = Icons; - } - - _computeStateObj(states, entityId) { - return states && entityId in states ? states[entityId] : null; - } - - _computeHeader(localize, stateObj) { - if (!stateObj) return ""; - return this._config.name - ? this._config.name - : this._label(localize, stateObj.state); - } - - _computeIcon(stateObj) { - return this._icons[stateObj.state] || "hass:shield-outline"; - } - - _label(localize, state) { - return ( - localize(`state.alarm_control_panel.${state}`) || - localize(`ui.card.alarm_control_panel.${state}`) - ); - } - - _stateIconLabel(state) { - const stateLabel = state.split("_").pop(); - return stateLabel === "disarmed" || stateLabel === "triggered" - ? "" - : stateLabel; - } - - _showActionToggle(state) { - return state === "disarmed"; - } - - _computeClassName(stateObj) { - if (!stateObj) return "not-found"; - return ""; - } - - _handlePadClick(e) { - const val = e.target.getAttribute("value"); - this._value = val === "clear" ? "" : this._value + val; - } - - _handleActionClick(e) { - this.hass.callService("alarm_control_panel", "alarm_" + e.target.id, { - entity_id: this._stateObj.entity_id, - code: this._value, - }); - this._value = ""; - } -} - -customElements.define("hui-alarm-panel-card", HuiAlarmPanelCard); diff --git a/src/panels/lovelace/cards/hui-alarm-panel-card.ts b/src/panels/lovelace/cards/hui-alarm-panel-card.ts new file mode 100644 index 0000000000..2f560f22a8 --- /dev/null +++ b/src/panels/lovelace/cards/hui-alarm-panel-card.ts @@ -0,0 +1,316 @@ +import { + html, + LitElement, + PropertyValues, + PropertyDeclarations, +} from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; +import { classMap } from "lit-html/directives/classMap"; + +import { LovelaceCard } from "../types"; +import { HomeAssistant } from "../../../types"; +import { LovelaceCardConfig } from "../../../data/lovelace"; +import { callAlarmAction } from "../../../data/alarm_control_panel"; +import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; + +import "../../../components/ha-card"; +import "../../../components/ha-label-badge"; +import { + createErrorCardConfig, + createErrorCardElement, +} from "./hui-error-card"; + +const ICONS = { + armed_away: "hass:security-lock", + armed_custom_bypass: "hass:security", + armed_home: "hass:security-home", + armed_night: "hass:security-home", + disarmed: "hass:verified", + pending: "hass:shield-outline", + triggered: "hass:bell-ring", +}; + +const BUTTONS = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "", "0", "clear"]; + +export interface Config extends LovelaceCardConfig { + entity: string; + name?: string; + states?: string[]; +} + +class HuiAlarmPanelCard extends hassLocalizeLitMixin(LitElement) + implements LovelaceCard { + public static async getConfigElement() { + await import(/* webpackChunkName: "hui-alarm-panel-card-editor" */ "../editor/config-elements/hui-alarm-panel-card-editor"); + return document.createElement("hui-alarm-panel-card-editor"); + } + + public static getStubConfig() { + return { states: ["arm_home", "arm_away"] }; + } + + public hass?: HomeAssistant; + private _config?: Config; + private _code?: string; + + static get properties(): PropertyDeclarations { + return { + hass: {}, + _config: {}, + _code: {}, + }; + } + + public getCardSize(): number { + return 4; + } + + public setConfig(config: Config): void { + if ( + !config || + !config.entity || + config.entity.split(".")[0] !== "alarm_control_panel" + ) { + throw new Error("Invalid card configuration"); + } + + const defaults = { + states: ["arm_away", "arm_home"], + }; + + this._config = { ...defaults, ...config }; + this._code = ""; + } + + protected shouldUpdate(changedProps: PropertyValues): boolean { + if (changedProps.has("_config") || changedProps.has("_code")) { + return true; + } + + const oldHass = changedProps.get("hass") as HomeAssistant | undefined; + if (oldHass) { + return ( + oldHass.states[this._config!.entity] !== + this.hass!.states[this._config!.entity] + ); + } + return true; + } + + protected render(): TemplateResult { + if (!this._config || !this.hass) { + return html``; + } + const stateObj = this.hass.states[this._config.entity]; + + if (!stateObj) { + const element = createErrorCardElement( + createErrorCardConfig("Entity not Found!", this._config) + ); + return html` + ${element} + `; + } + + return html` + ${this.renderStyle()} + + +
+ ${ + (stateObj.state === "disarmed" + ? this._config.states! + : ["disarm"] + ).map((state) => { + return html` + ${this._label(state)} + `; + }) + } +
+ ${ + !stateObj.attributes.code_format + ? html`` + : html` + + ` + } + ${ + stateObj.attributes.code_format !== "Number" + ? html`` + : html` +
+ ${ + BUTTONS.map((value) => { + return value === "" + ? html` + + ` + : html` + ${ + value === "clear" + ? this._label("clear_code") + : value + } + `; + }) + } +
+ ` + } +
+ `; + } + + private _stateIconLabel(state: string): string { + const stateLabel = state.split("_").pop(); + return stateLabel === "disarmed" || + stateLabel === "triggered" || + !stateLabel + ? "" + : stateLabel; + } + + private _label(state: string): string { + return ( + this.localize(`state.alarm_control_panel.${state}`) || + this.localize(`ui.card.alarm_control_panel.${state}`) + ); + } + + private _handlePadClick(e: MouseEvent): void { + const val = (e.currentTarget! as any).value; + this._code = val === "clear" ? "" : this._code + val; + } + + private _handleActionClick(e: MouseEvent): void { + callAlarmAction( + this.hass!, + this._config!.entity_id, + (e.currentTarget! as any).action, + this._code! + ); + this._code = ""; + } + + private renderStyle(): TemplateResult { + return html` + + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-alarm-panel-card": HuiAlarmPanelCard; + } +} + +customElements.define("hui-alarm-panel-card", HuiAlarmPanelCard); diff --git a/src/panels/lovelace/cards/hui-conditional-card.ts b/src/panels/lovelace/cards/hui-conditional-card.ts index 0f2e6329cc..51cdb07a3e 100644 --- a/src/panels/lovelace/cards/hui-conditional-card.ts +++ b/src/panels/lovelace/cards/hui-conditional-card.ts @@ -1,4 +1,4 @@ -import createCardElement from "../common/create-card-element"; +import { createCardElement } from "../common/create-card-element"; import { computeCardSize } from "../common/compute-card-size"; import { HomeAssistant } from "../../../types"; import { LovelaceCard } from "../types"; diff --git a/src/panels/lovelace/cards/hui-entities-card.ts b/src/panels/lovelace/cards/hui-entities-card.ts index 1462183742..13e36603f6 100644 --- a/src/panels/lovelace/cards/hui-entities-card.ts +++ b/src/panels/lovelace/cards/hui-entities-card.ts @@ -17,7 +17,7 @@ import { EntityConfig, EntityRow } from "../entity-rows/types"; import { LovelaceCard, LovelaceCardEditor } from "../types"; import { LovelaceCardConfig } from "../../../data/lovelace"; import { processConfigEntities } from "../common/process-config-entities"; -import createRowElement from "../common/create-row-element"; +import { createRowElement } from "../common/create-row-element"; import computeDomain from "../../../common/entity/compute_domain"; import applyThemesOnElement from "../../../common/dom/apply_themes_on_element"; @@ -40,7 +40,7 @@ export interface Config extends LovelaceCardConfig { class HuiEntitiesCard extends hassLocalizeLitMixin(LitElement) implements LovelaceCard { public static async getConfigElement(): Promise { - await import("../editor/config-elements/hui-entities-card-editor"); + await import(/* webpackChunkName: "hui-entities-card-editor" */ "../editor/config-elements/hui-entities-card-editor"); return document.createElement("hui-entities-card-editor"); } diff --git a/src/panels/lovelace/cards/hui-entity-button-card.ts b/src/panels/lovelace/cards/hui-entity-button-card.ts index d2b208ae40..31c826c77c 100644 --- a/src/panels/lovelace/cards/hui-entity-button-card.ts +++ b/src/panels/lovelace/cards/hui-entity-button-card.ts @@ -17,12 +17,12 @@ import computeStateName from "../../../common/entity/compute_state_name"; import applyThemesOnElement from "../../../common/dom/apply_themes_on_element"; import { HomeAssistant, LightEntity } from "../../../types"; import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; -import { LovelaceCard } from "../types"; +import { LovelaceCard, LovelaceCardEditor } from "../types"; import { LovelaceCardConfig, ActionConfig } from "../../../data/lovelace"; import { longPress } from "../common/directives/long-press-directive"; import { handleClick } from "../common/handle-click"; -interface Config extends LovelaceCardConfig { +export interface Config extends LovelaceCardConfig { entity: string; name?: string; icon?: string; @@ -33,6 +33,18 @@ interface Config extends LovelaceCardConfig { class HuiEntityButtonCard extends hassLocalizeLitMixin(LitElement) implements LovelaceCard { + public static async getConfigElement(): Promise { + await import(/* webpackChunkName: "hui-entity-button-card-editor" */ "../editor/config-elements/hui-entity-button-card-editor"); + return document.createElement("hui-entity-button-card-editor"); + } + + public static getStubConfig(): object { + return { + tap_action: { action: "more-info" }, + hold_action: { action: "none" }, + }; + } + public hass?: HomeAssistant; private _config?: Config; diff --git a/src/panels/lovelace/cards/hui-entity-filter-card.js b/src/panels/lovelace/cards/hui-entity-filter-card.js index 841dfaa0c5..7b854b9e5b 100644 --- a/src/panels/lovelace/cards/hui-entity-filter-card.js +++ b/src/panels/lovelace/cards/hui-entity-filter-card.js @@ -1,6 +1,6 @@ import { PolymerElement } from "@polymer/polymer/polymer-element"; -import createCardElement from "../common/create-card-element"; +import { createCardElement } from "../common/create-card-element"; import { processConfigEntities } from "../common/process-config-entities"; function getEntities(hass, filterState, entities) { diff --git a/src/panels/lovelace/cards/hui-error-card.ts b/src/panels/lovelace/cards/hui-error-card.ts index 4cbcedb3eb..59981fc7fb 100644 --- a/src/panels/lovelace/cards/hui-error-card.ts +++ b/src/panels/lovelace/cards/hui-error-card.ts @@ -3,13 +3,27 @@ import { html, LitElement } from "@polymer/lit-element"; import { LovelaceCard } from "../types"; import { LovelaceCardConfig } from "../../../data/lovelace"; import { TemplateResult } from "lit-html"; +import { HomeAssistant } from "../../../types"; interface Config extends LovelaceCardConfig { error: string; origConfig: LovelaceCardConfig; } -class HuiErrorCard extends LitElement implements LovelaceCard { +export const createErrorCardElement = (config) => { + const el = document.createElement("hui-error-card"); + el.setConfig(config); + return el; +}; + +export const createErrorCardConfig = (error, origConfig) => ({ + type: "error", + error, + origConfig, +}); + +export class HuiErrorCard extends LitElement implements LovelaceCard { + public hass?: HomeAssistant; private _config?: Config; static get properties() { diff --git a/src/panels/lovelace/cards/hui-gauge-card.ts b/src/panels/lovelace/cards/hui-gauge-card.ts index 3872d6ca12..27c1f1bb28 100644 --- a/src/panels/lovelace/cards/hui-gauge-card.ts +++ b/src/panels/lovelace/cards/hui-gauge-card.ts @@ -5,30 +5,40 @@ import { PropertyValues, } from "@polymer/lit-element"; import { TemplateResult } from "lit-html"; +import { styleMap } from "lit-html/directives/styleMap"; -import { LovelaceCard } from "../types"; +import "../../../components/ha-card"; import { LovelaceCardConfig } from "../../../data/lovelace"; import { HomeAssistant } from "../../../types"; import { fireEvent } from "../../../common/dom/fire_event"; import { hasConfigOrEntityChanged } from "../common/has-changed"; - import isValidEntityId from "../../../common/entity/valid_entity_id"; import applyThemesOnElement from "../../../common/dom/apply_themes_on_element"; import computeStateName from "../../../common/entity/compute_state_name"; -import "../../../components/ha-card"; +import { LovelaceCard, LovelaceCardEditor } from "../types"; +import { + createErrorCardConfig, + createErrorCardElement, +} from "./hui-error-card"; -interface Config extends LovelaceCardConfig { +export interface SeverityConfig { + green?: number; + yellow?: number; + red?: number; +} + +export interface Config extends LovelaceCardConfig { entity: string; name?: string; unit?: string; min?: number; max?: number; - severity?: object; + severity?: SeverityConfig; theme?: string; } -const severityMap = { +export const severityMap = { red: "var(--label-badge-red)", green: "var(--label-badge-green)", yellow: "var(--label-badge-yellow)", @@ -36,6 +46,14 @@ const severityMap = { }; class HuiGaugeCard extends LitElement implements LovelaceCard { + public static async getConfigElement(): Promise { + await import(/* webpackChunkName: "hui-gauge-card-editor" */ "../editor/config-elements/hui-gauge-card-editor"); + return document.createElement("hui-gauge-card-editor"); + } + public static getStubConfig(): object { + return {}; + } + public hass?: HomeAssistant; private _config?: Config; @@ -65,11 +83,23 @@ class HuiGaugeCard extends LitElement implements LovelaceCard { return html``; } const stateObj = this.hass.states[this._config.entity]; + let state; let error; + if (!stateObj) { error = "Entity not available: " + this._config.entity; - } else if (isNaN(Number(stateObj.state))) { - error = "Entity is non-numeric: " + this._config.entity; + } else { + state = Number(stateObj.state); + + if (isNaN(state)) { + error = "Entity is non-numeric: " + this._config.entity; + } + } + + if (error) { + return html` + ${createErrorCardElement(createErrorCardConfig(error, this._config))} + `; } return html` @@ -84,7 +114,15 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
-
+
${stateObj.state} @@ -109,38 +147,74 @@ class HuiGaugeCard extends LitElement implements LovelaceCard { return hasConfigOrEntityChanged(this, changedProps); } - protected updated(changedProps: PropertyValues): void { - super.updated(changedProps); - if (!this._config || !this.hass) { - return; - } - const stateObj = this.hass.states[this._config.entity]; - if (isNaN(Number(stateObj.state))) { - return; - } - - const turn = this._translateTurn(Number(stateObj.state), this._config); - - this.shadowRoot!.getElementById( - "gauge" - )!.style.cssText = `transform: rotate(${turn}turn); background-color: ${this._computeSeverity( - stateObj.state, - this._config.severity! - )}`; - + protected firstUpdated(): void { (this.shadowRoot!.querySelector( "ha-card" )! as HTMLElement).style.setProperty( "--base-unit", this._computeBaseUnit() ); + } + + protected updated(changedProps: PropertyValues): void { + super.updated(changedProps); + if (!this._config || !this.hass) { + return; + } const oldHass = changedProps.get("hass") as HomeAssistant | undefined; + if (!oldHass || oldHass.themes !== this.hass.themes) { applyThemesOnElement(this, this.hass.themes, this._config.theme); } } + private _computeSeverity(numberValue: number): string { + const sections = this._config!.severity; + + if (!sections) { + return severityMap.normal; + } + + const sectionsArray = Object.keys(sections); + const sortable = sectionsArray.map((severity) => [ + severity, + sections[severity], + ]); + + for (const severity of sortable) { + if (severityMap[severity[0]] == null || isNaN(severity[1])) { + return severityMap.normal; + } + } + sortable.sort((a, b) => a[1] - b[1]); + + if (numberValue >= sortable[0][1] && numberValue < sortable[1][1]) { + return severityMap[sortable[0][0]]; + } + if (numberValue >= sortable[1][1] && numberValue < sortable[2][1]) { + return severityMap[sortable[1][0]]; + } + if (numberValue >= sortable[2][1]) { + return severityMap[sortable[2][0]]; + } + return severityMap.normal; + } + + private _translateTurn(value: number): number { + const { min, max } = this._config!; + const maxTurnValue = Math.min(Math.max(value, min!), max!); + return (5 * (maxTurnValue - min!)) / (max! - min!) / 10; + } + + private _computeBaseUnit(): string { + return this.clientWidth < 200 ? this.clientWidth / 5 + "px" : "50px"; + } + + private _handleClick(): void { + fireEvent(this, "hass-more-info", { entityId: this._config!.entity }); + } + private renderStyle(): TemplateResult { return html` `; } - - private _computeSeverity(stateValue: string, sections: object): string { - const numberValue = Number(stateValue); - - if (!sections) { - return severityMap.normal; - } - - const sectionsArray = Object.keys(sections); - const sortable = sectionsArray.map((severity) => [ - severity, - sections[severity], - ]); - - for (const severity of sortable) { - if (severityMap[severity[0]] == null || isNaN(severity[1])) { - return severityMap.normal; - } - } - sortable.sort((a, b) => a[1] - b[1]); - - if (numberValue >= sortable[0][1] && numberValue < sortable[1][1]) { - return severityMap[sortable[0][0]]; - } - if (numberValue >= sortable[1][1] && numberValue < sortable[2][1]) { - return severityMap[sortable[1][0]]; - } - if (numberValue >= sortable[2][1]) { - return severityMap[sortable[2][0]]; - } - return severityMap.normal; - } - - private _translateTurn(value: number, config: Config): number { - const maxTurnValue = Math.min(Math.max(value, config.min!), config.max!); - return ( - (5 * (maxTurnValue - config.min!)) / (config.max! - config.min!) / 10 - ); - } - - private _computeBaseUnit(): string { - return this.clientWidth < 200 ? this.clientWidth / 5 + "px" : "50px"; - } - - private _handleClick(): void { - fireEvent(this, "hass-more-info", { entityId: this._config!.entity }); - } } declare global { diff --git a/src/panels/lovelace/cards/hui-glance-card.ts b/src/panels/lovelace/cards/hui-glance-card.ts index 537bc405e2..7d07f62b49 100644 --- a/src/panels/lovelace/cards/hui-glance-card.ts +++ b/src/panels/lovelace/cards/hui-glance-card.ts @@ -41,7 +41,7 @@ export interface Config extends LovelaceCardConfig { export class HuiGlanceCard extends hassLocalizeLitMixin(LitElement) implements LovelaceCard { public static async getConfigElement(): Promise { - await import("../editor/config-elements/hui-glance-card-editor"); + await import(/* webpackChunkName: "hui-glance-card-editor" */ "../editor/config-elements/hui-glance-card-editor"); return document.createElement("hui-glance-card-editor"); } public static getStubConfig(): object { diff --git a/src/panels/lovelace/cards/hui-iframe-card.ts b/src/panels/lovelace/cards/hui-iframe-card.ts index 93a643fc53..0b43162179 100644 --- a/src/panels/lovelace/cards/hui-iframe-card.ts +++ b/src/panels/lovelace/cards/hui-iframe-card.ts @@ -2,18 +2,26 @@ import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import "../../../components/ha-card"; -import { LovelaceCard } from "../types"; +import { LovelaceCard, LovelaceCardEditor } from "../types"; import { LovelaceCardConfig } from "../../../data/lovelace"; import { TemplateResult } from "lit-html"; import { styleMap } from "lit-html/directives/styleMap"; -interface Config extends LovelaceCardConfig { +export interface Config extends LovelaceCardConfig { aspect_ratio?: string; title?: string; url: string; } export class HuiIframeCard extends LitElement implements LovelaceCard { + public static async getConfigElement(): Promise { + await import(/* webpackChunkName: "hui-iframe-card-editor" */ "../editor/config-elements/hui-iframe-card-editor"); + return document.createElement("hui-iframe-card-editor"); + } + public static getStubConfig(): object { + return { url: "https://www.home-assistant.io", aspect_ratio: "50%" }; + } + protected _config?: Config; static get properties(): PropertyDeclarations { diff --git a/src/panels/lovelace/cards/hui-legacy-wrapper-card.js b/src/panels/lovelace/cards/hui-legacy-wrapper-card.js index cdd4de0554..c1b36d2e78 100644 --- a/src/panels/lovelace/cards/hui-legacy-wrapper-card.js +++ b/src/panels/lovelace/cards/hui-legacy-wrapper-card.js @@ -1,4 +1,4 @@ -import createErrorCardConfig from "../common/create-error-card-config"; +import { createErrorCardConfig } from "./hui-error-card"; import computeDomain from "../../../common/entity/compute_domain"; export default class LegacyWrapperCard extends HTMLElement { diff --git a/src/panels/lovelace/cards/hui-light-card.ts b/src/panels/lovelace/cards/hui-light-card.ts index dc0ecad1e4..22e58e4ca8 100644 --- a/src/panels/lovelace/cards/hui-light-card.ts +++ b/src/panels/lovelace/cards/hui-light-card.ts @@ -10,7 +10,7 @@ import { fireEvent } from "../../../common/dom/fire_event"; import { styleMap } from "lit-html/directives/styleMap"; import { HomeAssistant, LightEntity } from "../../../types"; import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; -import { LovelaceCard } from "../types"; +import { LovelaceCard, LovelaceCardEditor } from "../types"; import { LovelaceCardConfig } from "../../../data/lovelace"; import { longPress } from "../common/directives/long-press-directive"; import { hasConfigOrEntityChanged } from "../common/has-changed"; @@ -39,7 +39,7 @@ const lightConfig = { animation: false, }; -interface Config extends LovelaceCardConfig { +export interface Config extends LovelaceCardConfig { entity: string; name?: string; theme?: string; @@ -47,6 +47,14 @@ interface Config extends LovelaceCardConfig { export class HuiLightCard extends hassLocalizeLitMixin(LitElement) implements LovelaceCard { + public static async getConfigElement(): Promise { + await import(/* webpackChunkName: "hui-light-card-editor" */ "../editor/config-elements/hui-light-card-editor"); + return document.createElement("hui-light-card-editor"); + } + public static getStubConfig(): object { + return {}; + } + public hass?: HomeAssistant; private _config?: Config; private _brightnessTimout?: number; diff --git a/src/panels/lovelace/cards/hui-map-card.js b/src/panels/lovelace/cards/hui-map-card.js index 32a7631c6d..a77a0afc01 100644 --- a/src/panels/lovelace/cards/hui-map-card.js +++ b/src/panels/lovelace/cards/hui-map-card.js @@ -11,7 +11,24 @@ import computeStateName from "../../../common/entity/compute_state_name"; import debounce from "../../../common/util/debounce"; import parseAspectRatio from "../../../common/util/parse-aspect-ratio"; +// should be interface when converted to TS +export const Config = { + title: "", + aspect_ratio: "", + default_zoom: 14, + entities: [], +}; + class HuiMapCard extends PolymerElement { + static async getConfigElement() { + await import(/* webpackChunkName: "hui-map-card-editor" */ "../editor/config-elements/hui-map-card-editor"); + return document.createElement("hui-map-card-editor"); + } + + static getStubConfig() { + return { entities: [] }; + } + static get template() { return html` `; } - - private _dragEvent(e): void { - this.shadowRoot!.querySelector("#set-temperature")!.innerHTML = formatTemp( - String(e.value).split(",") - ); - } - - private _setTemperature(e): void { - const stateObj = this.hass!.states[this._config!.entity] as ClimateEntity; - if ( - stateObj.attributes.target_temp_low && - stateObj.attributes.target_temp_high - ) { - if (e.handle.index === 1) { - this.hass!.callService("climate", "set_temperature", { - entity_id: this._config!.entity, - target_temp_low: e.handle.value, - target_temp_high: stateObj.attributes.target_temp_high, - }); - } else { - this.hass!.callService("climate", "set_temperature", { - entity_id: this._config!.entity, - target_temp_low: stateObj.attributes.target_temp_low, - target_temp_high: e.handle.value, - }); - } - } else { - this.hass!.callService("climate", "set_temperature", { - entity_id: this._config!.entity, - temperature: e.value, - }); - } - } - - private _renderIcon(mode: string, currentMode: string): TemplateResult { - if (!modeIcons[mode]) { - return html``; - } - return html` - - `; - } - - private _handleModeClick(e: MouseEvent): void { - this.hass!.callService("climate", "set_operation_mode", { - entity_id: this._config!.entity, - operation_mode: (e.currentTarget as any).mode, - }); - } } declare global { diff --git a/src/panels/lovelace/cards/hui-weather-forecast-card.js b/src/panels/lovelace/cards/hui-weather-forecast-card.js index 091aced005..0ef2b8c2c2 100644 --- a/src/panels/lovelace/cards/hui-weather-forecast-card.js +++ b/src/panels/lovelace/cards/hui-weather-forecast-card.js @@ -2,7 +2,22 @@ import "../../../cards/ha-weather-card"; import LegacyWrapperCard from "./hui-legacy-wrapper-card"; +// should be interface when converted to TS +export const Config = { + entity: "", + name: "", +}; + class HuiWeatherForecastCard extends LegacyWrapperCard { + static async getConfigElement() { + await import(/* webpackChunkName: "hui-weather-forecast-card-editor" */ "../editor/config-elements/hui-weather-forecast-card-editor"); + return document.createElement("hui-weather-forecast-card-editor"); + } + + static getStubConfig() { + return {}; + } + constructor() { super("ha-weather-card", "weather"); } diff --git a/src/panels/lovelace/common/compute-unused-entities.js b/src/panels/lovelace/common/compute-unused-entities.js deleted file mode 100644 index faaedcc3a7..0000000000 --- a/src/panels/lovelace/common/compute-unused-entities.js +++ /dev/null @@ -1,38 +0,0 @@ -const EXCLUDED_DOMAINS = ["zone"]; - -function computeUsedEntities(config) { - const entities = new Set(); - - function addEntityId(entity) { - if (typeof entity === "string") { - entities.add(entity); - } else if (entity.entity) { - entities.add(entity.entity); - } - } - - function addEntities(obj) { - if (obj.entity) addEntityId(obj.entity); - if (obj.entities) obj.entities.forEach((entity) => addEntityId(entity)); - if (obj.card) addEntities(obj.card); - if (obj.cards) obj.cards.forEach((card) => addEntities(card)); - if (obj.badges) obj.badges.forEach((badge) => addEntityId(badge)); - } - - config.views.forEach((view) => addEntities(view)); - return entities; -} - -export default function computeUnusedEntities(hass, config) { - const usedEntities = computeUsedEntities(config); - return Object.keys(hass.states) - .filter( - (entity) => - !usedEntities.has(entity) && - !( - config.excluded_entities && config.excluded_entities.includes(entity) - ) && - !EXCLUDED_DOMAINS.includes(entity.split(".", 1)[0]) - ) - .sort(); -} diff --git a/src/panels/lovelace/common/compute-unused-entities.ts b/src/panels/lovelace/common/compute-unused-entities.ts new file mode 100644 index 0000000000..0c4fd0c331 --- /dev/null +++ b/src/panels/lovelace/common/compute-unused-entities.ts @@ -0,0 +1,54 @@ +import { LovelaceConfig } from "../../../data/lovelace"; +import { HomeAssistant } from "../../../types"; + +const EXCLUDED_DOMAINS = ["zone"]; + +const computeUsedEntities = (config) => { + const entities = new Set(); + + const addEntityId = (entity) => { + if (typeof entity === "string") { + entities.add(entity); + } else if (entity.entity) { + entities.add(entity.entity); + } + }; + + const addEntities = (obj) => { + if (obj.entity) { + addEntityId(obj.entity); + } + if (obj.entities) { + obj.entities.forEach((entity) => addEntityId(entity)); + } + if (obj.card) { + addEntities(obj.card); + } + if (obj.cards) { + obj.cards.forEach((card) => addEntities(card)); + } + if (obj.badges) { + obj.badges.forEach((badge) => addEntityId(badge)); + } + }; + + config.views.forEach((view) => addEntities(view)); + return entities; +}; + +export const computeUnusedEntities = ( + hass: HomeAssistant, + config: LovelaceConfig +): string[] => { + const usedEntities = computeUsedEntities(config); + return Object.keys(hass.states) + .filter( + (entity) => + !usedEntities.has(entity) && + !( + config.excluded_entities && config.excluded_entities.includes(entity) + ) && + !EXCLUDED_DOMAINS.includes(entity.split(".", 1)[0]) + ) + .sort(); +}; diff --git a/src/panels/lovelace/common/create-card-element.js b/src/panels/lovelace/common/create-card-element.ts similarity index 79% rename from src/panels/lovelace/common/create-card-element.js rename to src/panels/lovelace/common/create-card-element.ts index 60ff05659f..0b7c18a562 100644 --- a/src/panels/lovelace/common/create-card-element.js +++ b/src/panels/lovelace/common/create-card-element.ts @@ -5,7 +5,11 @@ import "../cards/hui-conditional-card"; import "../cards/hui-entities-card"; import "../cards/hui-entity-button-card"; import "../cards/hui-entity-filter-card"; -import "../cards/hui-error-card"; +import { + createErrorCardElement, + createErrorCardConfig, + HuiErrorCard, +} from "../cards/hui-error-card"; import "../cards/hui-glance-card"; import "../cards/hui-history-graph-card"; import "../cards/hui-horizontal-stack-card"; @@ -25,8 +29,8 @@ import "../cards/hui-shopping-list-card"; import "../cards/hui-thermostat-card"; import "../cards/hui-weather-forecast-card"; import "../cards/hui-gauge-card"; - -import createErrorCardConfig from "./create-error-card-config"; +import { LovelaceCard } from "../types"; +import { LovelaceCardConfig } from "../../../data/lovelace"; const CARD_TYPES = new Set([ "alarm-panel", @@ -58,24 +62,29 @@ const CARD_TYPES = new Set([ const CUSTOM_TYPE_PREFIX = "custom:"; const TIMEOUT = 2000; -function _createElement(tag, config) { - const element = document.createElement(tag); +const _createElement = ( + tag: string, + config: LovelaceCardConfig +): LovelaceCard | HuiErrorCard => { + const element = document.createElement(tag) as LovelaceCard; try { element.setConfig(config); } catch (err) { - // eslint-disable-next-line + // tslint:disable-next-line console.error(tag, err); - // eslint-disable-next-line return _createErrorElement(err.message, config); } return element; -} +}; -function _createErrorElement(error, config) { - return _createElement("hui-error-card", createErrorCardConfig(error, config)); -} +const _createErrorElement = ( + error: string, + config: LovelaceCardConfig +): HuiErrorCard => createErrorCardElement(createErrorCardConfig(error, config)); -export default function createCardElement(config) { +export const createCardElement = ( + config: LovelaceCardConfig +): LovelaceCard | HuiErrorCard => { if (!config || typeof config !== "object" || !config.type) { return _createErrorElement("No card type configured.", config); } @@ -111,4 +120,4 @@ export default function createCardElement(config) { } return _createElement(`hui-${config.type}-card`, config); -} +}; diff --git a/src/panels/lovelace/common/create-error-card-config.js b/src/panels/lovelace/common/create-error-card-config.js deleted file mode 100644 index f8bf388f81..0000000000 --- a/src/panels/lovelace/common/create-error-card-config.js +++ /dev/null @@ -1,7 +0,0 @@ -export default function createErrorConfig(error, origConfig) { - return { - type: "error", - error, - origConfig, - }; -} diff --git a/src/panels/lovelace/common/create-hui-element.js b/src/panels/lovelace/common/create-hui-element.ts similarity index 72% rename from src/panels/lovelace/common/create-hui-element.js rename to src/panels/lovelace/common/create-hui-element.ts index c9d10924db..eac2082a3d 100644 --- a/src/panels/lovelace/common/create-hui-element.js +++ b/src/panels/lovelace/common/create-hui-element.ts @@ -6,7 +6,12 @@ import "../elements/hui-state-icon-element"; import "../elements/hui-state-label-element"; import { fireEvent } from "../../../common/dom/fire_event"; -import createErrorCardConfig from "./create-error-card-config"; +import { + createErrorCardElement, + createErrorCardConfig, + HuiErrorCard, +} from "../cards/hui-error-card"; +import { LovelaceElementConfig, LovelaceElement } from "../elements/types"; const CUSTOM_TYPE_PREFIX = "custom:"; const ELEMENT_TYPES = new Set([ @@ -19,22 +24,25 @@ const ELEMENT_TYPES = new Set([ ]); const TIMEOUT = 2000; -function _createElement(tag, config) { - const element = document.createElement(tag); +const _createElement = ( + tag: string, + config: LovelaceElementConfig +): LovelaceElement | HuiErrorCard => { + const element = document.createElement(tag) as LovelaceElement; try { element.setConfig(config); } catch (err) { - // eslint-disable-next-line + // tslint:disable-next-line console.error(tag, err); - // eslint-disable-next-line return _createErrorElement(err.message, config); } return element; -} +}; -function _createErrorElement(error, config) { - return _createElement("hui-error-card", createErrorCardConfig(error, config)); -} +const _createErrorElement = ( + error: string, + config: LovelaceElementConfig +): HuiErrorCard => createErrorCardElement(createErrorCardConfig(error, config)); function _hideErrorElement(element) { element.style.display = "None"; @@ -43,7 +51,9 @@ function _hideErrorElement(element) { }, TIMEOUT); } -export default function createHuiElement(config) { +export const createHuiElement = ( + config: LovelaceElementConfig +): LovelaceElement | HuiErrorCard => { if (!config || typeof config !== "object" || !config.type) { return _createErrorElement("No element type configured.", config); } @@ -76,4 +86,4 @@ export default function createHuiElement(config) { } return _createElement(`hui-${config.type}-element`, config); -} +}; diff --git a/src/panels/lovelace/common/create-row-element.js b/src/panels/lovelace/common/create-row-element.ts similarity index 80% rename from src/panels/lovelace/common/create-row-element.js rename to src/panels/lovelace/common/create-row-element.ts index c9d1fa072c..a2bc40f8d0 100644 --- a/src/panels/lovelace/common/create-row-element.js +++ b/src/panels/lovelace/common/create-row-element.ts @@ -1,5 +1,10 @@ import { fireEvent } from "../../../common/dom/fire_event"; +import { + createErrorCardElement, + createErrorCardConfig, + HuiErrorCard, +} from "../cards/hui-error-card"; import "../entity-rows/hui-climate-entity-row"; import "../entity-rows/hui-cover-entity-row"; import "../entity-rows/hui-group-entity-row"; @@ -18,8 +23,7 @@ import "../special-rows/hui-call-service-row"; import "../special-rows/hui-divider-row"; import "../special-rows/hui-section-row"; import "../special-rows/hui-weblink-row"; - -import createErrorCardConfig from "./create-error-card-config"; +import { EntityConfig, EntityRow } from "../entity-rows/types"; const CUSTOM_TYPE_PREFIX = "custom:"; const SPECIAL_TYPES = new Set([ @@ -51,32 +55,37 @@ const DOMAIN_TO_ELEMENT_TYPE = { }; const TIMEOUT = 2000; -function _createElement(tag, config) { - const element = document.createElement(tag); +const _createElement = ( + tag: string, + config: EntityConfig +): EntityRow | HuiErrorCard => { + const element = document.createElement(tag) as EntityRow; try { - if ("setConfig" in element) element.setConfig(config); + element.setConfig(config); } catch (err) { - // eslint-disable-next-line + // tslint:disable-next-line console.error(tag, err); - // eslint-disable-next-line return _createErrorElement(err.message, config); } return element; -} +}; -function _createErrorElement(error, config) { - return _createElement("hui-error-card", createErrorCardConfig(error, config)); -} +const _createErrorElement = ( + error: string, + config: EntityConfig +): HuiErrorCard => createErrorCardElement(createErrorCardConfig(error, config)); -function _hideErrorElement(element) { +const _hideErrorElement = (element) => { element.style.display = "None"; return window.setTimeout(() => { element.style.display = ""; }, TIMEOUT); -} +}; -export default function createRowElement(config) { +export const createRowElement = ( + config: EntityConfig +): EntityRow | HuiErrorCard => { let tag; if ( @@ -116,4 +125,4 @@ export default function createRowElement(config) { tag = `hui-${DOMAIN_TO_ELEMENT_TYPE[domain] || "text"}-entity-row`; return _createElement(tag, config); -} +}; diff --git a/src/panels/lovelace/common/generate-lovelace-config.ts b/src/panels/lovelace/common/generate-lovelace-config.ts index 448f4507f3..def2e228dd 100644 --- a/src/panels/lovelace/common/generate-lovelace-config.ts +++ b/src/panels/lovelace/common/generate-lovelace-config.ts @@ -42,6 +42,11 @@ const computeCards = ( type: "alarm-panel", entity: entityId, }); + } else if (domain === "camera") { + cards.push({ + type: "picture-entity", + entity: entityId, + }); } else if (domain === "climate") { cards.push({ type: "thermostat", diff --git a/src/panels/lovelace/common/is-valid-object.js b/src/panels/lovelace/common/is-valid-object.ts similarity index 100% rename from src/panels/lovelace/common/is-valid-object.js rename to src/panels/lovelace/common/is-valid-object.ts diff --git a/src/panels/lovelace/components/hui-action-editor.ts b/src/panels/lovelace/components/hui-action-editor.ts new file mode 100644 index 0000000000..7058a2ab73 --- /dev/null +++ b/src/panels/lovelace/components/hui-action-editor.ts @@ -0,0 +1,132 @@ +import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; +import "@polymer/paper-input/paper-textarea"; +import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; +import "@polymer/paper-item/paper-item"; +import "@polymer/paper-listbox/paper-listbox"; + +import "../../../components/ha-service-picker"; + +import { HomeAssistant } from "../../../types"; +import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event"; +import { EditorTarget } from "../editor/types"; +import { + ActionConfig, + NavigateActionConfig, + CallServiceActionConfig, +} from "../../../data/lovelace"; + +declare global { + // for fire event + interface HASSDomEvents { + "action-changed": undefined; + } + // for add event listener + interface HTMLElementEventMap { + "action-changed": HASSDomEvent; + } +} + +export class HuiActionEditor extends LitElement { + public config?: ActionConfig; + public label?: string; + public actions?: string[]; + protected hass?: HomeAssistant; + + static get properties(): PropertyDeclarations { + return { hass: {}, config: {}, label: {}, actions: {} }; + } + + get _action(): string { + return this.config!.action || ""; + } + + get _navigation_path(): string { + const config = this.config! as NavigateActionConfig; + return config.navigation_path || ""; + } + + get _service(): string { + const config = this.config! as CallServiceActionConfig; + return config.service || ""; + } + + protected render(): TemplateResult { + if (!this.hass || !this.actions) { + return html``; + } + return html` + + + ${ + this.actions.map((action) => { + return html` + ${action} + `; + }) + } + + + ${ + this._action === "navigate" + ? html` + + ` + : "" + } + ${ + this.config && this.config.action === "call-service" + ? html` + +

Toggle Editor to input Service Data

+ ` + : "" + } + `; + } + + private _valueChanged(ev: Event): void { + if (!this.hass) { + return; + } + const target = ev.target! as EditorTarget; + if ( + this.config && + this.config[this[`${target.configValue}`]] === target.value + ) { + return; + } + if (target.configValue === "action") { + this.config = { action: "none" }; + } + if (target.configValue) { + this.config = { ...this.config!, [target.configValue!]: target.value }; + fireEvent(this, "action-changed"); + } + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-action-editor": HuiActionEditor; + } +} + +customElements.define("hui-action-editor", HuiActionEditor); diff --git a/src/panels/lovelace/components/hui-card-options.ts b/src/panels/lovelace/components/hui-card-options.ts index 53a8ee0ea9..4cff932dc7 100644 --- a/src/panels/lovelace/components/hui-card-options.ts +++ b/src/panels/lovelace/components/hui-card-options.ts @@ -1,20 +1,23 @@ import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import "@polymer/paper-button/paper-button"; +import "@polymer/paper-menu-button/paper-menu-button"; import "@polymer/paper-icon-button/paper-icon-button"; -import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog"; +import "@polymer/paper-listbox/paper-listbox"; +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"; import { Lovelace } from "../types"; import { swapCard } from "../editor/config-util"; +import { showMoveCardViewDialog } from "../editor/card-editor/show-move-card-view-dialog"; export class HuiCardOptions extends hassLocalizeLitMixin(LitElement) { public cardConfig?: LovelaceCardConfig; - protected hass?: HomeAssistant; - protected lovelace?: Lovelace; - protected path?: [number, number]; + public hass?: HomeAssistant; + public lovelace?: Lovelace; + public path?: [number, number]; static get properties(): PropertyDeclarations { return { hass: {}, lovelace: {}, path: {} }; @@ -23,49 +26,93 @@ export class HuiCardOptions extends hassLocalizeLitMixin(LitElement) { protected render() { return html` -
- ${ - this.localize("ui.panel.lovelace.editor.edit_card.edit") - } - - - +
+
+ ${ + this.localize("ui.panel.lovelace.editor.edit_card.edit") + } +
+
+ + + + + + Move Card + ${ + this.localize("ui.panel.lovelace.editor.edit_card.delete") + } + + +
`; } @@ -93,6 +140,13 @@ export class HuiCardOptions extends hassLocalizeLitMixin(LitElement) { ); } + private _moveCard(): void { + showMoveCardViewDialog(this, { + path: this.path!, + lovelace: this.lovelace!, + }); + } + private _deleteCard(): void { confDeleteCard(this.lovelace!, this.path!); } diff --git a/src/panels/lovelace/components/hui-theme-select-editor.ts b/src/panels/lovelace/components/hui-theme-select-editor.ts index 206919b213..68c452c3d2 100644 --- a/src/panels/lovelace/components/hui-theme-select-editor.ts +++ b/src/panels/lovelace/components/hui-theme-select-editor.ts @@ -19,7 +19,7 @@ declare global { export class HuiThemeSelectionEditor extends hassLocalizeLitMixin(LitElement) { public value?: string; - protected hass?: HomeAssistant; + public hass?: HomeAssistant; static get properties(): PropertyDeclarations { return { diff --git a/src/panels/lovelace/components/notifications/hui-notifications-button.js b/src/panels/lovelace/components/notifications/hui-notifications-button.js index f0a147664f..ddd8cc8ad6 100644 --- a/src/panels/lovelace/components/notifications/hui-notifications-button.js +++ b/src/panels/lovelace/components/notifications/hui-notifications-button.js @@ -46,7 +46,7 @@ export class HuiNotificationsButton extends EventsMixin(PolymerElement) { static get properties() { return { - notificationsOpen: { + open: { type: Boolean, notify: true, }, @@ -58,7 +58,7 @@ export class HuiNotificationsButton extends EventsMixin(PolymerElement) { } _clicked() { - this.notificationsOpen = true; + this.open = true; } _hasNotifications(notifications) { diff --git a/src/panels/lovelace/editor/card-editor/hui-card-preview.ts b/src/panels/lovelace/editor/card-editor/hui-card-preview.ts index 26552fc705..7f037282df 100644 --- a/src/panels/lovelace/editor/card-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 { createCardElement } from "../../common/create-card-element"; 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 { createErrorCardConfig } from "../../cards/hui-error-card"; export class HuiCardPreview extends HTMLElement { private _hass?: HomeAssistant; diff --git a/src/panels/lovelace/editor/card-editor/hui-dialog-move-card-view.ts b/src/panels/lovelace/editor/card-editor/hui-dialog-move-card-view.ts new file mode 100644 index 0000000000..7f88c436cf --- /dev/null +++ b/src/panels/lovelace/editor/card-editor/hui-dialog-move-card-view.ts @@ -0,0 +1,106 @@ +import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; +import "@polymer/paper-dialog/paper-dialog"; +import "@polymer/paper-item/paper-item"; +// tslint:disable-next-line:no-duplicate-imports +import { PaperDialogElement } from "@polymer/paper-dialog/paper-dialog"; + +import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin"; +import { moveCard } from "../config-util"; +import { MoveCardViewDialogParams } from "./show-move-card-view-dialog"; + +export class HuiDialogMoveCardView extends hassLocalizeLitMixin(LitElement) { + private _params?: MoveCardViewDialogParams; + + static get properties(): PropertyDeclarations { + return { + _params: {}, + }; + } + + public async showDialog(params: MoveCardViewDialogParams): Promise { + this._params = params; + await this.updateComplete; + } + + protected render(): TemplateResult { + if (!this._params) { + return html``; + } + return html` + + +

Choose view to move card

+ ${ + this._params!.lovelace!.config.views.map((view, index) => { + return html` + ${view.title} + `; + }) + } +
+ `; + } + + private get _dialog(): PaperDialogElement { + return this.shadowRoot!.querySelector("paper-dialog")!; + } + + private _moveCard(e: Event): void { + const newView = (e.currentTarget! as any).index; + const path = this._params!.path!; + if (newView === path[0]) { + return; + } + + const lovelace = this._params!.lovelace!; + + lovelace.saveConfig(moveCard(lovelace.config, path, [newView!])); + this._dialog.close(); + } + + private _openedChanged(ev: MouseEvent) { + if (!(ev.detail as any).value) { + this._params = undefined; + } + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-dialog-move-card-view": HuiDialogMoveCardView; + } +} + +customElements.define("hui-dialog-move-card-view", HuiDialogMoveCardView); diff --git a/src/panels/lovelace/editor/card-editor/hui-edit-card.ts b/src/panels/lovelace/editor/card-editor/hui-edit-card.ts index c793a97376..bd63979d8f 100644 --- a/src/panels/lovelace/editor/card-editor/hui-edit-card.ts +++ b/src/panels/lovelace/editor/card-editor/hui-edit-card.ts @@ -160,6 +160,7 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) { ? html`
`; } diff --git a/src/panels/lovelace/editor/card-editor/show-edit-card-dialog.ts b/src/panels/lovelace/editor/card-editor/show-edit-card-dialog.ts index 8fb12d0f01..0eb932f758 100644 --- a/src/panels/lovelace/editor/card-editor/show-edit-card-dialog.ts +++ b/src/panels/lovelace/editor/card-editor/show-edit-card-dialog.ts @@ -21,7 +21,8 @@ const registerEditCardDialog = (element: HTMLElement) => fireEvent(element, "register-dialog", { dialogShowEvent, dialogTag, - dialogImport: () => import("./hui-dialog-edit-card"), + dialogImport: () => + import(/* webpackChunkName: "hui-dialog-edit-card" */ "./hui-dialog-edit-card"), }); export const showEditCardDialog = ( diff --git a/src/panels/lovelace/editor/card-editor/show-move-card-view-dialog.ts b/src/panels/lovelace/editor/card-editor/show-move-card-view-dialog.ts new file mode 100644 index 0000000000..5ffccaaa44 --- /dev/null +++ b/src/panels/lovelace/editor/card-editor/show-move-card-view-dialog.ts @@ -0,0 +1,35 @@ +import { fireEvent } from "../../../../common/dom/fire_event"; +import { Lovelace } from "../../types"; + +declare global { + // for fire event + interface HASSDomEvents { + "show-move-card-view": MoveCardViewDialogParams; + } +} + +let registeredDialog = false; + +export interface MoveCardViewDialogParams { + path: [number, number]; + lovelace: Lovelace; +} + +const registerEditCardDialog = (element: HTMLElement) => + fireEvent(element, "register-dialog", { + dialogShowEvent: "show-move-card-view", + dialogTag: "hui-dialog-move-card-view", + dialogImport: () => + import(/* webpackChunkName: "hui-dialog-move-card-view" */ "./hui-dialog-move-card-view"), + }); + +export const showMoveCardViewDialog = ( + element: HTMLElement, + moveCardViewDialogParams: MoveCardViewDialogParams +) => { + if (!registeredDialog) { + registeredDialog = true; + registerEditCardDialog(element); + } + fireEvent(element, "show-move-card-view", moveCardViewDialogParams); +}; diff --git a/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts new file mode 100644 index 0000000000..f364c3cb7b --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts @@ -0,0 +1,195 @@ +import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; +import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; +import "@polymer/paper-item/paper-item"; +import "@polymer/paper-listbox/paper-listbox"; + +import { struct } from "../../common/structs/struct"; +import { EntitiesEditorEvent, EditorTarget } from "../types"; +import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin"; +import { HomeAssistant } from "../../../../types"; +import { LovelaceCardEditor } from "../../types"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { Config } from "../../cards/hui-alarm-panel-card"; +import { configElementStyle } from "./config-elements-style"; + +import "../../../../components/entity/ha-entity-picker"; +import "../../../../components/ha-icon"; + +const cardConfigStruct = struct({ + type: "string", + entity: "string?", + name: "string?", + states: "array?", +}); + +export class HuiAlarmPanelCardEditor extends hassLocalizeLitMixin(LitElement) + implements LovelaceCardEditor { + public hass?: HomeAssistant; + private _config?: Config; + + public setConfig(config: Config): void { + config = cardConfigStruct(config); + this._config = config; + } + + static get properties(): PropertyDeclarations { + return { hass: {}, _config: {} }; + } + + get _entity(): string { + return this._config!.entity || ""; + } + + get _name(): string { + return this._config!.name || ""; + } + + get _states(): string[] { + return this._config!.states || []; + } + + protected render(): TemplateResult { + if (!this.hass) { + return html``; + } + + const states = ["arm_home", "arm_away", "arm_night", "arm_custom_bypass"]; + + return html` + ${configElementStyle} ${this.renderStyle()} +
+
+ + +
+ Used States ${ + this._states.map((state, index) => { + return html` +
+ ${state} + +
+ `; + }) + } + + + ${ + states.map((state) => { + return html` + ${state} + `; + }) + } + + +
+ `; + } + + private renderStyle(): TemplateResult { + return html` + + `; + } + + private _stateRemoved(ev: EntitiesEditorEvent): void { + if (!this._config || !this._states || !this.hass) { + return; + } + + const target = ev.target! as EditorTarget; + const index = Number(target.value); + if (index > -1) { + const newStates = this._states; + newStates.splice(index, 1); + this._config = { + ...this._config, + states: newStates, + }; + fireEvent(this, "config-changed", { config: this._config }); + } + } + + private _stateAdded(ev: EntitiesEditorEvent): void { + if (!this._config || !this.hass) { + return; + } + const target = ev.target! as EditorTarget; + if (!target.value || this._states.indexOf(target.value) >= 0) { + return; + } + const newStates = this._states; + newStates.push(target.value); + this._config = { + ...this._config, + states: newStates, + }; + target.value = ""; + fireEvent(this, "config-changed", { config: this._config }); + } + + private _valueChanged(ev: EntitiesEditorEvent): void { + if (!this._config || !this.hass) { + return; + } + const target = ev.target! as EditorTarget; + if (this[`_${target.configValue}`] === target.value) { + return; + } + if (target.configValue) { + if (target.value === "") { + delete this._config[target.configValue!]; + } else { + this._config = { + ...this._config, + [target.configValue!]: target.value, + }; + } + } + fireEvent(this, "config-changed", { config: this._config }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-alarm-panel-card-editor": HuiAlarmPanelCardEditor; + } +} + +customElements.define("hui-alarm-panel-card-editor", HuiAlarmPanelCardEditor); 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 233c9e3ba5..c13b303a1a 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 @@ -1,13 +1,12 @@ import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import { TemplateResult } from "lit-html"; -import { struct } from "../../common/structs/struct"; import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-toggle-button/paper-toggle-button"; import { processEditorEntities } from "../process-editor-entities"; - +import { struct } from "../../common/structs/struct"; import { EntitiesEditorEvent, EditorTarget } from "../types"; import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin"; import { HomeAssistant } from "../../../../types"; @@ -59,8 +58,7 @@ export class HuiEntitiesCardEditor extends hassLocalizeLitMixin(LitElement) public setConfig(config: Config): void { config = cardConfigStruct(config); - - this._config = { type: "entities", ...config }; + this._config = config; this._configEntities = processEditorEntities(config.entities); } @@ -74,7 +72,7 @@ export class HuiEntitiesCardEditor extends hassLocalizeLitMixin(LitElement)
@@ -117,11 +115,15 @@ export class HuiEntitiesCardEditor extends hassLocalizeLitMixin(LitElement) this._config.entities = ev.detail.entities; this._configEntities = processEditorEntities(this._config.entities); } else if (target.configValue) { - this._config = { - ...this._config, - [target.configValue]: - target.checked !== undefined ? target.checked : target.value, - }; + if (target.value === "") { + delete this._config[target.configValue!]; + } else { + this._config = { + ...this._config, + [target.configValue]: + target.checked !== undefined ? target.checked : target.value, + }; + } } fireEvent(this, "config-changed", { config: this._config }); diff --git a/src/panels/lovelace/editor/config-elements/hui-entity-button-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-entity-button-card-editor.ts new file mode 100644 index 0000000000..d5a7669008 --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/hui-entity-button-card-editor.ts @@ -0,0 +1,165 @@ +import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; +import "@polymer/paper-input/paper-input"; + +import { struct } from "../../common/structs/struct"; +import { + EntitiesEditorEvent, + EditorTarget, + actionConfigStruct, +} from "../types"; +import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin"; +import { HomeAssistant } from "../../../../types"; +import { LovelaceCardEditor } from "../../types"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { Config } from "../../cards/hui-entity-button-card"; +import { configElementStyle } from "./config-elements-style"; +import { ActionConfig } from "../../../../data/lovelace"; + +import "../../components/hui-action-editor"; +import "../../components/hui-theme-select-editor"; +import "../../components/hui-entity-editor"; + +const cardConfigStruct = struct({ + type: "string", + entity: "string?", + name: "string?", + icon: "string?", + tap_action: actionConfigStruct, + hold_action: actionConfigStruct, + theme: "string?", +}); + +export class HuiEntityButtonCardEditor extends hassLocalizeLitMixin(LitElement) + implements LovelaceCardEditor { + public hass?: HomeAssistant; + private _config?: Config; + + public setConfig(config: Config): void { + config = cardConfigStruct(config); + this._config = config; + } + + static get properties(): PropertyDeclarations { + return { hass: {}, _config: {} }; + } + + get _entity(): string { + return this._config!.entity || ""; + } + + get _name(): string { + return this._config!.name || ""; + } + + get _icon(): string { + return this._config!.icon || ""; + } + + get _tap_action(): ActionConfig { + return this._config!.tap_action || { action: "more-info" }; + } + + get _hold_action(): ActionConfig { + return this._config!.hold_action || { action: "none" }; + } + + get _theme(): string { + return this._config!.theme || "default"; + } + + protected render(): TemplateResult { + if (!this.hass) { + return html``; + } + + const actions = ["more-info", "toggle", "navigate", "call-service", "none"]; + + return html` + ${configElementStyle} +
+ +
+ + +
+ +
+ + +
+
+ `; + } + + private _valueChanged(ev: EntitiesEditorEvent): void { + if (!this._config || !this.hass) { + return; + } + const target = ev.target! as EditorTarget; + + if ( + this[`_${target.configValue}`] === target.value || + this[`_${target.configValue}`] === target.config + ) { + return; + } + if (target.configValue) { + if (target.value === "") { + delete this._config[target.configValue!]; + } else { + this._config = { + ...this._config, + [target.configValue!]: target.value ? target.value : target.config, + }; + } + } + fireEvent(this, "config-changed", { config: this._config }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-entity-button-card-editor": HuiEntityButtonCardEditor; + } +} + +customElements.define( + "hui-entity-button-card-editor", + HuiEntityButtonCardEditor +); diff --git a/src/panels/lovelace/editor/config-elements/hui-gauge-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-gauge-card-editor.ts new file mode 100644 index 0000000000..1b7d734940 --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/hui-gauge-card-editor.ts @@ -0,0 +1,244 @@ +import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; +import "@polymer/paper-input/paper-input"; +import "@polymer/paper-toggle-button/paper-toggle-button"; + +import { struct } from "../../common/structs/struct"; +import { EntitiesEditorEvent, EditorTarget } from "../types"; +import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin"; +import { HomeAssistant } from "../../../../types"; +import { LovelaceCardEditor } from "../../types"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { Config, SeverityConfig } from "../../cards/hui-gauge-card"; +import { configElementStyle } from "./config-elements-style"; + +import "../../components/hui-theme-select-editor"; +import "../../components/hui-entity-editor"; + +const cardConfigStruct = struct({ + type: "string", + name: "string?", + entity: "string?", + unit: "string?", + min: "number?", + max: "number?", + severity: "object?", + theme: "string?", +}); + +export class HuiGaugeCardEditor extends hassLocalizeLitMixin(LitElement) + implements LovelaceCardEditor { + public hass?: HomeAssistant; + private _config?: Config; + private _useSeverity?: boolean; + + public setConfig(config: Config): void { + config = cardConfigStruct(config); + this._useSeverity = config.severity ? true : false; + this._config = config; + } + + static get properties(): PropertyDeclarations { + return { hass: {}, _config: {} }; + } + + get _name(): string { + return this._config!.name || ""; + } + + get _entity(): string { + return this._config!.entity || ""; + } + + get _unit(): string { + return this._config!.unit || ""; + } + + get _theme(): string { + return this._config!.theme || "default"; + } + + get _min(): number { + return this._config!.number || 0; + } + + get _max(): number { + return this._config!.max || 100; + } + + get _severity(): SeverityConfig | undefined { + return this._config!.severity || undefined; + } + + protected render(): TemplateResult { + if (!this.hass) { + return html``; + } + + return html` + ${configElementStyle} ${this.renderStyle()} +
+
+ + +
+
+ + +
+
+ + +
+
+ Define Severity? +
+ + + +
+
+
+ `; + } + + private renderStyle(): TemplateResult { + return html` + + `; + } + + private _toggleSeverity(ev: EntitiesEditorEvent): void { + if (!this._config || !this.hass) { + return; + } + const target = ev.target! as EditorTarget; + + this._config.severity = target.checked + ? { + green: 0, + yellow: 0, + red: 0, + } + : undefined; + fireEvent(this, "config-changed", { config: this._config }); + } + + private _severityChanged(ev: EntitiesEditorEvent): void { + if (!this._config || !this.hass) { + return; + } + const target = ev.target! as EditorTarget; + const severity = { + ...this._config.severity, + [target.configValue!]: Number(target.value), + }; + this._config = { + ...this._config, + severity, + }; + fireEvent(this, "config-changed", { config: this._config }); + } + + private _valueChanged(ev: EntitiesEditorEvent): void { + if (!this._config || !this.hass) { + return; + } + const target = ev.target! as EditorTarget; + + if (target.configValue) { + if ( + target.value === "" || + (target.type === "number" && isNaN(Number(target.value))) + ) { + delete this._config[target.configValue!]; + } else { + let value: any = target.value; + if (target.type === "number") { + value = Number(value); + } + this._config = { ...this._config, [target.configValue!]: value }; + } + } + fireEvent(this, "config-changed", { config: this._config }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-gauge-card-editor": HuiGaugeCardEditor; + } +} + +customElements.define("hui-gauge-card-editor", HuiGaugeCardEditor); 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 dadb82f702..da8b1ff2cd 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 @@ -1,11 +1,11 @@ import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import { TemplateResult } from "lit-html"; -import { struct } from "../../common/structs/struct"; import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-toggle-button/paper-toggle-button"; +import { struct } from "../../common/structs/struct"; import { processEditorEntities } from "../process-editor-entities"; import { EntitiesEditorEvent, EditorTarget } from "../types"; import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin"; @@ -48,8 +48,7 @@ export class HuiGlanceCardEditor extends hassLocalizeLitMixin(LitElement) public setConfig(config: Config): void { config = cardConfigStruct(config); - - this._config = { type: "glance", ...config }; + this._config = config; this._configEntities = processEditorEntities(config.entities); } @@ -65,8 +64,8 @@ export class HuiGlanceCardEditor extends hassLocalizeLitMixin(LitElement) return this._config!.theme || "Backend-selected"; } - get _columns(): string { - return this._config!.columns ? String(this._config!.columns) : ""; + get _columns(): number { + return this._config!.columns || NaN; } protected render(): TemplateResult { @@ -79,7 +78,7 @@ export class HuiGlanceCardEditor extends hassLocalizeLitMixin(LitElement)
@@ -93,7 +92,7 @@ export class HuiGlanceCardEditor extends hassLocalizeLitMixin(LitElement) @@ -127,22 +126,29 @@ export class HuiGlanceCardEditor extends hassLocalizeLitMixin(LitElement) } const target = ev.target! as EditorTarget; - if (this[`_${target.configValue}`] === target.value) { + if (target.configValue && this[`_${target.configValue}`] === target.value) { return; } if (ev.detail && ev.detail.entities) { this._config.entities = ev.detail.entities; this._configEntities = processEditorEntities(this._config.entities); } else if (target.configValue) { - let value: any = target.value; - if (target.type === "number") { - value = Number(value); + if ( + target.value === "" || + (target.type === "number" && isNaN(Number(target.value))) + ) { + delete this._config[target.configValue!]; + } else { + let value: any = target.value; + if (target.type === "number") { + value = Number(value); + } + this._config = { + ...this._config, + [target.configValue!]: + target.checked !== undefined ? target.checked : value, + }; } - this._config = { - ...this._config, - [target.configValue!]: - target.checked !== undefined ? target.checked : value, - }; } fireEvent(this, "config-changed", { config: this._config }); } diff --git a/src/panels/lovelace/editor/config-elements/hui-iframe-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-iframe-card-editor.ts new file mode 100644 index 0000000000..673156032d --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/hui-iframe-card-editor.ts @@ -0,0 +1,111 @@ +import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; +import "@polymer/paper-input/paper-input"; + +import { struct } from "../../common/structs/struct"; +import { EntitiesEditorEvent, EditorTarget } from "../types"; +import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin"; +import { HomeAssistant } from "../../../../types"; +import { LovelaceCardEditor } from "../../types"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { Config } from "../../cards/hui-iframe-card"; +import { configElementStyle } from "./config-elements-style"; + +const cardConfigStruct = struct({ + type: "string", + title: "string?", + url: "string?", + aspect_ratio: "string?", +}); + +export class HuiIframeCardEditor extends hassLocalizeLitMixin(LitElement) + implements LovelaceCardEditor { + public hass?: HomeAssistant; + private _config?: Config; + + public setConfig(config: Config): void { + config = cardConfigStruct(config); + this._config = config; + } + + static get properties(): PropertyDeclarations { + return { hass: {}, _config: {} }; + } + + get _title(): string { + return this._config!.title || ""; + } + + get _url(): string { + return this._config!.url || ""; + } + + get _aspect_ratio(): string { + return this._config!.aspect_ratio || ""; + } + + protected render(): TemplateResult { + if (!this.hass) { + return html``; + } + + return html` + ${configElementStyle} +
+
+ + +
+ +
+ `; + } + + private _valueChanged(ev: EntitiesEditorEvent): void { + if (!this._config || !this.hass) { + return; + } + const target = ev.target! as EditorTarget; + let value = target.value; + + if (target.configValue! === "aspect_ratio" && target.value) { + value += "%"; + } + + if (this[`_${target.configValue}`] === value) { + return; + } + if (target.configValue) { + if (target.value === "") { + delete this._config[target.configValue!]; + } else { + this._config = { ...this._config, [target.configValue!]: value }; + } + } + fireEvent(this, "config-changed", { config: this._config }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-iframe-card-editor": HuiIframeCardEditor; + } +} + +customElements.define("hui-iframe-card-editor", HuiIframeCardEditor); diff --git a/src/panels/lovelace/editor/config-elements/hui-light-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-light-card-editor.ts new file mode 100644 index 0000000000..ba65142b1b --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/hui-light-card-editor.ts @@ -0,0 +1,113 @@ +import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; +import "@polymer/paper-input/paper-input"; + +import { struct } from "../../common/structs/struct"; +import { EntitiesEditorEvent, EditorTarget } from "../types"; +import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin"; +import { HomeAssistant } from "../../../../types"; +import { LovelaceCardEditor } from "../../types"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { Config } from "../../cards/hui-light-card"; +import { configElementStyle } from "./config-elements-style"; + +import "../../components/hui-theme-select-editor"; +import "../../components/hui-entity-editor"; + +const cardConfigStruct = struct({ + type: "string", + name: "string?", + entity: "string?", + theme: "string?", +}); + +export class HuiLightCardEditor extends hassLocalizeLitMixin(LitElement) + implements LovelaceCardEditor { + public hass?: HomeAssistant; + private _config?: Config; + + public setConfig(config: Config): void { + config = cardConfigStruct(config); + this._config = config; + } + + static get properties(): PropertyDeclarations { + return { hass: {}, _config: {}, _configEntities: {} }; + } + + get _name(): string { + return this._config!.name || ""; + } + + get _theme(): string { + return this._config!.theme || "default"; + } + + get _entity(): string { + return this._config!.entity || ""; + } + + protected render(): TemplateResult { + if (!this.hass) { + return html``; + } + + return html` + ${configElementStyle} +
+ +
+ + +
+
+ `; + } + + private _valueChanged(ev: EntitiesEditorEvent): void { + if (!this._config || !this.hass) { + return; + } + const target = ev.target! as EditorTarget; + + if (this[`_${target.configValue}`] === target.value) { + return; + } + if (target.configValue) { + if (target.value === "") { + delete this._config[target.configValue!]; + } else { + this._config = { + ...this._config, + [target.configValue!]: target.value, + }; + } + } + fireEvent(this, "config-changed", { config: this._config }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-light-card-editor": HuiLightCardEditor; + } +} + +customElements.define("hui-light-card-editor", HuiLightCardEditor); diff --git a/src/panels/lovelace/editor/config-elements/hui-map-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-map-card-editor.ts new file mode 100644 index 0000000000..ed6531a0e0 --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/hui-map-card-editor.ts @@ -0,0 +1,143 @@ +import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; +import "@polymer/paper-input/paper-input"; + +import { struct } from "../../common/structs/struct"; +import { EntitiesEditorEvent, EditorTarget } from "../types"; +import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin"; +import { HomeAssistant } from "../../../../types"; +import { LovelaceCardEditor } from "../../types"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { Config } from "../../cards/hui-alarm-panel-card"; +import { configElementStyle } from "./config-elements-style"; +import { processEditorEntities } from "../process-editor-entities"; +import { EntityConfig } from "../../entity-rows/types"; + +import "../../components/hui-entity-editor"; + +const entitiesConfigStruct = struct.union([ + { + entity: "entity-id", + name: "string?", + icon: "icon?", + }, + "entity-id", +]); + +const cardConfigStruct = struct({ + type: "string", + title: "string?", + aspect_ratio: "string?", + default_zoom: "number?", + entities: [entitiesConfigStruct], +}); + +export class HuiMapCardEditor extends hassLocalizeLitMixin(LitElement) + implements LovelaceCardEditor { + public hass?: HomeAssistant; + private _config?: Config; + private _configEntities?: EntityConfig[]; + + public setConfig(config: Config): void { + config = cardConfigStruct(config); + this._config = config; + this._configEntities = processEditorEntities(config.entities); + } + + static get properties(): PropertyDeclarations { + return { hass: {}, _config: {}, _configEntities: {} }; + } + + get _title(): string { + return this._config!.title || ""; + } + + get _aspect_ratio(): string { + return this._config!.aspect_ratio || ""; + } + + get _default_zoom(): number { + return this._config!.default_zoom || NaN; + } + + get _entities(): string[] { + return this._config!.entities || []; + } + + protected render(): TemplateResult { + if (!this.hass) { + return html``; + } + + return html` + ${configElementStyle} +
+ +
+ + +
+ +
+ `; + } + + private _valueChanged(ev: EntitiesEditorEvent): void { + if (!this._config || !this.hass) { + return; + } + const target = ev.target! as EditorTarget; + if (target.configValue && this[`_${target.configValue}`] === target.value) { + return; + } + if (ev.detail && ev.detail.entities) { + this._config.entities = ev.detail.entities; + this._configEntities = processEditorEntities(this._config.entities); + } else if (target.configValue) { + if ( + target.value === "" || + (target.type === "number" && isNaN(Number(target.value))) + ) { + delete this._config[target.configValue!]; + } else { + let value: any = target.value; + if (target.type === "number") { + value = Number(value); + } + this._config = { + ...this._config, + [target.configValue!]: value, + }; + } + } + fireEvent(this, "config-changed", { config: this._config }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-map-card-editor": HuiMapCardEditor; + } +} + +customElements.define("hui-map-card-editor", HuiMapCardEditor); diff --git a/src/panels/lovelace/editor/config-elements/hui-markdown-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-markdown-card-editor.ts new file mode 100644 index 0000000000..c4b002eff1 --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/hui-markdown-card-editor.ts @@ -0,0 +1,99 @@ +import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; +import "@polymer/paper-input/paper-input"; +import "@polymer/paper-input/paper-textarea"; + +import { struct } from "../../common/structs/struct"; +import { EntitiesEditorEvent, EditorTarget } from "../types"; +import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin"; +import { HomeAssistant } from "../../../../types"; +import { LovelaceCardEditor } from "../../types"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { Config } from "../../cards/hui-glance-card"; +import { configElementStyle } from "./config-elements-style"; + +const cardConfigStruct = struct({ + type: "string", + title: "string?", + content: "string", +}); + +export class HuiMarkdownCardEditor extends hassLocalizeLitMixin(LitElement) + implements LovelaceCardEditor { + public hass?: HomeAssistant; + private _config?: Config; + + public setConfig(config: Config): void { + config = cardConfigStruct(config); + this._config = config; + } + + static get properties(): PropertyDeclarations { + return { hass: {}, _config: {} }; + } + + get _title(): string { + return this._config!.title || ""; + } + + get _content(): string { + return this._config!.content || ""; + } + + protected render(): TemplateResult { + if (!this.hass) { + return html``; + } + + return html` + ${configElementStyle} +
+ + +
+ `; + } + + private _valueChanged(ev: EntitiesEditorEvent): void { + if (!this._config || !this.hass) { + return; + } + const target = ev.target! as EditorTarget; + + if (this[`_${target.configValue}`] === target.value) { + return; + } + if (target.configValue) { + if (target.value === "") { + delete this._config[target.configValue!]; + } else { + this._config = { + ...this._config, + [target.configValue!]: target.value, + }; + } + } + fireEvent(this, "config-changed", { config: this._config }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-markdown-card-editor": HuiMarkdownCardEditor; + } +} + +customElements.define("hui-markdown-card-editor", HuiMarkdownCardEditor); diff --git a/src/panels/lovelace/editor/config-elements/hui-media-control-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-media-control-card-editor.ts new file mode 100644 index 0000000000..b6ac24fe3f --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/hui-media-control-card-editor.ts @@ -0,0 +1,87 @@ +import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; + +import { struct } from "../../common/structs/struct"; +import { EntitiesEditorEvent, EditorTarget } from "../types"; +import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin"; +import { HomeAssistant } from "../../../../types"; +import { LovelaceCardEditor } from "../../types"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { Config } from "../../cards/hui-media-control-card"; + +import "../../../../components/entity/ha-entity-picker"; + +const cardConfigStruct = struct({ + type: "string", + entity: "string?", +}); + +export class HuiMediaControlCardEditor extends hassLocalizeLitMixin(LitElement) + implements LovelaceCardEditor { + public hass?: HomeAssistant; + private _config?: Config; + + public setConfig(config: Config): void { + config = cardConfigStruct(config); + this._config = config; + } + + static get properties(): PropertyDeclarations { + return { hass: {}, _config: {} }; + } + + get _entity(): string { + return this._config!.entity || ""; + } + + protected render(): TemplateResult { + if (!this.hass) { + return html``; + } + + return html` +
+ +
+ `; + } + + private _valueChanged(ev: EntitiesEditorEvent): void { + if (!this._config || !this.hass) { + return; + } + const target = ev.target! as EditorTarget; + if (this[`_${target.configValue}`] === target.value) { + return; + } + if (target.configValue) { + if (target.value === "") { + delete this._config[target.configValue!]; + } else { + this._config = { + ...this._config, + [target.configValue!]: target.value, + }; + } + } + fireEvent(this, "config-changed", { config: this._config }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-media-control-card-editor": HuiMediaControlCardEditor; + } +} + +customElements.define( + "hui-media-control-card-editor", + HuiMediaControlCardEditor +); diff --git a/src/panels/lovelace/editor/config-elements/hui-picture-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-picture-card-editor.ts new file mode 100644 index 0000000000..52bba01b3c --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/hui-picture-card-editor.ts @@ -0,0 +1,124 @@ +import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; +import "@polymer/paper-input/paper-input"; + +import { struct } from "../../common/structs/struct"; +import { + EntitiesEditorEvent, + EditorTarget, + actionConfigStruct, +} from "../types"; +import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin"; +import { HomeAssistant } from "../../../../types"; +import { LovelaceCardEditor } from "../../types"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { Config } from "../../cards/hui-picture-card"; +import { configElementStyle } from "./config-elements-style"; +import { ActionConfig } from "../../../../data/lovelace"; + +import "../../components/hui-action-editor"; + +const cardConfigStruct = struct({ + type: "string", + image: "string?", + tap_action: actionConfigStruct, + hold_action: actionConfigStruct, +}); + +export class HuiPictureCardEditor extends hassLocalizeLitMixin(LitElement) + implements LovelaceCardEditor { + public hass?: HomeAssistant; + private _config?: Config; + + public setConfig(config: Config): void { + config = cardConfigStruct(config); + this._config = config; + } + + static get properties(): PropertyDeclarations { + return { hass: {}, _config: {} }; + } + + get _image(): string { + return this._config!.image || ""; + } + + get _tap_action(): ActionConfig { + return this._config!.tap_action || { action: "none" }; + } + + get _hold_action(): ActionConfig { + return this._config!.hold_action || { action: "none" }; + } + + protected render(): TemplateResult { + if (!this.hass) { + return html``; + } + + const actions = ["navigate", "call-service", "none"]; + + return html` + ${configElementStyle} +
+ +
+ + +
+
+ `; + } + + private _valueChanged(ev: EntitiesEditorEvent): void { + if (!this._config || !this.hass) { + return; + } + const target = ev.target! as EditorTarget; + + if ( + this[`_${target.configValue}`] === target.value || + this[`_${target.configValue}`] === target.config + ) { + return; + } + if (target.configValue) { + if (target.value === "") { + delete this._config[target.configValue!]; + } else { + this._config = { + ...this._config, + [target.configValue!]: target.value ? target.value : target.config, + }; + } + } + fireEvent(this, "config-changed", { config: this._config }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-picture-card-editor": HuiPictureCardEditor; + } +} + +customElements.define("hui-picture-card-editor", HuiPictureCardEditor); diff --git a/src/panels/lovelace/editor/config-elements/hui-plant-status-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-plant-status-card-editor.ts new file mode 100644 index 0000000000..d321f52b76 --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/hui-plant-status-card-editor.ts @@ -0,0 +1,101 @@ +import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; +import "@polymer/paper-input/paper-input"; + +import { struct } from "../../common/structs/struct"; +import { EntitiesEditorEvent, EditorTarget } from "../types"; +import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin"; +import { HomeAssistant } from "../../../../types"; +import { LovelaceCardEditor } from "../../types"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { Config } from "../../cards/hui-alarm-panel-card"; +import { configElementStyle } from "./config-elements-style"; + +import "../../../../components/entity/ha-entity-picker"; +import "../../../../components/ha-icon"; + +const cardConfigStruct = struct({ + type: "string", + entity: "string", + name: "string?", +}); + +export class HuiPlantStatusCardEditor extends hassLocalizeLitMixin(LitElement) + implements LovelaceCardEditor { + public hass?: HomeAssistant; + private _config?: Config; + + public setConfig(config: Config): void { + config = cardConfigStruct(config); + this._config = config; + } + + static get properties(): PropertyDeclarations { + return { hass: {}, _config: {} }; + } + + get _entity(): string { + return this._config!.entity || ""; + } + + get _name(): string { + return this._config!.name || ""; + } + + protected render(): TemplateResult { + if (!this.hass) { + return html``; + } + + return html` + ${configElementStyle} +
+
+ + +
+
+ `; + } + + private _valueChanged(ev: EntitiesEditorEvent): void { + if (!this._config || !this.hass) { + return; + } + const target = ev.target! as EditorTarget; + if (this[`_${target.configValue}`] === target.value) { + return; + } + if (target.configValue) { + if (target.value === "") { + delete this._config[target.configValue!]; + } else { + this._config = { + ...this._config, + [target.configValue!]: target.value, + }; + } + } + fireEvent(this, "config-changed", { config: this._config }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-plant-status-card-editor": HuiPlantStatusCardEditor; + } +} + +customElements.define("hui-plant-status-card-editor", HuiPlantStatusCardEditor); diff --git a/src/panels/lovelace/editor/config-elements/hui-sensor-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-sensor-card-editor.ts new file mode 100644 index 0000000000..dcf108c3ed --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/hui-sensor-card-editor.ts @@ -0,0 +1,197 @@ +import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; +import "@polymer/paper-input/paper-input"; +import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; +import "@polymer/paper-item/paper-item"; +import "@polymer/paper-listbox/paper-listbox"; + +import { struct } from "../../common/structs/struct"; +import { EntitiesEditorEvent, EditorTarget } from "../types"; +import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin"; +import { HomeAssistant } from "../../../../types"; +import { LovelaceCardEditor } from "../../types"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { Config } from "../../cards/hui-sensor-card"; +import { configElementStyle } from "./config-elements-style"; + +import "../../components/hui-theme-select-editor"; +import "../../../../components/entity/ha-entity-picker"; + +const cardConfigStruct = struct({ + type: "string", + entity: "string?", + name: "string?", + icon: "string?", + graph: "string?", + unit: "string?", + detail: "number?", + theme: "string?", + hours_to_show: "number?", +}); + +export class HuiSensorCardEditor extends hassLocalizeLitMixin(LitElement) + implements LovelaceCardEditor { + public hass?: HomeAssistant; + private _config?: Config; + + public setConfig(config: Config): void { + config = cardConfigStruct(config); + this._config = config; + } + + static get properties(): PropertyDeclarations { + return { hass: {}, _config: {} }; + } + + get _entity(): string { + return this._config!.entity || ""; + } + + get _name(): string { + return this._config!.name || ""; + } + + get _icon(): string { + return this._config!.icon || ""; + } + + get _graph(): string { + return this._config!.graph || "none"; + } + + get _unit(): string { + return this._config!.unit || ""; + } + + get _detail(): number | string { + return this._config!.number || "1"; + } + + get _theme(): string { + return this._config!.theme || "default"; + } + + get _hours_to_show(): number | string { + return this._config!.hours_to_show || "24"; + } + + protected render(): TemplateResult { + if (!this.hass) { + return html``; + } + + const graphs = ["line", "none"]; + + return html` + ${configElementStyle} +
+
+ + +
+
+ + + + ${ + graphs.map((graph) => { + return html` + ${graph} + `; + }) + } + + +
+
+ + +
+
+ + +
+
+ `; + } + + private _valueChanged(ev: EntitiesEditorEvent): void { + if (!this._config || !this.hass) { + return; + } + const target = ev.target! as EditorTarget; + + if (this[`_${target.configValue}`] === target.value) { + return; + } + if (target.configValue) { + if ( + target.value === "" || + (target.type === "number" && isNaN(Number(target.value))) + ) { + delete this._config[target.configValue!]; + } else { + let value: any = target.value; + if (target.type === "number") { + value = Number(value); + } + this._config = { ...this._config, [target.configValue!]: value }; + } + } + fireEvent(this, "config-changed", { config: this._config }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-sensor-card-editor": HuiSensorCardEditor; + } +} + +customElements.define("hui-sensor-card-editor", HuiSensorCardEditor); diff --git a/src/panels/lovelace/editor/config-elements/hui-shopping-list-editor.ts b/src/panels/lovelace/editor/config-elements/hui-shopping-list-editor.ts new file mode 100644 index 0000000000..6bd987db6a --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/hui-shopping-list-editor.ts @@ -0,0 +1,82 @@ +import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; +import "@polymer/paper-input/paper-input"; + +import { struct } from "../../common/structs/struct"; +import { EntitiesEditorEvent, EditorTarget } from "../types"; +import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin"; +import { HomeAssistant } from "../../../../types"; +import { LovelaceCardEditor } from "../../types"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { Config } from "../../cards/hui-shopping-list-card"; + +const cardConfigStruct = struct({ + type: "string", + title: "string?", +}); + +export class HuiShoppingListEditor extends hassLocalizeLitMixin(LitElement) + implements LovelaceCardEditor { + public hass?: HomeAssistant; + private _config?: Config; + + public setConfig(config: Config): void { + config = cardConfigStruct(config); + this._config = config; + } + + static get properties(): PropertyDeclarations { + return { hass: {}, _config: {} }; + } + + get _title(): string { + return this._config!.title || ""; + } + + protected render(): TemplateResult { + if (!this.hass) { + return html``; + } + + return html` +
+ +
+ `; + } + + private _valueChanged(ev: EntitiesEditorEvent): void { + if (!this._config || !this.hass) { + return; + } + const target = ev.target! as EditorTarget; + + if (this[`_${target.configValue}`] === target.value) { + return; + } + if (target.configValue) { + if (target.value === "") { + delete this._config[target.configValue!]; + } else { + this._config = { + ...this._config, + [target.configValue!]: target.value, + }; + } + } + fireEvent(this, "config-changed", { config: this._config }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-shopping-list-card-editor": HuiShoppingListEditor; + } +} + +customElements.define("hui-shopping-list-card-editor", HuiShoppingListEditor); diff --git a/src/panels/lovelace/editor/config-elements/hui-thermostat-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-thermostat-card-editor.ts new file mode 100644 index 0000000000..e1db48583e --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/hui-thermostat-card-editor.ts @@ -0,0 +1,110 @@ +import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; +import "@polymer/paper-input/paper-input"; + +import { struct } from "../../common/structs/struct"; +import { EntitiesEditorEvent, EditorTarget } from "../types"; +import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin"; +import { HomeAssistant } from "../../../../types"; +import { LovelaceCardEditor } from "../../types"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { Config } from "../../cards/hui-thermostat-card"; +import { configElementStyle } from "./config-elements-style"; + +import "../../components/hui-theme-select-editor"; +import "../../../../components/entity/ha-entity-picker"; + +const cardConfigStruct = struct({ + type: "string", + entity: "string", + name: "string?", + theme: "string?", +}); + +export class HuiThermostatCardEditor extends hassLocalizeLitMixin(LitElement) + implements LovelaceCardEditor { + public hass?: HomeAssistant; + private _config?: Config; + + public setConfig(config: Config): void { + config = cardConfigStruct(config); + this._config = config; + } + + static get properties(): PropertyDeclarations { + return { hass: {}, _config: {} }; + } + + get _entity(): string { + return this._config!.entity || ""; + } + + get _name(): string { + return this._config!.name || ""; + } + + get _theme(): string { + return this._config!.theme || "default"; + } + + protected render(): TemplateResult { + if (!this.hass) { + return html``; + } + + return html` + ${configElementStyle} +
+ +
+ + +
+
+ `; + } + + private _valueChanged(ev: EntitiesEditorEvent): void { + if (!this._config || !this.hass) { + return; + } + const target = ev.target! as EditorTarget; + + if (this[`_${target.configValue}`] === target.value) { + return; + } + if (target.configValue) { + if (target.value === "") { + delete this._config[target.configValue!]; + } else { + this._config = { ...this._config, [target.configValue!]: target.value }; + } + } + fireEvent(this, "config-changed", { config: this._config }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-thermostat-card-editor": HuiThermostatCardEditor; + } +} + +customElements.define("hui-thermostat-card-editor", HuiThermostatCardEditor); diff --git a/src/panels/lovelace/editor/config-elements/hui-weather-forecast-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-weather-forecast-card-editor.ts new file mode 100644 index 0000000000..8854c411a8 --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/hui-weather-forecast-card-editor.ts @@ -0,0 +1,103 @@ +import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; + +import { struct } from "../../common/structs/struct"; +import { EntitiesEditorEvent, EditorTarget } from "../types"; +import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin"; +import { HomeAssistant } from "../../../../types"; +import { LovelaceCardEditor } from "../../types"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { Config } from "../../cards/hui-weather-forecast-card"; +import { configElementStyle } from "./config-elements-style"; + +import "../../../../components/entity/ha-entity-picker"; + +const cardConfigStruct = struct({ + type: "string", + entity: "string?", + name: "string?", +}); + +export class HuiWeatherForecastCardEditor + extends hassLocalizeLitMixin(LitElement) + implements LovelaceCardEditor { + public hass?: HomeAssistant; + private _config?: Config; + + public setConfig(config: Config): void { + config = cardConfigStruct(config); + this._config = config; + } + + static get properties(): PropertyDeclarations { + return { hass: {}, _config: {} }; + } + + get _entity(): string { + return this._config!.entity || ""; + } + + get _name(): string { + return this._config!.name || ""; + } + + protected render(): TemplateResult { + if (!this.hass) { + return html``; + } + + return html` + ${configElementStyle} +
+
+ + +
+
+ `; + } + + private _valueChanged(ev: EntitiesEditorEvent): void { + if (!this._config || !this.hass) { + return; + } + const target = ev.target! as EditorTarget; + if (this[`_${target.configValue}`] === target.value) { + return; + } + if (target.configValue) { + if (target.value === "") { + delete this._config[target.configValue!]; + } else { + this._config = { + ...this._config, + [target.configValue!]: target.value, + }; + } + } + fireEvent(this, "config-changed", { config: this._config }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-weather-forecast-card-editor": HuiWeatherForecastCardEditor; + } +} + +customElements.define( + "hui-weather-forecast-card-editor", + HuiWeatherForecastCardEditor +); diff --git a/src/panels/lovelace/editor/config-util.ts b/src/panels/lovelace/editor/config-util.ts index 2c87b57737..d1f834a2c8 100644 --- a/src/panels/lovelace/editor/config-util.ts +++ b/src/panels/lovelace/editor/config-util.ts @@ -121,6 +121,44 @@ export const swapCard = ( }; }; +export const moveCard = ( + config: LovelaceConfig, + fromPath: [number, number], + toPath: [number] +): LovelaceConfig => { + if (fromPath[0] === toPath[0]) { + throw new Error("You can not move a card to the view it is in."); + } + const fromView = config.views[fromPath[0]]; + const card = fromView.cards![fromPath[1]]; + + const newView1 = { + ...fromView, + cards: (fromView.cards || []).filter( + (_origConf, ind) => ind !== fromPath[1] + ), + }; + + const toView = config.views[toPath[0]]; + const cards = toView.cards ? [...toView.cards, card] : [card]; + + const newView2 = { + ...toView, + cards, + }; + + return { + ...config, + views: config.views.map((origView, index) => + index === toPath[0] + ? newView2 + : index === fromPath[0] + ? newView1 + : origView + ), + }; +}; + export const addView = ( config: LovelaceConfig, viewConfig: LovelaceViewConfig diff --git a/src/panels/lovelace/editor/hui-dialog-save-config.ts b/src/panels/lovelace/editor/hui-dialog-save-config.ts index 7e00454cfa..295d9ae87c 100644 --- a/src/panels/lovelace/editor/hui-dialog-save-config.ts +++ b/src/panels/lovelace/editor/hui-dialog-save-config.ts @@ -14,7 +14,7 @@ import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin"; import { SaveDialogParams } from "./show-save-config-dialog"; export class HuiSaveConfig extends hassLocalizeLitMixin(LitElement) { - protected hass?: HomeAssistant; + public hass?: HomeAssistant; private _params?: SaveDialogParams; private _saving: boolean; diff --git a/src/panels/lovelace/editor/lovelace-editor/hui-dialog-edit-lovelace.ts b/src/panels/lovelace/editor/lovelace-editor/hui-dialog-edit-lovelace.ts new file mode 100644 index 0000000000..2cb86b013b --- /dev/null +++ b/src/panels/lovelace/editor/lovelace-editor/hui-dialog-edit-lovelace.ts @@ -0,0 +1,152 @@ +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 "@polymer/paper-dialog-scrollable/paper-dialog-scrollable"; +import "./hui-lovelace-editor"; +import { HomeAssistant } from "../../../../types"; +import { LovelaceConfig } from "../../../../data/lovelace"; +import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin"; +import { Lovelace } from "../../types"; + +export class HuiDialogEditLovelace extends hassLocalizeLitMixin(LitElement) { + public hass?: HomeAssistant; + private _lovelace?: Lovelace; + private _config?: LovelaceConfig; + private _saving: boolean; + + static get properties(): PropertyDeclarations { + return { + hass: {}, + _lovelace: {}, + }; + } + + protected constructor() { + super(); + this._saving = false; + } + + public async showDialog(lovelace: Lovelace): Promise { + this._lovelace = lovelace; + if (this._dialog == null) { + await this.updateComplete; + } + + const { views, ...lovelaceConfig } = this._lovelace!.config; + this._config = lovelaceConfig as LovelaceConfig; + + this._dialog.open(); + } + + private get _dialog(): PaperDialogElement { + return this.shadowRoot!.querySelector("paper-dialog")!; + } + + protected render(): TemplateResult { + return html` + ${this.renderStyle()} + +

Edit Lovelace

+ + +
+ ${this.localize("ui.common.cancel")} + + + ${this.localize("ui.common.save")} +
+
+ `; + } + + private _closeDialog(): void { + this._config = undefined; + this._dialog.close(); + } + + private async _save(): Promise { + if (!this._config) { + return; + } + if (!this._isConfigChanged()) { + this._closeDialog(); + return; + } + + this._saving = true; + const lovelace = this._lovelace!; + + const config: LovelaceConfig = { + ...lovelace.config, + ...this._config, + }; + + try { + await lovelace.saveConfig(config); + this._closeDialog(); + } catch (err) { + alert(`Saving failed: ${err.message}`); + } finally { + this._saving = false; + } + } + + private _ConfigChanged(ev: CustomEvent): void { + if (ev.detail && ev.detail.config) { + this._config = ev.detail.config; + } + } + + private _isConfigChanged(): boolean { + const { views, ...lovelaceConfig } = this._lovelace!.config; + return JSON.stringify(this._config) !== JSON.stringify(lovelaceConfig); + } + + private renderStyle(): TemplateResult { + return html` + + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-dialog-edit-lovelace": HuiDialogEditLovelace; + } +} + +customElements.define("hui-dialog-edit-lovelace", HuiDialogEditLovelace); diff --git a/src/panels/lovelace/editor/lovelace-editor/hui-lovelace-editor.ts b/src/panels/lovelace/editor/lovelace-editor/hui-lovelace-editor.ts new file mode 100644 index 0000000000..68761be409 --- /dev/null +++ b/src/panels/lovelace/editor/lovelace-editor/hui-lovelace-editor.ts @@ -0,0 +1,80 @@ +import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; +import "@polymer/paper-input/paper-input"; + +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/config-elements-style"; + +import { LovelaceConfig } from "../../../../data/lovelace"; + +declare global { + interface HASSDomEvents { + "lovelace-config-changed": { + config: LovelaceConfig; + }; + } +} + +export class HuiLovelaceEditor extends hassLocalizeLitMixin(LitElement) { + static get properties(): PropertyDeclarations { + return { hass: {}, config: {} }; + } + + public hass?: HomeAssistant; + public config?: LovelaceConfig; + + get _title(): string { + if (!this.config) { + return ""; + } + return this.config.title || ""; + } + + protected render(): TemplateResult { + return html` + ${configElementStyle} +
+ +
+ `; + } + + private _valueChanged(ev: Event): void { + if (!this.config) { + return; + } + + const target = ev.currentTarget! as EditorTarget; + + if (this[`_${target.configValue}`] === target.value) { + return; + } + + let newConfig; + + if (target.configValue) { + newConfig = { + ...this.config, + [target.configValue]: target.value, + }; + } + + fireEvent(this, "lovelace-config-changed", { config: newConfig }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-lovelace-editor": HuiLovelaceEditor; + } +} + +customElements.define("hui-lovelace-editor", HuiLovelaceEditor); diff --git a/src/panels/lovelace/editor/lovelace-editor/show-edit-lovelace-dialog.ts b/src/panels/lovelace/editor/lovelace-editor/show-edit-lovelace-dialog.ts new file mode 100644 index 0000000000..bc042dfa15 --- /dev/null +++ b/src/panels/lovelace/editor/lovelace-editor/show-edit-lovelace-dialog.ts @@ -0,0 +1,32 @@ +import { fireEvent } from "../../../../common/dom/fire_event"; +import { Lovelace } from "../../types"; + +declare global { + // for fire event + interface HASSDomEvents { + "show-edit-lovelace": Lovelace; + } +} + +let registeredDialog = false; +const dialogShowEvent = "show-edit-lovelace"; +const dialogTag = "hui-dialog-edit-lovelace"; + +const registerEditLovelaceDialog = (element: HTMLElement) => + fireEvent(element, "register-dialog", { + dialogShowEvent, + dialogTag, + dialogImport: () => + import(/* webpackChunkName: "hui-dialog-edit-lovelace" */ "./hui-dialog-edit-lovelace"), + }); + +export const showEditLovelaceDialog = ( + element: HTMLElement, + lovelace: Lovelace +) => { + if (!registeredDialog) { + registeredDialog = true; + registerEditLovelaceDialog(element); + } + fireEvent(element, dialogShowEvent, lovelace); +}; diff --git a/src/panels/lovelace/editor/show-save-config-dialog.ts b/src/panels/lovelace/editor/show-save-config-dialog.ts index f5207c8241..8025d5a7b7 100644 --- a/src/panels/lovelace/editor/show-save-config-dialog.ts +++ b/src/panels/lovelace/editor/show-save-config-dialog.ts @@ -26,7 +26,8 @@ export const showSaveDialog = ( fireEvent(element, "register-dialog", { dialogShowEvent, dialogTag, - dialogImport: () => import("./hui-dialog-save-config"), + dialogImport: () => + import(/* webpackChunkName: "hui-dialog-save-config" */ "./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 b91e381632..aea4a4e65c 100644 --- a/src/panels/lovelace/editor/types.ts +++ b/src/panels/lovelace/editor/types.ts @@ -1,6 +1,11 @@ -import { LovelaceCardConfig, LovelaceViewConfig } from "../../../data/lovelace"; +import { + LovelaceCardConfig, + LovelaceViewConfig, + ActionConfig, +} from "../../../data/lovelace"; import { EntityConfig } from "../entity-rows/types"; import { InputType } from "zlib"; +import { struct } from "../common/structs/struct"; export interface YamlChangedEvent extends Event { detail: { @@ -37,8 +42,16 @@ export interface EditorTarget extends EventTarget { checked?: boolean; configValue?: string; type?: InputType; + config: ActionConfig; } export interface CardPickTarget extends EventTarget { type: string; } + +export const actionConfigStruct = struct({ + action: "string", + navigation_path: "string?", + service: "string?", + service_data: "object?", +}); diff --git a/src/panels/lovelace/editor/view-editor/hui-edit-view.ts b/src/panels/lovelace/editor/view-editor/hui-edit-view.ts index 733ec46a39..8b27068812 100644 --- a/src/panels/lovelace/editor/view-editor/hui-edit-view.ts +++ b/src/panels/lovelace/editor/view-editor/hui-edit-view.ts @@ -30,7 +30,7 @@ import { deleteView, addView, replaceView } from "../config-util"; export class HuiEditView extends hassLocalizeLitMixin(LitElement) { public lovelace?: Lovelace; public viewIndex?: number; - protected hass?: HomeAssistant; + public hass?: HomeAssistant; private _config?: LovelaceViewConfig; private _badges?: EntityConfig[]; private _cards?: LovelaceCardConfig[]; diff --git a/src/panels/lovelace/editor/view-editor/hui-view-editor.ts b/src/panels/lovelace/editor/view-editor/hui-view-editor.ts index f9f95d7ad2..e85cbff0d1 100644 --- a/src/panels/lovelace/editor/view-editor/hui-view-editor.ts +++ b/src/panels/lovelace/editor/view-editor/hui-view-editor.ts @@ -69,19 +69,19 @@ export class HuiViewEditor extends hassLocalizeLitMixin(LitElement) {
diff --git a/src/panels/lovelace/editor/view-editor/show-edit-view-dialog.ts b/src/panels/lovelace/editor/view-editor/show-edit-view-dialog.ts index a912d89662..a3f7748de7 100644 --- a/src/panels/lovelace/editor/view-editor/show-edit-view-dialog.ts +++ b/src/panels/lovelace/editor/view-editor/show-edit-view-dialog.ts @@ -26,7 +26,8 @@ const registerEditViewDialog = (element: HTMLElement) => fireEvent(element, "register-dialog", { dialogShowEvent, dialogTag, - dialogImport: () => import("./hui-dialog-edit-view"), + dialogImport: () => + import(/* webpackChunkName: "hui-dialog-edit-view" */ "./hui-dialog-edit-view"), }); export const showEditViewDialog = ( diff --git a/src/panels/lovelace/entity-rows/hui-climate-entity-row.ts b/src/panels/lovelace/entity-rows/hui-climate-entity-row.ts index 35d6720205..3186c9c62b 100644 --- a/src/panels/lovelace/entity-rows/hui-climate-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-climate-entity-row.ts @@ -31,12 +31,22 @@ class HuiClimateEntityRow extends LitElement implements EntityRow { return html``; } + const stateObj = this.hass.states[this._config.entity]; + + if (!stateObj) { + return html` + + `; + } + return html` ${this.renderStyle()} `; diff --git a/src/panels/lovelace/entity-rows/types.ts b/src/panels/lovelace/entity-rows/types.ts index 88e983bc69..c73cf9ef50 100644 --- a/src/panels/lovelace/entity-rows/types.ts +++ b/src/panels/lovelace/entity-rows/types.ts @@ -29,7 +29,7 @@ export type EntityRowConfig = | WeblinkConfig | CallServiceConfig; -export interface EntityRow { +export interface EntityRow extends HTMLElement { hass?: HomeAssistant; setConfig(config: EntityRowConfig); } diff --git a/src/panels/lovelace/hui-editor.ts b/src/panels/lovelace/hui-editor.ts index 202de5b0bb..f2ac450904 100644 --- a/src/panels/lovelace/hui-editor.ts +++ b/src/panels/lovelace/hui-editor.ts @@ -1,4 +1,5 @@ import { LitElement, html } from "@polymer/lit-element"; +import { classMap } from "lit-html/directives/classMap"; import { TemplateResult } from "lit-html"; import yaml from "js-yaml"; @@ -7,20 +8,37 @@ import "@polymer/app-layout/app-header/app-header"; import "@polymer/app-layout/app-toolbar/app-toolbar"; import "@polymer/paper-button/paper-button"; import "@polymer/paper-icon-button/paper-icon-button"; +import "@polymer/paper-spinner/paper-spinner"; +import { struct } from "./common/structs/struct"; import { Lovelace } from "./types"; import { hassLocalizeLitMixin } from "../../mixins/lit-localize-mixin"; +import "../../components/ha-icon"; + const TAB_INSERT = " "; +const lovelaceStruct = struct.partial({ + title: "string?", + views: ["object"], +}); + class LovelaceFullConfigEditor extends hassLocalizeLitMixin(LitElement) { public lovelace?: Lovelace; public closeEditor?: () => void; private _haStyle?: DocumentFragment; + private _saving?: boolean; + private _changed?: boolean; + private _hashAdded?: boolean; + private _hash?: boolean; static get properties() { return { lovelace: {}, + _saving: {}, + _changed: {}, + _hashAdded: {}, + _hash: {}, }; } @@ -32,10 +50,26 @@ class LovelaceFullConfigEditor extends hassLocalizeLitMixin(LitElement) {
Edit Config
+ ${ + this._hash + ? html` + Comments will be not be saved! + ` + : "" + } Save +
@@ -44,6 +78,7 @@ class LovelaceFullConfigEditor extends hassLocalizeLitMixin(LitElement) { autocorrect="off" autocapitalize="off" spellcheck="false" + @input="${this._yamlChanged}" >
@@ -54,6 +89,11 @@ class LovelaceFullConfigEditor extends hassLocalizeLitMixin(LitElement) { const textArea = this.textArea; textArea.value = yaml.safeDump(this.lovelace!.config); textArea.addEventListener("keydown", (e) => { + if (e.keyCode === 51) { + this._hashAdded = true; + return; + } + if (e.keyCode !== 9) { return; } @@ -90,10 +130,17 @@ class LovelaceFullConfigEditor extends hassLocalizeLitMixin(LitElement) { app-header-layout { height: 100vh; } - paper-button { font-size: 16px; } + app-toolbar { + background-color: var(--dark-background-color, #455a64); + color: var(--dark-text-color); + } + + .comments { + font-size: 16px; + } .content { height: calc(100vh - 68px); @@ -110,20 +157,80 @@ class LovelaceFullConfigEditor extends hassLocalizeLitMixin(LitElement) { font-family: "Courier New", Courier, monospace; padding: 8px; } + + .save-button { + opacity: 0; + margin-left: -16px; + margin-top: -4px; + transition: opacity 1.5s; + } + + .saved { + opacity: 1; + } `; } - private _handleSave() { + private _closeEditor() { + if (this._changed) { + if ( + !confirm("You have unsafed changes, are you sure you want to exit?") + ) { + return; + } + } + window.onbeforeunload = null; + this.closeEditor!(); + } + + private async _handleSave() { + this._saving = true; + + if (this._hashAdded) { + if ( + !confirm( + "Your config might contain comments, these will not be saved. Do you want to continue?" + ) + ) { + return; + } + } + let value; try { value = yaml.safeLoad(this.textArea.value); } catch (err) { alert(`Unable to parse YAML: ${err}`); + this._saving = false; return; } + try { + value = lovelaceStruct(value); + } catch (err) { + alert(`Your config is not valid: ${err}`); + return; + } + try { + await this.lovelace!.saveConfig(value); + } catch (err) { + alert(`Unable to save YAML: ${err}`); + } + window.onbeforeunload = null; + this._saving = false; + this._changed = false; + this._hashAdded = false; + } - this.lovelace!.saveConfig(value); + private _yamlChanged() { + this._hash = this._hashAdded || this.textArea.value.includes("#"); + if (this._changed) { + return; + } + window.onbeforeunload = () => { + return true; + }; + this._changed = true; } private get textArea(): HTMLTextAreaElement { @@ -131,4 +238,10 @@ class LovelaceFullConfigEditor extends hassLocalizeLitMixin(LitElement) { } } +declare global { + interface HTMLElementTagNameMap { + "hui-editor": LovelaceFullConfigEditor; + } +} + customElements.define("hui-editor", LovelaceFullConfigEditor); diff --git a/src/panels/lovelace/hui-root.js b/src/panels/lovelace/hui-root.js deleted file mode 100644 index 415855e555..0000000000 --- a/src/panels/lovelace/hui-root.js +++ /dev/null @@ -1,454 +0,0 @@ -import "@polymer/app-layout/app-header-layout/app-header-layout"; -import "@polymer/app-layout/app-header/app-header"; -import "@polymer/app-layout/app-scroll-effects/effects/waterfall"; -import "@polymer/app-layout/app-toolbar/app-toolbar"; -import "@polymer/app-route/app-route"; -import "@polymer/paper-icon-button/paper-icon-button"; -import "@polymer/paper-button/paper-button"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; -import "@polymer/paper-menu-button/paper-menu-button"; -import "@polymer/paper-tabs/paper-tab"; -import "@polymer/paper-tabs/paper-tabs"; - -import { html } from "@polymer/polymer/lib/utils/html-tag"; -import { PolymerElement } from "@polymer/polymer/polymer-element"; - -import scrollToTarget from "../../common/dom/scroll-to-target"; - -import EventsMixin from "../../mixins/events-mixin"; -import localizeMixin from "../../mixins/localize-mixin"; -import NavigateMixin from "../../mixins/navigate-mixin"; - -import "../../layouts/ha-app-layout"; -import "../../components/ha-start-voice-button"; -import "../../components/ha-icon"; -import { loadModule, loadCSS, loadJS } from "../../common/dom/load_resource"; -import { subscribeNotifications } from "../../data/ws-notifications"; -import { computeNotifications } from "./common/compute-notifications"; -import "./components/notifications/hui-notification-drawer"; -import "./components/notifications/hui-notifications-button"; -import "./hui-unused-entities"; -import "./hui-view"; -import debounce from "../../common/util/debounce"; -import createCardElement from "./common/create-card-element"; -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 = {}; -const JS_CACHE = {}; - -class HUIRoot extends NavigateMixin( - EventsMixin(localizeMixin(PolymerElement)) -) { - static get template() { - return html` - - - - - - - - -
- - - - -
-
-
- - `; - } - - static get properties() { - return { - narrow: Boolean, - showMenu: Boolean, - hass: { type: Object, observer: "_hassChanged" }, - config: { - type: Object, - computed: "_computeConfig(lovelace)", - observer: "_configChanged", - }, - lovelace: { type: Object }, - columns: { type: Number, observer: "_columnsChanged" }, - _curView: { type: Number, value: 0 }, - route: { type: Object, observer: "_routeChanged" }, - notificationsOpen: { type: Boolean, value: false }, - _persistentNotifications: { type: Array, value: [] }, - _notifications: { - type: Array, - computed: "_updateNotifications(hass.states, _persistentNotifications)", - }, - _yamlMode: { - type: Boolean, - computed: "_computeYamlMode(lovelace)", - }, - _storageMode: { - type: Boolean, - computed: "_computeStorageMode(lovelace)", - }, - _editMode: { - type: Boolean, - value: false, - computed: "_computeEditMode(lovelace)", - observer: "_editModeChanged", - }, - routeData: Object, - }; - } - - constructor() { - super(); - this._debouncedConfigChanged = debounce( - () => this._selectView(this._curView), - 100 - ); - } - - connectedCallback() { - super.connectedCallback(); - this._unsubNotifications = subscribeNotifications( - this.hass.connection, - (notifications) => { - this._persistentNotifications = notifications; - } - ); - } - - disconnectedCallback() { - super.disconnectedCallback(); - if (typeof this._unsubNotifications === "function") { - this._unsubNotifications(); - } - } - - _updateNotifications(states, persistent) { - if (!states) return persistent; - - const configurator = computeNotifications(states); - return persistent.concat(configurator); - } - - _routeChanged(route) { - const views = this.config && this.config.views; - if (route.path === "" && route.prefix === "/lovelace" && views) { - 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].path === view || i === parseInt(view)) { - index = i; - break; - } - } - if (index !== this._curView) this._selectView(index); - } - } - - _computeViewPath(path, index) { - return path || index; - } - - _computeTitle(config) { - return config.title || "Home Assistant"; - } - - _computeTabsHidden(views, editMode) { - return views.length < 2 && !editMode; - } - - _computeTabTitle(title) { - return title || "Unnamed view"; - } - - _handleRefresh() { - this.fire("config-refresh"); - } - - _handleUnusedEntities() { - this._selectView("unused"); - } - - _deselect(ev) { - ev.target.selected = null; - } - - _handleHelp() { - window.open("https://www.home-assistant.io/lovelace/", "_blank"); - } - - _handleFullEditor() { - this.lovelace.enableFullEditMode(); - } - - _editModeEnable() { - if (this._yamlMode) { - window.alert("The edit UI is not available when in YAML mode."); - return; - } - this.lovelace.setEditMode(true); - if (this.config.views.length < 2) { - this.$.view.classList.remove("tabs-hidden"); - this.fire("iron-resize"); - } - } - - _editModeDisable() { - this.lovelace.setEditMode(false); - if (this.config.views.length < 2) { - this.$.view.classList.add("tabs-hidden"); - this.fire("iron-resize"); - } - } - - _editModeChanged() { - this._selectView(this._curView); - } - - _editView() { - showEditViewDialog(this, { - lovelace: this.lovelace, - viewIndex: this._curView, - }); - } - - _addView() { - showEditViewDialog(this, { - lovelace: this.lovelace, - }); - } - - _handleViewSelected(ev) { - const index = ev.detail.selected; - this._navigateView(index); - } - - _navigateView(viewIndex) { - if (viewIndex !== this._curView) { - const path = this.config.views[viewIndex].path || viewIndex; - this.navigate(`/lovelace/${path}`); - } - scrollToTarget(this, this.$.layout.header.scrollTarget); - } - - _selectView(viewIndex) { - this._curView = viewIndex; - - // Recreate a new element to clear the applied themes. - const root = this.$.view; - if (root.lastChild) { - root.removeChild(root.lastChild); - } - - let view; - let background = this.config.background || ""; - - if (viewIndex === "unused") { - view = document.createElement("hui-unused-entities"); - view.setConfig(this.config); - } else { - const viewConfig = this.config.views[this._curView]; - if (!viewConfig) { - this._editModeEnable(); - return; - } - if (viewConfig.panel) { - view = createCardElement(viewConfig.cards[0]); - view.isPanel = true; - } else { - view = document.createElement("hui-view"); - view.lovelace = this.lovelace; - view.config = viewConfig; - view.columns = this.columns; - view.index = viewIndex; - } - if (viewConfig.background) background = viewConfig.background; - } - - this.$.view.style.background = background; - - view.hass = this.hass; - root.appendChild(view); - } - - _hassChanged(hass) { - if (!this.$.view.lastChild) return; - this.$.view.lastChild.hass = hass; - } - - _configChanged(config) { - this._loadResources(config.resources || []); - // On config change, recreate the view from scratch. - this._selectView(this._curView); - this.$.view.classList.toggle("tabs-hidden", config.views.length < 2); - } - - _columnsChanged(columns) { - if (!this.$.view.lastChild) return; - this.$.view.lastChild.columns = columns; - } - - _loadResources(resources) { - resources.forEach((resource) => { - switch (resource.type) { - case "css": - if (resource.url in CSS_CACHE) break; - CSS_CACHE[resource.url] = loadCSS(resource.url); - break; - - case "js": - if (resource.url in JS_CACHE) break; - JS_CACHE[resource.url] = loadJS(resource.url); - break; - - case "module": - loadModule(resource.url); - break; - - case "html": - import(/* webpackChunkName: "import-href-polyfill" */ "../../resources/html-import/import-href").then( - ({ importHref }) => importHref(resource.url) - ); - break; - - default: - // eslint-disable-next-line - console.warn("Unknown resource type specified: ${resource.type}"); - } - }); - } - - _computeConfig(lovelace) { - return lovelace ? lovelace.config : null; - } - - _computeYamlMode(lovelace) { - return lovelace ? lovelace.mode === "yaml" : false; - } - - _computeStorageMode(lovelace) { - return lovelace ? lovelace.mode === "storage" : false; - } - - _computeEditMode(lovelace) { - return lovelace ? lovelace.editMode : false; - } -} -customElements.define("hui-root", HUIRoot); diff --git a/src/panels/lovelace/hui-root.ts b/src/panels/lovelace/hui-root.ts new file mode 100644 index 0000000000..b261529b38 --- /dev/null +++ b/src/panels/lovelace/hui-root.ts @@ -0,0 +1,665 @@ +import { + html, + LitElement, + PropertyDeclarations, + PropertyValues, +} from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; +import { classMap } from "lit-html/directives/classMap"; +import "@polymer/app-layout/app-header-layout/app-header-layout"; +import "@polymer/app-layout/app-header/app-header"; +import "@polymer/app-layout/app-scroll-effects/effects/waterfall"; +import "@polymer/app-layout/app-toolbar/app-toolbar"; +import "@polymer/app-route/app-route"; +import "@polymer/paper-icon-button/paper-icon-button"; +import "@polymer/paper-button/paper-button"; +import "@polymer/paper-item/paper-item"; +import "@polymer/paper-listbox/paper-listbox"; +import "@polymer/paper-menu-button/paper-menu-button"; +import "@polymer/paper-tabs/paper-tab"; +import "@polymer/paper-tabs/paper-tabs"; +import { HassEntities } from "home-assistant-js-websocket"; + +import scrollToTarget from "../../common/dom/scroll-to-target"; + +import "../../layouts/ha-app-layout"; +import "../../components/ha-start-voice-button"; +import "../../components/ha-icon"; +import { loadModule, loadCSS, loadJS } from "../../common/dom/load_resource"; +import { subscribeNotifications } from "../../data/ws-notifications"; +import debounce from "../../common/util/debounce"; +import { hassLocalizeLitMixin } from "../../mixins/lit-localize-mixin"; +import { HomeAssistant } from "../../types"; +import { LovelaceConfig } from "../../data/lovelace"; +import { navigate } from "../../common/navigate"; +import { fireEvent } from "../../common/dom/fire_event"; +import { computeNotifications } from "./common/compute-notifications"; +import "./components/notifications/hui-notification-drawer"; +import "./components/notifications/hui-notifications-button"; +import "./hui-view"; +// Not a duplicate import, this one is for type +// tslint:disable-next-line +import { HUIView } from "./hui-view"; +import { createCardElement } from "./common/create-card-element"; +import { showEditViewDialog } from "./editor/view-editor/show-edit-view-dialog"; +import { showEditLovelaceDialog } from "./editor/lovelace-editor/show-edit-lovelace-dialog"; +import { Lovelace } from "./types"; +import { afterNextRender } from "../../common/util/render-status"; + +// CSS and JS should only be imported once. Modules and HTML are safe. +const CSS_CACHE = {}; +const JS_CACHE = {}; + +declare global { + // tslint:disable-next-line + interface HASSDomEvents { + "rebuild-view": {}; + } +} + +let loadedUnusedEntities = false; + +class HUIRoot extends hassLocalizeLitMixin(LitElement) { + public narrow?: boolean; + public showMenu?: boolean; + public hass?: HomeAssistant; + public lovelace?: Lovelace; + public columns?: number; + public route?: { path: string; prefix: string }; + private _routeData?: { view: string }; + private _curView?: number | "hass-unused-entities"; + private _notificationsOpen: boolean; + private _persistentNotifications?: Notification[]; + private _haStyle?: DocumentFragment; + private _viewCache?: { [viewId: string]: HUIView }; + + private _debouncedConfigChanged: () => void; + private _unsubNotifications?: () => void; + + static get properties(): PropertyDeclarations { + return { + narrow: {}, + showMenu: {}, + hass: {}, + lovelace: {}, + columns: {}, + route: {}, + _routeData: {}, + _curView: {}, + _notificationsOpen: {}, + _persistentNotifications: {}, + }; + } + + constructor() { + super(); + this._notificationsOpen = false; + // The view can trigger a re-render when it knows that certain + // web components have been loaded. + this._debouncedConfigChanged = debounce( + () => this._selectView(this._curView, true), + 100 + ); + } + + public connectedCallback(): void { + super.connectedCallback(); + this._unsubNotifications = subscribeNotifications( + this.hass!.connection, + (notifications) => { + this._persistentNotifications = notifications; + } + ); + } + + public disconnectedCallback(): void { + super.disconnectedCallback(); + if (this._unsubNotifications) { + this._unsubNotifications(); + } + } + + protected render(): TemplateResult { + return html` + ${this.renderStyle()} + + + + + ${ + this._editMode + ? html` + + +
+ ${ + this.config.title || + this.localize("ui.panel.lovelace.editor.header") + } + +
+ + + + + Raw config editor + + +
+ ` + : html` + + +
${this.config.title || "Home Assistant"}
+ + + + + + ${ + this._yamlMode + ? html` + Refresh + ` + : "" + } + Unused entities + ${ + this.localize("ui.panel.lovelace.editor.configure_ui") + } + Help + + +
+ ` + } + + ${ + this.lovelace!.config.views.length > 1 || this._editMode + ? html` +
+ + ${ + this.lovelace!.config.views.map( + (view) => html` + + ${ + view.icon + ? html` + + ` + : view.title || "Unnamed view" + } + ${ + this._editMode + ? html` + + ` + : "" + } + + ` + ) + } + ${ + this._editMode + ? html` + + + + ` + : "" + } + +
+ ` + : "" + } +
+
+ + `; + } + + protected renderStyle(): TemplateResult { + if (!this._haStyle) { + this._haStyle = document.importNode( + (document.getElementById("ha-style")! + .children[0] as HTMLTemplateElement).content, + true + ); + } + + return html` + ${this._haStyle} + + `; + } + + protected updated(changedProperties: PropertyValues): void { + super.updated(changedProperties); + + const view = this._viewRoot; + const huiView = view.lastChild as HUIView; + + if (changedProperties.has("columns") && huiView) { + huiView.columns = this.columns; + } + + if (changedProperties.has("hass") && huiView) { + huiView.hass = this.hass; + } + + let newSelectView; + let force = false; + + if (changedProperties.has("route")) { + const views = this.config && this.config.views; + if ( + this.route!.path === "" && + this.route!.prefix === "/lovelace" && + views + ) { + navigate(this, `/lovelace/${views[0].path || 0}`, true); + } else if (this._routeData!.view === "hass-unused-entities") { + newSelectView = "hass-unused-entities"; + } else if (this._routeData!.view) { + const selectedView = this._routeData!.view; + const selectedViewInt = parseInt(selectedView, 10); + let index = 0; + for (let i = 0; i < views.length; i++) { + if (views[i].path === selectedView || i === selectedViewInt) { + index = i; + break; + } + } + newSelectView = index; + } + } + + if (changedProperties.has("lovelace")) { + const oldLovelace = changedProperties.get("lovelace") as + | Lovelace + | undefined; + + if (!oldLovelace || oldLovelace.config !== this.lovelace!.config) { + this._loadResources(this.lovelace!.config.resources || []); + // On config change, recreate the current view from scratch. + force = true; + } + + if (!oldLovelace || oldLovelace.editMode !== this.lovelace!.editMode) { + // On edit mode change, recreate the current view from scratch + force = true; + } + } + + if (newSelectView !== undefined || force) { + if (force && newSelectView === undefined) { + newSelectView = this._curView; + } + this._selectView(newSelectView, force); + } + } + + private get _notifications() { + return this._updateNotifications( + this.hass!.states, + this._persistentNotifications! || [] + ); + } + + private get config(): LovelaceConfig { + return this.lovelace!.config; + } + + private get _yamlMode(): boolean { + return this.lovelace!.mode === "yaml"; + } + + private get _editMode() { + return this.lovelace!.editMode; + } + + private get _layout(): any { + return this.shadowRoot!.getElementById("layout"); + } + + private get _viewRoot(): HTMLDivElement { + return this.shadowRoot!.getElementById("view") as HTMLDivElement; + } + + private _routeDataChanged(ev): void { + this._routeData = ev.detail.value; + } + + private _handleNotificationsOpenChanged(ev): void { + this._notificationsOpen = ev.detail.value; + } + + private _updateNotifications( + states: HassEntities, + persistent: Array + ): Array { + const configurator = computeNotifications(states); + return persistent.concat(configurator); + } + + private _handleRefresh(): void { + fireEvent(this, "config-refresh"); + } + + private _handleUnusedEntities(): void { + navigate(this, `/lovelace/hass-unused-entities`); + } + + private _deselect(ev): void { + ev.target.selected = null; + } + + private _handleHelp(): void { + window.open("https://www.home-assistant.io/lovelace/", "_blank"); + } + + private _editModeEnable(): void { + if (this._yamlMode) { + window.alert("The edit UI is not available when in YAML mode."); + return; + } + this.lovelace!.setEditMode(true); + if (this.config.views.length < 2) { + fireEvent(this, "iron-resize"); + } + } + + private _editModeDisable(): void { + this.lovelace!.setEditMode(false); + if (this.config.views.length < 2) { + fireEvent(this, "iron-resize"); + } + } + + private _editLovelace() { + showEditLovelaceDialog(this, this.lovelace!); + } + + private _editView() { + showEditViewDialog(this, { + lovelace: this.lovelace!, + viewIndex: this._curView as number, + }); + } + + private _addView() { + showEditViewDialog(this, { + lovelace: this.lovelace!, + }); + } + + private _handleViewSelected(ev) { + const viewIndex = ev.detail.selected as number; + + if (viewIndex !== this._curView) { + const path = this.config.views[viewIndex].path || viewIndex; + navigate(this, `/lovelace/${path}`); + } + scrollToTarget(this, this._layout.header.scrollTarget); + } + + private async _selectView( + viewIndex: HUIRoot["_curView"], + force: boolean + ): Promise { + if (!force && this._curView === viewIndex) { + return; + } + + viewIndex = viewIndex === undefined ? 0 : viewIndex; + + this._curView = viewIndex; + + if (force) { + this._viewCache = {}; + } + + // Recreate a new element to clear the applied themes. + const root = this._viewRoot; + + if (root.lastChild) { + root.removeChild(root.lastChild); + } + + if (viewIndex === "hass-unused-entities") { + if (!loadedUnusedEntities) { + loadedUnusedEntities = true; + await import(/* webpackChunkName: "hui-unused-entities" */ "./hui-unused-entities"); + } + const unusedEntities = document.createElement("hui-unused-entities"); + unusedEntities.setConfig(this.config); + unusedEntities.hass = this.hass!; + root.style.background = this.config.background || ""; + root.appendChild(unusedEntities); + return; + } + + let view; + const viewConfig = this.config.views[viewIndex]; + + if (!viewConfig) { + this._editModeEnable(); + return; + } + + if (!force && this._viewCache![viewIndex]) { + view = this._viewCache![viewIndex]; + } else { + await new Promise((resolve) => afterNextRender(resolve)); + + if (viewConfig.panel && viewConfig.cards && viewConfig.cards.length > 0) { + view = createCardElement(viewConfig.cards[0]); + view.isPanel = true; + } else { + view = document.createElement("hui-view"); + view.lovelace = this.lovelace; + view.columns = this.columns; + view.index = viewIndex; + } + this._viewCache![viewIndex] = view; + } + + view.hass = this.hass; + root.style.background = + viewConfig.background || this.config.background || ""; + root.appendChild(view); + } + + private _loadResources(resources) { + resources.forEach((resource) => { + switch (resource.type) { + case "css": + if (resource.url in CSS_CACHE) { + break; + } + CSS_CACHE[resource.url] = loadCSS(resource.url); + break; + + case "js": + if (resource.url in JS_CACHE) { + break; + } + JS_CACHE[resource.url] = loadJS(resource.url); + break; + + case "module": + loadModule(resource.url); + break; + + case "html": + import(/* webpackChunkName: "import-href-polyfill" */ "../../resources/html-import/import-href").then( + ({ importHref }) => importHref(resource.url) + ); + break; + + default: + // tslint:disable-next-line + console.warn(`Unknown resource type specified: ${resource.type}`); + } + }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-root": HUIRoot; + } +} + +customElements.define("hui-root", HUIRoot); diff --git a/src/panels/lovelace/hui-unused-entities.ts b/src/panels/lovelace/hui-unused-entities.ts index 9548c44bec..ec3970b9ae 100644 --- a/src/panels/lovelace/hui-unused-entities.ts +++ b/src/panels/lovelace/hui-unused-entities.ts @@ -2,15 +2,16 @@ import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element"; import "./cards/hui-entities-card"; -import computeUnusedEntities from "./common/compute-unused-entities"; -import createCardElement from "./common/create-card-element"; +import { computeUnusedEntities } from "./common/compute-unused-entities"; +import { createCardElement } from "./common/create-card-element"; import { HomeAssistant } from "../../types"; import { TemplateResult } from "lit-html"; import { LovelaceCard } from "./types"; +import { LovelaceConfig } from "../../data/lovelace"; export class HuiUnusedEntities extends LitElement { private _hass?: HomeAssistant; - private _config?: object; + private _config?: LovelaceConfig; private _element?: LovelaceCard; static get properties(): PropertyDeclarations { @@ -29,10 +30,7 @@ export class HuiUnusedEntities extends LitElement { this._element.hass = this._hass; } - public setConfig(config: object): void { - if (!config) { - throw new Error("Card config incorrect"); - } + public setConfig(config: LovelaceConfig): void { this._config = config; this._createElement(); } @@ -62,7 +60,7 @@ export class HuiUnusedEntities extends LitElement { private _createElement(): void { if (this._hass) { - const entities = computeUnusedEntities(this._hass, this._config).map( + const entities = computeUnusedEntities(this._hass, this._config!).map( (entity) => ({ entity, secondary_info: "entity-id", diff --git a/src/panels/lovelace/hui-view-editable.ts b/src/panels/lovelace/hui-view-editable.ts new file mode 100644 index 0000000000..8740b29f13 --- /dev/null +++ b/src/panels/lovelace/hui-view-editable.ts @@ -0,0 +1,3 @@ +// hui-view dependencies for when in edit mode. +import "@polymer/paper-fab/paper-fab"; +import "./components/hui-card-options"; diff --git a/src/panels/lovelace/hui-view.js b/src/panels/lovelace/hui-view.js deleted file mode 100644 index 369dd3fb1c..0000000000 --- a/src/panels/lovelace/hui-view.js +++ /dev/null @@ -1,251 +0,0 @@ -import { html } from "@polymer/polymer/lib/utils/html-tag"; -import { PolymerElement } from "@polymer/polymer/polymer-element"; - -import "@polymer/paper-fab/paper-fab"; -import "../../components/entity/ha-state-label-badge"; -import "./components/hui-card-options"; - -import applyThemesOnElement from "../../common/dom/apply_themes_on_element"; - -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/card-editor/show-edit-card-dialog"; - -class HUIView extends localizeMixin(EventsMixin(PolymerElement)) { - static get template() { - return html` - -
-
- - `; - } - - static get properties() { - return { - hass: { - type: Object, - observer: "_hassChanged", - }, - lovelace: Object, - config: Object, - columns: Number, - editMode: Boolean, - index: Number, - }; - } - - static get observers() { - return [ - // Put all properties in 1 observer so we only call configChanged once - "_createBadges(config)", - "_createCards(config, columns, editMode)", - ]; - } - - constructor() { - super(); - this._cards = []; - this._badges = []; - } - - _addCard() { - showEditCardDialog(this, { - lovelace: this.lovelace, - path: [this.index], - }); - } - - _createBadges(config) { - const root = this.$.badges; - while (root.lastChild) { - root.removeChild(root.lastChild); - } - - if (!config || !config.badges || !Array.isArray(config.badges)) { - root.style.display = "none"; - this._badges = []; - return; - } - - const elements = []; - for (const entityId of config.badges) { - if (!(entityId in this.hass.states)) continue; - - const element = document.createElement("ha-state-label-badge"); - element.setProperties({ - hass: this.hass, - state: this.hass.states[entityId], - }); - elements.push({ element, entityId }); - root.appendChild(element); - } - this._badges = elements; - root.style.display = elements.length > 0 ? "block" : "none"; - } - - _createCards(config) { - const root = this.$.columns; - - while (root.lastChild) { - root.removeChild(root.lastChild); - } - - if (!config || !config.cards || !Array.isArray(config.cards)) { - this._cards = []; - return; - } - - const elements = []; - const elementsToAppend = []; - config.cards.forEach((cardConfig, cardIndex) => { - const element = createCardElement(cardConfig); - element.hass = this.hass; - elements.push(element); - - if (!this.lovelace.editMode) { - elementsToAppend.push(element); - return; - } - - const wrapper = document.createElement("hui-card-options"); - wrapper.hass = this.hass; - wrapper.lovelace = this.lovelace; - wrapper.path = [this.index, cardIndex]; - wrapper.appendChild(element); - elementsToAppend.push(wrapper); - }); - - let columns = []; - const columnEntityCount = []; - for (let i = 0; i < this.columns; i++) { - columns.push([]); - columnEntityCount.push(0); - } - - // Find column with < 5 entities, else column with lowest count - function getColumnIndex(size) { - let minIndex = 0; - for (let i = 0; i < columnEntityCount.length; i++) { - if (columnEntityCount[i] < 5) { - minIndex = i; - break; - } - if (columnEntityCount[i] < columnEntityCount[minIndex]) { - minIndex = i; - } - } - - columnEntityCount[minIndex] += size; - - return minIndex; - } - - elements.forEach((el, index) => { - const cardSize = computeCardSize(el); - // Element to append might be the wrapped card when we're editing. - columns[getColumnIndex(cardSize)].push(elementsToAppend[index]); - }); - - // Remove empty columns - columns = columns.filter((val) => val.length > 0); - - columns.forEach((column) => { - const columnEl = document.createElement("div"); - columnEl.classList.add("column"); - column.forEach((el) => columnEl.appendChild(el)); - root.appendChild(columnEl); - }); - - this._cards = elements; - - if ("theme" in config) { - applyThemesOnElement(root, this.hass.themes, config.theme); - } - } - - _hassChanged(hass) { - this._badges.forEach((badge) => { - const { element, entityId } = badge; - element.setProperties({ - hass, - state: hass.states[entityId], - }); - }); - this._cards.forEach((element) => { - element.hass = hass; - }); - } -} - -customElements.define("hui-view", HUIView); diff --git a/src/panels/lovelace/hui-view.ts b/src/panels/lovelace/hui-view.ts new file mode 100644 index 0000000000..471e917c1e --- /dev/null +++ b/src/panels/lovelace/hui-view.ts @@ -0,0 +1,298 @@ +import { + html, + LitElement, + PropertyValues, + PropertyDeclarations, +} from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; + +import "../../components/entity/ha-state-label-badge"; +// This one is for types +// tslint:disable-next-line +import { HaStateLabelBadge } from "../../components/entity/ha-state-label-badge"; + +import applyThemesOnElement from "../../common/dom/apply_themes_on_element"; + +import { hassLocalizeLitMixin } from "../../mixins/lit-localize-mixin"; +import { LovelaceViewConfig } from "../../data/lovelace"; +import { HomeAssistant } from "../../types"; + +import { Lovelace, LovelaceCard } from "./types"; +import { createCardElement } from "./common/create-card-element"; +import { computeCardSize } from "./common/compute-card-size"; +import { showEditCardDialog } from "./editor/card-editor/show-edit-card-dialog"; + +let editCodeLoaded = false; + +// Find column with < 5 entities, else column with lowest count +const getColumnIndex = (columnEntityCount: number[], size: number) => { + let minIndex = 0; + for (let i = 0; i < columnEntityCount.length; i++) { + if (columnEntityCount[i] < 5) { + minIndex = i; + break; + } + if (columnEntityCount[i] < columnEntityCount[minIndex]) { + minIndex = i; + } + } + + columnEntityCount[minIndex] += size; + + return minIndex; +}; + +export class HUIView extends hassLocalizeLitMixin(LitElement) { + public hass?: HomeAssistant; + public lovelace?: Lovelace; + public columns?: number; + public index?: number; + private _cards: LovelaceCard[]; + private _badges: Array<{ element: HaStateLabelBadge; entityId: string }>; + + static get properties(): PropertyDeclarations { + return { + hass: {}, + lovelace: {}, + columns: {}, + index: {}, + _cards: {}, + _badges: {}, + }; + } + + constructor() { + super(); + this._cards = []; + this._badges = []; + } + + protected render(): TemplateResult { + return html` + ${this.renderStyles()} +
+
+ ${ + this.lovelace!.editMode + ? html` + + ` + : "" + } + `; + } + + protected renderStyles(): TemplateResult { + return html` + + `; + } + + protected updated(changedProperties: PropertyValues): void { + super.updated(changedProperties); + + const lovelace = this.lovelace!; + + if (lovelace.editMode && !editCodeLoaded) { + editCodeLoaded = true; + import(/* webpackChunkName: "hui-view-editable" */ "./hui-view-editable"); + } + + let editModeChanged = false; + let configChanged = false; + + if (changedProperties.has("lovelace")) { + const oldLovelace = changedProperties.get("lovelace") as Lovelace; + editModeChanged = + !oldLovelace || lovelace.editMode !== oldLovelace.editMode; + configChanged = !oldLovelace || lovelace.config !== oldLovelace.config; + } + + if (configChanged) { + this._createBadges(lovelace.config.views[this.index!]); + } else if (changedProperties.has("hass")) { + this._badges.forEach((badge) => { + const { element, entityId } = badge; + element.hass = this.hass!; + element.state = this.hass!.states[entityId]; + }); + } + + if (configChanged || editModeChanged || changedProperties.has("columns")) { + this._createCards(lovelace.config.views[this.index!]); + } else if (changedProperties.has("hass")) { + this._cards.forEach((element) => { + element.hass = this.hass; + }); + } + } + + private _addCard(): void { + showEditCardDialog(this, { + lovelace: this.lovelace!, + path: [this.index!], + }); + } + + private _createBadges(config: LovelaceViewConfig): void { + const root = this.shadowRoot!.getElementById("badges")!; + + while (root.lastChild) { + root.removeChild(root.lastChild); + } + + if (!config || !config.badges || !Array.isArray(config.badges)) { + root.style.display = "none"; + this._badges = []; + return; + } + + const elements: HUIView["_badges"] = []; + for (const entityId of config.badges) { + const element = document.createElement("ha-state-label-badge"); + element.hass = this.hass; + element.state = this.hass!.states[entityId]; + elements.push({ element, entityId }); + root.appendChild(element); + } + this._badges = elements; + root.style.display = elements.length > 0 ? "block" : "none"; + } + + private _createCards(config: LovelaceViewConfig): void { + const root = this.shadowRoot!.getElementById("columns")!; + + while (root.lastChild) { + root.removeChild(root.lastChild); + } + + if (!config || !config.cards || !Array.isArray(config.cards)) { + this._cards = []; + return; + } + + const elements: LovelaceCard[] = []; + const elementsToAppend: HTMLElement[] = []; + config.cards.forEach((cardConfig, cardIndex) => { + const element = createCardElement(cardConfig) as LovelaceCard; + element.hass = this.hass; + elements.push(element); + + if (!this.lovelace!.editMode) { + elementsToAppend.push(element); + return; + } + + const wrapper = document.createElement("hui-card-options"); + wrapper.hass = this.hass; + wrapper.lovelace = this.lovelace; + wrapper.path = [this.index!, cardIndex]; + wrapper.appendChild(element); + elementsToAppend.push(wrapper); + }); + + let columns: HTMLElement[][] = []; + const columnEntityCount: number[] = []; + for (let i = 0; i < this.columns!; i++) { + columns.push([]); + columnEntityCount.push(0); + } + + elements.forEach((el, index) => { + const cardSize = computeCardSize(el); + // Element to append might be the wrapped card when we're editing. + columns[getColumnIndex(columnEntityCount, cardSize)].push( + elementsToAppend[index] + ); + }); + + // Remove empty columns + columns = columns.filter((val) => val.length > 0); + + columns.forEach((column) => { + const columnEl = document.createElement("div"); + columnEl.classList.add("column"); + column.forEach((el) => columnEl.appendChild(el)); + root.appendChild(columnEl); + }); + + this._cards = elements; + + if ("theme" in config) { + applyThemesOnElement(root, this.hass!.themes, config.theme); + } + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-view": HUIView; + } +} + +customElements.define("hui-view", HUIView); diff --git a/src/panels/mailbox/ha-panel-mailbox.js b/src/panels/mailbox/ha-panel-mailbox.js index 6cde979b4a..2d10df3961 100644 --- a/src/panels/mailbox/ha-panel-mailbox.js +++ b/src/panels/mailbox/ha-panel-mailbox.js @@ -167,7 +167,8 @@ class HaPanelMailbox extends EventsMixin(LocalizeMixin(PolymerElement)) { this.fire("register-dialog", { dialogShowEvent: "show-audio-message-dialog", dialogTag: "ha-dialog-show-audio-message", - dialogImport: () => import("./ha-dialog-show-audio-message"), + dialogImport: () => + import(/* webpackChunkName: "ha-dialog-show-audio-message" */ "./ha-dialog-show-audio-message"), }); } this.hassChanged = this.hassChanged.bind(this); diff --git a/src/panels/profile/ha-mfa-modules-card.js b/src/panels/profile/ha-mfa-modules-card.js index daaccd1c2b..50d07606f5 100644 --- a/src/panels/profile/ha-mfa-modules-card.js +++ b/src/panels/profile/ha-mfa-modules-card.js @@ -90,7 +90,8 @@ class HaMfaModulesCard extends EventsMixin(LocalizeMixin(PolymerElement)) { this.fire("register-dialog", { dialogShowEvent: "show-mfa-module-setup-flow", dialogTag: "ha-mfa-module-setup-flow", - dialogImport: () => import("./ha-mfa-module-setup-flow"), + dialogImport: () => + import(/* webpackChunkName: "ha-mfa-module-setup-flow" */ "./ha-mfa-module-setup-flow"), }); } } diff --git a/src/translations/en.json b/src/translations/en.json index 6b85bcad9f..cb2e5b6a0e 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -35,6 +35,7 @@ "updater": "Updater", "vacuum": "Vacuum", "weblink": "Weblink", + "zha": "ZHA", "zwave": "Z-Wave" }, "panel": { @@ -472,6 +473,19 @@ } }, "dialogs": { + "more_info_control": { + "script": { + "last_action": "Last Action" + }, + "sun": { + "elevation": "Elevation", + "rising": "Rising", + "setting": "Setting" + }, + "updater": { + "title": "Update Instructions" + } + }, "more_info_settings": { "save": "Save", "name": "Name", @@ -751,6 +765,10 @@ "delete_user": "Delete user" } }, + "zha": { + "caption": "ZHA", + "description": "Zigbee Home Automation network management" + }, "zwave": { "caption": "Z-Wave", "description": "Manage your Z-Wave network" diff --git a/src/util/register-service-worker.js b/src/util/register-service-worker.js index aa49340885..893f27c01e 100644 --- a/src/util/register-service-worker.js +++ b/src/util/register-service-worker.js @@ -14,8 +14,8 @@ export default () => { !__DEV__ ) { // Notify users here of a new frontend being available. - import("./show-new-frontend-toast").then((mod) => - mod.default(installingWorker) + import(/* webpackChunkName: "show-new-frontend-toast" */ "./show-new-frontend-toast").then( + (mod) => mod.default(installingWorker) ); } }); diff --git a/test-mocha/panels/lovelace/editor/config-util.spec.ts b/test-mocha/panels/lovelace/editor/config-util.spec.ts index b8a9717b21..fc417e2ca1 100644 --- a/test-mocha/panels/lovelace/editor/config-util.spec.ts +++ b/test-mocha/panels/lovelace/editor/config-util.spec.ts @@ -1,6 +1,9 @@ import * as assert from "assert"; -import { swapCard } from "../../../../src/panels/lovelace/editor/config-util"; +import { + swapCard, + moveCard, +} from "../../../../src/panels/lovelace/editor/config-util"; import { LovelaceConfig } from "../../../../src/data/lovelace"; describe("swapCard", () => { @@ -52,3 +55,77 @@ describe("swapCard", () => { assert.deepEqual(expected, result); }); }); + +describe("moveCard", () => { + it("move a card to an empty view", () => { + const config: LovelaceConfig = { + views: [ + {}, + { + cards: [{ type: "card1" }, { type: "card2" }], + }, + ], + }; + + const result = moveCard(config, [1, 0], [0]); + const expected = { + views: [ + { + cards: [{ type: "card1" }], + }, + { + cards: [{ type: "card2" }], + }, + ], + }; + assert.deepEqual(expected, result); + }); + + it("move a card to different view", () => { + const config: LovelaceConfig = { + views: [ + { + cards: [{ type: "v1-c1" }, { type: "v1-c2" }], + }, + { + cards: [{ type: "v2-c1" }, { type: "v2-c2" }], + }, + ], + }; + + const result = moveCard(config, [1, 0], [0]); + const expected = { + views: [ + { + cards: [{ type: "v1-c1" }, { type: "v1-c2" }, { type: "v2-c1" }], + }, + { + cards: [{ type: "v2-c2" }], + }, + ], + }; + assert.deepEqual(expected, result); + }); + + it("move a card to the same view", () => { + const config: LovelaceConfig = { + views: [ + { + cards: [{ type: "v1-c1" }, { type: "v1-c2" }], + }, + { + cards: [{ type: "v2-c1" }, { type: "v2-c2" }], + }, + ], + }; + + const result = () => { + moveCard(config, [1, 0], [1]); + }; + assert.throws( + result, + Error, + "You can not move a card to the view it is in." + ); + }); +}); diff --git a/translations/bg.json b/translations/bg.json index 123623a634..d97b7127f2 100644 --- a/translations/bg.json +++ b/translations/bg.json @@ -53,8 +53,8 @@ "on": "Регистриран" }, "motion": { - "off": "Чисто", - "on": "Регистрирано" + "off": "Без движение", + "on": "Движение" }, "occupancy": { "off": "Чисто", @@ -144,7 +144,8 @@ "performance": "Производителност", "high_demand": "Високо натоварване", "heat_pump": "Термопомпа", - "gas": "Газ" + "gas": "Газ", + "manual": "Ръчен режим" }, "configurator": { "configure": "Настройване", @@ -185,8 +186,8 @@ "on": "Включен" }, "light": { - "off": "Изключена", - "on": "Включен" + "off": "Изключено", + "on": "Включено" }, "lock": { "locked": "Заключен", @@ -422,6 +423,10 @@ "event": "Събитие", "enter": "Влизане", "leave": "Излизане" + }, + "webhook": { + "label": "Webhook", + "webhook_id": "Webhook идентификатор" } } }, @@ -712,6 +717,47 @@ "required_fields": "Попълнете всички задължителни полета" } } + }, + "lovelace": { + "cards": { + "shopping-list": { + "checked_items": "Отметнати артикули", + "clear_items": "Изтрий отметнатите артикули", + "add_item": "Добави артикул" + } + }, + "editor": { + "edit_card": { + "header": "Конфигуриране на Карта", + "save": "Запази", + "toggle_editor": "Превключете редактора", + "pick_card": "Изберете картата, която искате да добавите.", + "add": "Добавяне на карта", + "edit": "Редактиране", + "delete": "Изтриване" + }, + "migrate": { + "header": "Несъвместима конфигурация", + "para_no_id": "Този елемент няма идентификатор. Моля, добавете идентификатор към този елемент в \"ui-lovelace.yaml\".", + "para_migrate": "Home Assistant може автоматично да добави идентификатори към всичките ви карти и изгледи, като натиска бутона \"Мигриране на конфигурация\".", + "migrate": "Мигриране на конфигурация" + }, + "header": "Редактиране на потребителския интерфейс", + "configure_ui": "Конфигуриране на потребителския интерфейс", + "edit_view": { + "header": "Конфигурация на изглед", + "add": "Добавяне на изглед", + "edit": "Редактиране на изгледа", + "delete": "Изтриване на изгледа" + }, + "save_config": { + "header": "Поемете контрол над потребителския интерфейс на Lovelace", + "para": "По подразбиране Home Assistant поддържа потребителския интерфейс, като го актуализира, когато нови обекти или компоненти на Lovelace станат достъпни. Ако поемете контрол, ние вече няма да правим автоматично промени вместо вас.", + "para_sure": "Наистина ли искате да поемете управлението на потребителския интерфейс?", + "cancel": "Няма значение", + "save": "Поемете контрола" + } + } } }, "sidebar": { @@ -720,7 +766,8 @@ }, "common": { "loading": "Зареждане", - "cancel": "Отмени" + "cancel": "Отмени", + "save": "Запази" }, "duration": { "day": "{count}{count, plural,\n one {ден}\n other {дни}\n}", @@ -780,7 +827,9 @@ "clear_code": "Изчистване", "disarm": "Деактивирaне", "arm_home": "Под охрана - вкъщи", - "arm_away": "Под охрана" + "arm_away": "Под охрана", + "arm_night": "Под охрана - нощ", + "armed_custom_bypass": "Потребителски байпас" }, "automation": { "last_triggered": "Последно задействане", diff --git a/translations/ca.json b/translations/ca.json index 6287c8b1a5..5b0e98263c 100644 --- a/translations/ca.json +++ b/translations/ca.json @@ -133,8 +133,8 @@ "climate": { "off": "Apagat", "on": "Encès", - "heat": "Calent", - "cool": "Fred", + "heat": "Escalfar", + "cool": "Refredar", "idle": "Inactiu", "auto": "Automàtic", "dry": "Assecar", @@ -423,6 +423,10 @@ "event": "Esdeveniment:", "enter": "Entrar", "leave": "Sortir" + }, + "webhook": { + "label": "Webhook", + "webhook_id": "ID de Webhook" } } }, @@ -820,7 +824,7 @@ }, "alarm_control_panel": { "code": "Codi", - "clear_code": "Esborrar", + "clear_code": "Supr.", "disarm": "Desactivar", "arm_home": "Activar, a casa", "arm_away": "Activar, fora", diff --git a/translations/cs.json b/translations/cs.json index c8d3940366..a298eb373b 100644 --- a/translations/cs.json +++ b/translations/cs.json @@ -423,6 +423,10 @@ "event": "Událost:", "enter": "Vstup", "leave": "Opuštění" + }, + "webhook": { + "label": "Webhook", + "webhook_id": "Webhook ID" } } }, @@ -726,13 +730,32 @@ "edit_card": { "header": "Konfigurace karty", "save": "Uložit", - "toggle_editor": "Přepnout Editor" + "toggle_editor": "Přepnout Editor", + "pick_card": "Vyberte kartu, kterou chcete přidat.", + "add": "Přidat kartu", + "edit": "Upravit", + "delete": "Odstranit" }, "migrate": { "header": "Konfigurace není kompatibilní", "para_no_id": "Tento prvek nemá ID. Přidejte k tomuto prvku ID v 'ui-lovelace.yaml'.", "para_migrate": "Home Assistant může automaticky přidávat ID ke všem kartám a pohledům stisknutím tlačítka Migrate config.", "migrate": "Migrovat konfiguraci" + }, + "header": "Upravit UI", + "configure_ui": "Konfigurovat UI", + "edit_view": { + "header": "Zobrazit konfiguraci", + "add": "Přidat pohled", + "edit": "Upravit pohled", + "delete": "Odstranit pohled" + }, + "save_config": { + "header": "Převzít kontrolu nad vaší Lovelace UI", + "para": "Ve výchozím nastavení Home Assistant bude usprávovat vaše uživatelské rozhraní, aktualizovat při přidání nové entity nebo Lovelace komponenty. Pokud převezmete kontrolu nebudeme již provádět změny automaticky za vás.", + "para_sure": "Opravdu chcete převzít kontrolu nad uživalským rohraním ?", + "cancel": "Zahodit změnu", + "save": "Převzít kontrolu" } } } @@ -743,7 +766,8 @@ }, "common": { "loading": "Načítání", - "cancel": "Zrušit" + "cancel": "Zrušit", + "save": "Uložit" }, "duration": { "day": "{count} {count, plural,\none {den}\nother {dny}\n}", diff --git a/translations/da.json b/translations/da.json index 9c9d1aca1e..607c4045b6 100644 --- a/translations/da.json +++ b/translations/da.json @@ -423,6 +423,10 @@ "event": "Begivenhed", "enter": "Ankom", "leave": "Forlade" + }, + "webhook": { + "label": "Webhook", + "webhook_id": "Webhook-ID" } } }, @@ -605,12 +609,17 @@ "logout": "Log af", "change_password": { "header": "Skift adgangskode", + "current_password": "Nuværende adgangskode", + "new_password": "Ny adgangskode", + "confirm_new_password": "Bekræft ny adgangskode", + "error_required": "Påkrævet", "submit": "Gem og afslut" }, "mfa": { "header": "Multifaktor godkendelsesmoduler", "disable": "Deaktiver", - "enable": "Aktiver" + "enable": "Aktiver", + "confirm_disable": "Er du sikker på du vil deaktivere {name}?" }, "mfa_setup": { "title_aborted": "Afbrudt", @@ -708,6 +717,47 @@ "required_fields": "Udfyld alle obligatoriske felter" } } + }, + "lovelace": { + "cards": { + "shopping-list": { + "checked_items": "Markerede elementer", + "clear_items": "Ryd markerede elementer", + "add_item": "Tilføj element" + } + }, + "editor": { + "edit_card": { + "header": "Kortkonfiguration", + "save": "Gem", + "toggle_editor": "Skifte Editor", + "pick_card": "Vælg det kort, du vil tilføje.", + "add": "Tilføj kort", + "edit": "Rediger", + "delete": "Slet" + }, + "migrate": { + "header": "Konfiguration ikke færdiggjort", + "para_no_id": "Dette element har ikke et id. Tilføj venligst et id til dette element i \"ui-lovelace.yaml\".", + "para_migrate": "Hjem assistent kan tilføje id'er til alle dine kort og oversigter automatisk for dig ved at trykke på knappen 'Overfør configuration'.", + "migrate": "Migrer opsætning" + }, + "header": "Rediger UI", + "configure_ui": "Konfigurer UI", + "edit_view": { + "header": "Vis konfiguration", + "add": "Tilføje visning", + "edit": "Redigere visning", + "delete": "Slette visning" + }, + "save_config": { + "header": "Tage kontrol over din 2Lovelace\" UI", + "para": "Som standard vil hjem assistenten fastholde din brugergrænseflade, opdaterer den når nye enheder eller \"Lovelace\" komponenter bliver tilgængelige. Hvis du tager kontrol så vil vi ikke længere foretage ændringer automatisk for dig.", + "para_sure": "Er du sikker på du ønsker at tage kontrol over din brugergrænseflade?", + "cancel": "Glem det", + "save": "tag kontrol" + } + } } }, "sidebar": { @@ -716,7 +766,8 @@ }, "common": { "loading": "Indlæser", - "cancel": "Annuller" + "cancel": "Annuller", + "save": "Gem" }, "duration": { "day": "{count} {count, plural,\none {dag}\nother {dage}\n}", diff --git a/translations/de.json b/translations/de.json index 5dbe8b5044..0ac9086658 100644 --- a/translations/de.json +++ b/translations/de.json @@ -365,7 +365,7 @@ "alias": "Name", "triggers": { "header": "Auslöser", - "introduction": "Auslöser starten automatisierte Abläufe. Es ist mögliche, mehrere Auslöser für dieselbe Abfolge zu definieren. Wenn ein Auslöser aktiviert wird, prüft Home Assistant die Bedingungen, sofern vorhanden, und führt die Aktion aus.\n\n[Erfahre mehr über Auslöser.](https:\/\/home-assistant.io\/docs\/automation\/trigger\/)", + "introduction": "Auslöser starten automatisierte Abläufe. Es ist möglich, mehrere Auslöser für dieselbe Abfolge zu definieren. Wenn ein Auslöser aktiviert wird, prüft Home Assistant die Bedingungen, sofern vorhanden, und führt die Aktion aus.\n\n[Erfahre mehr über Auslöser.](https:\/\/home-assistant.io\/docs\/automation\/trigger\/)", "add": "Auslöser hinzufügen", "duplicate": "Duplizieren", "delete": "Löschen", @@ -423,12 +423,16 @@ "event": "Ereignis:", "enter": "Betreten", "leave": "Verlassen" + }, + "webhook": { + "label": "Webhook", + "webhook_id": "Webhook ID" } } }, "conditions": { "header": "Bedingungen", - "introduction": "Bedingungen sind ein optionaler Bestandteil bei automatisierten Abläufen und können das Ausführen einer Aktion verhindern. Bedingungen sind Auslösern ähnlich, aber dennoch verschieden. Ein Auslöser beobachtet im System ablaufende Ereignisse, wohingegen Bedingungen nur den aktuellen Systemzustand betrachten. Ein Auslöser kann beobachten, dass ein Schalter aktiviert wird. Eine Bedingung kann lediglich sehen, ob ein Schalter aktuell an oder aus ist.\n\n[Erfahre mehr über Bedingungen.](https:\/\/home-assistant.io\/docs\/scripts\/conditions\/)", + "introduction": "Bedingungen sind ein optionaler Bestandteil bei automatisierten Abläufen und können das Ausführen einer Aktion verhindern. Bedingungen sind Auslösern ähnlich, aber dennoch verschieden. Ein Auslöser beobachtet im System ablaufende Ereignisse, wohingegen Bedingungen nur den aktuellen Systemzustand betrachten. Ein Auslöser kann beobachten, ob ein Schalter aktiviert wird. Eine Bedingung kann lediglich sehen, ob ein Schalter aktuell an oder aus ist.\n\n[Erfahre mehr über Bedingungen.](https:\/\/home-assistant.io\/docs\/scripts\/conditions\/)", "add": "Bedingung hinzufügen", "duplicate": "Duplizieren", "delete": "Löschen", @@ -717,9 +721,9 @@ "lovelace": { "cards": { "shopping-list": { - "checked_items": "Markierte Items", + "checked_items": "Markierte Artikel", "clear_items": "Markierte Elemente löschen", - "add_item": "Item hinzufügen" + "add_item": "Artikel hinzufügen" } }, "editor": { @@ -735,6 +739,7 @@ "migrate": { "header": "Konfiguration inkompatibel", "para_no_id": "Dieses Element hat keine ID. Bitte füge diesem Element eine ID in 'ui-lovelace.yaml' hinzu.", + "para_migrate": "Home Assistant kann für alle Ihre Karten und Ansichten die IDs automatisch generieren, wenn Sie den \"Konfiguration migrieren\"-Button klicken.", "migrate": "Konfiguration migrieren" }, "header": "Benutzeroberfläche bearbeiten", @@ -746,6 +751,9 @@ "delete": "Ansicht löschen" }, "save_config": { + "header": "Lovelace Userinterface selbst verwalten", + "para": "Standardmäßig verwaltet Home Assistant Ihre Benutzeroberfläche und aktualisiert sie, sobald neue Entitäten oder Lovelace-Komponenten verfügbar sind. Wenn Sie die Verwaltung selbst übernehmen, nehmen wir für Sie keine Änderungen mehr vor.", + "para_sure": "Bist du dir sicher, dass du die Benutzeroberfläche selbst verwalten möchtest?", "cancel": "Abbrechen", "save": "Kontrolle übernehmen" } diff --git a/translations/el.json b/translations/el.json index cab81d65c9..56810e8123 100644 --- a/translations/el.json +++ b/translations/el.json @@ -222,8 +222,67 @@ "config": { "zwave": { "caption": "Z-Wave" + }, + "automation": { + "editor": { + "triggers": { + "type": { + "state": { + "for": "Για" + } + } + } + } + } + }, + "lovelace": { + "editor": { + "edit_card": { + "save": "Αποθήκευση", + "edit": "Επεξεργασία", + "delete": "Διαγραφή" + }, + "header": "Επεξεργασία UI", + "configure_ui": "Ρύθμιση UI", + "edit_view": { + "header": "Ρυθμίσεις", + "add": "Προσθήκη προβολής", + "edit": "Επεξεργασία προβολής", + "delete": "Διαγραφή προβολής" + } } } + }, + "card": { + "cover": { + "position": "Θέση", + "tilt_position": "Θέση ανάκλισης" + }, + "fan": { + "speed": "Ταχύτητα", + "oscillate": "Περιστροφή", + "direction": "Κατεύθυνση" + }, + "light": { + "brightness": "Φωτεινότητα", + "color_temperature": "Θερμοκρασία χρώματος", + "white_value": "Τιμή λευκού", + "effect": "Εφέ" + }, + "media_player": { + "text_to_speak": "Κείμενο για εκφώνηση" + }, + "climate": { + "currently": "Αυτή τη στιγμή", + "target_temperature": "Επιθυμητή θερμοκρασία", + "target_humidity": "Επιθυμητή υγρασία", + "operation": "Λειτουργία", + "fan_mode": "Λειτουργία ανεμιστήρα", + "away_mode": "Λειτουργία εκτός σπιτιού" + } + }, + "common": { + "save": "Αποθήκευση" } }, "domain": { diff --git a/translations/en.json b/translations/en.json index b5d0c50e59..935b669e1f 100644 --- a/translations/en.json +++ b/translations/en.json @@ -423,6 +423,10 @@ "event": "Event:", "enter": "Enter", "leave": "Leave" + }, + "webhook": { + "label": "Webhook", + "webhook_id": "Webhook ID" } } }, diff --git a/translations/es.json b/translations/es.json index 07e1f98cac..a9b491062a 100644 --- a/translations/es.json +++ b/translations/es.json @@ -423,6 +423,10 @@ "event": "Evento:", "enter": "Entrar", "leave": "Salir" + }, + "webhook": { + "label": "Webhook", + "webhook_id": "ID de webhook" } } }, @@ -726,13 +730,32 @@ "edit_card": { "header": "Configuración de la tarjeta", "save": "Guardar", - "toggle_editor": "Alternar editor" + "toggle_editor": "Alternar editor", + "pick_card": "Escoge la tarjeta que desea agregar.", + "add": "Añadir tarjeta", + "edit": "Editar", + "delete": "Eliminar" }, "migrate": { "header": "Configuración incompatible", "para_no_id": "Este elemento no tiene un ID. Por favor agregue uno este elemento en 'ui-lovelace.yaml'.", "para_migrate": "Home Assistant puede agregar ID a todas sus tarjetas y vistas automáticamente por usted presionando el botón 'Migrar configuración'.", "migrate": "Migrar configuración" + }, + "header": "Editar la interfaz de usuario", + "configure_ui": "Configurar la interfaz de usuario", + "edit_view": { + "header": "Ver configuración", + "add": "Añadir vista", + "edit": "Editar vista", + "delete": "Borrar vista" + }, + "save_config": { + "header": "Tomar el control de la interfaz de usuario de Lovelace", + "para": "Por defecto, Home Assistant mantendrá su interfaz de usuario y la actualizará cuando haya nuevas entidades o componentes de Lovelace disponibles. Si usted toma el control, ya no haremos cambios automáticamente para usted.", + "para_sure": "¿Está seguro de que desea tomar el control de su interfaz de usuario?", + "cancel": "No importa", + "save": "Tomar el control" } } } @@ -743,7 +766,8 @@ }, "common": { "loading": "Cargando", - "cancel": "Cancelar" + "cancel": "Cancelar", + "save": "Guardar" }, "duration": { "day": "{count} {count, plural,\none {día}\nother {días}\n}", diff --git a/translations/fi.json b/translations/fi.json index a1c5304b1c..2d8395b35c 100644 --- a/translations/fi.json +++ b/translations/fi.json @@ -719,13 +719,25 @@ "edit_card": { "header": "Kortti-asetukset", "save": "Tallenna", - "toggle_editor": "Vaihda editori" + "toggle_editor": "Vaihda editori", + "pick_card": "Valitse kortti jonka haluat lisätä", + "add": "Lisää kortti", + "edit": "Muokkaa", + "delete": "Poista" }, "migrate": { "header": "Epäkelvot asetukset", "para_no_id": "Elementillä ei ole ID. Lisää ID elementille 'ui-lovelace.yaml'-tiedostossa.", "para_migrate": "Home Assistant voi lisätä ID:t kaikkiin kortteihisi ja näkymiin automaattisesti painamalla 'Tuo vanhat asetukset'-nappia.", "migrate": "Tuo vanhat asetukset" + }, + "header": "Muokkaa käyttöliittymää", + "configure_ui": "Määrittele käyttöliittymä", + "edit_view": { + "header": "Näytä asetukset", + "add": "Lisää näkymä", + "edit": "Muokkaa näkymää", + "delete": "Poista näkymä" } } } @@ -736,7 +748,8 @@ }, "common": { "loading": "Ladataan", - "cancel": "Peruuta" + "cancel": "Peruuta", + "save": "Tallenna" }, "duration": { "day": "{count} {count, plural,\n one {päivä}\n other {päivää}\n}", @@ -755,7 +768,7 @@ "not_available": "Kuvaa ei saatavilla" }, "persistent_notification": { - "dismiss": "Peruuta" + "dismiss": "Hylkää" }, "scene": { "activate": "Aktivoi" diff --git a/translations/fr.json b/translations/fr.json index 2d1782d341..637d24b272 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -70,7 +70,7 @@ }, "vibration": { "off": "RAS", - "on": "Détecté" + "on": "Détectée" }, "opening": { "off": "Fermé", @@ -275,8 +275,8 @@ "alarm_control_panel": { "armed": "Activé", "disarmed": "Désactivé", - "armed_home": "Activée (présent)", - "armed_away": "Armée (absent)", + "armed_home": "Armé", + "armed_away": "Armé", "armed_night": "Activé", "pending": "En cours", "arming": "Activer", @@ -301,7 +301,7 @@ "period": "Période" }, "logbook": { - "showing_entries": "Afficher les entrées pour" + "showing_entries": "Afficher les entrées pour le" }, "mailbox": { "empty": "Vous n'avez aucun message", @@ -423,6 +423,10 @@ "event": "Événement :", "enter": "Entre", "leave": "Quitter" + }, + "webhook": { + "label": "Webhook", + "webhook_id": "ID Webhook" } } }, @@ -432,7 +436,7 @@ "add": "Ajouter une condition", "duplicate": "Dupliquer", "delete": "Effacement", - "delete_confirm": "Voulez-vous effacer ?", + "delete_confirm": "Voulez-vous vraiment effacer ?", "unsupported_condition": "Condition non supportée : {condition}", "type_select": "Type de condition", "type": { @@ -476,7 +480,7 @@ "introduction": "Les actions sont ce que Home Assistant fera quand une automatisation est déclenchée.\n\n[En apprendre plus sur les actions.](https:\/\/home-assistant.io\/docs\/automation\/action\/)", "add": "Ajouter une action", "duplicate": "Dupliquer", - "delete": "Effacer", + "delete": "Supprimer", "delete_confirm": "Voulez-vous vraiment effacer ?", "unsupported_action": "Action non supportée : {action}", "type_select": "Type d'action", @@ -529,7 +533,7 @@ } }, "cloud": { - "caption": "Home Assistant Cloud", + "caption": "Nuage Home Assistant", "description_login": "Connecté en tant que {email}", "description_not_login": "Pas connecté" }, @@ -718,19 +722,40 @@ "cards": { "shopping-list": { "checked_items": "Éléments cochés", + "clear_items": "Effacer éléments cochés", "add_item": "Ajouter un élément" } }, "editor": { "edit_card": { + "header": "Configuration de la carte", "save": "Enregistrer", - "toggle_editor": "Activer\/désactiver l’éditeur" + "toggle_editor": "Activer\/désactiver l’éditeur", + "pick_card": "Choisissez l'automatisation à ajouter", + "add": "Ajouter une action", + "edit": "Modifier", + "delete": "Supprimer" }, "migrate": { "header": "Configuration incompatible", "para_no_id": "Cet élément n'a pas d'identifiant. Veuillez ajouter un identifiant à cet élément dans 'ui-lovelace.yaml'.", "para_migrate": "Home Assistant peut ajouter automatiquement des identifiants à toutes vos cartes et vues en appuyant sur le bouton «Migrer la configuration».", "migrate": "Migrer la configuration" + }, + "header": "Modifier l'interface utilisateur", + "configure_ui": "Configurer l'interface utilisateur", + "edit_view": { + "header": "Voir la configuration", + "add": "Ajouter la vue\n", + "edit": "Modifier la vue", + "delete": "Supprimer la vue" + }, + "save_config": { + "header": "Prenez le controle de Lovelace UI", + "para": "Par défaut, Home Assistant maintient votre interface utilisateur et la met à jour lorsque de nouvelles entités ou de nouveaux composants Lovelace sont disponibles. Si vous prenez le contrôle, nous ne ferons plus les changements automatiquement pour vous.", + "para_sure": "Êtes-vous sûr de vouloir prendre le controle de l'interface utilisateur?", + "cancel": "Oublie ce que j'ai dit, c'est pas grave.", + "save": "Prenez le contrôle" } } } @@ -741,7 +766,8 @@ }, "common": { "loading": "Chargement", - "cancel": "Annuler" + "cancel": "Annuler", + "save": "Enregistrer" }, "duration": { "day": "{count} {count, plural,\none {jour}\nother {jours}\n}", @@ -801,7 +827,9 @@ "clear_code": "Effacer", "disarm": "Désarmer", "arm_home": "Armer (domicile)", - "arm_away": "Armer (absent)" + "arm_away": "Armer (absent)", + "arm_night": "Armer nuit", + "armed_custom_bypass": "Bypass personnalisé" }, "automation": { "last_triggered": "Dernier déclenchement", diff --git a/translations/he.json b/translations/he.json index cb3d396540..233496a4bd 100644 --- a/translations/he.json +++ b/translations/he.json @@ -423,6 +423,10 @@ "event": "אירוע:", "enter": "כניסה", "leave": "יציאה" + }, + "webhook": { + "label": "Webhook", + "webhook_id": "Webhook ID" } } }, @@ -557,7 +561,7 @@ "profile": { "push_notifications": { "header": "הודעות דחיפה", - "description": "שלח התראות למכשיר זה", + "description": "שלח התראות למכשיר זה.", "error_load_platform": "הגדר את notify.html5.", "error_use_https": "נדרש SSL מופעל עבור הממשק.", "push_notifications": "הודעות דחיפה", diff --git a/translations/hr.json b/translations/hr.json index ff5c500471..747b55ad88 100644 --- a/translations/hr.json +++ b/translations/hr.json @@ -251,11 +251,13 @@ }, "vacuum": { "cleaning": "Čišćenje", + "docked": "Usidreni", "error": "Greška", "idle": "Besposlen", "off": "Ugašeno", "on": "Upaljeno", - "paused": "Pauzirano" + "paused": "Pauzirano", + "returning": "Povratak na dok" } }, "state_badge": { @@ -345,12 +347,24 @@ "unsaved_confirm": "Imate nespremljene izmjene. Jeste li sigurni da želite napustiti?", "alias": "Ime", "triggers": { + "header": "Okidači", "introduction": "Okidači su ono što pokreće obradu pravila o automatizaciji. Moguće je odrediti više okidača za isto pravilo. Kada pokrenete okidač, Home Assistant provjerit će uvjete, ako ih ima i pozvati akciju. \n\n [Saznajte više o pokretačima.] (Https:\/\/home-assistant.io\/docs\/automation\/trigger\/)", + "add": "Dodaj okidač", + "duplicate": "Udvostruči", "delete": "Obriši", + "delete_confirm": "Jeste li sigurni dai želite izbrisati?", "unsupported_platform": "Nepodržana platforma: {platform}", + "type_select": "Tip okidača", "type": { + "event": { + "label": "Događaj:", + "event_type": "Vrsta događaja", + "event_data": "Podaci o događaju" + }, "state": { + "label": "Stanje", "from": "Od", + "to": "Do", "for": "Za" }, "homeassistant": { @@ -361,7 +375,8 @@ }, "mqtt": { "label": "MQTT", - "topic": "Tema" + "topic": "Tema", + "payload": "Opterećenje (opcionalno)" }, "numeric_state": { "label": "Numeričko stanje", @@ -391,6 +406,10 @@ "event": "Event:", "enter": "Unesite", "leave": "Napustiti" + }, + "webhook": { + "label": "Webhook", + "webhook_id": "Webhook ID" } } }, @@ -474,8 +493,12 @@ } } }, + "script": { + "description": "Stvaranje i uređivanje skripti" + }, "zwave": { - "caption": "Z-Wave" + "caption": "Z-Wave", + "description": "Upravljajte mrežom Z-Wave" }, "users": { "caption": "Korisnici", @@ -632,7 +655,8 @@ "mfa": { "data": { "code": "Kôd autentifikacije s dva faktora" - } + }, + "description": "Otvori **{mfa_module_name}** na svojem uređaju kao bi vidio kod dvofaktorske autentikacije i provjerio svoj identitet:" } }, "error": { @@ -688,13 +712,32 @@ "edit_card": { "header": "Konfiguracija Kartice ", "save": "Spremi", - "toggle_editor": "Uključi uređivač" + "toggle_editor": "Uključi uređivač", + "pick_card": "Odaberite karticu koju želite dodati.", + "add": "Dodaj karticu", + "edit": "Uredi", + "delete": "Izbrisati" }, "migrate": { "header": "Konfiguracija nije kompatibilna", "para_no_id": "Ovaj element nema ID. Dodajte ID ovom elementu u 'ui-lovelace.yaml'.", "para_migrate": "Home Assistant može dodati ID-ove na sve vaše kartice i pogleda automatski za vas pritiskom na gumb 'Migriraj konfiguriranje'.", "migrate": "Migriraj konfiguraciju" + }, + "header": "Uredi UI", + "configure_ui": "Konfiguriraj UI", + "edit_view": { + "header": "Pogledaj konfiguraciju", + "add": "Dodaj prikaz", + "edit": "Uredi prikaz", + "delete": "Izbriši prikaz" + }, + "save_config": { + "header": "Preuzmite kontrolu nad Lovelace UI", + "para": "Home Assistant će prema zadanim postavkama održavati vaše korisničko sučelje, ažurirati ga kada novi entiteti ili Lovelace komponente postanu dostupni. Ako preuzmete kontrolu, više nećemo automatski vršiti izmjene za vas.", + "para_sure": "Jeste li sigurni da želite preuzeti kontrolu nad svojim korisničkim sučeljem?", + "cancel": "Nema veze", + "save": "Preuzmi kontrolu" } } } @@ -705,7 +748,8 @@ }, "common": { "loading": "Učitavam", - "cancel": "Otkazati" + "cancel": "Otkazati", + "save": "Spremi" }, "duration": { "day": "{count} {count, plural,\n one {dan}\n other {dani}\n}", @@ -736,21 +780,38 @@ "wind_speed": "Brzina vjetra" }, "cardinal_direction": { + "e": "I", + "ene": "ISI", "ese": "ESE", "n": "N", "ne": "NE", "nne": "NNE", - "nw": "NW" - } + "nw": "NW", + "nnw": "SSZ", + "s": "J", + "se": "JI", + "sse": "JJI", + "ssw": "JJZ", + "sw": "JZ", + "w": "Z", + "wnw": "ZSZ", + "wsw": "ZJZ" + }, + "forecast": "Prognoza" }, "alarm_control_panel": { "code": "Kod", + "clear_code": "Vedro", "disarm": "Deaktiviraj", "arm_home": "Aktiviran doma", "arm_away": "Aktiviran odsutno", "arm_night": "Aktiviran nočni", "armed_custom_bypass": "Prilagođena obilaznica" }, + "automation": { + "last_triggered": "Zadnje aktivirano", + "trigger": "Okidač" + }, "fan": { "speed": "Brzina", "oscillate": "Oscilirati", @@ -784,6 +845,8 @@ }, "vacuum": { "actions": { + "resume_cleaning": "Nastavi čišćenje", + "return_to_base": "Povratak na dok", "start_cleaning": "Započnite čišćenje", "turn_on": "Uključiti", "turn_off": "Isključiti" @@ -803,8 +866,14 @@ "entity": "Entitet" } }, + "service-picker": { + "service": "Usluga" + }, "relative_time": { - "past": "{vrijeme} prije" + "past": "{vrijeme} prije", + "duration": { + "week": "{count} {count, plural,\n jedan {week}\n ostali {weeks}\n}" + } }, "history_charts": { "loading_history": "Učitavanje povijesti stanja ...", @@ -814,7 +883,8 @@ "dialogs": { "more_info_settings": { "save": "Spremi", - "name": "Ime" + "name": "Ime", + "entity_id": "ID entiteta" } }, "auth_store": { @@ -863,7 +933,8 @@ "switch": "Prekidač", "updater": "Ažuriranje", "weblink": "WebLink", - "zwave": "Z-Wave" + "zwave": "Z-Wave", + "vacuum": "Vakuum" }, "attribute": { "weather": { diff --git a/translations/hu.json b/translations/hu.json index ba41a66451..d135760d3f 100644 --- a/translations/hu.json +++ b/translations/hu.json @@ -292,7 +292,7 @@ "ui": { "panel": { "shopping-list": { - "clear_completed": "Kijelöltek törlése", + "clear_completed": "Bejelöltek törlése", "add_item": "Tétel hozzáadása", "microphone_tip": "Koppints a jobb felső sarokban található mikrofonra, és mondd ki: \"Add candy to my shopping list\"" }, @@ -423,6 +423,10 @@ "event": "Esemény:", "enter": "Érkezés", "leave": "Távozás" + }, + "webhook": { + "label": "Webhook", + "webhook_id": "Webhook ID" } } }, @@ -546,8 +550,8 @@ "no_device": "Entitások eszközök nélkül", "delete_confirm": "Biztosan törölni szeretnéd ezt az integrációt?", "restart_confirm": "Indítsd újra a Home Assistant-ot az integráció törlésének befejezéséhez", - "manuf": "Gyártó: {manufacturer}", - "hub": "Csatlakoztatva", + "manuf": "{manufacturer} által", + "hub": "Kapcsolódva", "firmware": "Firmware: {version}", "device_unavailable": "eszköz nem érhető el", "entity_unavailable": "entitás nem érhető el" @@ -620,7 +624,7 @@ "mfa_setup": { "title_aborted": "Megszakítva", "title_success": "Siker!", - "step_done": "Beállítás kész {step}", + "step_done": "{step} beállítás elvégezve", "close": "Bezárás", "submit": "Küldés" } @@ -699,7 +703,7 @@ } }, "page-onboarding": { - "intro": "Készen állsz arra, hogy felébreszd az otthonod, visszaszerezed a magánéleted és csatlakozz egy világhálós közösséghez?", + "intro": "Készen állsz arra, hogy felébreszd az otthonod, visszaszerezd a magánéleted és csatlakozz egy világhálós közösséghez?", "user": { "intro": "Kezdjük a felhasználói fiók létrehozásával.", "required_field": "Szükséges", @@ -717,8 +721,8 @@ "lovelace": { "cards": { "shopping-list": { - "checked_items": "Kijelölt tételek", - "clear_items": "Kijelölt tételek törlése", + "checked_items": "Bejelölt tételek", + "clear_items": "Bejelölt tételek törlése", "add_item": "Tétel hozzáadása" } }, @@ -734,8 +738,8 @@ }, "migrate": { "header": "Inkompatibilis Konfiguráció", - "para_no_id": "Ez az elem nem rendelkezik azonosítóval. Kérlek, adj hozzá egyet az 'ui-lovelace.yaml' fájlban!", - "para_migrate": "A Home Assistant a 'Konfiguráció áttelepítése' gomb megnyomásával az összes kártyához és nézethez automatikusan létre tud hozni azonosítókat.", + "para_no_id": "Ez az elem nem rendelkezik ID-val. Kérlek, adj hozzá egyet az 'ui-lovelace.yaml' fájlban!", + "para_migrate": "A Home Assistant a 'Konfiguráció áttelepítése' gomb megnyomásával az összes kártyához és nézethez automatikusan létre tud hozni ID-kat.", "migrate": "Konfiguráció áttelepítése" }, "header": "Felhasználói felület szerkesztése", diff --git a/translations/it.json b/translations/it.json index 60fc3a16db..d733a1b540 100644 --- a/translations/it.json +++ b/translations/it.json @@ -274,7 +274,7 @@ }, "alarm_control_panel": { "armed": "Attivo", - "disarmed": "Disattivo", + "disarmed": "Disattiva", "armed_home": "Attivo", "armed_away": "Attivo", "armed_night": "Attivo", @@ -368,7 +368,7 @@ "introduction": "I trigger sono ciò che avvia l'elaborazione di una regola di automazione. È possibile specificare più trigger per la stessa regola. Una volta avviato il trigger, Home Assistant convaliderà le condizioni, se presenti, e chiamerà l'azione. \n\n [Ulteriori informazioni sui trigger.](Https:\/\/home-assistant.io\/docs\/automation\/trigger\/)", "add": "Aggiungi trigger", "duplicate": "Duplica", - "delete": "Cancella", + "delete": "Elimina", "delete_confirm": "Sicuro di voler eliminare?", "unsupported_platform": "Piattaforma non supportata: {platform}", "type_select": "Tipo di trigger", @@ -423,6 +423,10 @@ "event": "Evento", "enter": "Ingresso", "leave": "Uscita" + }, + "webhook": { + "label": "Webhook", + "webhook_id": "ID Webhook" } } }, @@ -431,7 +435,7 @@ "introduction": "Le condizioni sono una parte facoltativa di una regola di automazione e possono essere utilizzate per impedire che un'azione si verifichi quando viene attivata. Le condizioni sembrano molto simili ai trigger, ma sono molto diverse. Un trigger analizzerà gli eventi che si verificano nel sistema mentre una condizione analizza solo l'aspetto del sistema in questo momento. Un trigger può osservare che un interruttore è in fase di accensione. Una condizione può vedere solo se un interruttore è attivo o meno. \n\n [Ulteriori informazioni sulle condizioni.](Https:\/\/home-assistant.io\/docs\/scripts\/conditions\/)", "add": "Aggiungi condizione", "duplicate": "Duplica", - "delete": "Cancella", + "delete": "Elimina", "delete_confirm": "Sicuro di voler eliminare?", "unsupported_condition": "Condizione non supportata: {condition}", "type_select": "Tipo di condizione", @@ -476,7 +480,7 @@ "introduction": "Le azioni sono ciò che Home Assistant farà quando un trigger attiva un automazione. \n\n [Ulteriori informazioni sulle azioni.](Https:\/\/home-assistant.io\/docs\/automation\/action\/)", "add": "Aggiungi azione", "duplicate": "Duplica", - "delete": "Cancella", + "delete": "Elimina", "delete_confirm": "Sicuro di voler eliminare?", "unsupported_action": "Azione non supportata: {action}", "type_select": "Tipo di azione", @@ -500,7 +504,7 @@ "event": { "label": "Scatena Evento", "event": "Evento:", - "service_data": "Dato servizio" + "service_data": "Dati servizio" } } } @@ -531,7 +535,7 @@ "cloud": { "caption": "Home Assistant Cloud", "description_login": "Connesso come {email}", - "description_not_login": "Non loggato" + "description_not_login": "Accesso non effettuato" }, "integrations": { "caption": "integrazioni", @@ -547,7 +551,7 @@ "delete_confirm": "Sei sicuro di voler eliminare questa integrazione?", "restart_confirm": "Riavvia Home Assistant per terminare la rimozione di questa integrazione", "manuf": "da {manufacturer}", - "hub": "Connesso via", + "hub": "Connesso tramite", "firmware": "Firmware: {version}", "device_unavailable": "dispositivo non disponibile", "entity_unavailable": "entità non disponibile" @@ -596,7 +600,7 @@ "create_failed": "Impossibile creare il token di accesso.", "prompt_name": "Nome?", "prompt_copy_token": "Copia il tuo token di accesso. Non verrà più mostrato.", - "empty_state": "Non hai ancora token di accesso di lunga durata.", + "empty_state": "Non hai ancora un token di accesso di lunga durata.", "last_used": "Utilizzato l'ultima volta il {date} da {location}", "not_used": "Non è mai stato usato" }, @@ -726,13 +730,32 @@ "edit_card": { "header": "Configurazione della scheda", "save": "Salva", - "toggle_editor": "Attiva \/ disattiva l'editor" + "toggle_editor": "Attiva \/ disattiva l'editor", + "pick_card": "Scegliere la scheda che si desidera aggiungere.", + "add": "Aggiungi scheda", + "edit": "Modifica", + "delete": "Elimina" }, "migrate": { "header": "Configurazione incompatibile", "para_no_id": "Questo elemento non ha un ID. Aggiungi un ID a questo elemento in 'ui-lovelace.yaml'.", "para_migrate": "Home Assistant può aggiungere automaticamente gli ID a tutte le tue schede e visualizzazioni automaticamente premendo il pulsante \"Eporta configurazione\".", "migrate": "Esporta configurazione" + }, + "header": "Modifica dell'interfaccia utente", + "configure_ui": "Configurare l'interfaccia utente", + "edit_view": { + "header": "Visualizza configurazione", + "add": "Aggiungi vista", + "edit": "Modifica vista", + "delete": "Cancella vista" + }, + "save_config": { + "header": "Prendi il controllo della tua interfaccia utente di Lovelace", + "para": "Per impostazione predefinita, Home Assistant manterrà l'interfaccia utente, aggiornandola quando nuove entità o componenti Lovelace diventano disponibili. Se prendi il controllo non effettueremo più automaticamente le modifiche per te.", + "para_sure": "Sei sicuro di voler prendere il controllo della tua interfaccia utente?", + "cancel": "Rinuncia", + "save": "Prendere il controllo" } } } @@ -743,7 +766,8 @@ }, "common": { "loading": "Caricamento", - "cancel": "Annulla" + "cancel": "Annulla", + "save": "Salva" }, "duration": { "day": "{count} {count, plural,\none {giorno}\nother {giorni}\n}", @@ -801,8 +825,8 @@ "alarm_control_panel": { "code": "Codice", "clear_code": "Canc", - "disarm": "Disattivato", - "arm_home": "Attivo in casa", + "disarm": "Disattiva", + "arm_home": "Attiva In casa", "arm_away": "Attiva Fuori Casa", "arm_night": "Attiva Notte", "armed_custom_bypass": "Attiva con bypass" diff --git a/translations/ko.json b/translations/ko.json index 37a9ec5b85..6326175fce 100644 --- a/translations/ko.json +++ b/translations/ko.json @@ -423,6 +423,10 @@ "event": "이벤트:", "enter": "입장", "leave": "퇴장" + }, + "webhook": { + "label": "Webhook", + "webhook_id": "Webhook ID" } } }, @@ -751,7 +755,7 @@ "para": "기본적으로 Home Assistant 는 사용자 인터페이스를 유지 관리하고, 사용할 수 있는 새로운 구성요소 또는 Lovelace 구성요소가 있을 때 업데이트를 합니다. 사용자가 직접 관리하는 경우 Home Assistant 는 더 이상 자동으로 변경하지 않습니다.", "para_sure": "사용자 인터페이스를 직접 관리하시겠습니까?", "cancel": "아닙니다", - "save": "직접 관리할께요" + "save": "직접 관리할게요" } } } diff --git a/translations/lb.json b/translations/lb.json index 98c7829ee3..fe13994b79 100644 --- a/translations/lb.json +++ b/translations/lb.json @@ -275,9 +275,9 @@ "alarm_control_panel": { "armed": "Aktivéiert", "disarmed": "Desaktivéieren", - "armed_home": "Aktivéiert", - "armed_away": "Aktivéiert", - "armed_night": "Aktivéiert", + "armed_home": "Uzbrojony", + "armed_away": "Uzbrojony", + "armed_night": "Uzbrojony", "pending": "Ustoend", "arming": "Aktivéieren", "disarming": "Desaktivéieren", @@ -317,7 +317,7 @@ "description": "Konfiguratioun validéieren an de Server kontrolléieren", "section": { "core": { - "header": "Konfiguratioun an Server Kontroll", + "header": "Konfiguracja i konktrola serwera", "introduction": "D'Ännere vun der Konfiguratioun kann e lästege Prozess sinn. Mir wëssen dat. Dës Sektioun probéiert fir Äert Liewen e bësse méi einfach ze maachen.", "validation": { "heading": "Validatioun vun der Konfiguratioun", @@ -419,10 +419,14 @@ "zone": { "label": "Zone", "entity": "Entitéit mam Standuert", - "zone": "Zon", + "zone": "Strefa", "event": "Evenement:", "enter": "Eran", "leave": "Verloossen" + }, + "webhook": { + "label": "Webhook", + "webhook_id": "Webhook ID" } } }, @@ -441,7 +445,7 @@ "state": "Zoustand" }, "numeric_state": { - "label": "Numereschen Zoustand", + "label": "Stan (numeryczny)", "above": "Iwwert", "below": "Ënnert", "value_template": "Wäerte Modell (optional)" @@ -465,15 +469,15 @@ "before": "Virdrun" }, "zone": { - "label": "Zon", + "label": "Strefa", "entity": "Entitéit mam Standuert", - "zone": "Zon" + "zone": "Strefa" } } }, "actions": { "header": "Aktiounen", - "introduction": "Aktioune déi den HomeAssistant ausféiert wann den Automatisme ausgeléist gouf.\n[Léier méi iwwert Aktioune.](https:\/\/home-assistant.io\/docs\/automation\/action\/)", + "introduction": "Aktioune déi den Home Assistant ausféiert wann den Automatisme ausgeléist gouf.\n[Léier méi iwwert Aktioune.](https:\/\/home-assistant.io\/docs\/automation\/action\/)", "add": "Aktioun dobäisetzen", "duplicate": "Replikéiere", "delete": "Läschen", @@ -487,7 +491,7 @@ }, "delay": { "label": "Delai", - "delay": "Delai" + "delay": "Opóźnienie" }, "wait_template": { "label": "Waart", @@ -581,7 +585,7 @@ "created_at": "Erstallt um {date}", "confirm_delete": "Sécher fir den Erneierungs Token fir {name} ze läsche?", "delete_failed": "Fehler beim läschen vum Erneierungs Token", - "last_used": "Läscht benotz um {date} vun {location}", + "last_used": "Fir d'Läscht benotzt um {date} vun {location}", "not_used": "Nach nie benotzt ginn", "current_token_tooltip": "Fehler beim läschen vum aktuellen Erneierungs Token" }, @@ -748,6 +752,7 @@ }, "save_config": { "header": "Kontroll iwwert Loveloce UI iwwerhuelen", + "para": "Standardméisseg verwalt Home Assistant de Benotzer Interface an aktualiséiert en soubal nei Entitéiten oder Lovelace-Komponenten disponibel sinn. Wann dir d'Kontrolle iwwerhuelt, kënne mir keng automatesch Ännerung méi fir iech maachen.", "para_sure": "Sécher fir d'Kontrolle iwwert de Benotzer Interface z'iwwerhuelen?", "cancel": "Vergiess et", "save": "Kontroll iwwerhuelen" @@ -870,7 +875,7 @@ "actions": { "resume_cleaning": "Fuer mam botzen weider", "return_to_base": "Zeréck zur Statioun kommen", - "start_cleaning": "Fänkt mam botzen un", + "start_cleaning": "Fänk mam botzen un", "turn_on": "Uschalten", "turn_off": "Ausschalten" } @@ -910,8 +915,8 @@ } }, "notification_toast": { - "entity_turned_on": "{entity} gouf ausgeschalt", - "entity_turned_off": "{entity} gouf ugeschalt", + "entity_turned_on": "{entity} gouf ugeschalt", + "entity_turned_off": "{entity} gouf ausgeschalt", "service_called": "Service {service} operuff", "service_call_failed": "Fehler beim opruffen vun {service}", "connection_lost": "Verbindung verluer. Verbindung gëtt nees opgebaut..." diff --git a/translations/lv.json b/translations/lv.json index 6ac3539796..0a6ee3432e 100644 --- a/translations/lv.json +++ b/translations/lv.json @@ -317,7 +317,7 @@ "description": "Veiciet konfigurācijas failu pārbaudi un pārvaldiet serveri", "section": { "core": { - "header": "Konfigurācijas un servera pārvaldība", + "header": "Konfigurācijas un Servera pārvaldība", "introduction": "Izmaiņas konfigurācijā var būt nogurdinošs process. Mēs zinām. Šai sadaļai vajadzētu padarīt dzīvi mazliet vieglāku.", "validation": { "heading": "Konfigurācijas pārbaude", @@ -351,7 +351,7 @@ "caption": "Automatizācija", "description": "Veidojiet un rediģējiet automatizācijas", "picker": { - "header": "Automatizāciju redaktors", + "header": "Automatizāciju Redaktors", "introduction": "Automatizācijas redaktors ļauj jums izveidot un rediģēt automatizācijas. Lūdzu, izlasiet [norādījumus] (https:\/\/home-assistant.io\/docs\/automation\/editor\/), lai pārliecinātos, ka esat pareizi konfigurējis Home Assistant.", "pick_automation": "Izvēlieties automatizāciju kuru rediģēt", "no_automations": "Mēs nevarējām atrast rediģējamas automatizācijas", @@ -423,6 +423,10 @@ "event": "Notikums:", "enter": "Ieiet", "leave": "Iziet" + }, + "webhook": { + "label": "Webhook", + "webhook_id": "Webhook ID" } } }, @@ -713,6 +717,47 @@ "required_fields": "Aizpildiet visus obligātos laukus" } } + }, + "lovelace": { + "cards": { + "shopping-list": { + "checked_items": "Atzīmētie vienumi", + "clear_items": "Notīrīt atzīmētos vienumus", + "add_item": "Pievienot vienumu" + } + }, + "editor": { + "edit_card": { + "header": "Kartes Konfigurācija", + "save": "Saglabāt", + "toggle_editor": "Pārslēgt Redaktoru", + "pick_card": "Izvēlieties karti, kuru vēlaties pievienot.", + "add": "Pievienot karti", + "edit": "Rediģēt", + "delete": "Dzēst" + }, + "migrate": { + "header": "Konfigurācija Nesaderīga", + "para_no_id": "Šim elementam nav ID. Lūdzu, pievienojiet ID šim elementam 'ui-lovelace.yaml' failā.", + "para_migrate": "Home Assistant var automātiski pievienot ID visām kartēm un skatiem, nospiežot pogu \"Pārvietot konfigurāciju\".", + "migrate": "Pārvietot konfigurāciju" + }, + "header": "Rediģēt lietotāja interfeisu", + "configure_ui": "Konfigurēt lietotāja interfeisu", + "edit_view": { + "header": "Skatīt konfigurāciju", + "add": "Pievienot skatu", + "edit": "Rediģēt skatu", + "delete": "Dzēst skatu" + }, + "save_config": { + "header": "Pārņemt kontroli pār savu Lovelace UI", + "para": "Pēc noklusējuma Home Assistant uzturēs jūsu lietotāja interfeisu, atjauninot to, kad būs pieejami jauni objekti vai Lovelace komponenti. Ja jūs uzņematies kontroli, mēs jums vairs neveiksim izmaiņas automātiski jūsu vietā.", + "para_sure": "Vai tiešām vēlaties kontrolēt savu lietotāja interfeisu?", + "cancel": "Nekas", + "save": "Pārņemt kontroli" + } + } } }, "sidebar": { @@ -721,7 +766,8 @@ }, "common": { "loading": "Ielāde", - "cancel": "Atcelt" + "cancel": "Atcelt", + "save": "Saglabāt" }, "duration": { "day": "{count} {count, plural,\none {diena}\nother {dienas}\n}", @@ -778,10 +824,12 @@ }, "alarm_control_panel": { "code": "Kods", - "clear_code": "Dzēst", + "clear_code": "Notīrīt", "disarm": "Atslēgt", "arm_home": "Pieslēgt mājas", - "arm_away": "Pieslēgt prombūtni" + "arm_away": "Pieslēgt prombūtni", + "arm_night": "Pieslēgts uz nakti", + "armed_custom_bypass": "Pielāgots apvedceļš" }, "automation": { "last_triggered": "Pēdējais izsaukums", diff --git a/translations/nl.json b/translations/nl.json index f8c4c8c005..8dbf492b90 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -236,7 +236,7 @@ "ready": "Gereed" }, "query_stage": { - "initializing": "Initialiseren ({query_stage})", + "initializing": "Voorbereiden ({query_stage})", "dead": "Onbereikbaar ({query_stage})" } }, @@ -248,7 +248,7 @@ "lightning": "Bliksem", "lightning-rainy": "Bliksem, regenachtig", "partlycloudy": "Gedeeltelijk bewolkt", - "pouring": "Gieten", + "pouring": "Regen", "rainy": "Regenachtig", "snowy": "Sneeuwachtig", "snowy-rainy": "Sneeuw-, regenachtig", @@ -276,8 +276,8 @@ "armed": "Actief", "disarmed": "Uit", "armed_home": "Actief", - "armed_away": "Ingeschakeld", - "armed_night": "Ingeschakeld", + "armed_away": "Actief", + "armed_night": "Actief", "pending": "Wacht", "arming": "Activeren", "disarming": "Uitschakelen", @@ -301,7 +301,7 @@ "period": "Periode" }, "logbook": { - "showing_entries": "Toon items voor" + "showing_entries": "Toont gegevens van" }, "mailbox": { "empty": "Je hebt geen berichten", @@ -423,6 +423,10 @@ "event": "Gebeurtenis:", "enter": "Betreden", "leave": "Verlaten" + }, + "webhook": { + "label": "Webhook", + "webhook_id": "Webhook ID" } } }, @@ -441,7 +445,7 @@ "state": "Staat" }, "numeric_state": { - "label": "Numerieke status", + "label": "Numerieke staat", "above": "Boven", "below": "Onder", "value_template": "Waardetemplate (optioneel)" @@ -726,13 +730,32 @@ "edit_card": { "header": "Kaart configuratie", "save": "Opslaan", - "toggle_editor": "Toggle Editor" + "toggle_editor": "Toggle Editor", + "pick_card": "Kies de kaart die je wilt toevoegen.", + "add": "Kaart toevoegen", + "edit": "Bewerken", + "delete": "Verwijder" }, "migrate": { "header": "Configuratie incompatibel", "para_no_id": "Dit element heeft geen ID. Voeg een ID toe aan dit element in 'ui-lovelace.yaml'.", "para_migrate": "Home Assistant kan ID's voor al je kaarten en weergaven automatisch voor je toevoegen door op de knop 'Migrate config' te klikken.", "migrate": "Configuratie migreren" + }, + "header": "Bewerk UI", + "configure_ui": "Configureer UI", + "edit_view": { + "header": "Bekijk de configuratie", + "add": "Weergave toevoegen", + "edit": "Weergave bewerken", + "delete": "Weergave verwijderen" + }, + "save_config": { + "header": "Neem de controle over uw Lovelace UI", + "para": "Normaal gesproken onderhoudt Home Assistant je gebruikersinterface en update die met nieuwe entiteiten of Lovelace-onderdelen wanneer deze beschikbaar zijn. Als je het beheer overneemt, zullen we niet langer automatisch wijzigingen aanbrengen.", + "para_sure": "Weet je zeker dat je de controle wilt over je gebruikersinterface?", + "cancel": "Laat maar", + "save": "Neem over" } } } @@ -743,7 +766,8 @@ }, "common": { "loading": "Bezig met laden", - "cancel": "Annuleren" + "cancel": "Annuleren", + "save": "Opslaan" }, "duration": { "day": "{count} {count, plural,\none {dag}\nother {dagen}\n}", diff --git a/translations/nn.json b/translations/nn.json index e8a893de38..32fa52bc00 100644 --- a/translations/nn.json +++ b/translations/nn.json @@ -145,7 +145,7 @@ "high_demand": "Høg etterspurnad", "heat_pump": "Varmepumpe", "gas": "Gass", - "manual": "Håndbok" + "manual": "Handbok" }, "configurator": { "configure": "Konfigurerer", @@ -423,6 +423,10 @@ "event": "Hending:", "enter": "Kjem", "leave": "Forlet" + }, + "webhook": { + "label": "Webhook", + "webhook_id": "Webhook ID" } } }, @@ -545,12 +549,12 @@ "no_devices": "Essa integração não possui dispositivos.", "no_device": "Entidades sem dispositivos.", "delete_confirm": "Voc^tem certeza que deseja apagar essa integração?", - "restart_confirm": "Reinicie Home Assistant para finalizar essa integração", - "manuf": "por {fabricante}", - "hub": "Conectado via", - "firmware": "Firmware: {versão}", - "device_unavailable": "Dispositivo indisponível", - "entity_unavailable": "Entidade indisponível" + "restart_confirm": "Restart Home Assistant for å fjerne denne integrasjonen", + "manuf": "av {manufacturer}", + "hub": "Tilkopla via", + "firmware": "Firmware: {version}", + "device_unavailable": "Eininga utilgjengelig", + "entity_unavailable": "Oppføringa utilgjengelig" } } }, @@ -600,11 +604,11 @@ "last_used": "Sist brukt den {date} frå {location}", "not_used": "Har aldri vore brukt" }, - "current_user": "Você está atualmente conectado como {NomeCompleto}.", + "current_user": "Du er for augeblinken logga inn som {fullName}.", "is_owner": "Du er ein eigar", - "logout": "Sair", + "logout": "Logg ut", "change_password": { - "header": "Mudar Senha", + "header": "Bytt passord", "current_password": "Senha Atual", "new_password": "Nova Senha", "confirm_new_password": "Confirme Nova Senha", @@ -713,6 +717,47 @@ "required_fields": "Fyll ut dei nødvendige felta" } } + }, + "lovelace": { + "cards": { + "shopping-list": { + "checked_items": "Markerte element", + "clear_items": "Fjern dei markerrte elementa", + "add_item": "Legg til element" + } + }, + "editor": { + "edit_card": { + "header": "Kortkonfigurasjon", + "save": "Lagre", + "toggle_editor": "Bytt redigeringsverktøy", + "pick_card": "Vel kortet du vil legge til.", + "add": "Legg til kort", + "edit": "Redigere", + "delete": "Slett" + }, + "migrate": { + "header": "Konfigurasjonen er ikkje kompatibel", + "para_no_id": "Dette elementet har ikkje ein ID. Ver vennleg og legg til ein ID til dette elementet i \"ui-lovelace.yaml\"-fila di.", + "para_migrate": "Home assistant kan legge til ID-ar til alle korta og sidene dine automatisk for deg ved å trykke \"Overfør konfigurasjon\"-knappen.", + "migrate": "Overfør konfigurasjon" + }, + "header": "Rediger brukargrensesnitt", + "configure_ui": "Konfigurer brukargrensesnitt", + "edit_view": { + "header": "Vis konfigurasjon", + "add": "Legg til side", + "edit": "Rediger sida", + "delete": "Slett sida" + }, + "save_config": { + "header": "Ta kontroll over Lovelace-brukargrensesnittet", + "para": "Som standard kjem Home Assistant til å vedlikehalde brukargrensesnittet, og oppdatere det når nye oppføringar eller Lovelace-komponentar vert tilgjengelege. Dersom du tek kontroll, vil vi ikkje lenger kunne lage dette til automatisk for deg.", + "para_sure": "Er du sikker på at du vil ta kontroll over brukergrensesnittet ditt?", + "cancel": "Gløym det", + "save": "Ta kontroll" + } + } } }, "sidebar": { @@ -721,7 +766,8 @@ }, "common": { "loading": "Lastar", - "cancel": "Avbryt\n" + "cancel": "Avbryt\n", + "save": "Lagre" }, "duration": { "day": "{count} {count, plural,\none {dag}\nother {dagar}\n}", @@ -782,8 +828,8 @@ "disarm": "Skru av", "arm_home": "Heimemodus", "arm_away": "Bortemodus", - "arm_night": "Acionamento noturno", - "armed_custom_bypass": "Atalho configurado" + "arm_night": "Aktiver natt", + "armed_custom_bypass": "Tilpassa bypass" }, "automation": { "last_triggered": "Sist utløyst", @@ -835,11 +881,11 @@ } }, "water_heater": { - "currently": "Atualmente", - "on_off": "Liga \/ desliga", - "target_temperature": "Temperatura desejada", - "operation": "Operação", - "away_mode": "Modo distante" + "currently": "For augeblinken", + "on_off": "På \/ av", + "target_temperature": "Temperaturmål", + "operation": "Operasjon", + "away_mode": "Bortemodus" } }, "components": { diff --git a/translations/no.json b/translations/no.json index eabe963409..5d40fcf0d6 100644 --- a/translations/no.json +++ b/translations/no.json @@ -423,6 +423,10 @@ "event": "Hendelse:", "enter": "Ankommer", "leave": "Forlater" + }, + "webhook": { + "label": "Webhook", + "webhook_id": "Webhook ID" } } }, @@ -823,7 +827,7 @@ "clear_code": "Klarer", "disarm": "Deaktiver", "arm_home": "Armer hjemme", - "arm_away": "Armer bort", + "arm_away": "Armer borte", "arm_night": "Armer natt", "armed_custom_bypass": "Tilpasset bypass" }, diff --git a/translations/pl.json b/translations/pl.json index 91ef3e2103..41cb3ed7d4 100644 --- a/translations/pl.json +++ b/translations/pl.json @@ -423,6 +423,10 @@ "event": "Zdarzenie", "enter": "Wprowadź", "leave": "Opuść" + }, + "webhook": { + "label": "Webhook", + "webhook_id": "Identyfikator Webhook" } } }, @@ -482,7 +486,7 @@ "type_select": "Typ akcji", "type": { "service": { - "label": "Wykonanie usługi", + "label": "Wywołanie usługi", "service_data": "Dane usługi" }, "delay": { @@ -490,7 +494,7 @@ "delay": "Opóźnienie" }, "wait_template": { - "label": "Czekaj", + "label": "Oczekiwanie", "wait_template": "Szablon czekania", "timeout": "Limit czasu (opcjonalnie)" }, @@ -498,7 +502,7 @@ "label": "Warunek" }, "event": { - "label": "Uruchom zdarzenie", + "label": "Wywołanie zdarzenia", "event": "Zdarzenie:", "service_data": "Dane usługi" } @@ -821,11 +825,11 @@ "alarm_control_panel": { "code": "Kod", "clear_code": "Wyczyść", - "disarm": "Rozbrojony", - "arm_home": "uzbrojony (w domu)", - "arm_away": "uzbrojony (nieobecny)", - "arm_night": "uzbrojony (noc)", - "armed_custom_bypass": "uzbrojony (częściowo)" + "disarm": "Rozbrojenie", + "arm_home": "Uzbrojenie (w domu)", + "arm_away": "Uzbrojenie (nieobecny)", + "arm_night": "Uzbrojenie (noc)", + "armed_custom_bypass": "Uzbrój (częściowo)" }, "automation": { "last_triggered": "Ostatnie uruchomienie", diff --git a/translations/pt-BR.json b/translations/pt-BR.json index d41350931c..feaf717d7d 100644 --- a/translations/pt-BR.json +++ b/translations/pt-BR.json @@ -423,6 +423,10 @@ "event": "Evento:", "enter": "Entrar", "leave": "Sair" + }, + "webhook": { + "label": "Webhook", + "webhook_id": "ID da Webhook" } } }, @@ -726,13 +730,32 @@ "edit_card": { "header": "Configuração de cartão", "save": "Salvar", - "toggle_editor": "Alternar Editor" + "toggle_editor": "Alternar Editor", + "pick_card": "Escolha o cartão que você deseja adicionar.", + "add": "Adicionar cartão", + "edit": "Editar", + "delete": "Excluir" }, "migrate": { "header": "Configuração Incompatível", "para_no_id": "Este elemento não possui um ID. Por favor adicione um ID a este elemento em 'ui-lovelace.yaml'.", "para_migrate": "O Home Assistant pode adicionar IDs a todos os seus cards e visualizações automaticamente clicando no botão 'Migrar config'.", "migrate": "Migrar configuração" + }, + "header": "Editar “interface” do usuário", + "configure_ui": "Configurar “interface” do usuário", + "edit_view": { + "header": "Configurações", + "add": "Editar visualização", + "edit": "Editar visualização", + "delete": "Excluir visualização" + }, + "save_config": { + "header": "Assuma o controle da sua interface do Lovelace", + "para": "Por padrão, o Home Assistant manterá sua interface de usuário, atualizando-a quando novas entidades ou componentes do Lovelace estiverem disponíveis. Se você assumir o controle, não faremos mais alterações automaticamente para você.", + "para_sure": "Tem certeza de que deseja assumir o controle da sua interface de usuário?", + "cancel": "Nunca", + "save": "Assuma o controle" } } } @@ -743,7 +766,8 @@ }, "common": { "loading": "Carregando", - "cancel": "Cancelar" + "cancel": "Cancelar", + "save": "Salvar" }, "duration": { "day": "{count} {count, plural,\none {dia}\nother {dias}\n}", diff --git a/translations/pt.json b/translations/pt.json index 5a44af4799..2b5ecef9ed 100644 --- a/translations/pt.json +++ b/translations/pt.json @@ -273,16 +273,16 @@ "unavailable": "Indisp" }, "alarm_control_panel": { - "armed": "Armad", + "armed": "Armado", "disarmed": "Desarm", - "armed_home": "Armad", - "armed_away": "Armad", - "armed_night": "Armad", + "armed_home": "Armado", + "armed_away": "Armado", + "armed_night": "Armado", "pending": "Pend", "arming": "A armar", "disarming": "Desarmar", "triggered": "Disp", - "armed_custom_bypass": "Armad" + "armed_custom_bypass": "Armado" }, "device_tracker": { "home": "Casa", @@ -423,6 +423,10 @@ "event": "Evento:", "enter": "Entrar", "leave": "Sair" + }, + "webhook": { + "label": "", + "webhook_id": "" } } }, @@ -601,7 +605,7 @@ "not_used": "Nunca foi utilizado" }, "current_user": "Esta actualmente ligado como {fullName}", - "is_owner": "Você é o proprietário.", + "is_owner": "Você é um proprietário.", "logout": "Sair", "change_password": { "header": "Alterar palavra-passe", @@ -726,13 +730,32 @@ "edit_card": { "header": "Configuração do cartão", "save": "Guardar", - "toggle_editor": "Alterar para editor" + "toggle_editor": "Alterar para editor", + "pick_card": "Escolha o cartão que deseja adicionar.", + "add": "Adicionar Cartão", + "edit": "Editar", + "delete": "Apagar" }, "migrate": { "header": "Configuração Incompatível", "para_no_id": "Este elemento não possui um ID. Por favor adicione um ID a este elemento em 'ui-lovelace.yaml'.", "para_migrate": "O Home Assistant pode adicionar IDs a todos os seus cartões e vistas automaticamente clicando no botão 'Migrar configuração'.", "migrate": "Migrar configuração" + }, + "header": "Editar UI", + "configure_ui": "Configurar UI", + "edit_view": { + "header": "Ver configuração", + "add": "Acrescentar vista", + "edit": "Editar vista", + "delete": "Apagar a vista" + }, + "save_config": { + "header": "Assumir controle sobre a interface do Lovelace", + "para": "Por omissão o Home Assistant irá manter a sua interface de utilizador, actualizando sempre que uma entidade nova ou componentes Lovelace fiquem disponíveis. Se assumir o controle não será possivel fazer alterações automáticas por si.", + "para_sure": "Tem certeza que deseja assumir o controle sobre a interface de utilizador?", + "cancel": "Cancelar", + "save": "Assumir o controle" } } } @@ -743,7 +766,8 @@ }, "common": { "loading": "A carregar", - "cancel": "Cancelar" + "cancel": "Cancelar", + "save": "Guardar" }, "duration": { "day": "{count} {count, plural,\n one {dia}\n other {dias}\n}", diff --git a/translations/ru.json b/translations/ru.json index 1f0489d37e..f63699c76c 100644 --- a/translations/ru.json +++ b/translations/ru.json @@ -101,8 +101,8 @@ "on": "Охлаждение" }, "door": { - "off": "Закрыто", - "on": "Открыто" + "off": "Закрыта", + "on": "Открыта" }, "garage_door": { "off": "Закрыто", @@ -182,20 +182,20 @@ "problem": "Проблема" }, "input_boolean": { - "off": "Выключен", - "on": "Включен" + "off": "Выкл", + "on": "Вкл" }, "light": { - "off": "Выключен", - "on": "Включен" + "off": "Выкл", + "on": "Вкл" }, "lock": { "locked": "Закрыт", "unlocked": "Открыт" }, "media_player": { - "off": "Выключен", - "on": "Включен", + "off": "Выкл", + "on": "Вкл", "playing": "Воспроизведение", "paused": "Пауза", "idle": "Ожидание", @@ -206,8 +206,8 @@ "problem": "Проблема" }, "remote": { - "off": "Выключено", - "on": "Включено" + "off": "Выкл", + "on": "Вкл" }, "scene": { "scening": "Переход к сцене" @@ -388,11 +388,11 @@ "label": "Home Assistant", "event": "Событие:", "start": "Запуск", - "shutdown": "Выключение" + "shutdown": "Завершение работы" }, "mqtt": { "label": "MQTT", - "topic": "Топик", + "topic": "Тема", "payload": "Значение (опционально)" }, "numeric_state": { @@ -423,6 +423,10 @@ "event": "Событие:", "enter": "Войти", "leave": "Покинуть" + }, + "webhook": { + "label": "Webhook", + "webhook_id": "Идентификатор Webhook" } } }, diff --git a/translations/sk.json b/translations/sk.json index ff0f983fc4..a5a5764fb1 100644 --- a/translations/sk.json +++ b/translations/sk.json @@ -13,7 +13,8 @@ "dev-templates": "Šablóny", "dev-mqtt": "MQTT", "dev-info": "Info", - "calendar": "Kalendár" + "calendar": "Kalendár", + "profile": "Profil" }, "state": { "default": { @@ -116,8 +117,8 @@ "on": "Otvorené" }, "lock": { - "off": "Zamknuté", - "on": "Odomknuté" + "off": "Zamknutý", + "on": "Odomknutý" } }, "calendar": { @@ -166,17 +167,17 @@ "on": "Zapnutý" }, "group": { - "off": "Vypnuté", - "on": "Zapnuté", + "off": "Vypnutá", + "on": "Zapnutá", "home": "Doma", "not_home": "Preč", - "open": "Otvorené", + "open": "Otvorená", "opening": "Otvára sa", - "closed": "Zatvorené", + "closed": "Zatvorená", "closing": "Zatvára sa", "stopped": "Zastavené", - "locked": "Zamknuté", - "unlocked": "Odomknuté", + "locked": "Zamknutá", + "unlocked": "Odomknutá", "ok": "OK", "problem": "Problém" }, @@ -274,14 +275,14 @@ "alarm_control_panel": { "armed": "Zakód", "disarmed": "Odkód", - "armed_home": "Aktívny", - "armed_away": "Aktívny", - "armed_night": "Aktívny", + "armed_home": "Zakód", + "armed_away": "Zakód", + "armed_night": "Zakód", "pending": "Čaká", "arming": "Aktivácia", "disarming": "Deakt", "triggered": "Alarm", - "armed_custom_bypass": "Zapnutý" + "armed_custom_bypass": "Zakódovaný" }, "device_tracker": { "home": "Doma", @@ -300,7 +301,7 @@ "period": "Obdobie" }, "logbook": { - "showing_entries": "Zobrazujú sa záznamy pre" + "showing_entries": "Zobrazujú sa záznamy za obdobie" }, "mailbox": { "empty": "Nemáte žiadne správy", @@ -380,7 +381,8 @@ "state": { "label": "Stav", "from": "Z", - "to": "Na" + "to": "Na", + "for": "Trvanie stavu" }, "homeassistant": { "label": "Home Assistant", @@ -421,6 +423,10 @@ "event": "Udalosť:", "enter": "Vstúpenie", "leave": "Opustenie" + }, + "webhook": { + "label": "Webhook", + "webhook_id": "Webhook ID" } } }, @@ -497,7 +503,7 @@ }, "event": { "label": "Odpáliť udalosť", - "event": "Udalosť", + "event": "Udalosť:", "service_data": "Dáta služby" } } @@ -525,6 +531,31 @@ "deactivate_user": "Deaktivovať používateľa", "delete_user": "Vymazať používateľa" } + }, + "cloud": { + "caption": "Home Assistant Cloud", + "description_login": "Prihlásený ako {email}", + "description_not_login": "Neprihlásený" + }, + "integrations": { + "caption": "Integrácie", + "description": "Spravovať pripojené zariadenia a služby", + "discovered": "Objavené", + "configured": "Nakonfigurovaný", + "new": "Nastaviť novú integráciu", + "configure": "Konfigurovať", + "none": "Nič zatiaľ nebolo nakonfigurované", + "config_entry": { + "no_devices": "Táto integrácia nemá žiadne zariadenia.", + "no_device": "Entity bez zariadení", + "delete_confirm": "Naozaj chcete odstrániť túto integráciu?", + "restart_confirm": "Ak chcete dokončiť odstránenie tejto integrácie, reštartujte Home Assistant", + "manuf": "od {manufacturer}", + "hub": "Pripojené cez", + "firmware": "Firmvér: {version}", + "device_unavailable": "zariadenie nie je dostupné", + "entity_unavailable": "Entita nie je dostupná" + } } }, "profile": { @@ -546,20 +577,187 @@ "error_no_theme": "Nie sú k dispozícii žiadne témy.", "link_promo": "Získajte viac informácií o témach", "dropdown_label": "Téma" + }, + "refresh_tokens": { + "header": "Obnovovacie Tokeny", + "description": "Každý obnovovací token predstavuje prihlásenú reláciu. Obnovovacie tokeny sa po kliknutí na tlačidlo Odhlásiť sa automaticky odstránia. Pre váš účet sú aktívne nasledovné obnovovacie tokeny.", + "token_title": "Obnovovací token pre {clientId}", + "created_at": "Vytvorený {date}", + "confirm_delete": "Naozaj chcete odstrániť obnovovací token pre {name} ?", + "delete_failed": "Nepodarilo sa odstrániť obnovovací token", + "last_used": "Naposledy použitý {date} z {location}", + "not_used": "Nikdy nebol použitý", + "current_token_tooltip": "Nedá sa odstrániť aktuálny obnovovací token" + }, + "long_lived_access_tokens": { + "header": "Prístupové tokeny s dlhou životnosťou", + "description": "Vytvorte prístupové tokeny s dlhou životnosťou, ktoré umožnia vašim skriptom komunikovať s vašou inštanciou Home Assistant. Každý token bude platný 10 rokov od vytvorenia. Nasledujúce prístupové tokeny s dlhou životnosťou sú v súčasnosti aktívne.", + "learn_auth_requests": "Zistite, ako vytvárať overené požiadavky.", + "created_at": "Vytvorený {date}", + "confirm_delete": "Naozaj chcete odstrániť prístupový token pre {name} ?", + "delete_failed": "Nepodarilo sa odstrániť prístupový token.", + "create": "Vytvoriť Token", + "create_failed": "Nepodarilo sa vytvoriť prístupový token.", + "prompt_name": "Názov?", + "prompt_copy_token": "Skopírujte svoj nový prístupový token. Znova sa nezobrazí.", + "empty_state": "Nemáte žiadne prístupové tokeny s dlhou životnosťou.", + "last_used": "Naposledy použitý {date} z {location}", + "not_used": "Nikdy nebol použitý" + }, + "current_user": "Momentálne ste prihlásení ako {fullName} .", + "is_owner": "Ste vlastníkom.", + "logout": "Odhlásiť sa", + "change_password": { + "header": "Zmena hesla", + "current_password": "Aktuálne heslo", + "new_password": "Nové heslo", + "confirm_new_password": "Potvrďte nové heslo", + "error_required": "Požadované", + "submit": "Odoslať" + }, + "mfa": { + "header": "Multifaktorové autentifikačné moduly", + "disable": "Zakázať", + "enable": "Povoliť", + "confirm_disable": "Naozaj chcete zakázať {name} ?" + }, + "mfa_setup": { + "title_aborted": "Prerušené", + "title_success": "Úspech!", + "step_done": "Nastavenie dokončené krok {step} ", + "close": "Zavrieť", + "submit": "Odoslať" } }, "page-authorize": { + "initializing": "Inicializácia", + "authorizing_client": "Chystáte sa poskytnúť {clientId} prístup k vašej inštancii Home Assistantu.", + "logging_in_with": "Prihlasovanie pomocou ** {authProviderName} **.", + "pick_auth_provider": "Alebo sa prihláste prostredníctvom", + "abort_intro": "Prihlásenie bolo zrušené", "form": { + "working": "Prosím čakajte", + "unknown_error": "Niečo sa pokazilo", "providers": { + "homeassistant": { + "step": { + "init": { + "data": { + "username": "Používateľské meno", + "password": "Heslo" + } + }, + "mfa": { + "data": { + "code": "Kód dvojfaktorovej autentifikácie" + }, + "description": "Otvorte ** {mfa_module_name} ** v zariadení, aby ste si pozreli svoj dvojfaktorový autentifikačný kód a overili svoju totožnosť:" + } + }, + "error": { + "invalid_auth": "Nesprávne používateľské meno alebo heslo", + "invalid_code": "Neplatný overovací kód" + }, + "abort": { + "login_expired": "Platnosť relácie skončila, prosím prihláste znova." + } + }, + "legacy_api_password": { + "step": { + "init": { + "data": { + "password": "Heslo API rozhrania" + }, + "description": "Prosím zadajte heslo API rozhrania v konfigurácií http:" + }, + "mfa": { + "data": { + "code": "Kód dvojfaktorovej autentifikácie" + }, + "description": "Otvorte **{mfa_module_name}** na vašom zariadení a pozrite si kód dvojfaktorovej autentifikácie a overte svoju totožnosť." + } + }, + "error": { + "invalid_auth": "Neplatné heslo rozhrania API", + "invalid_code": "Neplatný autentifikačný kód" + }, + "abort": { + "no_api_password_set": "Nemáte nakonfigurované heslo rozhrania API.", + "login_expired": "Relácia vypršala, prosím prihláste sa znova." + } + }, "trusted_networks": { "step": { "init": { + "data": { + "user": "Používateľ" + }, "description": "Välj en användare att logga in som:" } + }, + "abort": { + "not_whitelisted": "Váš počítač nie je v zozname povolených zariadení." } } } } + }, + "page-onboarding": { + "intro": "Ste pripravení prebudiť váš domov, získať vaše súkromie a pripojiť sa k celosvetovej komunite bastličov?", + "user": { + "intro": "Poďme začať vytvorením používateľského konta.", + "required_field": "Požadované", + "data": { + "name": "Meno", + "username": "Používateľské meno", + "password": "Heslo" + }, + "create_account": "Vytvoriť účet", + "error": { + "required_fields": "Vyplňte všetky povinné polia" + } + } + }, + "lovelace": { + "cards": { + "shopping-list": { + "checked_items": "Začiarknuté položky", + "clear_items": "Vymažte označené položky", + "add_item": "Pridať položku" + } + }, + "editor": { + "edit_card": { + "header": "Konfigurácia karty", + "save": "Uložiť", + "toggle_editor": "Prepnúť Editor", + "pick_card": "Vyberte kartu, ktorú chcete pridať.", + "add": "Pridať kartu", + "edit": "Upraviť", + "delete": "Vymazať" + }, + "migrate": { + "header": "Nekompatibilná konfigurácia", + "para_no_id": "Tento prvok nemá ID. Pridajte ID k tomuto prvku v ui-lovelace.yaml.", + "para_migrate": "Home Assistant dokáže automaticky pridať ID na všetky vaše karty a zobrazenia stlačením tlačidla Migrácia konfigurácie.", + "migrate": "Migrácia konfigurácie" + }, + "header": "Úprava používateľského rozhrania", + "configure_ui": "Konfigurácia používateľského rozhrania", + "edit_view": { + "header": "Konfigurácia zobrazenia", + "add": "Pridať zobrazenie", + "edit": "Upraviť zobrazenie", + "delete": "Odstrániť zobrazenie" + }, + "save_config": { + "header": "Prevziať kontrolu vášho Lovelace rozhrania", + "para": "Štandardne Home Assistant bude udržiavať vaše užívateľské rozhranie a aktualizovať ho, keď budú k dispozícii nové entity alebo komponenty služby Lovelace. Ak prevezmete kontrolu, nebudeme už automaticky robiť zmeny.", + "para_sure": "Naozaj chcete prevziať kontrolu vášho užívateľského rozhrania?", + "cancel": "Nevadí", + "save": "Prevziať kontrolu" + } + } } }, "sidebar": { @@ -568,7 +766,8 @@ }, "common": { "loading": "Načítava sa", - "cancel": "Zrušiť" + "cancel": "Zrušiť", + "save": "Uložiť" }, "duration": { "day": "{count} {count, plural,\none {deň}\nfew {dni}\nother {dní}\n}", @@ -628,7 +827,9 @@ "clear_code": "Zrušiť", "disarm": "Odkódovať", "arm_home": "Zakódovať doma", - "arm_away": "Zakódovať odchod" + "arm_away": "Zakódovať odchod", + "arm_night": "Zakódovať na noc", + "armed_custom_bypass": "Prispôsobené vylúčenie" }, "automation": { "last_triggered": "Naposledy spustené", @@ -678,6 +879,13 @@ "turn_on": "Zapnúť", "turn_off": "Vypnúť" } + }, + "water_heater": { + "currently": "Aktuálne", + "on_off": "Zapnúť \/ vypnúť", + "target_temperature": "Cieľová teplota", + "operation": "V prevádzke", + "away_mode": "Režim neprítomnosti" } }, "components": { @@ -724,6 +932,11 @@ "ask": "Chcete tieto prihlasovacie údaje uložiť?", "decline": "Nie ďakujem", "confirm": "Uložiť prihlasovacie údaje" + }, + "notification_drawer": { + "click_to_configure": "Kliknutím na tlačidlo nakonfigurujete {entity}", + "empty": "Žiadne upozornenia", + "title": "Upozornenia" } }, "domain": { diff --git a/translations/sl.json b/translations/sl.json index 09be57ec64..150082c742 100644 --- a/translations/sl.json +++ b/translations/sl.json @@ -423,6 +423,10 @@ "event": "Dogodek:", "enter": "Vnesite", "leave": "Odidi" + }, + "webhook": { + "label": "Webhook", + "webhook_id": "Webhook ID" } } }, diff --git a/translations/sv.json b/translations/sv.json index 419a226696..f9d8808532 100644 --- a/translations/sv.json +++ b/translations/sv.json @@ -423,6 +423,10 @@ "event": "Händelse", "enter": "Ankommer", "leave": "Lämnar" + }, + "webhook": { + "label": "Webhook", + "webhook_id": "Webhook ID" } } }, @@ -726,13 +730,31 @@ "edit_card": { "header": "Kortkonfiguration", "save": "Spara", - "toggle_editor": "Visa \/ Dölj redigerare" + "toggle_editor": "Visa \/ Dölj redigerare", + "pick_card": "Välj det kort du vill lägga till.", + "add": "Lägg till kort", + "edit": "Redigera", + "delete": "Ta bort" }, "migrate": { "header": "Konfigurationen är inte giltig", "para_no_id": "Det här elementet har inget ID. Lägg till ett ID till det här elementet i \"ui-lovelace.yaml\".", "para_migrate": "Home Assistant kan automatiskt lägga till IDn till alla dina kort och vyer genom att du klickar på \"Migrera konfiguration\".", "migrate": "Migrera konfigurationen" + }, + "header": "Ändra användargränssnittet", + "configure_ui": "Konfigurera användargränssnittet", + "edit_view": { + "header": "Visa konfiguration", + "add": "Lägg till vy", + "edit": "Redigera vy", + "delete": "Radera vy" + }, + "save_config": { + "header": "Ta kontroll över din Lovelace UI", + "para_sure": "Är du säker på att du vill ta kontroll över ditt användargränssnitt?", + "cancel": "Glöm det", + "save": "Ta kontroll" } } } @@ -743,7 +765,8 @@ }, "common": { "loading": "Läser in", - "cancel": "Avbryt" + "cancel": "Avbryt", + "save": "Spara" }, "duration": { "day": "{count} {count, plural,\none {dag}\nother {dagar}\n}", diff --git a/translations/uk.json b/translations/uk.json index 34a09cbfd7..07bfdf8c67 100644 --- a/translations/uk.json +++ b/translations/uk.json @@ -50,7 +50,7 @@ }, "gas": { "off": "Чисто", - "on": "Виявлено" + "on": "Виявлено газ" }, "motion": { "off": "Немає руху", @@ -58,26 +58,26 @@ }, "occupancy": { "off": "Чисто", - "on": "Виявлено" + "on": "Виявлено присутність" }, "smoke": { "off": "Чисто", - "on": "Виявлено" + "on": "Виявлено дим" }, "sound": { "off": "Чисто", - "on": "Виявлено" + "on": "Виявлено звук" }, "vibration": { - "off": "Чисто", - "on": "Виявлено" + "off": "Не виявлено", + "on": "Виявлена вібрація" }, "opening": { "off": "Закрито", "on": "Відкритий" }, "safety": { - "off": "Безпека", + "off": "Безпечно", "on": "Небезпечно" }, "presence": { @@ -106,14 +106,14 @@ }, "garage_door": { "off": "ЗачиненІ", - "on": "Відкрито" + "on": "Відкриті" }, "heat": { "off": "Норма", "on": "Нагрівання" }, "window": { - "off": "Зачинено", + "off": "Зачинене", "on": "Відчинене" }, "lock": { @@ -255,6 +255,9 @@ "sunny": "Сонячно", "windy": "Вітряно", "windy-variant": "Вітряно" + }, + "vacuum": { + "idle": "Очікування" } }, "state_badge": { @@ -605,6 +608,8 @@ "lovelace": { "cards": { "shopping-list": { + "checked_items": "Позначені елементи", + "clear_items": "Очистити позначені елементи", "add_item": "Додати елемент" } }, @@ -612,20 +617,32 @@ "edit_card": { "header": "Конфігурація картки", "save": "Зберегти", + "toggle_editor": "Перемкнути редактор", "pick_card": "Виберіть картку, яку хочете додати.", "add": "Додати картку", "edit": "Редагувати", "delete": "Видалити" }, "migrate": { - "header": "Конфігурація несумісна" + "header": "Конфігурація несумісна", + "para_no_id": "Цей елемент не має ID. Додайте ID до цього елемента в 'ui-lovelace.yaml'.", + "para_migrate": "Домашній помічник може автоматично додавати ідентифікатори ID до всіх ваших карт і переглядів, натиснувши кнопку \"Перенести налаштування\".", + "migrate": "Перенесення конфігурації" }, "header": "Редагування інтерфейсу", + "configure_ui": "Налаштувати інтерфейс користувача", "edit_view": { - "header": "Перегляд Конфігурації " + "header": "Перегляд Конфігурації ", + "add": "Додати вигляд", + "edit": "Редагувати вигляд", + "delete": "Видалити вигляд" }, "save_config": { - "header": "Візьміть під свій контроль Lovelace UI" + "header": "Візьміть під свій контроль Lovelace UI", + "para": "За замовчуванням Home Assistant буде підтримувати ваш користувальницький інтерфейс, оновлюючи його, коли з'являться нові об'єкти або компоненти Lovelace. Якщо ви візьмете під контроль, ми більше не будемо автоматично вносити зміни для вас.", + "para_sure": "Ви впевнені, що хочете взяти під свій контроль користувальницький інтерфейс?", + "cancel": "Неважливо", + "save": "Взяти під контроль" } } } @@ -680,7 +697,8 @@ "disarm": "Зняття з охорони", "arm_home": "Поставити на охорону", "arm_away": "Охорона (не вдома)", - "arm_night": "Нічна охорона" + "arm_night": "Нічна охорона", + "armed_custom_bypass": "Користувацький обхід" }, "automation": { "last_triggered": "Спрацьовано", diff --git a/translations/vi.json b/translations/vi.json index 38c49a5f1f..65012c9e14 100644 --- a/translations/vi.json +++ b/translations/vi.json @@ -28,7 +28,7 @@ "disarmed": "Vô hiệu hóa", "armed_home": "Bảo vệ ở nhà", "armed_away": "Bảo vệ đi vắng", - "armed_night": "An ninh ban đêm", + "armed_night": "Ban đêm", "pending": "Đang chờ xử lý", "arming": "Kích hoạt", "disarming": "Giải giáp", @@ -747,8 +747,8 @@ "code": "Mã số", "clear_code": "Xóa", "disarm": "Vô hiệu hoá", - "arm_home": "An ninh ở nhà", - "arm_away": "An ninh đi vắng", + "arm_home": "Ở nhà", + "arm_away": "Đi vắng", "arm_night": "An ninh ban đêm", "armed_custom_bypass": "Bỏ qua tùy chỉnh" }, diff --git a/translations/zh-Hant.json b/translations/zh-Hant.json index 7914810c0f..5c83df2a18 100644 --- a/translations/zh-Hant.json +++ b/translations/zh-Hant.json @@ -423,6 +423,10 @@ "event": "事件:", "enter": "進入區域", "leave": "離開區域" + }, + "webhook": { + "label": "Webhook", + "webhook_id": "Webhook ID" } } }, @@ -750,8 +754,8 @@ "header": "自行編輯 Lovelace UI", "para": "Home Assistant 於預設下,將維護您的使用者介面、於新物件或 Lovelace 元件可使用時進行更新。假如選擇自行編輯,系統將不再為您自動進行變更。", "para_sure": "確定要自行編輯使用者介面?", - "cancel": "取消", - "save": "儲存" + "cancel": "我再想想", + "save": "自行編輯" } } }