From ff270c4b7d0430358c33195393785941473ffc61 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Thu, 9 Jan 2020 00:32:44 +0000 Subject: [PATCH 001/126] [ci skip] Translation update --- translations/da.json | 3 ++ translations/en.json | 24 +++++++++++--- translations/nb.json | 20 +++++++++++- translations/nl.json | 35 ++++++++++++++++++++ translations/ru.json | 76 +++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 152 insertions(+), 6 deletions(-) diff --git a/translations/da.json b/translations/da.json index 16f944559c..30a56e7518 100644 --- a/translations/da.json +++ b/translations/da.json @@ -1925,10 +1925,13 @@ "para_no_id": "Dette element har ikke et id. Tilføj venligst et id til dette element i 'ui-lovelace.yaml'." }, "raw_editor": { + "confirm_remove_config_text": "Vi genererer automatisk dine Lovelace-visninger med dine områder og enheder, hvis du fjerner din Lovelace-konfiguration.", + "confirm_remove_config_title": "Er du sikker på, at du vil fjerne din Lovelace-konfiguration? Vi vil automatisk generere dine Lovelace-visninger med dine områder og enheder.", "confirm_unsaved_changes": "Du har ikke-gemte ændringer, er du sikker på, at du vil afslutte?", "confirm_unsaved_comments": "Din konfiguration indeholder kommentarer. Disse vil ikke blive gemt. Vil du fortsætte?", "error_invalid_config": "Din konfiguration er ugyldig: {error}", "error_parse_yaml": "Kunne ikke fortolke YAML: {error}", + "error_remove": "Ude af stand til at fjerne konfiguration: {error}", "error_save_yaml": "Kunne ikke gemme YAML: {error}", "header": "Rediger konfiguration", "save": "Gem", diff --git a/translations/en.json b/translations/en.json index 5d10a90176..a3b0d8cd3d 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1477,6 +1477,10 @@ "search_again": "Search Again", "spinner": "Searching for ZHA Zigbee devices..." }, + "add": { + "caption": "Add Devices", + "description": "Add devices to the Zigbee network" + }, "caption": "ZHA", "cluster_attributes": { "attributes_of_cluster": "Attributes of the selected cluster", @@ -1496,7 +1500,9 @@ "issue_zigbee_command": "Issue Zigbee Command" }, "clusters": { - "help_cluster_dropdown": "Select a cluster to view attributes and commands." + "header": "Clusters", + "help_cluster_dropdown": "Select a cluster to view attributes and commands.", + "introduction": "Clusters are the building blocks for Zigbee functionality. They seperate functionality into logical units. There are client and server types and that are comprised of attributes and commands." }, "common": { "add_devices": "Add Devices", @@ -1511,20 +1517,27 @@ "device_name_placeholder": "User given name", "update_name_button": "Update Name" }, + "devices": { + "header": "Zigbee Home Automation - Device" + }, "groups": { "add_members": "Add Members", "adding_members": "Adding Members", + "caption": "Groups", "create": "Create Group", - "create_group": "Create New ZHA Zigbee Group", + "create_group": "Zigbee Home Automation - Create Group", "create_group_details": "Enter the required details to create a new zigbee group", "creating_group": "Creating Group", + "description": "Create and modify Zigbee groups", "group_details": "Here are all the details for the selected Zigbee group.", "group_id": "Group ID", "group_info": "Group Information", "group_name_placeholder": "Group Name", "group_not_found": "Group not found!", + "group-header": "Zigbee Home Automation - Group Details", "groups": "Groups", - "header": "Zigbee Group Management", + "groups-header": "Zigbee Home Automation - Group Management", + "header": "Zigbee Home Automation - Group Management", "introduction": "Create and modify zigbee groups", "manage_groups": "Manage Zigbee Groups", "members": "Members", @@ -1534,6 +1547,8 @@ "removing_members": "Removing Members", "zha_zigbee_groups": "ZHA Zigbee Groups" }, + "header": "Configure Zigbee Home Automation", + "introduction": "Here it is possible to configure the ZHA component. Not everything is possible to configure from the UI yet, but we're working on it.", "network_management": { "header": "Network Management", "introduction": "Commands that affect the entire network" @@ -1549,7 +1564,8 @@ "reconfigure": "Reconfigure ZHA device (heal device). Use this if you are having issues with the device. If the device in question is a battery powered device please ensure it is awake and accepting commands when you use this service.", "remove": "Remove a device from the ZigBee network.", "updateDeviceName": "Set a custom name for this device in the device registry." - } + }, + "title": "Zigbee Home Automation" }, "zwave": { "caption": "Z-Wave", diff --git a/translations/nb.json b/translations/nb.json index 58afbca551..ce0d9ea3d2 100644 --- a/translations/nb.json +++ b/translations/nb.json @@ -533,6 +533,7 @@ }, "common": { "cancel": "Avbryt", + "close": "Lukk", "loading": "Laster", "no": "Nei", "save": "Lagre", @@ -1198,17 +1199,26 @@ "update": "OPPDATER" }, "picker": { + "filter": { + "filter": "Filter" + }, "header": "Entiteter", "headers": { "enabled": "Aktivert", "entity_id": "Entitets-ID", "integration": "Integrering", - "name": "Navn" + "name": "Navn", + "status": "Status" }, "integrations_page": "Integrasjonsside", "introduction": "Home Assistant bygger opp et register over hver entitet den har sett som kan identifiseres unikt. Hver av disse entitetene vil ha en entitets-ID som er reservert kun til denne entiteten.", "introduction2": "Bruk entitetsregisteret for å overskrive navnet, endre entitets-ID eller fjern oppføringen fra Home Assistant. Merk at fjerning av entitetsregisteroppføringer ikke fjerner entiteten. For å gjøre det, følg linken under og fjern den fra integrasjonssiden.", "show_disabled": "Vis deaktiverte entiteter", + "status": { + "disabled": "Deaktivert", + "ok": "Ok", + "unavailable": "Utilgjengelig" + }, "unavailable": "(utilgjengelig)" } }, @@ -1470,6 +1480,13 @@ "device_name_placeholder": "Brukers navn", "update_name_button": "Oppdatér navn" }, + "groups": { + "add_members": "Legg til medlemmer", + "create": "Opprett gruppe", + "groups": "Grupper", + "remove_groups": "Fjern grupper", + "removing_groups": "Fjerner grupper" + }, "network_management": { "header": "Nettverksadministrasjon", "introduction": "Kommandoer som påvirker hele nettverket" @@ -1860,6 +1877,7 @@ "confirm_unsaved_comments": "Konfigurasjonen din inneholder kommentar(er), disse vil ikke bli lagret. Vil du fortsette?", "error_invalid_config": "Konfigurasjonen er ikke gyldig: {error}", "error_parse_yaml": "Kan ikke analysere YAML: {error}", + "error_remove": "Kan ikke fjerne konfigurasjonen: {error}", "error_save_yaml": "Kan ikke lagre YAML: {error}", "header": "Rediger konfigurasjon", "save": "Lagre", diff --git a/translations/nl.json b/translations/nl.json index 324b2e9b88..171bf54f59 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -533,6 +533,7 @@ }, "common": { "cancel": "Annuleren", + "close": "Sluiten", "loading": "Bezig met laden", "no": "Nee", "save": "Opslaan", @@ -588,6 +589,13 @@ }, "more_info_control": { "dismiss": "Dialoogvenster sluiten", + "restored": { + "confirm_remove_text": "Weet u zeker dat u dit item wilt verwijderen?", + "confirm_remove_title": "Entiteit verwijderen?", + "not_provided": "Deze entiteit is momenteel niet beschikbaar en is een wees voor een verwijderde, gewijzigde of disfunctionele integratie of apparaat.", + "remove_action": "Entiteit verwijderen", + "remove_intro": "Als de entiteit niet meer in gebruik is, kunt u deze opruimen door deze te verwijderen." + }, "script": { "last_action": "Laatste actie" }, @@ -1178,6 +1186,11 @@ "description": "Beheer verbonden apparaten", "details": "Hier zijn alle details van uw apparaat", "device_not_found": "Apparaat niet gevonden.", + "entities": { + "add_entities_lovelace": "Voeg alle apparaatentiteiten toe aan Lovelace", + "entities": "Entiteiten", + "none": "Dit apparaat heeft geen entiteiten" + }, "info": "Apparaat info", "unknown_error": "Onbekende fout", "unnamed_device": "Naamloos apparaat" @@ -1198,6 +1211,11 @@ "update": "BIJWERKEN" }, "picker": { + "filter": { + "filter": "filter", + "show_disabled": "Uitgeschakelde entiteiten weergeven", + "show_unavailable": "Uitgeschakelde entiteiten weergeven" + }, "header": "Entiteiten", "headers": { "enabled": "Ingeschakeld", @@ -1208,7 +1226,14 @@ "integrations_page": "Integratiespagina", "introduction": "Home Assistant houdt een register bij van elke entiteit die het ooit heeft gezien en die uniek kan worden geïdentificeerd. Elk van deze entiteiten krijgt een entiteit-ID toegewezen die alleen voor deze entiteit wordt gereserveerd.", "introduction2": "Gebruik het entiteitenregister om de naam te overschrijven, de entiteits-ID te wijzigen of het item te verwijderen uit Home Assistant. Opmerking: als u de entiteitsregistervermelding verwijdert, wordt de entiteit niet verwijderd. Hiertoe volgt u de onderstaande koppeling en verwijdert u deze van de integratiespagina.", + "remove_selected": { + "button": "Verwijder geselecteerde" + }, "show_disabled": "Uitgeschakelde entiteiten weergeven", + "status": { + "disabled": "Uitgeschakeld", + "unavailable": "Onbeschikbaar" + }, "unavailable": "(niet beschikbaar)" } }, @@ -1470,6 +1495,16 @@ "device_name_placeholder": "Door gebruiker ingegeven naam", "update_name_button": "Naam bijwerken" }, + "groups": { + "group_id": "Groep", + "group_info": "Groepsinformatie", + "groups": "groepen", + "header": "Zigbee groep management", + "introduction": "Maak en verwijder zigbee groepen", + "members": "Leden", + "remove_groups": "verwijder groepen", + "removing_groups": "groepen verwijderen" + }, "network_management": { "header": "Netwerkbeheer", "introduction": "Commando's die het hele netwerk beïnvloeden" diff --git a/translations/ru.json b/translations/ru.json index b9cbe9024a..1c4d36ec97 100644 --- a/translations/ru.json +++ b/translations/ru.json @@ -533,6 +533,7 @@ }, "common": { "cancel": "Отменить", + "close": "Закрыть", "loading": "Загрузка", "no": "Нет", "save": "Сохранить", @@ -588,6 +589,13 @@ }, "more_info_control": { "dismiss": "Закрыть диалог", + "restored": { + "confirm_remove_text": "Вы уверены, что хотите удалить этот объект?", + "confirm_remove_title": "Удалить объект?", + "not_provided": "Этот объект в настоящее время недоступен и его родительская интеграция или устройство удалены, изменены либо неисправны.", + "remove_action": "Удалить объект", + "remove_intro": "Если объект больше не используется, Вы можете удалить его." + }, "script": { "last_action": "Последнее действие" }, @@ -955,6 +963,7 @@ "manage_entities": "Управление объектами", "security_devices": "Устройства безопасности", "sync_entities": "Синхронизировать объекты", + "sync_entities_404_message": "Не удалось синхронизировать Ваши объекты с Google. Попробуйте запустить синхронизацию путём голосового запроса: «Эй, Google, синхронизируй мои устройства».", "title": "Google Assistant" }, "integrations": "Интеграции", @@ -1178,6 +1187,10 @@ "description": "Управляйте подключенными устройствами", "details": "Здесь приведена вся доступная информация об этом устройстве.", "device_not_found": "Устройство не найдено.", + "entities": { + "entities": "Объекты", + "none": "У этого устройства нет объектов" + }, "info": "Информация об устройстве", "unknown_error": "Неизвестная ошибка.", "unnamed_device": "Безымянное устройство" @@ -1198,17 +1211,44 @@ "update": "ОБНОВИТЬ" }, "picker": { + "disable_selected": { + "button": "Отключить выбранные", + "confirm_text": "Отключенные объекты не будут доступны в Home Assistant.", + "confirm_title": "Вы уверены, что хотите отключить выбранные объекты? ({number})" + }, + "enable_selected": { + "button": "Включить выбранные", + "confirm_text": "Если эти объекты ранее были отключены, они снова будут доступны в Home Assistant.", + "confirm_title": "Вы уверены, что хотите включить выбранные объекты? ({number})" + }, + "filter": { + "filter": "Фильтр", + "show_disabled": "Показывать отключенные объекты", + "show_unavailable": "Показывать недоступные объекты" + }, "header": "Объекты", "headers": { "enabled": "Включено", "entity_id": "ID объекта", "integration": "Интеграция", - "name": "Название" + "name": "Название", + "status": "Состояние" }, "integrations_page": "Страница интеграций", "introduction": "Home Assistant ведет реестр каждого объекта, который когда-либо был создан в системе. Каждому из этих объектов присвоен ID, который зарезервирован только для этого объекта.", "introduction2": "Используйте данный реестр, чтобы изменить ID или название объекта либо удалить запись из Home Assistant. Обратите внимание, что удаление записи из реестра объектов не удалит сам объект. Для этого перейдите по указанной ниже ссылке и удалите его со страницы интеграций.", + "remove_selected": { + "button": "Удалить выбранные", + "confirm_text": "Объекты могут быть удалены только в том случае, если они не принадлежат рабочей интеграции.", + "confirm_title": "Вы уверены, что хотите удалить выбранные объекты? ({number})" + }, + "selected": "Выбрано: {number}", "show_disabled": "Показывать отключенные объекты", + "status": { + "disabled": "Отключено", + "ok": "Ok", + "unavailable": "Недоступно" + }, "unavailable": "(недоступен)" } }, @@ -1470,6 +1510,27 @@ "device_name_placeholder": "Название", "update_name_button": "Обновить название" }, + "groups": { + "add_members": "Добавить участников", + "adding_members": "Добавление участников", + "create": "Создать группу", + "create_group": "Создать новую ZHA Zigbee группу", + "create_group_details": "Укажите необходимые данные для создания новой группы ZigBee", + "creating_group": "Создание группы", + "group_details": "Здесь вся информация для выбранной группы Zigbee.", + "group_id": "ID группы", + "group_info": "Информация о группе", + "group_name_placeholder": "Имя группы", + "group_not_found": "Группа не найдена!", + "groups": "Группы", + "manage_groups": "Управление группами Zigbee", + "members": "Участники", + "remove_groups": "Удалить группы", + "remove_members": "Удалить участников", + "removing_groups": "Удаляем группы", + "removing_members": "Удаление участников", + "zha_zigbee_groups": "Группы ZHA Zigbee" + }, "network_management": { "header": "Управление сетью", "introduction": "Команды, которые влияют на всю сеть" @@ -1667,6 +1728,11 @@ "showing_entries": "Отображение записей за" }, "lovelace": { + "add_entities": { + "generated_unsupported": "Эту функцию можно использовать только в режиме управление Lovelace.", + "saving_failed": "Не удалось сохранить настройки Lovelace", + "yaml_unsupported": "Вы не можете использовать эту функцию если Lovelace работает в режиме YAML." + }, "cards": { "confirm_delete": "Вы уверены, что хотите удалить эту карточку?", "empty_state": { @@ -1856,10 +1922,13 @@ "para_no_id": "Этот элемент не имеет ID. Добавьте ID для этого элемента в 'ui-lovelace.yaml'." }, "raw_editor": { + "confirm_remove_config_text": "Если Вы очистите конфигурацию Lovelace, вкладки с Вашими устройствами и помещениями будут создаваться автоматически.", + "confirm_remove_config_title": "Вы уверены, что хотите очистить конфигурацию Lovelace?", "confirm_unsaved_changes": "У вас есть несохраненные изменения. Вы уверены, что хотите выйти?", "confirm_unsaved_comments": "Ваша конфигурация содержит комментарии, они не будут сохранены. Вы хотите продолжить?", "error_invalid_config": "Ваша конфигурация недействительна: {error}", "error_parse_yaml": "Ошибка при разборе синтаксиса YAML: {error}", + "error_remove": "Не удалось удалить конфигурацию: {error}", "error_save_yaml": "Не удалось сохранить YAML: {error}", "header": "Редактирование конфигурации", "save": "Сохранить", @@ -1873,6 +1942,11 @@ "para_sure": "Вы уверены, что хотите самостоятельно контролировать пользовательский интерфейс?", "save": "Получить контроль" }, + "suggest_card": { + "add": "Добавить в Lovelace", + "create_own": "Создать свою", + "header": "Мы создали " + }, "view": { "panel_mode": { "description": "Первая карточка будет растянута на всю ширину. Другие карточки не будут отображаться на этой вкладке", From bf71b3a8692ae8eaea9fb3baef4425a02240ab3a Mon Sep 17 00:00:00 2001 From: Ian Richardson Date: Thu, 9 Jan 2020 03:22:23 -0600 Subject: [PATCH 002/126] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20convert=20ha-attri?= =?UTF-8?q?butes=20to=20lit-element=20(#4350)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :recycle: convert ha-attributes to lit-element * Address comments * inline items * :bug: Fix attribution display logic --- src/components/ha-attributes.js | 91 ------------------------------- src/components/ha-attributes.ts | 97 +++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 91 deletions(-) delete mode 100644 src/components/ha-attributes.js create mode 100644 src/components/ha-attributes.ts diff --git a/src/components/ha-attributes.js b/src/components/ha-attributes.js deleted file mode 100644 index 1a1836c2f2..0000000000 --- a/src/components/ha-attributes.js +++ /dev/null @@ -1,91 +0,0 @@ -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 hassAttributeUtil from "../util/hass-attributes-util"; - -class HaAttributes extends PolymerElement { - static get template() { - return html` - - - -
- -
- [[computeAttribution(stateObj)]] -
-
- `; - } - - static get properties() { - return { - stateObj: { - type: Object, - }, - extraFilters: { - type: String, - value: "", - }, - filtersArray: { - type: Array, - computed: "computeFiltersArray(extraFilters)", - }, - }; - } - - computeFiltersArray(extraFilters) { - return Object.keys(hassAttributeUtil.LOGIC_STATE_ATTRIBUTES).concat( - extraFilters ? extraFilters.split(",") : [] - ); - } - - computeDisplayAttributes(stateObj, filtersArray) { - if (!stateObj) { - return []; - } - return Object.keys(stateObj.attributes).filter(function(key) { - return filtersArray.indexOf(key) === -1; - }); - } - - formatAttribute(attribute) { - return attribute.replace(/_/g, " "); - } - - formatAttributeValue(stateObj, attribute) { - var value = stateObj.attributes[attribute]; - if (value === null) return "-"; - if (Array.isArray(value)) { - return value.join(", "); - } - return value instanceof Object ? JSON.stringify(value, null, 2) : value; - } - - computeAttribution(stateObj) { - return stateObj.attributes.attribution; - } -} - -customElements.define("ha-attributes", HaAttributes); diff --git a/src/components/ha-attributes.ts b/src/components/ha-attributes.ts new file mode 100644 index 0000000000..d2eab599fe --- /dev/null +++ b/src/components/ha-attributes.ts @@ -0,0 +1,97 @@ +import { + property, + LitElement, + TemplateResult, + html, + CSSResult, + css, + customElement, +} from "lit-element"; +import { HassEntity } from "home-assistant-js-websocket"; + +import hassAttributeUtil from "../util/hass-attributes-util"; + +@customElement("ha-attributes") +class HaAttributes extends LitElement { + @property() public stateObj?: HassEntity; + @property() public extraFilters?: string; + + protected render(): TemplateResult | void { + if (!this.stateObj) { + return html``; + } + + return html` +
+ ${this.computeDisplayAttributes( + Object.keys(hassAttributeUtil.LOGIC_STATE_ATTRIBUTES).concat( + this.extraFilters ? this.extraFilters.split(",") : [] + ) + ).map( + (attribute) => html` +
+
${attribute.replace(/_/g, " ")}
+
+ ${this.formatAttributeValue(attribute)} +
+
+ ` + )} + ${this.stateObj.attributes.attribution + ? html` +
+ ${this.stateObj.attributes.attribution} +
+ ` + : ""} +
+ `; + } + + static get styles(): CSSResult { + return css` + .data-entry { + display: flex; + flex-direction: row; + justify-content: space-between; + } + .data-entry .value { + max-width: 200px; + overflow-wrap: break-word; + } + .attribution { + color: var(--secondary-text-color); + text-align: right; + } + `; + } + + private computeDisplayAttributes(filtersArray: string[]): string[] { + if (!this.stateObj) { + return []; + } + return Object.keys(this.stateObj.attributes).filter((key) => { + return filtersArray.indexOf(key) === -1; + }); + } + + private formatAttributeValue(attribute: string): string { + if (!this.stateObj) { + return "-"; + } + const value = this.stateObj.attributes[attribute]; + if (value === null) { + return "-"; + } + if (Array.isArray(value)) { + return value.join(", "); + } + return value instanceof Object ? JSON.stringify(value, null, 2) : value; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-attributes": HaAttributes; + } +} From 80eb80619a6f40092fe121f42a7e8173b106e28c Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 9 Jan 2020 11:40:25 +0100 Subject: [PATCH 003/126] Add configuration for Lock Threads on closed pull requests (#4443) --- .github/lock.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/lock.yml diff --git a/.github/lock.yml b/.github/lock.yml new file mode 100644 index 0000000000..63db6533f2 --- /dev/null +++ b/.github/lock.yml @@ -0,0 +1,27 @@ +# Configuration for Lock Threads - https://github.com/dessant/lock-threads + +# Number of days of inactivity before a closed issue or pull request is locked +daysUntilLock: 1 + +# Skip issues and pull requests created before a given timestamp. Timestamp must +# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable +skipCreatedBefore: 2020-01-01 + +# Issues and pull requests with these labels will be ignored. Set to `[]` to disable +exemptLabels: [] + +# Label to add before locking, such as `outdated`. Set to `false` to disable +lockLabel: false + +# Comment to post before locking. Set to `false` to disable +lockComment: false + +# Assign `resolved` as the reason for locking. Set to `false` to disable +setLockReason: false + +# Limit to only `issues` or `pulls` +only: pulls + +# Optionally, specify configuration settings just for `issues` or `pulls` +issues: + daysUntilLock: 30 From 4e5406b27b0a14788e80859a96d2b54871954b39 Mon Sep 17 00:00:00 2001 From: Sean Mooney Date: Thu, 9 Jan 2020 10:29:42 -0500 Subject: [PATCH 004/126] Typo fix in issue template (#4445) fixes small typo, necesarry = necessary --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index a293b99f4a..e59509d257 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -59,7 +59,7 @@ Give the config of both the integration that is used, the Lovelace config, scene **Steps to reproduce this problem:** +## Checklist + +- [ ] I have updated to the latest available Home Assistant version. +- [ ] I have cleared the cache of my browser. +- [ ] I have tried a different browser to see if it is related to my browser. + +## The problem + + + +## Expected behavior + + + +## Steps to reproduce + + + +## Environment + + +- Home Assistant release with the issue: +- Last working Home Assistant release (if known): +- UI Type (States or Lovelace): +- Browser and browser version: +- Operating system: + +## Problem-relevant configuration + + +```yaml + +``` + +## Javascript errors shown in your browser console/inspector + + +```txt + +``` + +## Additional information + diff --git a/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md b/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md new file mode 100644 index 0000000000..634724fcbe --- /dev/null +++ b/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md @@ -0,0 +1,25 @@ +--- +name: Request a feature for the UI, Frontend or Lovelace +about: Request an new feature for the Home Assistant frontend. +labels: feature request +--- + +## The request + + + +## The alternatives + + + +## Additional information + diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index e59509d257..0000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,78 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: "" -labels: bug -assignees: "" ---- - - - -**Checklist:** - -- [ ] I updated to the latest version available -- [ ] I cleared the cache of my browser - -**Home Assistant release with the issue:** - - - -**Last working Home Assistant release (if known):** - -**UI (States or Lovelace UI?):** - - - -**Browser and Operating System:** - - - -**Description of problem:** - - - -**Expected behaviour:** - - - -**Relevant config:** - - - -**Steps to reproduce this problem:** - - - -**Javascript errors shown in the web inspector (if applicable):** - -``` - -``` - -**Additional information:** diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..7581c03f17 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,14 @@ +blank_issues_enabled: false +contact_links: + - name: Report a bug that is NOT related to the UI, Frontend or Lovelace + url: https://github.com/home-assistant/home-assistant/issues + about: This is the issue tracker for our frontend. Please report other issues with the backend repository. + - name: Report incorrect or missing information on our website + url: https://github.com/home-assistant/home-assistant.io/issues + about: Our documentation has its own issue tracker. Please report issues with the website there. + - name: I have a question or need support + url: https://www.home-assistant.io/help + about: We use GitHub for tracking bugs, check our website for resources on getting help. + - name: I'm unsure where to go + url: https://www.home-assistant.io/join-chat + about: If you are unsure where to go, then joining our chat is recommended; Just ask! diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 51d1465c16..0000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: "" -labels: feature request -assignees: "" ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..f73933940c --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,77 @@ + +## Breaking change + + + +## Proposed change + + + +## Type of change + + +- [ ] Dependency upgrade +- [ ] Bugfix (non-breaking change which fixes an issue) +- [ ] New feature (thank you!) +- [ ] Breaking change (fix/feature causing existing functionality to break) +- [ ] Code quality improvements to existing code or addition of tests + +## Example configuration + + +```yaml + +``` + +## Additional information + + +- This PR fixes or closes issue: fixes # +- This PR is related to issue: +- Link to documentation pull request: + +## Checklist + + +- [ ] The code change is tested and works locally. +- [ ] There is no commented out code in this PR. +- [ ] Tests have been added to verify that the new code works. + +If user exposed functionality or configuration variables are added/changed: + +- [ ] Documentation added/updated for [www.home-assistant.io][docs-repository] + + +[docs-repository]: https://github.com/home-assistant/home-assistant.io From 49611e285fc01db748a4ee6ad1c8c6ade65b428a Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 22 Jan 2020 20:29:51 +0100 Subject: [PATCH 074/126] Add zones config UI (#4556) * Add zones config UI * Update en.json * Update dialog-zone-detail.ts * Update hc-cast.ts * Update more-info-content.ts * Add drag radius and icon to dialog * Review comments --- package.json | 2 + src/common/dom/setup-leaflet-map.ts | 8 +- src/components/map/ha-location-editor.ts | 161 ++++++++-- src/components/map/ha-locations-editor.ts | 273 ++++++++++++++++ src/data/zone.ts | 46 +++ .../config/dashboard/ha-config-dashboard.ts | 3 +- src/panels/config/ha-config-router.ts | 7 + src/panels/config/ha-panel-config.ts | 3 +- .../ha-config-section-server-control.js | 22 ++ src/panels/config/zone/dialog-zone-detail.ts | 275 ++++++++++++++++ src/panels/config/zone/ha-config-zone.ts | 296 ++++++++++++++++++ .../config/zone/show-dialog-zone-detail.ts | 23 ++ src/translations/en.json | 28 +- yarn.lock | 19 ++ 14 files changed, 1142 insertions(+), 24 deletions(-) create mode 100644 src/components/map/ha-locations-editor.ts create mode 100644 src/data/zone.ts create mode 100644 src/panels/config/zone/dialog-zone-detail.ts create mode 100644 src/panels/config/zone/ha-config-zone.ts create mode 100644 src/panels/config/zone/show-dialog-zone-detail.ts diff --git a/package.json b/package.json index 255429537b..170891b349 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "intl-messageformat": "^2.2.0", "js-yaml": "^3.13.1", "leaflet": "^1.4.0", + "leaflet-draw": "^1.0.4", "lit-element": "^2.2.1", "lit-html": "^1.1.0", "lit-virtualizer": "^0.4.2", @@ -123,6 +124,7 @@ "@types/hls.js": "^0.12.3", "@types/js-yaml": "^3.12.1", "@types/leaflet": "^1.4.3", + "@types/leaflet-draw": "^1.0.1", "@types/memoize-one": "4.1.0", "@types/mocha": "^5.2.6", "@types/webspeechapi": "^0.0.29", diff --git a/src/common/dom/setup-leaflet-map.ts b/src/common/dom/setup-leaflet-map.ts index abf9008295..f29b5e257b 100644 --- a/src/common/dom/setup-leaflet-map.ts +++ b/src/common/dom/setup-leaflet-map.ts @@ -2,10 +2,12 @@ import { Map } from "leaflet"; // Sets up a Leaflet map on the provided DOM element export type LeafletModuleType = typeof import("leaflet"); +export type LeafletDrawModuleType = typeof import("leaflet-draw"); export const setupLeafletMap = async ( mapElement: HTMLElement, - darkMode = false + darkMode = false, + draw = false ): Promise<[Map, LeafletModuleType]> => { if (!mapElement.parentNode) { throw new Error("Cannot setup Leaflet map on disconnected element"); @@ -16,6 +18,10 @@ export const setupLeafletMap = async ( )) as LeafletModuleType; Leaflet.Icon.Default.imagePath = "/static/images/leaflet/images/"; + if (draw) { + await import(/* webpackChunkName: "leaflet-draw" */ "leaflet-draw"); + } + const map = Leaflet.map(mapElement); const style = document.createElement("link"); style.setAttribute("href", "/static/images/leaflet/leaflet.css"); diff --git a/src/components/map/ha-location-editor.ts b/src/components/map/ha-location-editor.ts index 0d4133b832..c386967e58 100644 --- a/src/components/map/ha-location-editor.ts +++ b/src/components/map/ha-location-editor.ts @@ -8,24 +8,35 @@ import { customElement, PropertyValues, } from "lit-element"; -import { Marker, Map, LeafletMouseEvent, DragEndEvent, LatLng } from "leaflet"; +import { + Marker, + Map, + LeafletMouseEvent, + DragEndEvent, + LatLng, + Circle, + DivIcon, +} from "leaflet"; import { setupLeafletMap, LeafletModuleType, } from "../../common/dom/setup-leaflet-map"; import { fireEvent } from "../../common/dom/fire_event"; +import { nextRender } from "../../common/util/render-status"; @customElement("ha-location-editor") class LocationEditor extends LitElement { @property() public location?: [number, number]; + @property() public radius?: number; + @property() public icon?: string; public fitZoom = 16; - + private _iconEl?: DivIcon; private _ignoreFitToMap?: [number, number]; // tslint:disable-next-line private Leaflet?: LeafletModuleType; private _leafletMap?: Map; - private _locationMarker?: Marker; + private _locationMarker?: Marker | Circle; public fitMap(): void { if (!this._leafletMap || !this.location) { @@ -53,11 +64,24 @@ class LocationEditor extends LitElement { return; } - this._updateMarker(); - if (!this._ignoreFitToMap || this._ignoreFitToMap !== this.location) { - this.fitMap(); + if (changedProps.has("location")) { + this._updateMarker(); + if ( + this.location && + (!this._ignoreFitToMap || + this._ignoreFitToMap[0] !== this.location[0] || + this._ignoreFitToMap[1] !== this.location[1]) + ) { + this.fitMap(); + } + this._ignoreFitToMap = undefined; + } + if (changedProps.has("radius")) { + this._updateRadius(); + } + if (changedProps.has("icon")) { + this._updateIcon(); } - this._ignoreFitToMap = undefined; } private get _mapEl(): HTMLDivElement { @@ -65,18 +89,23 @@ class LocationEditor extends LitElement { } private async _initMap(): Promise { - [this._leafletMap, this.Leaflet] = await setupLeafletMap(this._mapEl); + [this._leafletMap, this.Leaflet] = await setupLeafletMap( + this._mapEl, + false, + Boolean(this.radius) + ); this._leafletMap.addEventListener( "click", // @ts-ignore - (ev: LeafletMouseEvent) => this._updateLocation(ev.latlng) + (ev: LeafletMouseEvent) => this._locationUpdated(ev.latlng) ); + this._updateIcon(); this._updateMarker(); this.fitMap(); this._leafletMap.invalidateSize(); } - private _updateLocation(latlng: LatLng) { + private _locationUpdated(latlng: LatLng) { let longitude = latlng.lng; if (Math.abs(longitude) > 180.0) { // Normalize longitude if map provides values beyond -180 to +180 degrees. @@ -86,7 +115,68 @@ class LocationEditor extends LitElement { fireEvent(this, "change", undefined, { bubbles: false }); } - private _updateMarker(): void { + private _radiusUpdated() { + this._ignoreFitToMap = this.location; + this.radius = (this._locationMarker as Circle).getRadius(); + fireEvent(this, "change", undefined, { bubbles: false }); + } + + private _updateIcon() { + if (!this.icon) { + this._iconEl = undefined; + return; + } + + // create icon + let iconHTML = ""; + const el = document.createElement("ha-icon"); + el.setAttribute("icon", this.icon); + iconHTML = el.outerHTML; + + this._iconEl = this.Leaflet!.divIcon({ + html: iconHTML, + iconSize: [24, 24], + className: "light leaflet-edit-move", + }); + this._setIcon(); + } + + private _setIcon() { + if (!this._locationMarker || !this._iconEl) { + return; + } + + if (!this.radius) { + (this._locationMarker as Marker).setIcon(this._iconEl); + return; + } + + // @ts-ignore + const moveMarker = this._locationMarker.editing._moveMarker; + moveMarker.setIcon(this._iconEl); + } + + private _setupEdit() { + // @ts-ignore + this._locationMarker.editing.enable(); + // @ts-ignore + const moveMarker = this._locationMarker.editing._moveMarker; + // @ts-ignore + const resizeMarker = this._locationMarker.editing._resizeMarkers[0]; + this._setIcon(); + moveMarker.addEventListener( + "dragend", + // @ts-ignore + (ev: DragEndEvent) => this._locationUpdated(ev.target.getLatLng()) + ); + resizeMarker.addEventListener( + "dragend", + // @ts-ignore + (ev: DragEndEvent) => this._radiusUpdated(ev) + ); + } + + private async _updateMarker(): Promise { if (!this.location) { if (this._locationMarker) { this._locationMarker.remove(); @@ -97,17 +187,41 @@ class LocationEditor extends LitElement { if (this._locationMarker) { this._locationMarker.setLatLng(this.location); + if (this.radius) { + // @ts-ignore + this._locationMarker.editing.disable(); + await nextRender(); + this._setupEdit(); + } return; } - this._locationMarker = this.Leaflet!.marker(this.location, { - draggable: true, - }); - this._locationMarker.addEventListener( - "dragend", - // @ts-ignore - (ev: DragEndEvent) => this._updateLocation(ev.target.getLatLng()) - ); - this._leafletMap!.addLayer(this._locationMarker); + + if (!this.radius) { + this._locationMarker = this.Leaflet!.marker(this.location, { + draggable: true, + }); + this._setIcon(); + this._locationMarker.addEventListener( + "dragend", + // @ts-ignore + (ev: DragEndEvent) => this._locationUpdated(ev.target.getLatLng()) + ); + this._leafletMap!.addLayer(this._locationMarker); + } else { + this._locationMarker = this.Leaflet!.circle(this.location, { + color: "#FF9800", + radius: this.radius, + }); + this._leafletMap!.addLayer(this._locationMarker); + this._setupEdit(); + } + } + + private _updateRadius(): void { + if (!this._locationMarker || !this.radius) { + return; + } + (this._locationMarker as Circle).setRadius(this.radius); } static get styles(): CSSResult { @@ -119,6 +233,13 @@ class LocationEditor extends LitElement { #map { height: 100%; } + .leaflet-edit-move { + cursor: move !important; + } + .leaflet-edit-resize { + border-radius: 50%; + cursor: nesw-resize !important; + } `; } } diff --git a/src/components/map/ha-locations-editor.ts b/src/components/map/ha-locations-editor.ts new file mode 100644 index 0000000000..c8ef9325fe --- /dev/null +++ b/src/components/map/ha-locations-editor.ts @@ -0,0 +1,273 @@ +import { + LitElement, + property, + TemplateResult, + html, + CSSResult, + css, + customElement, + PropertyValues, +} from "lit-element"; +import { + Marker, + Map, + DragEndEvent, + LatLng, + Circle, + MarkerOptions, + DivIcon, +} from "leaflet"; +import { + setupLeafletMap, + LeafletModuleType, +} from "../../common/dom/setup-leaflet-map"; +import { fireEvent } from "../../common/dom/fire_event"; + +declare global { + // for fire event + interface HASSDomEvents { + "location-updated": { id: string; location: [number, number] }; + "radius-updated": { id: string; radius: number }; + "marker-clicked": { id: string }; + } +} + +export interface Location { + latitude: number; + longitude: number; + radius: number; + name: string; + id: string; + icon: string; +} + +@customElement("ha-locations-editor") +export class HaLocationsEditor extends LitElement { + @property() public locations?: Location[]; + public fitZoom = 16; + + // tslint:disable-next-line + private Leaflet?: LeafletModuleType; + // tslint:disable-next-line + private _leafletMap?: Map; + private _locationMarkers?: { [key: string]: Marker | Circle }; + + public fitMap(): void { + if ( + !this._leafletMap || + !this._locationMarkers || + !Object.keys(this._locationMarkers).length + ) { + return; + } + const bounds = this.Leaflet!.latLngBounds( + Object.values(this._locationMarkers).map((item) => item.getLatLng()) + ); + this._leafletMap.fitBounds(bounds.pad(0.5)); + } + + public fitMarker(id: string): void { + if (!this._leafletMap || !this._locationMarkers) { + return; + } + const marker = this._locationMarkers[id]; + if (!marker) { + return; + } + this._leafletMap.setView(marker.getLatLng(), this.fitZoom); + } + + protected render(): TemplateResult | void { + return html` +
+ `; + } + + protected firstUpdated(changedProps: PropertyValues): void { + super.firstUpdated(changedProps); + this._initMap(); + } + + protected updated(changedProps: PropertyValues): void { + super.updated(changedProps); + + // Still loading. + if (!this.Leaflet) { + return; + } + + if (changedProps.has("locations")) { + this._updateMarkers(); + } + } + + private get _mapEl(): HTMLDivElement { + return this.shadowRoot!.querySelector("div")!; + } + + private async _initMap(): Promise { + [this._leafletMap, this.Leaflet] = await setupLeafletMap( + this._mapEl, + false, + true + ); + this._updateMarkers(); + this.fitMap(); + this._leafletMap.invalidateSize(); + } + + private _updateLocation(ev: DragEndEvent) { + const marker = ev.target; + const latlng: LatLng = marker.getLatLng(); + let longitude: number = latlng.lng; + if (Math.abs(longitude) > 180.0) { + // Normalize longitude if map provides values beyond -180 to +180 degrees. + longitude = (((longitude % 360.0) + 540.0) % 360.0) - 180.0; + } + const location: [number, number] = [latlng.lat, longitude]; + fireEvent( + this, + "location-updated", + { id: marker.id, location }, + { bubbles: false } + ); + } + + private _updateRadius(ev: DragEndEvent) { + const marker = ev.target; + const circle = this._locationMarkers![marker.id] as Circle; + fireEvent( + this, + "radius-updated", + { id: marker.id, radius: circle.getRadius() }, + { bubbles: false } + ); + } + + private _markerClicked(ev: DragEndEvent) { + const marker = ev.target; + fireEvent(this, "marker-clicked", { id: marker.id }, { bubbles: false }); + } + + private _updateMarkers(): void { + if (this._locationMarkers) { + Object.values(this._locationMarkers).forEach((marker) => { + marker.remove(); + }); + this._locationMarkers = undefined; + } + + if (!this.locations || !this.locations.length) { + return; + } + + this._locationMarkers = {}; + + this.locations.forEach((location: Location) => { + let icon: DivIcon | undefined; + if (location.icon) { + // create icon + let iconHTML = ""; + const el = document.createElement("ha-icon"); + el.setAttribute("icon", location.icon); + iconHTML = el.outerHTML; + + icon = this.Leaflet!.divIcon({ + html: iconHTML, + iconSize: [24, 24], + className: "light leaflet-edit-move", + }); + } + if (location.radius) { + const circle = this.Leaflet!.circle( + [location.latitude, location.longitude], + { + color: "#FF9800", + radius: location.radius, + } + ); + // @ts-ignore + circle.editing.enable(); + circle.addTo(this._leafletMap!); + // @ts-ignore + const moveMarker = circle.editing._moveMarker; + // @ts-ignore + const resizeMarker = circle.editing._resizeMarkers[0]; + if (icon) { + moveMarker.setIcon(icon); + } + resizeMarker.id = moveMarker.id = location.id; + moveMarker + .addEventListener( + "dragend", + // @ts-ignore + (ev: DragEndEvent) => this._updateLocation(ev) + ) + .addEventListener( + "click", + // @ts-ignore + (ev: MouseEvent) => this._markerClicked(ev) + ); + resizeMarker.addEventListener( + "dragend", + // @ts-ignore + (ev: DragEndEvent) => this._updateRadius(ev) + ); + this._locationMarkers![location.id] = circle; + } else { + const options: MarkerOptions = { + draggable: true, + title: location.name, + }; + + if (icon) { + options.icon = icon; + } + + const marker = this.Leaflet!.marker( + [location.latitude, location.longitude], + options + ) + .addEventListener( + "dragend", + // @ts-ignore + (ev: DragEndEvent) => this._updateLocation(ev) + ) + .addEventListener( + "click", + // @ts-ignore + (ev: MouseEvent) => this._markerClicked(ev) + ) + .addTo(this._leafletMap); + marker.id = location.id; + + this._locationMarkers![location.id] = marker; + } + }); + } + + static get styles(): CSSResult { + return css` + :host { + display: block; + height: 300px; + } + #map { + height: 100%; + } + .leaflet-edit-move { + cursor: move !important; + } + .leaflet-edit-resize { + border-radius: 50%; + cursor: nesw-resize !important; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-locations-editor": HaLocationsEditor; + } +} diff --git a/src/data/zone.ts b/src/data/zone.ts new file mode 100644 index 0000000000..b76f73aab0 --- /dev/null +++ b/src/data/zone.ts @@ -0,0 +1,46 @@ +import { HomeAssistant } from "../types"; + +export interface Zone { + id: string; + name: string; + icon?: string; + latitude?: number; + longitude?: number; + passive?: boolean; + radius?: number; +} + +export interface ZoneMutableParams { + icon: string; + latitude: number; + longitude: number; + name: string; + passive: boolean; + radius: number; +} + +export const fetchZones = (hass: HomeAssistant) => + hass.callWS({ type: "zone/list" }); + +export const createZone = (hass: HomeAssistant, values: ZoneMutableParams) => + hass.callWS({ + type: "zone/create", + ...values, + }); + +export const updateZone = ( + hass: HomeAssistant, + zoneId: string, + updates: Partial +) => + hass.callWS({ + type: "zone/update", + zone_id: zoneId, + ...updates, + }); + +export const deleteZone = (hass: HomeAssistant, zoneId: string) => + hass.callWS({ + type: "zone/delete", + zone_id: zoneId, + }); diff --git a/src/panels/config/dashboard/ha-config-dashboard.ts b/src/panels/config/dashboard/ha-config-dashboard.ts index 893e237f4f..143c80bb51 100644 --- a/src/panels/config/dashboard/ha-config-dashboard.ts +++ b/src/panels/config/dashboard/ha-config-dashboard.ts @@ -94,6 +94,7 @@ class HaConfigDashboard extends LitElement { .pages=${[ { page: "integrations", core: true }, { page: "devices", core: true }, + { page: "entities", core: true }, { page: "automation" }, { page: "script" }, { page: "scene" }, @@ -107,8 +108,8 @@ class HaConfigDashboard extends LitElement { .pages=${[ { page: "core", core: true }, { page: "server_control", core: true }, - { page: "entities", core: true }, { page: "areas", core: true }, + { page: "zone" }, { page: "person" }, { page: "users", core: true }, { page: "zha" }, diff --git a/src/panels/config/ha-config-router.ts b/src/panels/config/ha-config-router.ts index 81f5ab3f53..dd883f0d0a 100644 --- a/src/panels/config/ha-config-router.ts +++ b/src/panels/config/ha-config-router.ts @@ -125,6 +125,13 @@ class HaConfigRouter extends HassRouterPage { /* webpackChunkName: "panel-config-users" */ "./users/ha-config-users" ), }, + zone: { + tag: "ha-config-zone", + load: () => + import( + /* webpackChunkName: "panel-config-zone" */ "./zone/ha-config-zone" + ), + }, zha: { tag: "zha-config-dashboard-router", load: () => diff --git a/src/panels/config/ha-panel-config.ts b/src/panels/config/ha-panel-config.ts index 092287e9a9..b864929901 100644 --- a/src/panels/config/ha-panel-config.ts +++ b/src/panels/config/ha-panel-config.ts @@ -115,6 +115,7 @@ class HaPanelConfig extends LitElement { { page: "scene" }, { page: "core", core: true }, { page: "areas", core: true }, + { page: "zone" }, { page: "person" }, { page: "users", core: true }, { page: "server_control", core: true }, @@ -163,7 +164,7 @@ class HaPanelConfig extends LitElement { } .side-bar { - border-right: 1px solid #e0e0e0; + border-right: 1px solid var(--divider-color); background: white; width: 320px; float: left; diff --git a/src/panels/config/server_control/ha-config-section-server-control.js b/src/panels/config/server_control/ha-config-section-server-control.js index eac4ace9b5..3e44176e9e 100644 --- a/src/panels/config/server_control/ha-config-section-server-control.js +++ b/src/panels/config/server_control/ha-config-section-server-control.js @@ -148,6 +148,24 @@ class HaConfigSectionServerControl extends LocalizeMixin(PolymerElement) { + +
+ [[localize('ui.panel.config.server_control.section.reloading.zone')]] + +
{ + this._params = params; + this._error = undefined; + if (this._params.entry) { + this._name = this._params.entry.name || ""; + this._icon = this._params.entry.icon || ""; + this._latitude = this._params.entry.latitude || this.hass.config.latitude; + this._longitude = + this._params.entry.longitude || this.hass.config.longitude; + this._passive = this._params.entry.passive || false; + this._radius = this._params.entry.radius || 100; + } else { + this._name = ""; + this._icon = ""; + this._latitude = this.hass.config.latitude; + this._longitude = this.hass.config.longitude; + this._passive = false; + this._radius = 100; + } + await this.updateComplete; + } + + protected render(): TemplateResult | void { + if (!this._params) { + return html``; + } + return html` + +
+ ${this._error + ? html` +
${this._error}
+ ` + : ""} +
+ + + + + + +

+ ${this.hass!.localize("ui.panel.config.zone.detail.passive_note")} +

+ ${this.hass!.localize( + "ui.panel.config.zone.detail.passive" + )} +
+
+ ${this._params.entry + ? html` + + ${this.hass!.localize("ui.panel.config.zone.detail.delete")} + + ` + : html``} + + ${this._params.entry + ? this.hass!.localize("ui.panel.config.zone.detail.update") + : this.hass!.localize("ui.panel.config.zone.detail.create")} + +
+ `; + } + + private get _locationValue() { + return [Number(this._latitude), Number(this._longitude)]; + } + + private _locationChanged(ev) { + [this._latitude, this._longitude] = ev.currentTarget.location; + this._radius = ev.currentTarget.radius; + } + + private _passiveChanged(ev) { + this._passive = ev.target.checked; + } + + private _valueChanged(ev: CustomEvent) { + const configValue = (ev.target as any).configValue; + + this._error = undefined; + this[`_${configValue}`] = ev.detail.value; + } + + private async _updateEntry() { + this._submitting = true; + try { + const values: ZoneMutableParams = { + name: this._name.trim(), + icon: this._icon.trim(), + latitude: this._latitude, + longitude: this._longitude, + passive: this._passive, + radius: this._radius, + }; + if (this._params!.entry) { + await this._params!.updateEntry!(values); + } else { + await this._params!.createEntry(values); + } + this._params = undefined; + } catch (err) { + this._error = err ? err.message : "Unknown error"; + } finally { + this._submitting = false; + } + } + + private async _deleteEntry() { + this._submitting = true; + try { + if (await this._params!.removeEntry!()) { + this._params = undefined; + } + } finally { + this._submitting = false; + } + } + + private _close(): void { + this._params = undefined; + } + + static get styles(): CSSResult[] { + return [ + css` + mwc-dialog { + --mdc-dialog-title-ink-color: var(--primary-text-color); + } + @media only screen and (min-width: 600px) { + mwc-dialog { + --mdc-dialog-min-width: 600px; + } + } + .form { + padding-bottom: 24px; + } + ha-user-picker { + margin-top: 16px; + } + mwc-button.warning { + --mdc-theme-primary: var(--google-red-500); + } + .error { + color: var(--google-red-500); + } + a { + color: var(--primary-color); + } + p { + color: var(--primary-text-color); + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "dialog-zone-detail": DialogZoneDetail; + } +} + +customElements.define("dialog-zone-detail", DialogZoneDetail); diff --git a/src/panels/config/zone/ha-config-zone.ts b/src/panels/config/zone/ha-config-zone.ts new file mode 100644 index 0000000000..1ab286f0c0 --- /dev/null +++ b/src/panels/config/zone/ha-config-zone.ts @@ -0,0 +1,296 @@ +import { + LitElement, + TemplateResult, + html, + css, + CSSResult, + property, + customElement, + query, +} from "lit-element"; +import "@polymer/paper-listbox/paper-listbox"; +import "@polymer/paper-item/paper-icon-item"; +import "@polymer/paper-item/paper-item-body"; + +import "../../../components/map/ha-locations-editor"; + +import { HomeAssistant } from "../../../types"; +import "../../../components/ha-card"; +import "../../../components/ha-fab"; +import "../../../layouts/hass-subpage"; +import "../../../layouts/hass-loading-screen"; +import { compare } from "../../../common/string/compare"; +import "../ha-config-section"; +import { showZoneDetailDialog } from "./show-dialog-zone-detail"; +import { + Zone, + fetchZones, + createZone, + updateZone, + deleteZone, + ZoneMutableParams, +} from "../../../data/zone"; +// tslint:disable-next-line +import { HaLocationsEditor } from "../../../components/map/ha-locations-editor"; + +@customElement("ha-config-zone") +export class HaConfigZone extends LitElement { + @property() public hass?: HomeAssistant; + @property() public isWide?: boolean; + @property() public narrow?: boolean; + @property() private _storageItems?: Zone[]; + @property() private _activeEntry: string = ""; + @query("ha-locations-editor") private _map?: HaLocationsEditor; + + protected render(): TemplateResult | void { + if (!this.hass || this._storageItems === undefined) { + return html` + + `; + } + const hass = this.hass; + const listBox = html` + + ${this._storageItems.map((entry) => { + return html` + + + + + ${entry.name} + + ${ + !this.narrow + ? html` + + ` + : "" + } + + + `; + })} + + ${this._storageItems.length === 0 + ? html` +
+ ${hass.localize("ui.panel.config.zone.no_zones_created_yet")} + + ${hass.localize("ui.panel.config.zone.create_zone")} +
+ ` + : html``} + `; + + return html` + + ${this.narrow + ? html` + + + ${hass.localize("ui.panel.config.zone.introduction")} + + ${listBox} + + ` + : ""} + ${!this.narrow + ? html` +
+ + ${listBox} +
+ ` + : ""} +
+ + + `; + } + + protected firstUpdated(changedProps) { + super.firstUpdated(changedProps); + this._fetchData(); + } + + private async _fetchData() { + this._storageItems = (await fetchZones(this.hass!)).sort((ent1, ent2) => + compare(ent1.name, ent2.name) + ); + } + + private _locationUpdated(ev: CustomEvent) { + this._activeEntry = ev.detail.id; + const entry = this._storageItems!.find((item) => item.id === ev.detail.id); + if (!entry) { + return; + } + this._updateEntry(entry, { + latitude: ev.detail.location[0], + longitude: ev.detail.location[1], + }); + } + + private _radiusUpdated(ev: CustomEvent) { + this._activeEntry = ev.detail.id; + const entry = this._storageItems!.find((item) => item.id === ev.detail.id); + if (!entry) { + return; + } + this._updateEntry(entry, { + radius: ev.detail.radius, + }); + } + + private _markerClicked(ev: CustomEvent) { + this._activeEntry = ev.detail.id; + } + + private _createZone() { + this._openDialog(); + } + + private _itemClicked(ev: MouseEvent) { + if (this.narrow) { + this._openEditEntry(ev); + return; + } + + const entry: Zone = (ev.currentTarget! as any).entry; + this._map?.fitMarker(entry.id); + } + + private _openEditEntry(ev: MouseEvent) { + const entry: Zone = (ev.currentTarget! as any).entry; + this._openDialog(entry); + } + + private async _createEntry(values: ZoneMutableParams) { + const created = await createZone(this.hass!, values); + this._storageItems = this._storageItems!.concat( + created + ).sort((ent1, ent2) => compare(ent1.name, ent2.name)); + } + + private async _updateEntry(entry: Zone, values: Partial) { + const updated = await updateZone(this.hass!, entry!.id, values); + this._storageItems = this._storageItems!.map((ent) => + ent === entry ? updated : ent + ); + } + + private async _removeEntry(entry: Zone) { + if ( + !confirm(`${this.hass!.localize("ui.panel.config.zone.confirm_delete")} + +${this.hass!.localize("ui.panel.config.zone.confirm_delete2")}`) + ) { + return false; + } + + try { + await deleteZone(this.hass!, entry!.id); + this._storageItems = this._storageItems!.filter((ent) => ent !== entry); + return true; + } catch (err) { + return false; + } + } + + private async _openDialog(entry?: Zone) { + showZoneDetailDialog(this, { + entry, + createEntry: (values) => this._createEntry(values), + updateEntry: entry + ? (values) => this._updateEntry(entry, values) + : undefined, + removeEntry: entry ? () => this._removeEntry(entry) : undefined, + }); + } + + static get styles(): CSSResult { + return css` + a { + color: var(--primary-color); + } + ha-card { + max-width: 600px; + margin: 16px auto; + overflow: hidden; + } + .empty { + text-align: center; + padding: 8px; + } + .flex { + display: flex; + height: 100%; + } + ha-locations-editor { + flex-grow: 1; + height: 100%; + } + .flex paper-listbox { + border-left: 1px solid var(--divider-color); + width: 250px; + } + paper-icon-item { + padding-top: 4px; + padding-bottom: 4px; + } + paper-icon-item.iron-selected:before { + border-radius: 4px; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + pointer-events: none; + content: ""; + background-color: var(--sidebar-selected-icon-color); + opacity: 0.12; + transition: opacity 15ms linear; + will-change: opacity; + } + ha-card paper-item { + cursor: pointer; + } + ha-fab { + position: fixed; + bottom: 16px; + right: 16px; + z-index: 1; + } + ha-fab[is-wide] { + bottom: 24px; + right: 24px; + } + `; + } +} diff --git a/src/panels/config/zone/show-dialog-zone-detail.ts b/src/panels/config/zone/show-dialog-zone-detail.ts new file mode 100644 index 0000000000..58a9c9261a --- /dev/null +++ b/src/panels/config/zone/show-dialog-zone-detail.ts @@ -0,0 +1,23 @@ +import { fireEvent } from "../../../common/dom/fire_event"; +import { Zone, ZoneMutableParams } from "../../../data/zone"; + +export interface ZoneDetailDialogParams { + entry?: Zone; + createEntry: (values: ZoneMutableParams) => Promise; + updateEntry?: (updates: Partial) => Promise; + removeEntry?: () => Promise; +} + +export const loadZoneDetailDialog = () => + import(/* webpackChunkName: "zone-detail-dialog" */ "./dialog-zone-detail"); + +export const showZoneDetailDialog = ( + element: HTMLElement, + systemLogDetailParams: ZoneDetailDialogParams +): void => { + fireEvent(element, "show-dialog", { + dialogTag: "dialog-zone-detail", + dialogImport: loadZoneDetailDialog, + dialogParams: systemLogDetailParams, + }); +}; diff --git a/src/translations/en.json b/src/translations/en.json index 1c5e3f76a8..490639a65b 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -739,7 +739,9 @@ "group": "Reload groups", "automation": "Reload automations", "script": "Reload scripts", - "scene": "Reload scenes" + "scene": "Reload scenes", + "person": "Reload persons", + "zone": "Reload zones" }, "server_management": { "heading": "Server management", @@ -1335,6 +1337,30 @@ "update": "Update" } }, + "zone": { + "caption": "Zones", + "description": "Manage the zones you want to track persons in.", + "introduction": "Zones allow you to specify certain regions on earth. When a person is within a zone, the state will take the name from the zone. Zones can also be used as a trigger or condition inside automation setups.", + "no_zones_created_yet": "Looks like you have not created any zones yet.", + "create_zone": "Create Zone", + "add_zone": "Add Zone", + "confirm_delete": "Are you sure you want to delete this zone?", + "detail": { + "new_zone": "New Zone", + "name": "Name", + "icon": "Icon", + "icon_error_msg": "Icon should be in the format prefix:iconname, for example: mdi:home", + "radius": "Radius", + "latitude": "Latitude", + "longitude": "Longitude", + "passive": "Passive", + "passive_note": "Passive zones are hidden in the frontend and are not used as location for device trackers. This is usefull if you just want to use it for automations.", + "required_error_msg": "This field is required", + "delete": "Delete", + "create": "Create", + "update": "Update" + } + }, "integrations": { "caption": "Integrations", "description": "Manage and setup integrations", diff --git a/yarn.lock b/yarn.lock index a9acee3454..ade5077687 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2473,6 +2473,20 @@ resolved "https://registry.yarnpkg.com/@types/launchpad/-/launchpad-0.6.0.tgz#37296109b7f277f6e6c5fd7e0c0706bc918fbb51" integrity sha1-NylhCbfyd/bmxf1+DAcGvJGPu1E= +"@types/leaflet-draw@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/leaflet-draw/-/leaflet-draw-1.0.1.tgz#66e0c2c8b93b23487f836a8d65a769b98aa0bc5b" + integrity sha512-/urwtXkpvv7rtre5A6plvXHSUDmFvDrwqpQRKseBCC2bIhIhBtMDf+plqQmi0vhvSk0Pqgk8qH1rtC8EVxPdmg== + dependencies: + "@types/leaflet" "*" + +"@types/leaflet@*": + version "1.5.8" + resolved "https://registry.yarnpkg.com/@types/leaflet/-/leaflet-1.5.8.tgz#1c550803672fc5866b8b2c38512009f2b5d4205d" + integrity sha512-qpi5n4LmwenUFZ+VZ7ytRgHK+ZAclIvloL2zoKCmmj244WD2hBcLbUZ6Szvajfe3sIkSYEJ8WZ1p9VYl8tRsMA== + dependencies: + "@types/geojson" "*" + "@types/leaflet@^1.4.3": version "1.4.3" resolved "https://registry.yarnpkg.com/@types/leaflet/-/leaflet-1.4.3.tgz#62638cb73770eeaed40222042afbcc7b495f0cc4" @@ -8649,6 +8663,11 @@ lead@^1.0.0: dependencies: flush-write-stream "^1.0.2" +leaflet-draw@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/leaflet-draw/-/leaflet-draw-1.0.4.tgz#45be92f378ed253e7202fdeda1fcc71885198d46" + integrity sha512-rsQ6saQO5ST5Aj6XRFylr5zvarWgzWnrg46zQ1MEOEIHsppdC/8hnN8qMoFvACsPvTioAuysya/TVtog15tyAQ== + leaflet@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.4.0.tgz#d5f56eeb2aa32787c24011e8be4c77e362ae171b" From 8a9e149d3336deca5621bcffba8ce7633b1a2737 Mon Sep 17 00:00:00 2001 From: Ian Richardson Date: Wed, 22 Jan 2020 15:01:02 -0600 Subject: [PATCH 075/126] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20convert=20more-inf?= =?UTF-8?q?o-script=20to=20LitElement/TypeScript=20(#4545)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ♻️ convert more-info-script to Lit/TypeScript * ♻️ convert more-info-script to LitElement/TypeScript * 👌 use relative time --- .../more-info/controls/more-info-script.js | 31 ------------- .../more-info/controls/more-info-script.ts | 46 +++++++++++++++++++ src/translations/en.json | 3 +- 3 files changed, 48 insertions(+), 32 deletions(-) delete mode 100644 src/dialogs/more-info/controls/more-info-script.js create mode 100644 src/dialogs/more-info/controls/more-info-script.ts diff --git a/src/dialogs/more-info/controls/more-info-script.js b/src/dialogs/more-info/controls/more-info-script.js deleted file mode 100644 index fec483e033..0000000000 --- a/src/dialogs/more-info/controls/more-info-script.js +++ /dev/null @@ -1,31 +0,0 @@ -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 LocalizeMixin(PolymerElement) { - static get template() { - return html` - - -
-
-
- [[localize('ui.dialogs.more_info_control.script.last_action')]] -
-
[[stateObj.attributes.last_action]]
-
-
- `; - } - - static get properties() { - return { - stateObj: { - type: Object, - }, - }; - } -} - -customElements.define("more-info-script", MoreInfoScript); diff --git a/src/dialogs/more-info/controls/more-info-script.ts b/src/dialogs/more-info/controls/more-info-script.ts new file mode 100644 index 0000000000..512cac1c43 --- /dev/null +++ b/src/dialogs/more-info/controls/more-info-script.ts @@ -0,0 +1,46 @@ +import { + LitElement, + html, + TemplateResult, + property, + customElement, +} from "lit-element"; +import { HassEntity } from "home-assistant-js-websocket"; + +import { HomeAssistant } from "../../../types"; + +import "../../../components/ha-relative-time"; + +@customElement("more-info-script") +class MoreInfoScript extends LitElement { + @property() public hass!: HomeAssistant; + @property() public stateObj?: HassEntity; + + protected render(): TemplateResult | void { + if (!this.hass || !this.stateObj) { + return html``; + } + + return html` +
+ ${this.hass.localize( + "ui.dialogs.more_info_control.script.last_triggered" + )}: + ${this.stateObj.attributes.last_triggered + ? html` + + ` + : this.hass.localize("ui.components.relative_time.never")} +
+ `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "more-info-script": MoreInfoScript; + } +} diff --git a/src/translations/en.json b/src/translations/en.json index 490639a65b..f4eb9acaf9 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -574,7 +574,8 @@ "settings": "Entity settings", "edit": "Edit entity", "script": { - "last_action": "Last Action" + "last_action": "Last Action", + "last_triggered": "Last Triggered" }, "sun": { "elevation": "Elevation", From a544295167de0dec5174274e946a40d39b623167 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Wed, 22 Jan 2020 23:42:32 +0100 Subject: [PATCH 076/126] Add netlify pipeline (#4563) * Add netlify pipeline * Address comments --- azure-pipelines-netlify.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 azure-pipelines-netlify.yml diff --git a/azure-pipelines-netlify.yml b/azure-pipelines-netlify.yml new file mode 100644 index 0000000000..83cf89dcf9 --- /dev/null +++ b/azure-pipelines-netlify.yml @@ -0,0 +1,27 @@ +# https://dev.azure.com/home-assistant + +trigger: none +pr: none +schedules: + - cron: "0 0 * * *" + displayName: "build preview" + branches: + include: + - dev + always: false +variables: + - group: netlify + +jobs: + +- job: 'Netlify_preview' + pool: + vmImage: 'ubuntu-latest' + steps: + - script: | + # Cast + curl -X POST -d {} https://api.netlify.com/build_hooks/${NETLIFY_CAST} + + # Demo + curl -X POST -d {} https://api.netlify.com/build_hooks/${NETLIFY_DEMO} + displayName: 'Trigger netlify build preview' From ae8a9940ed4d39a73537cc21e6429b73e602e4f6 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 23 Jan 2020 00:03:47 +0100 Subject: [PATCH 077/126] Add state and related to entity reg dialog (#4473) * Add state and related to entity reg dialog * Replace more-info-settings, remove state tab add state button --- src/components/ha-related-items.ts | 323 ++++++++++++++++ src/data/search.ts | 33 ++ src/dialogs/ha-more-info-dialog.js | 42 +- src/dialogs/make-dialog-manager.ts | 1 + src/dialogs/more-info/more-info-controls.js | 8 +- src/dialogs/more-info/more-info-settings.js | 141 ------- .../entities/dialog-entity-registry-detail.ts | 366 +++++++++--------- .../entities/entity-registry-settings.ts | 237 ++++++++++++ .../config/entities/ha-config-entities.ts | 35 +- src/translations/en.json | 25 +- 10 files changed, 824 insertions(+), 387 deletions(-) create mode 100644 src/components/ha-related-items.ts create mode 100644 src/data/search.ts delete mode 100644 src/dialogs/more-info/more-info-settings.js create mode 100644 src/panels/config/entities/entity-registry-settings.ts diff --git a/src/components/ha-related-items.ts b/src/components/ha-related-items.ts new file mode 100644 index 0000000000..0039e16575 --- /dev/null +++ b/src/components/ha-related-items.ts @@ -0,0 +1,323 @@ +import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket"; +import { + customElement, + html, + LitElement, + property, + PropertyValues, + TemplateResult, + CSSResult, + css, +} from "lit-element"; +import { fireEvent } from "../common/dom/fire_event"; +import { + AreaRegistryEntry, + subscribeAreaRegistry, +} from "../data/area_registry"; +import { ConfigEntry, getConfigEntries } from "../data/config_entries"; +import { + DeviceRegistryEntry, + subscribeDeviceRegistry, +} from "../data/device_registry"; +import { SceneEntity } from "../data/scene"; +import { findRelated, ItemType, RelatedResult } from "../data/search"; +import { SubscribeMixin } from "../mixins/subscribe-mixin"; +import { HomeAssistant } from "../types"; +import "./ha-switch"; + +@customElement("ha-related-items") +export class HaRelatedItems extends SubscribeMixin(LitElement) { + @property() public hass!: HomeAssistant; + @property() public itemType!: ItemType; + @property() public itemId!: string; + @property() private _entries?: ConfigEntry[]; + @property() private _devices?: DeviceRegistryEntry[]; + @property() private _areas?: AreaRegistryEntry[]; + @property() private _related?: RelatedResult; + + public hassSubscribe(): UnsubscribeFunc[] { + return [ + subscribeDeviceRegistry(this.hass.connection!, (devices) => { + this._devices = devices; + }), + subscribeAreaRegistry(this.hass.connection!, (areas) => { + this._areas = areas; + }), + ]; + } + + protected firstUpdated(changedProps: PropertyValues) { + super.firstUpdated(changedProps); + getConfigEntries(this.hass).then((configEntries) => { + this._entries = configEntries; + }); + } + + protected updated(changedProps: PropertyValues) { + super.updated(changedProps); + if ( + (changedProps.has("itemId") || changedProps.has("itemType")) && + this.itemId && + this.itemType + ) { + this._findRelated(); + } + } + + protected render(): TemplateResult | void { + if (!this._related) { + return html``; + } + return html` + ${this._related.config_entry && this._entries + ? this._related.config_entry.map((relatedConfigEntryId) => { + const entry: ConfigEntry | undefined = this._entries!.find( + (configEntry) => configEntry.entry_id === relatedConfigEntryId + ); + if (!entry) { + return; + } + return html` +

+ ${this.hass.localize( + "ui.components.related-items.integration" + )}: +

+ + ${this.hass.localize(`component.${entry.domain}.config.title`)}: + ${entry.title} + + `; + }) + : ""} + ${this._related.device && this._devices + ? this._related.device.map((relatedDeviceId) => { + const device: DeviceRegistryEntry | undefined = this._devices!.find( + (dev) => dev.id === relatedDeviceId + ); + if (!device) { + return; + } + return html` +

+ ${this.hass.localize("ui.components.related-items.device")}: +

+ + ${device.name_by_user || device.name} + + `; + }) + : ""} + ${this._related.area && this._areas + ? this._related.area.map((relatedAreaId) => { + const area: AreaRegistryEntry | undefined = this._areas!.find( + (ar) => ar.area_id === relatedAreaId + ); + if (!area) { + return; + } + return html` +

+ ${this.hass.localize("ui.components.related-items.area")}: +

+ ${area.name} + `; + }) + : ""} + ${this._related.entity + ? html` +

+ ${this.hass.localize("ui.components.related-items.entity")}: +

+
    + ${this._related.entity.map((entityId) => { + const entity: HassEntity | undefined = this.hass.states[ + entityId + ]; + if (!entity) { + return; + } + return html` +
  • + +
  • + `; + })} +
+ ` + : ""} + ${this._related.group + ? html` +

${this.hass.localize("ui.components.related-items.group")}:

+
    + ${this._related.group.map((groupId) => { + const group: HassEntity | undefined = this.hass.states[groupId]; + if (!group) { + return; + } + return html` +
  • + +
  • + `; + })} +
+ ` + : ""} + ${this._related.scene + ? html` +

${this.hass.localize("ui.components.related-items.scene")}:

+
    + ${this._related.scene.map((sceneId) => { + const scene: SceneEntity | undefined = this.hass.states[ + sceneId + ]; + if (!scene) { + return; + } + return html` +
  • + +
  • + `; + })} +
+ ` + : ""} + ${this._related.automation + ? html` +

+ ${this.hass.localize("ui.components.related-items.automation")}: +

+
    + ${this._related.automation.map((automationId) => { + const automation: HassEntity | undefined = this.hass.states[ + automationId + ]; + if (!automation) { + return; + } + return html` +
  • + +
  • + `; + })} +
+ ` + : ""} + ${this._related.script + ? html` +

+ ${this.hass.localize("ui.components.related-items.script")}: +

+
    + ${this._related.script.map((scriptId) => { + const script: HassEntity | undefined = this.hass.states[ + scriptId + ]; + if (!script) { + return; + } + return html` +
  • + +
  • + `; + })} +
+ ` + : ""} + `; + } + + private async _findRelated() { + this._related = await findRelated(this.hass, this.itemType, this.itemId); + await this.updateComplete; + fireEvent(this, "iron-resize"); + } + + private _openMoreInfo(ev: CustomEvent) { + const entityId = (ev.target as any).entityId; + fireEvent(this, "hass-more-info", { entityId }); + } + + private _close() { + fireEvent(this, "close-dialog"); + } + + static get styles(): CSSResult { + return css` + a { + color: var(--primary-color); + } + button.link { + color: var(--primary-color); + text-align: left; + cursor: pointer; + background: none; + border-width: initial; + border-style: none; + border-color: initial; + border-image: initial; + padding: 0px; + font: inherit; + text-decoration: underline; + } + h3 { + font-family: var(--paper-font-title_-_font-family); + -webkit-font-smoothing: var( + --paper-font-title_-_-webkit-font-smoothing + ); + font-size: var(--paper-font-title_-_font-size); + font-weight: var(--paper-font-headline-_font-weight); + letter-spacing: var(--paper-font-title_-_letter-spacing); + line-height: var(--paper-font-title_-_line-height); + opacity: var(--dark-primary-opacity); + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-related-items": HaRelatedItems; + } +} diff --git a/src/data/search.ts b/src/data/search.ts new file mode 100644 index 0000000000..1e00372c9d --- /dev/null +++ b/src/data/search.ts @@ -0,0 +1,33 @@ +import { HomeAssistant } from "../types"; + +export interface RelatedResult { + area?: string[]; + automation?: string[]; + config_entry?: string[]; + device?: string[]; + entity?: string[]; + group?: string[]; + scene?: string[]; + script?: string[]; +} + +export type ItemType = + | "area" + | "automation" + | "config_entry" + | "device" + | "entity" + | "group" + | "scene" + | "script"; + +export const findRelated = ( + hass: HomeAssistant, + itemType: ItemType, + itemId: string +): Promise => + hass.callWS({ + type: "search/related", + item_type: itemType, + item_id: itemId, + }); diff --git a/src/dialogs/ha-more-info-dialog.js b/src/dialogs/ha-more-info-dialog.js index 1d95816055..3a34c1cfc9 100644 --- a/src/dialogs/ha-more-info-dialog.js +++ b/src/dialogs/ha-more-info-dialog.js @@ -6,7 +6,6 @@ import { PolymerElement } from "@polymer/polymer/polymer-element"; import "../resources/ha-style"; import "./more-info/more-info-controls"; -import "./more-info/more-info-settings"; import { computeStateDomain } from "../common/entity/compute_state_domain"; import { isComponentLoaded } from "../common/config/is_component_loaded"; @@ -26,8 +25,7 @@ class HaMoreInfoDialog extends DialogMixin(PolymerElement) { border-radius: 2px; } - more-info-controls, - more-info-settings { + more-info-controls { --more-info-header-background: var(--secondary-background-color); --more-info-header-color: var(--primary-text-color); --ha-more-info-app-toolbar-title: { @@ -46,8 +44,7 @@ class HaMoreInfoDialog extends DialogMixin(PolymerElement) { /* overrule the ha-style-dialog max-height on small screens */ @media all and (max-width: 450px), all and (max-height: 500px) { - more-info-controls, - more-info-settings { + more-info-controls { --more-info-header-background: var(--primary-color); --more-info-header-color: var(--text-primary-color); } @@ -79,24 +76,14 @@ class HaMoreInfoDialog extends DialogMixin(PolymerElement) { } - - + `; } @@ -118,11 +105,6 @@ class HaMoreInfoDialog extends DialogMixin(PolymerElement) { _dialogElement: Object, _registryInfo: Object, - _page: { - type: String, - value: null, - }, - dataDomain: { computed: "_computeDomain(stateObj)", reflectToAttribute: true, @@ -137,9 +119,6 @@ class HaMoreInfoDialog extends DialogMixin(PolymerElement) { ready() { super.ready(); this._dialogElement = this; - this.addEventListener("more-info-page", (ev) => { - this._page = ev.detail.page; - }); } _computeDomain(stateObj) { @@ -154,7 +133,6 @@ class HaMoreInfoDialog extends DialogMixin(PolymerElement) { if (!newVal) { this.setProperties({ opened: false, - _page: null, _registryInfo: null, large: false, }); diff --git a/src/dialogs/make-dialog-manager.ts b/src/dialogs/make-dialog-manager.ts index 41e2cc5cba..02af7d4e96 100644 --- a/src/dialogs/make-dialog-manager.ts +++ b/src/dialogs/make-dialog-manager.ts @@ -5,6 +5,7 @@ declare global { // for fire event interface HASSDomEvents { "show-dialog": ShowDialogParams; + "close-dialog": undefined; } // for add event listener interface HTMLElementEventMap { diff --git a/src/dialogs/more-info/more-info-controls.js b/src/dialogs/more-info/more-info-controls.js index d0ac64aad9..65c24a5846 100644 --- a/src/dialogs/more-info/more-info-controls.js +++ b/src/dialogs/more-info/more-info-controls.js @@ -22,6 +22,7 @@ import LocalizeMixin from "../../mixins/localize-mixin"; import { computeRTL } from "../../common/util/compute_rtl"; import { removeEntityRegistryEntry } from "../../data/entity_registry"; import { showConfirmationDialog } from "../confirmation/show-dialog-confirmation"; +import { showEntityRegistryDetailDialog } from "../../panels/config/entities/show-dialog-entity-registry-detail"; const DOMAINS_NO_INFO = ["camera", "configurator", "history_graph"]; const EDITABLE_DOMAINS_WITH_ID = ["scene", "automation"]; @@ -87,7 +88,7 @@ class MoreInfoControls extends LocalizeMixin(EventsMixin(PolymerElement)) {
[[_computeStateName(stateObj)]]
- @@ -52,6 +56,7 @@ class HaConfigAutomation extends PolymerElement { hass: Object, route: Object, isWide: Boolean, + narrow: Boolean, _routeData: Object, _routeMatches: Boolean, _creatingNew: Boolean, diff --git a/src/panels/config/cloud/account/cloud-account.js b/src/panels/config/cloud/account/cloud-account.js index 2bc95e8f32..19f37a5aff 100644 --- a/src/panels/config/cloud/account/cloud-account.js +++ b/src/panels/config/cloud/account/cloud-account.js @@ -63,10 +63,7 @@ class CloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) { color: var(--primary-color); } - +
- +
-
-
+ `; } @@ -52,10 +58,16 @@ class HaConfigCore extends LocalizeMixin(PolymerElement) { return { hass: Object, isWide: Boolean, + narrow: Boolean, showAdvanced: Boolean, + route: Object, }; } + _computeTabs() { + return configSections.general; + } + computeClasses(isWide) { return isWide ? "content" : "content narrow"; } diff --git a/src/panels/config/customize/ha-config-customize.js b/src/panels/config/customize/ha-config-customize.js index b5d622fc4b..abd812191f 100644 --- a/src/panels/config/customize/ha-config-customize.js +++ b/src/panels/config/customize/ha-config-customize.js @@ -1,10 +1,8 @@ -import "@polymer/app-layout/app-header-layout/app-header-layout"; -import "@polymer/app-layout/app-header/app-header"; -import "@polymer/app-layout/app-toolbar/app-toolbar"; import "@polymer/paper-icon-button/paper-icon-button"; import { html } from "@polymer/polymer/lib/utils/html-tag"; import { PolymerElement } from "@polymer/polymer/polymer-element"; +import "../../../layouts/hass-tabs-subpage"; import "../../../resources/ha-style"; import "../../../components/ha-paper-icon-button-arrow-prev"; @@ -17,6 +15,8 @@ import { computeStateDomain } from "../../../common/entity/compute_state_domain" import { sortStatesByName } from "../../../common/entity/states_sort_by_name"; import LocalizeMixin from "../../../mixins/localize-mixin"; +import { configSections } from "../ha-panel-config"; + /* * @appliesMixin LocalizeMixin */ @@ -29,19 +29,14 @@ class HaConfigCustomize extends LocalizeMixin(PolymerElement) { } - - - - -
- [[localize('ui.panel.config.customize.caption')]] -
-
-
- +
@@ -59,7 +54,7 @@ class HaConfigCustomize extends LocalizeMixin(PolymerElement) {
-
+ `; } @@ -67,7 +62,9 @@ class HaConfigCustomize extends LocalizeMixin(PolymerElement) { return { hass: Object, isWide: Boolean, - + narrow: Boolean, + route: Object, + showAdvanced: Boolean, entities: { type: Array, computed: "computeEntities(hass)", @@ -95,6 +92,10 @@ class HaConfigCustomize extends LocalizeMixin(PolymerElement) { history.back(); } + _computeTabs() { + return configSections.general; + } + computeEntities(hass) { return Object.keys(hass.states) .map((key) => hass.states[key]) diff --git a/src/panels/config/dashboard/ha-config-dashboard.ts b/src/panels/config/dashboard/ha-config-dashboard.ts index 95e4256a7b..5114a62631 100644 --- a/src/panels/config/dashboard/ha-config-dashboard.ts +++ b/src/panels/config/dashboard/ha-config-dashboard.ts @@ -15,7 +15,7 @@ import "../../../components/ha-menu-button"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; -import { CloudStatus, CloudStatusLoggedIn } from "../../../data/cloud"; +import { CloudStatus } from "../../../data/cloud"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import "../../../components/ha-card"; @@ -23,6 +23,7 @@ import "../../../components/ha-icon-next"; import "../ha-config-section"; import "./ha-config-navigation"; +import { configSections } from "../ha-panel-config"; @customElement("ha-config-dashboard") class HaConfigDashboard extends LitElement { @@ -41,7 +42,6 @@ class HaConfigDashboard extends LitElement { .hass=${this.hass} .narrow=${this.narrow} > -
${this.hass.localize("panel.config")}
@@ -57,68 +57,33 @@ class HaConfigDashboard extends LitElement { ${this.cloudStatus && isComponentLoaded(this.hass, "cloud") ? html` - - - - ${this.hass.localize("ui.panel.config.cloud.caption")} - ${this.cloudStatus.logged_in - ? html` -
- ${this.hass.localize( - "ui.panel.config.cloud.description_login", - "email", - (this.cloudStatus as CloudStatusLoggedIn) - .email - )} -
- ` - : html` -
- ${this.hass.localize( - "ui.panel.config.cloud.description_features" - )} -
- `} -
- -
-
+
` : ""} - - - - - - - - + ${Object.values(configSections).map( + (section) => html` + + + + ` + )} ${!this.showAdvanced ? html`
@@ -142,9 +107,15 @@ class HaConfigDashboard extends LitElement { return [ haStyle, css` + app-header { + --app-header-background-color: var(--primary-background-color); + } ha-config-navigation:last-child { margin-bottom: 24px; } + ha-config-section { + margin-top: -20px; + } ha-card { overflow: hidden; } diff --git a/src/panels/config/dashboard/ha-config-navigation.ts b/src/panels/config/dashboard/ha-config-navigation.ts index 0d55475475..3af4194ac9 100644 --- a/src/panels/config/dashboard/ha-config-navigation.ts +++ b/src/panels/config/dashboard/ha-config-navigation.ts @@ -1,6 +1,6 @@ import "@polymer/iron-icon/iron-icon"; import "@polymer/paper-item/paper-item-body"; -import "@polymer/paper-item/paper-item"; +import "@polymer/paper-item/paper-icon-item"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; @@ -17,70 +17,65 @@ import { } from "lit-element"; import { HomeAssistant } from "../../../types"; import { CloudStatus, CloudStatusLoggedIn } from "../../../data/cloud"; - -export interface ConfigPageNavigation { - page: string; - core?: boolean; - advanced?: boolean; - info?: any; -} +import { PageNavigation } from "../../../layouts/hass-tabs-subpage"; @customElement("ha-config-navigation") class HaConfigNavigation extends LitElement { @property() public hass!: HomeAssistant; @property() public showAdvanced!: boolean; - @property() public pages!: ConfigPageNavigation[]; - @property() public curPage!: string; + @property() public pages!: PageNavigation[]; protected render(): TemplateResult { return html` - - ${this.pages.map(({ page, core, advanced, info }) => - (core || isComponentLoaded(this.hass, page)) && - (!advanced || this.showAdvanced) - ? html` - - - - ${this.hass.localize(`ui.panel.config.${page}.caption`)} - ${page === "cloud" && (info as CloudStatus) - ? info.logged_in - ? html` -
- ${this.hass.localize( - "ui.panel.config.cloud.description_login", - "email", - (info as CloudStatusLoggedIn).email - )} -
- ` - : html` -
- ${this.hass.localize( - "ui.panel.config.cloud.description_features" - )} -
- ` + ${this.pages.map((page) => + (!page.component || + page.core || + isComponentLoaded(this.hass, page.component)) && + (!page.exportOnly || this.showAdvanced) + ? html` +
+ + + + ${this.hass.localize( + `ui.panel.config.${page.component}.caption` + )} + ${page.component === "cloud" && (page.info as CloudStatus) + ? page.info.logged_in + ? html` +
+ ${this.hass.localize( + "ui.panel.config.cloud.description_login", + "email", + (page.info as CloudStatusLoggedIn).email + )} +
+ ` : html`
${this.hass.localize( - `ui.panel.config.${page}.description` + "ui.panel.config.cloud.description_features" )}
- `} -
- - -
- ` - : "" - )} -
+ ` + : html` +
+ ${this.hass.localize( + `ui.panel.config.${page.component}.description` + )} +
+ `} + + + + + ` + : "" + )} `; } @@ -93,6 +88,10 @@ class HaConfigNavigation extends LitElement { display: block; outline: 0; } + ha-icon, + ha-icon-next { + color: var(--secondary-text-color); + } .iron-selected paper-item::before, a:not(.iron-selected):focus::before { position: absolute; @@ -105,10 +104,6 @@ class HaConfigNavigation extends LitElement { transition: opacity 15ms linear; will-change: opacity; } - .iron-selected paper-item::before { - background-color: var(--sidebar-selected-icon-color); - opacity: 0.12; - } a:not(.iron-selected):focus::before { background-color: currentColor; opacity: var(--dark-divider-opacity); @@ -117,12 +112,6 @@ class HaConfigNavigation extends LitElement { .iron-selected:focus paper-item::before { opacity: 0.2; } - .iron-selected paper-item[pressed]::before { - opacity: 0.37; - } - paper-listbox { - padding: 0; - } `; } } diff --git a/src/panels/config/devices/ha-config-device-page.ts b/src/panels/config/devices/ha-config-device-page.ts index 2bc8d4fb0d..336d4642ff 100644 --- a/src/panels/config/devices/ha-config-device-page.ts +++ b/src/panels/config/devices/ha-config-device-page.ts @@ -9,7 +9,7 @@ import { import memoizeOne from "memoize-one"; -import "../../../layouts/hass-subpage"; +import "../../../layouts/hass-tabs-subpage"; import "../../../layouts/hass-error-screen"; import "../ha-config-section"; @@ -18,7 +18,7 @@ import "./device-detail/ha-device-triggers-card"; import "./device-detail/ha-device-conditions-card"; import "./device-detail/ha-device-actions-card"; import "./device-detail/ha-device-entities-card"; -import { HomeAssistant } from "../../../types"; +import { HomeAssistant, Route } from "../../../types"; import { ConfigEntry } from "../../../data/config_entries"; import { EntityRegistryEntry, @@ -45,6 +45,7 @@ import { import { compare } from "../../../common/string/compare"; import { computeStateName } from "../../../common/entity/compute_state_name"; import { createValidEntityId } from "../../../common/entity/valid_entity_id"; +import { configSections } from "../ha-panel-config"; export interface EntityRegistryStateEntry extends EntityRegistryEntry { stateName?: string; @@ -60,6 +61,7 @@ export class HaConfigDevicePage extends LitElement { @property() public deviceId!: string; @property() public narrow!: boolean; @property() public showAdvanced!: boolean; + @property() public route!: Route; @property() private _triggers: DeviceTrigger[] = []; @property() private _conditions: DeviceCondition[] = []; @property() private _actions: DeviceAction[] = []; @@ -133,7 +135,12 @@ export class HaConfigDevicePage extends LitElement { const entities = this._entities(this.deviceId, this.entities); return html` - + - + `; } diff --git a/src/panels/config/devices/ha-config-devices-dashboard.ts b/src/panels/config/devices/ha-config-devices-dashboard.ts index ef2f32d684..e60b990e92 100644 --- a/src/panels/config/devices/ha-config-devices-dashboard.ts +++ b/src/panels/config/devices/ha-config-devices-dashboard.ts @@ -1,4 +1,4 @@ -import "../../../layouts/hass-subpage"; +import "../../../layouts/hass-tabs-subpage"; import "./ha-devices-data-table"; import { @@ -10,11 +10,12 @@ import { CSSResult, css, } from "lit-element"; -import { HomeAssistant } from "../../../types"; +import { HomeAssistant, Route } from "../../../types"; import { DeviceRegistryEntry } from "../../../data/device_registry"; import { EntityRegistryEntry } from "../../../data/entity_registry"; import { ConfigEntry } from "../../../data/config_entries"; import { AreaRegistryEntry } from "../../../data/area_registry"; +import { configSections } from "../ha-panel-config"; @customElement("ha-config-devices-dashboard") export class HaConfigDeviceDashboard extends LitElement { @@ -26,12 +27,16 @@ export class HaConfigDeviceDashboard extends LitElement { @property() public entities!: EntityRegistryEntry[]; @property() public areas!: AreaRegistryEntry[]; @property() public domain!: string; + @property() public route!: Route; protected render(): TemplateResult { return html` -
-
+ `; } diff --git a/src/panels/config/devices/ha-config-devices.ts b/src/panels/config/devices/ha-config-devices.ts index 0f0a78fcd6..2db8938019 100644 --- a/src/panels/config/devices/ha-config-devices.ts +++ b/src/panels/config/devices/ha-config-devices.ts @@ -100,6 +100,7 @@ class HaConfigDevices extends HassRouterPage { pageEl.narrow = this.narrow; pageEl.isWide = this.isWide; pageEl.showAdvanced = this.showAdvanced; + pageEl.route = this.routeTail; } private _loadData() { diff --git a/src/panels/config/entities/entity-registry-settings.ts b/src/panels/config/entities/entity-registry-settings.ts index 547838c8fb..0e4fcb95a8 100644 --- a/src/panels/config/entities/entity-registry-settings.ts +++ b/src/panels/config/entities/entity-registry-settings.ts @@ -1,32 +1,29 @@ +import "@polymer/paper-input/paper-input"; +import { HassEntity } from "home-assistant-js-websocket"; import { - LitElement, - html, css, CSSResult, - TemplateResult, - property, customElement, + html, + LitElement, + property, PropertyValues, + TemplateResult, } from "lit-element"; -import "@polymer/paper-input/paper-input"; - -import "../../../components/ha-switch"; - -import { PolymerChangedEvent } from "../../../polymer-types"; -import { HomeAssistant } from "../../../types"; -import { HassEntity } from "home-assistant-js-websocket"; -// tslint:disable-next-line: no-duplicate-imports -import { HaSwitch } from "../../../components/ha-switch"; - +import { fireEvent } from "../../../common/dom/fire_event"; import { computeDomain } from "../../../common/entity/compute_domain"; import { computeStateName } from "../../../common/entity/compute_state_name"; +import "../../../components/ha-switch"; +// tslint:disable-next-line: no-duplicate-imports +import { HaSwitch } from "../../../components/ha-switch"; import { - updateEntityRegistryEntry, - removeEntityRegistryEntry, EntityRegistryEntry, + removeEntityRegistryEntry, + updateEntityRegistryEntry, } from "../../../data/entity_registry"; import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box"; -import { fireEvent } from "../../../common/dom/fire_event"; +import { PolymerChangedEvent } from "../../../polymer-types"; +import { HomeAssistant } from "../../../types"; @customElement("entity-registry-settings") export class EntityRegistrySettings extends LitElement { diff --git a/src/panels/config/entities/ha-config-entities.ts b/src/panels/config/entities/ha-config-entities.ts index 00a95df4da..f16cfd5fb2 100644 --- a/src/panels/config/entities/ha-config-entities.ts +++ b/src/panels/config/entities/ha-config-entities.ts @@ -1,59 +1,59 @@ -import { - LitElement, - TemplateResult, - html, - css, - CSSResult, - property, - query, - customElement, -} from "lit-element"; -import { styleMap } from "lit-html/directives/style-map"; - import "@polymer/paper-checkbox/paper-checkbox"; import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; import "@polymer/paper-item/paper-icon-item"; import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-tooltip/paper-tooltip"; - -import { HomeAssistant } from "../../../types"; +import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { - EntityRegistryEntry, - computeEntityRegistryName, - subscribeEntityRegistry, - removeEntityRegistryEntry, - updateEntityRegistryEntry, -} from "../../../data/entity_registry"; -import "../../../layouts/hass-subpage"; -import "../../../layouts/hass-loading-screen"; -import "../../../components/data-table/ha-data-table"; -import "../../../components/ha-icon"; + css, + CSSResult, + customElement, + html, + LitElement, + property, + query, + TemplateResult, +} from "lit-element"; +import { styleMap } from "lit-html/directives/style-map"; +import memoize from "memoize-one"; +import { computeDomain } from "../../../common/entity/compute_domain"; import { domainIcon } from "../../../common/entity/domain_icon"; import { stateIcon } from "../../../common/entity/state_icon"; -import { computeDomain } from "../../../common/entity/compute_domain"; -import { - showEntityRegistryDetailDialog, - loadEntityRegistryDetailDialog, -} from "./show-dialog-entity-registry-detail"; -import { UnsubscribeFunc } from "home-assistant-js-websocket"; -import memoize from "memoize-one"; +import "../../../components/data-table/ha-data-table"; // tslint:disable-next-line import { DataTableColumnContainer, + DataTableColumnData, + HaDataTable, RowClickedEvent, SelectionChangedEvent, - HaDataTable, - DataTableColumnData, } from "../../../components/data-table/ha-data-table"; +import "../../../components/ha-icon"; +import { + computeEntityRegistryName, + EntityRegistryEntry, + removeEntityRegistryEntry, + subscribeEntityRegistry, + updateEntityRegistryEntry, +} from "../../../data/entity_registry"; import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box"; +import "../../../layouts/hass-loading-screen"; +import "../../../layouts/hass-tabs-subpage"; import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; +import { HomeAssistant, Route } from "../../../types"; import { DialogEntityRegistryDetail } from "./dialog-entity-registry-detail"; +import { + loadEntityRegistryDetailDialog, + showEntityRegistryDetailDialog, +} from "./show-dialog-entity-registry-detail"; +import { configSections } from "../ha-panel-config"; @customElement("ha-config-entities") export class HaConfigEntities extends SubscribeMixin(LitElement) { @property() public hass!: HomeAssistant; @property() public isWide!: boolean; @property() public narrow!: boolean; + @property() public route!: Route; @property() private _entities?: EntityRegistryEntry[]; @property() private _showDisabled = false; @property() private _showUnavailable = true; @@ -224,9 +224,12 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { `; } return html` -
@@ -367,7 +370,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
-
+ `; } @@ -490,6 +493,10 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { static get styles(): CSSResult { return css` + hass-loading-screen { + --app-header-background-color: var(--sidebar-background-color); + --app-header-text-color: var(--sidebar-text-color); + } a { color: var(--primary-color); } diff --git a/src/panels/config/ha-config-router.ts b/src/panels/config/ha-config-router.ts deleted file mode 100644 index dd883f0d0a..0000000000 --- a/src/panels/config/ha-config-router.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { property, customElement } from "lit-element"; -import "../../layouts/hass-loading-screen"; -import { HomeAssistant } from "../../types"; -import { CloudStatus } from "../../data/cloud"; -import { HassRouterPage, RouterOptions } from "../../layouts/hass-router-page"; -import { PolymerElement } from "@polymer/polymer"; - -declare global { - // for fire event - interface HASSDomEvents { - "ha-refresh-cloud-status": undefined; - } -} - -@customElement("ha-config-router") -class HaConfigRouter extends HassRouterPage { - @property() public hass!: HomeAssistant; - @property() public narrow!: boolean; - @property() public wideSidebar: boolean = false; - @property() public wide: boolean = false; - @property() public isWide: boolean = false; - @property() public showAdvanced: boolean = false; - @property() public cloudStatus?: CloudStatus; - - protected routerOptions: RouterOptions = { - defaultPage: "dashboard", - cacheAll: true, - preloadAll: true, - routes: { - areas: { - tag: "ha-config-areas", - load: () => - import( - /* webpackChunkName: "panel-config-areas" */ "./areas/ha-config-areas" - ), - }, - automation: { - tag: "ha-config-automation", - load: () => - import( - /* webpackChunkName: "panel-config-automation" */ "./automation/ha-config-automation" - ), - }, - cloud: { - tag: "ha-config-cloud", - load: () => - import( - /* webpackChunkName: "panel-config-cloud" */ "./cloud/ha-config-cloud" - ), - }, - core: { - tag: "ha-config-core", - load: () => - import( - /* webpackChunkName: "panel-config-core" */ "./core/ha-config-core" - ), - }, - devices: { - tag: "ha-config-devices", - load: () => - import( - /* webpackChunkName: "panel-config-devices" */ "./devices/ha-config-devices" - ), - }, - server_control: { - tag: "ha-config-server-control", - load: () => - import( - /* webpackChunkName: "panel-config-server-control" */ "./server_control/ha-config-server-control" - ), - }, - customize: { - tag: "ha-config-customize", - load: () => - import( - /* webpackChunkName: "panel-config-customize" */ "./customize/ha-config-customize" - ), - }, - dashboard: { - tag: "ha-config-dashboard", - load: () => - import( - /* webpackChunkName: "panel-config-dashboard" */ "./dashboard/ha-config-dashboard" - ), - }, - entities: { - tag: "ha-config-entities", - load: () => - import( - /* webpackChunkName: "panel-config-entities" */ "./entities/ha-config-entities" - ), - }, - integrations: { - tag: "ha-config-integrations", - load: () => - import( - /* webpackChunkName: "panel-config-integrations" */ "./integrations/ha-config-integrations" - ), - }, - person: { - tag: "ha-config-person", - load: () => - import( - /* webpackChunkName: "panel-config-person" */ "./person/ha-config-person" - ), - }, - script: { - tag: "ha-config-script", - load: () => - import( - /* webpackChunkName: "panel-config-script" */ "./script/ha-config-script" - ), - }, - scene: { - tag: "ha-config-scene", - load: () => - import( - /* webpackChunkName: "panel-config-scene" */ "./scene/ha-config-scene" - ), - }, - users: { - tag: "ha-config-users", - load: () => - import( - /* webpackChunkName: "panel-config-users" */ "./users/ha-config-users" - ), - }, - zone: { - tag: "ha-config-zone", - load: () => - import( - /* webpackChunkName: "panel-config-zone" */ "./zone/ha-config-zone" - ), - }, - zha: { - tag: "zha-config-dashboard-router", - load: () => - import( - /* webpackChunkName: "panel-config-zha" */ "./zha/zha-config-dashboard-router" - ), - }, - zwave: { - tag: "ha-config-zwave", - load: () => - import( - /* webpackChunkName: "panel-config-zwave" */ "./zwave/ha-config-zwave" - ), - }, - }, - }; - protected updatePageEl(el) { - if ("setProperties" in el) { - // As long as we have Polymer panels - (el as PolymerElement).setProperties({ - route: this.routeTail, - hass: this.hass, - showAdvanced: this.showAdvanced, - isWide: this.isWide, - narrow: this.narrow, - cloudStatus: this.cloudStatus, - }); - } else { - el.route = this.routeTail; - el.hass = this.hass; - el.showAdvanced = this.showAdvanced; - el.isWide = this.isWide; - el.narrow = this.narrow; - el.cloudStatus = this.cloudStatus; - } - } -} - -declare global { - interface HTMLElementTagNameMap { - "ha-config-router": HaConfigRouter; - } -} diff --git a/src/panels/config/ha-config-section.ts b/src/panels/config/ha-config-section.ts index 802bd65128..2e4e70db3c 100644 --- a/src/panels/config/ha-config-section.ts +++ b/src/panels/config/ha-config-section.ts @@ -1,18 +1,59 @@ -import { customElement } from "lit-element"; +import { customElement, LitElement, html, css, property } from "lit-element"; +import { classMap } from "lit-html/directives/class-map"; @customElement("ha-config-section") -export class HaConfigSection extends HTMLElement { - constructor() { - super(); - this.attachShadow({ mode: "open" }); - this.shadowRoot!.innerHTML = ` - -
-
-
-
-
-
-
+ + .narrow.content { + max-width: 640px; + } + .narrow .together { + margin-top: 20px; + } + .narrow .intro { + padding-bottom: 20px; + margin-right: 0; + max-width: 500px; + } `; } } diff --git a/src/panels/config/ha-panel-config.ts b/src/panels/config/ha-panel-config.ts index d0b763ddc7..f5c31b369a 100644 --- a/src/panels/config/ha-panel-config.ts +++ b/src/panels/config/ha-panel-config.ts @@ -1,12 +1,4 @@ -import { - property, - PropertyValues, - customElement, - LitElement, - html, - CSSResult, - css, -} from "lit-element"; +import { property, PropertyValues, customElement } from "lit-element"; import "@polymer/paper-item/paper-item-body"; import "@polymer/paper-item/paper-item"; import "../../layouts/hass-loading-screen"; @@ -18,9 +10,9 @@ import { getOptimisticFrontendUserDataCollection, CoreFrontendUserData, } from "../../data/frontend"; -import "./ha-config-router"; -import "./dashboard/ha-config-navigation"; -import { classMap } from "lit-html/directives/class-map"; +import { HassRouterPage, RouterOptions } from "../../layouts/hass-router-page"; +import { PolymerElement } from "@polymer/polymer"; +import { PageNavigation } from "../../layouts/hass-tabs-subpage"; declare global { // for fire event @@ -29,14 +21,252 @@ declare global { } } -const NO_SIDEBAR_PAGES = ["zone"]; +export const configSections: { [name: string]: PageNavigation[] } = { + integrations: [ + { + component: "integrations", + path: "/config/integrations", + translationKey: "ui.panel.config.integrations.caption", + icon: "hass:puzzle", + core: true, + }, + { + component: "devices", + path: "/config/devices", + translationKey: "ui.panel.config.devices.caption", + icon: "hass:devices", + core: true, + }, + { + component: "entities", + path: "/config/entities", + translationKey: "ui.panel.config.entities.caption", + icon: "hass:shape", + core: true, + }, + { + component: "areas", + path: "/config/areas", + translationKey: "ui.panel.config.areas.caption", + icon: "hass:sofa", + core: true, + }, + ], + automation: [ + { + component: "automation", + path: "/config/automation", + translationKey: "ui.panel.config.automation.caption", + icon: "hass:robot", + }, + { + component: "scene", + path: "/config/scene", + translationKey: "ui.panel.config.scene.caption", + icon: "hass:palette", + }, + { + component: "script", + path: "/config/script", + translationKey: "ui.panel.config.script.caption", + icon: "hass:script-text", + }, + ], + persons: [ + { + component: "person", + path: "/config/person", + translationKey: "ui.panel.config.person.caption", + icon: "hass:account", + }, + { + component: "zone", + path: "/config/zone", + translationKey: "ui.panel.config.zone.caption", + icon: "hass:map-marker-radius", + core: true, + }, + { + component: "users", + path: "/config/users", + translationKey: "ui.panel.config.users.caption", + icon: "hass:account-badge-horizontal", + core: true, + }, + ], + general: [ + { + component: "core", + path: "/config/core", + translationKey: "ui.panel.config.core.caption", + icon: "hass:home-assistant", + core: true, + }, + { + component: "server_control", + path: "/config/server_control", + translationKey: "ui.panel.config.server_control.caption", + icon: "hass:server", + core: true, + }, + { + component: "customize", + path: "/config/customize", + translationKey: "ui.panel.config.customize.caption", + icon: "hass:pencil", + core: true, + exportOnly: true, + }, + ], + other: [ + { + component: "zha", + path: "/config/zha", + translationKey: "ui.panel.config.zha.caption", + icon: "hass:zigbee", + }, + { + component: "zwave", + path: "/config/zwave", + translationKey: "ui.panel.config.zwave.caption", + icon: "hass:z-wave", + }, + ], +}; @customElement("ha-panel-config") -class HaPanelConfig extends LitElement { +class HaPanelConfig extends HassRouterPage { @property() public hass!: HomeAssistant; @property() public narrow!: boolean; @property() public route!: Route; + protected routerOptions: RouterOptions = { + defaultPage: "dashboard", + cacheAll: true, + preloadAll: true, + routes: { + areas: { + tag: "ha-config-areas", + load: () => + import( + /* webpackChunkName: "panel-config-areas" */ "./areas/ha-config-areas" + ), + }, + automation: { + tag: "ha-config-automation", + load: () => + import( + /* webpackChunkName: "panel-config-automation" */ "./automation/ha-config-automation" + ), + }, + cloud: { + tag: "ha-config-cloud", + load: () => + import( + /* webpackChunkName: "panel-config-cloud" */ "./cloud/ha-config-cloud" + ), + }, + core: { + tag: "ha-config-core", + load: () => + import( + /* webpackChunkName: "panel-config-core" */ "./core/ha-config-core" + ), + }, + devices: { + tag: "ha-config-devices", + load: () => + import( + /* webpackChunkName: "panel-config-devices" */ "./devices/ha-config-devices" + ), + }, + server_control: { + tag: "ha-config-server-control", + load: () => + import( + /* webpackChunkName: "panel-config-server-control" */ "./server_control/ha-config-server-control" + ), + }, + customize: { + tag: "ha-config-customize", + load: () => + import( + /* webpackChunkName: "panel-config-customize" */ "./customize/ha-config-customize" + ), + }, + dashboard: { + tag: "ha-config-dashboard", + load: () => + import( + /* webpackChunkName: "panel-config-dashboard" */ "./dashboard/ha-config-dashboard" + ), + }, + entities: { + tag: "ha-config-entities", + load: () => + import( + /* webpackChunkName: "panel-config-entities" */ "./entities/ha-config-entities" + ), + }, + integrations: { + tag: "ha-config-integrations", + load: () => + import( + /* webpackChunkName: "panel-config-integrations" */ "./integrations/ha-config-integrations" + ), + }, + person: { + tag: "ha-config-person", + load: () => + import( + /* webpackChunkName: "panel-config-person" */ "./person/ha-config-person" + ), + }, + script: { + tag: "ha-config-script", + load: () => + import( + /* webpackChunkName: "panel-config-script" */ "./script/ha-config-script" + ), + }, + scene: { + tag: "ha-config-scene", + load: () => + import( + /* webpackChunkName: "panel-config-scene" */ "./scene/ha-config-scene" + ), + }, + users: { + tag: "ha-config-users", + load: () => + import( + /* webpackChunkName: "panel-config-users" */ "./users/ha-config-users" + ), + }, + zone: { + tag: "ha-config-zone", + load: () => + import( + /* webpackChunkName: "panel-config-zone" */ "./zone/ha-config-zone" + ), + }, + zha: { + tag: "zha-config-dashboard-router", + load: () => + import( + /* webpackChunkName: "panel-config-zha" */ "./zha/zha-config-dashboard-router" + ), + }, + zwave: { + tag: "ha-config-zwave", + load: () => + import( + /* webpackChunkName: "panel-config-zwave" */ "./zwave/ha-config-zwave" + ), + }, + }, + }; + @property() private _wideSidebar: boolean = false; @property() private _wide: boolean = false; @property() private _coreUserData?: CoreFrontendUserData; @@ -85,64 +315,42 @@ class HaPanelConfig extends LitElement { this.addEventListener("ha-refresh-cloud-status", () => this._updateCloudStatus() ); + this.style.setProperty( + "--app-header-background-color", + "var(--sidebar-background-color)" + ); + this.style.setProperty( + "--app-header-text-color", + "var(--sidebar-text-color)" + ); + this.style.setProperty( + "--app-header-border-bottom", + "1px solid var(--divider-color)" + ); } - protected render() { - const dividerPos = this.route.path.indexOf("/", 1); - const curPage = - dividerPos === -1 - ? this.route.path.substr(1) - : this.route.path.substr(1, dividerPos - 1); - + protected updatePageEl(el) { const isWide = this.hass.dockedSidebar === "docked" ? this._wideSidebar : this._wide; - const showSidebar = isWide && !NO_SIDEBAR_PAGES.includes(curPage); - return html` - ${showSidebar - ? html` - - ` - : ""} - - `; + if ("setProperties" in el) { + // As long as we have Polymer panels + (el as PolymerElement).setProperties({ + route: this.routeTail, + hass: this.hass, + showAdvanced: this._showAdvanced, + isWide, + narrow: this.narrow, + cloudStatus: this._cloudStatus, + }); + } else { + el.route = this.routeTail; + el.hass = this.hass; + el.showAdvanced = this._showAdvanced; + el.isWide = isWide; + el.narrow = this.narrow; + el.cloudStatus = this._cloudStatus; + } } private async _updateCloudStatus() { @@ -152,54 +360,6 @@ class HaPanelConfig extends LitElement { setTimeout(() => this._updateCloudStatus(), 5000); } } - - static get styles(): CSSResult { - return css` - :host { - display: block; - height: 100%; - background-color: var(--primary-background-color); - } - - a { - text-decoration: none; - color: var(--primary-text-color); - } - - .side-bar { - border-right: 1px solid var(--divider-color); - background: white; - width: 320px; - float: left; - box-sizing: border-box; - position: fixed; - } - - .toolbar { - display: flex; - align-items: center; - font-size: 20px; - height: 64px; - padding: 0 16px 0 16px; - pointer-events: none; - background-color: var(--primary-background-color); - font-weight: 400; - color: var(--primary-text-color); - border-bottom: 1px solid var(--divider-color); - } - - .wide-config { - float: right; - width: calc(100% - 320px); - height: 100%; - } - - .navigation { - height: calc(100vh - 64px); - overflow: auto; - } - `; - } } declare global { diff --git a/src/panels/config/integrations/ha-config-entries-dashboard.ts b/src/panels/config/integrations/ha-config-entries-dashboard.ts index 2c55f68544..6aa95b96e5 100644 --- a/src/panels/config/integrations/ha-config-entries-dashboard.ts +++ b/src/panels/config/integrations/ha-config-entries-dashboard.ts @@ -12,7 +12,7 @@ import "../../../components/ha-card"; import "../../../components/ha-icon-next"; import "../../../components/ha-fab"; import "../../../components/entity/ha-state-icon"; -import "../../../layouts/hass-subpage"; +import "../../../layouts/hass-tabs-subpage"; import "../../../resources/ha-style"; import "../../../components/ha-icon"; @@ -38,18 +38,21 @@ import { css, CSSResult, } from "lit-element"; -import { HomeAssistant } from "../../../types"; +import { HomeAssistant, Route } from "../../../types"; import { ConfigEntry, deleteConfigEntry } from "../../../data/config_entries"; import { fireEvent } from "../../../common/dom/fire_event"; import { EntityRegistryEntry } from "../../../data/entity_registry"; import { DataEntryFlowProgress } from "../../../data/data_entry_flow"; import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box"; +import { configSections } from "../ha-panel-config"; @customElement("ha-config-entries-dashboard") export class HaConfigManagerDashboard extends LitElement { @property() public hass!: HomeAssistant; @property() public showAdvanced!: boolean; @property() public isWide!: boolean; + @property() public narrow!: boolean; + @property() public route!: Route; @property() private configEntries!: ConfigEntry[]; @@ -72,9 +75,12 @@ export class HaConfigManagerDashboard extends LitElement { protected render(): TemplateResult { return html` - - + `; } @@ -361,7 +368,9 @@ export class HaConfigManagerDashboard extends LitElement { right: 16px; z-index: 1; } - + ha-fab[narrow] { + bottom: 84px; + } ha-fab[rtl] { right: auto; left: 16px; diff --git a/src/panels/config/integrations/ha-config-integrations.ts b/src/panels/config/integrations/ha-config-integrations.ts index e3abd2f4fe..511f4e6390 100644 --- a/src/panels/config/integrations/ha-config-integrations.ts +++ b/src/panels/config/integrations/ha-config-integrations.ts @@ -104,7 +104,7 @@ class HaConfigIntegrations extends HassRouterPage { pageEl.narrow = this.narrow; pageEl.isWide = this.isWide; pageEl.showAdvanced = this.showAdvanced; - + pageEl.route = this.routeTail; if (this._currentPage === "dashboard") { pageEl.configEntriesInProgress = this._configEntriesInProgress; return; diff --git a/src/panels/config/person/ha-config-person.ts b/src/panels/config/person/ha-config-person.ts index b2b30089b0..59aa055f0a 100644 --- a/src/panels/config/person/ha-config-person.ts +++ b/src/panels/config/person/ha-config-person.ts @@ -9,7 +9,7 @@ import { import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item-body"; -import { HomeAssistant } from "../../../types"; +import { HomeAssistant, Route } from "../../../types"; import { Person, fetchPersons, @@ -19,7 +19,7 @@ import { } from "../../../data/person"; import "../../../components/ha-card"; import "../../../components/ha-fab"; -import "../../../layouts/hass-subpage"; +import "../../../layouts/hass-tabs-subpage"; import "../../../layouts/hass-loading-screen"; import { compare } from "../../../common/string/compare"; import "../ha-config-section"; @@ -28,10 +28,13 @@ import { loadPersonDetailDialog, } from "./show-dialog-person-detail"; import { User, fetchUsers } from "../../../data/user"; +import { configSections } from "../ha-panel-config"; class HaConfigPerson extends LitElement { @property() public hass?: HomeAssistant; @property() public isWide?: boolean; + @property() public narrow?: boolean; + @property() public route!: Route; @property() private _storageItems?: Person[]; @property() private _configItems?: Person[]; private _usersLoad?: Promise; @@ -48,9 +51,12 @@ class HaConfigPerson extends LitElement { } const hass = this.hass; return html` - - +
@@ -72,8 +77,7 @@ class HaSceneDashboard extends LitElement { ` : this.scenes.map( (scene) => html` - -
+ ` )} - - + `; } @@ -152,13 +154,11 @@ class HaSceneDashboard extends LitElement { css` :host { display: block; - } - - hass-subpage { - min-height: 100vh; + height: 100%; } ha-card { + padding-bottom: 8px; margin-bottom: 56px; } @@ -173,6 +173,10 @@ class HaSceneDashboard extends LitElement { color: var(--primary-text-color); } + .actions { + display: flex; + } + ha-entity-toggle { margin-right: 16px; } @@ -188,7 +192,9 @@ class HaSceneDashboard extends LitElement { bottom: 24px; right: 24px; } - + ha-fab[narrow] { + bottom: 84px; + } ha-fab[rtl] { right: auto; left: 16px; diff --git a/src/panels/config/scene/ha-scene-editor.ts b/src/panels/config/scene/ha-scene-editor.ts index 295e5192a1..d1b10ff3f5 100644 --- a/src/panels/config/scene/ha-scene-editor.ts +++ b/src/panels/config/scene/ha-scene-editor.ts @@ -26,7 +26,7 @@ import "../../../layouts/ha-app-layout"; import { computeStateName } from "../../../common/entity/compute_state_name"; import { haStyle } from "../../../resources/styles"; -import { HomeAssistant } from "../../../types"; +import { HomeAssistant, Route } from "../../../types"; import { navigate } from "../../../common/navigate"; import { computeRTL } from "../../../common/util/compute_rtl"; import { @@ -55,6 +55,7 @@ import memoizeOne from "memoize-one"; import { computeDomain } from "../../../common/entity/compute_domain"; import { HassEvent } from "home-assistant-js-websocket"; import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box"; +import { configSections } from "../ha-panel-config"; interface DeviceEntities { id: string; @@ -69,7 +70,9 @@ interface DeviceEntitiesLookup { @customElement("ha-scene-editor") export class HaSceneEditor extends SubscribeMixin(LitElement) { @property() public hass!: HomeAssistant; - @property() public isWide?: boolean; + @property() public narrow!: boolean; + @property() public isWide!: boolean; + @property() public route!: Route; @property() public scene?: SceneEntity; @property() public creatingNew?: boolean; @property() public showAdvanced!: boolean; @@ -157,39 +160,36 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) { this._deviceRegistryEntries ); return html` - - - - -
- ${this.scene - ? computeStateName(this.scene) - : this.hass.localize( - "ui.panel.config.scene.editor.default_name" - )} -
- ${this.creatingNew - ? "" - : html` - - `} -
-
+ this._backTapped()} + .tabs=${configSections.automation} + > -
- ${this._errors - ? html` -
${this._errors}
- ` - : ""} + ${ + this.creatingNew + ? "" + : html` + + ` + } + + ${ + this._errors + ? html` +
${this._errors}
+ ` + : "" + }
- ${this.scene - ? computeStateName(this.scene) - : this.hass.localize( - "ui.panel.config.scene.editor.default_name" - )} + ${ + this.scene + ? computeStateName(this.scene) + : this.hass.localize( + "ui.panel.config.scene.editor.default_name" + ) + }
${this.hass.localize( @@ -291,87 +293,88 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) { - ${this.showAdvanced - ? html` - -
- ${this.hass.localize( - "ui.panel.config.scene.editor.entities.header" - )} -
-
- ${this.hass.localize( - "ui.panel.config.scene.editor.entities.introduction" - )} -
- ${entities.length - ? html` - - ${entities.map((entityId) => { - const stateObj = this.hass.states[entityId]; - if (!stateObj) { - return html``; - } - return html` - - - - ${computeStateName(stateObj)} - - - - `; - })} - - ` - : ""} - - -
+ ${ + this.showAdvanced + ? html` + +
${this.hass.localize( - "ui.panel.config.scene.editor.entities.device_entities" + "ui.panel.config.scene.editor.entities.header" )} -
- -
- ` - : ""} +
+ ${this.hass.localize( + "ui.panel.config.scene.editor.entities.introduction" + )} +
+ ${entities.length + ? html` + + ${entities.map((entityId) => { + const stateObj = this.hass.states[entityId]; + if (!stateObj) { + return html``; + } + return html` + + + + ${computeStateName(stateObj)} + + + + `; + })} + + ` + : ""} + + +
+ ${this.hass.localize( + "ui.panel.config.scene.editor.entities.device_entities" + )} + +
+
+ + ` + : "" + }
-
@@ -42,6 +44,8 @@ class HaConfigScript extends PolymerElement { hass="[[hass]]" script="[[script]]" is-wide="[[isWide]]" + narrow="[[narrow]]" + route="[[route]]" creating-new="[[_creatingNew]]" > @@ -53,6 +57,7 @@ class HaConfigScript extends PolymerElement { hass: Object, route: Object, isWide: Boolean, + narrow: Boolean, _routeData: Object, _routeMatches: Boolean, _creatingNew: Boolean, diff --git a/src/panels/config/script/ha-script-editor.ts b/src/panels/config/script/ha-script-editor.ts index bf527d6b3b..3dcd615c03 100644 --- a/src/panels/config/script/ha-script-editor.ts +++ b/src/panels/config/script/ha-script-editor.ts @@ -11,7 +11,6 @@ import { TemplateResult, } from "lit-element"; import { classMap } from "lit-html/directives/class-map"; -import { computeStateName } from "../../../common/entity/compute_state_name"; import { navigate } from "../../../common/navigate"; import { computeRTL } from "../../../common/util/compute_rtl"; import "../../../components/ha-fab"; @@ -25,14 +24,17 @@ import { import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box"; import "../../../layouts/ha-app-layout"; import { haStyle } from "../../../resources/styles"; -import { HomeAssistant } from "../../../types"; +import { HomeAssistant, Route } from "../../../types"; import "../automation/action/ha-automation-action"; import { computeObjectId } from "../../../common/entity/compute_object_id"; +import { configSections } from "../ha-panel-config"; export class HaScriptEditor extends LitElement { @property() public hass!: HomeAssistant; @property() public script!: ScriptEntity; @property() public isWide?: boolean; + @property() public narrow!: boolean; + @property() public route!: Route; @property() public creatingNew?: boolean; @property() private _config?: ScriptConfig; @property() private _dirty?: boolean; @@ -40,32 +42,25 @@ export class HaScriptEditor extends LitElement { protected render(): TemplateResult { return html` - - - - -
- ${this.script - ? computeStateName(this.script) - : this.hass.localize( - "ui.panel.config.script.editor.default_name" - )} -
- ${this.creatingNew - ? "" - : html` - - `} -
-
+ this._backTapped()} + .tabs=${configSections.automation} + > + ${this.creatingNew + ? "" + : html` + + `}
${this._errors @@ -134,9 +129,9 @@ export class HaScriptEditor extends LitElement {
-
+ `; } @@ -301,7 +296,10 @@ export class HaScriptEditor extends LitElement { bottom: 24px; right: 24px; } - + ha-fab[narrow] { + bottom: 84px; + margin-bottom: -140px; + } ha-fab[dirty] { margin-bottom: 0; } diff --git a/src/panels/config/script/ha-script-picker.ts b/src/panels/config/script/ha-script-picker.ts index 82dbaee3a6..836cf63f1a 100644 --- a/src/panels/config/script/ha-script-picker.ts +++ b/src/panels/config/script/ha-script-picker.ts @@ -11,7 +11,7 @@ import "@polymer/paper-icon-button/paper-icon-button"; import "@polymer/paper-item/paper-item-body"; import { HassEntity } from "home-assistant-js-websocket"; -import "../../../layouts/hass-subpage"; +import "../../../layouts/hass-tabs-subpage"; import { computeRTL } from "../../../common/util/compute_rtl"; @@ -22,21 +22,27 @@ import "../ha-config-section"; import { computeStateName } from "../../../common/entity/compute_state_name"; import { haStyle } from "../../../resources/styles"; -import { HomeAssistant } from "../../../types"; +import { HomeAssistant, Route } from "../../../types"; import { triggerScript } from "../../../data/script"; import { showToast } from "../../../util/toast"; +import { configSections } from "../ha-panel-config"; @customElement("ha-script-picker") class HaScriptPicker extends LitElement { @property() public hass!: HomeAssistant; @property() public scripts!: HassEntity[]; @property() public isWide!: boolean; + @property() public narrow!: boolean; + @property() public route!: Route; protected render(): TemplateResult { return html` -
@@ -99,8 +105,8 @@ class HaScriptPicker extends LitElement { - + `; } @@ -169,7 +175,9 @@ class HaScriptPicker extends LitElement { bottom: 24px; right: 24px; } - + ha-fab[narrow] { + bottom: 84px; + } ha-fab[rtl] { right: auto; left: 16px; diff --git a/src/panels/config/server_control/ha-config-server-control.js b/src/panels/config/server_control/ha-config-server-control.js index 21c9162b87..ef93fe8fd6 100644 --- a/src/panels/config/server_control/ha-config-server-control.js +++ b/src/panels/config/server_control/ha-config-server-control.js @@ -4,12 +4,13 @@ import "@polymer/paper-icon-button/paper-icon-button"; import { html } from "@polymer/polymer/lib/utils/html-tag"; import { PolymerElement } from "@polymer/polymer/polymer-element"; -import "../../../layouts/hass-subpage"; +import "../../../layouts/hass-tabs-subpage"; import "../../../resources/ha-style"; import "./ha-config-section-server-control"; import LocalizeMixin from "../../../mixins/localize-mixin"; +import { configSections } from "../ha-panel-config"; /* * @appliesMixin LocalizeMixin @@ -33,9 +34,13 @@ class HaConfigServerControl extends LocalizeMixin(PolymerElement) { } -
-
+ `; } @@ -52,10 +57,16 @@ class HaConfigServerControl extends LocalizeMixin(PolymerElement) { return { hass: Object, isWide: Boolean, + narrow: Boolean, + route: Object, showAdvanced: Boolean, }; } + _computeTabs() { + return configSections.general; + } + computeClasses(isWide) { return isWide ? "content" : "content narrow"; } diff --git a/src/panels/config/users/ha-config-user-picker.js b/src/panels/config/users/ha-config-user-picker.js index babfea2436..c7a78b581e 100644 --- a/src/panels/config/users/ha-config-user-picker.js +++ b/src/panels/config/users/ha-config-user-picker.js @@ -3,7 +3,7 @@ import "@polymer/paper-item/paper-item-body"; import { html } from "@polymer/polymer/lib/utils/html-tag"; import { PolymerElement } from "@polymer/polymer/polymer-element"; -import "../../../layouts/hass-subpage"; +import "../../../layouts/hass-tabs-subpage"; import "../../../components/ha-icon-next"; import "../../../components/ha-card"; import "../../../components/ha-fab"; @@ -13,6 +13,7 @@ import NavigateMixin from "../../../mixins/navigate-mixin"; import { EventsMixin } from "../../../mixins/events-mixin"; import { computeRTL } from "../../../common/util/compute_rtl"; +import { configSections } from "../ha-panel-config"; let registeredDialog = false; @@ -41,6 +42,9 @@ class HaUserPicker extends EventsMixin( right: auto; left: 16px; } + ha-fab[narrow] { + bottom: 84px; + } ha-fab[rtl][is-wide] { bottom: 24px; right: auto; @@ -58,9 +62,12 @@ class HaUserPicker extends EventsMixin( } - `; @@ -47,6 +51,7 @@ class HaConfigUsers extends NavigateMixin(PolymerElement) { return { hass: Object, isWide: Boolean, + narrow: Boolean, route: { type: Object, observer: "_checkRoute", diff --git a/src/panels/config/users/ha-user-editor.ts b/src/panels/config/users/ha-user-editor.ts index 207cde707c..2aa50c413c 100644 --- a/src/panels/config/users/ha-user-editor.ts +++ b/src/panels/config/users/ha-user-editor.ts @@ -10,10 +10,10 @@ import { import { until } from "lit-html/directives/until"; import "@material/mwc-button"; -import "../../../layouts/hass-subpage"; +import "../../../layouts/hass-tabs-subpage"; import { haStyle } from "../../../resources/styles"; import "../../../components/ha-card"; -import { HomeAssistant } from "../../../types"; +import { HomeAssistant, Route } from "../../../types"; import { fireEvent } from "../../../common/dom/fire_event"; import { navigate } from "../../../common/navigate"; import { @@ -29,6 +29,7 @@ import { showConfirmationDialog, showPromptDialog, } from "../../../dialogs/generic/show-dialog-box"; +import { configSections } from "../ha-panel-config"; declare global { interface HASSDomEvents { @@ -42,6 +43,8 @@ const GROUPS = [SYSTEM_GROUP_ID_USER, SYSTEM_GROUP_ID_ADMIN]; class HaUserEditor extends LitElement { @property() public hass?: HomeAssistant; @property() public user?: User; + @property() public narrow?: boolean; + @property() public route!: Route; protected render(): TemplateResult { const hass = this.hass; @@ -51,8 +54,11 @@ class HaUserEditor extends LitElement { } return html` - @@ -130,7 +136,7 @@ class HaUserEditor extends LitElement { : ""} - + `; } @@ -225,7 +231,7 @@ class HaUserEditor extends LitElement { } ha-card { max-width: 600px; - margin: 0 auto 16px; + margin: 16px auto 16px; } hass-subpage ha-card:first-of-type { direction: ltr; diff --git a/src/panels/config/zone/ha-config-zone.ts b/src/panels/config/zone/ha-config-zone.ts index 8554d5433e..96ff793607 100644 --- a/src/panels/config/zone/ha-config-zone.ts +++ b/src/panels/config/zone/ha-config-zone.ts @@ -16,10 +16,10 @@ import "@polymer/paper-tooltip/paper-tooltip"; import "../../../components/map/ha-locations-editor"; -import { HomeAssistant } from "../../../types"; +import { HomeAssistant, Route } from "../../../types"; import "../../../components/ha-card"; import "../../../components/ha-fab"; -import "../../../layouts/hass-subpage"; +import "../../../layouts/hass-tabs-subpage"; import "../../../layouts/hass-loading-screen"; import { compare } from "../../../common/string/compare"; import "../ha-config-section"; @@ -42,12 +42,14 @@ import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket"; import memoizeOne from "memoize-one"; import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { subscribeEntityRegistry } from "../../../data/entity_registry"; +import { configSections } from "../ha-panel-config"; @customElement("ha-config-zone") export class HaConfigZone extends SubscribeMixin(LitElement) { @property() public hass!: HomeAssistant; @property() public isWide?: boolean; @property() public narrow?: boolean; + @property() public route!: Route; @property() private _storageItems?: Zone[]; @property() private _stateItems?: HassEntity[]; @property() private _activeEntry: string = ""; @@ -179,7 +181,13 @@ export class HaConfigZone extends SubscribeMixin(LitElement) { `; return html` - + ${this.narrow ? html` @@ -206,10 +214,11 @@ export class HaConfigZone extends SubscribeMixin(LitElement) { ` : ""} - + Date: Tue, 28 Jan 2020 22:40:57 +0100 Subject: [PATCH 114/126] Update ha-config-areas.ts --- src/panels/config/areas/ha-config-areas.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/config/areas/ha-config-areas.ts b/src/panels/config/areas/ha-config-areas.ts index 7c3cf09932..c7b767480a 100644 --- a/src/panels/config/areas/ha-config-areas.ts +++ b/src/panels/config/areas/ha-config-areas.ts @@ -60,7 +60,7 @@ export class HaConfigAreas extends LitElement { .narrow=${this.narrow} back-path="/config" .route=${this.route} - .tabs=${configSections.persons} + .tabs=${configSections.integrations} > From cdbd51f6f7c74bd99d373325cc1dac0e31422dd5 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 28 Jan 2020 22:44:21 +0100 Subject: [PATCH 115/126] Update hass-tabs-subpage.ts --- src/layouts/hass-tabs-subpage.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/layouts/hass-tabs-subpage.ts b/src/layouts/hass-tabs-subpage.ts index 4a51d61785..fc2b689f9e 100644 --- a/src/layouts/hass-tabs-subpage.ts +++ b/src/layouts/hass-tabs-subpage.ts @@ -129,7 +129,7 @@ class HassTabsSubpage extends LitElement { display: flex; align-items: center; font-size: 20px; - height: 64px; + height: 65px; background-color: var(--sidebar-background-color); font-weight: 400; color: var(--sidebar-text-color); @@ -214,7 +214,7 @@ class HassTabsSubpage extends LitElement { .content { position: relative; width: 100%; - height: calc(100% - 64px); + height: calc(100% - 65px); overflow-y: auto; overflow: auto; -webkit-overflow-scrolling: touch; From 7937714ce68439f24215137c3903a73c36a2e7d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Tue, 28 Jan 2020 23:37:42 +0100 Subject: [PATCH 116/126] Fixes add-on install button (#4635) * Fixes add-on install button * Set the default * linting issue --- hassio/src/addon-view/hassio-addon-info.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/hassio/src/addon-view/hassio-addon-info.ts b/hassio/src/addon-view/hassio-addon-info.ts index 40e744a8fe..076b7f16a1 100644 --- a/hassio/src/addon-view/hassio-addon-info.ts +++ b/hassio/src/addon-view/hassio-addon-info.ts @@ -14,6 +14,7 @@ import { import { classMap } from "lit-html/directives/class-map"; import "../../../src/components/buttons/ha-call-api-button"; +import "../../../src/components/buttons/ha-progress-button"; import "../../../src/components/ha-label-badge"; import "../../../src/components/ha-markdown"; import "../../../src/components/ha-switch"; @@ -94,6 +95,7 @@ class HassioAddonInfo extends LitElement { @property() public hass!: HomeAssistant; @property() public addon!: HassioAddonDetails; @property() private _error?: string; + @property({ type: Boolean }) private _installing = false; protected render(): TemplateResult { return html` @@ -445,13 +447,14 @@ class HassioAddonInfo extends LitElement {

` : ""} - Install - + `} @@ -749,6 +752,7 @@ class HassioAddonInfo extends LitElement { private async _installClicked(): Promise { this._error = undefined; + this._installing = true; try { await installHassioAddon(this.hass, this.addon.slug); const eventdata = { @@ -760,6 +764,7 @@ class HassioAddonInfo extends LitElement { } catch (err) { this._error = `Failed to install addon, ${err.body?.message || err}`; } + this._installing = false; } private async _uninstallClicked(): Promise { From 036eedc69d2a7ad000feb872941cfd7e7811eb7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Tue, 28 Jan 2020 23:37:53 +0100 Subject: [PATCH 117/126] Add styles to addon-header (#4632) * Add styles to addon-header * Update hassio/src/addon-view/hassio-addon-info.ts Co-Authored-By: Paulus Schoutsen Co-authored-by: Paulus Schoutsen --- hassio/src/addon-view/hassio-addon-info.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hassio/src/addon-view/hassio-addon-info.ts b/hassio/src/addon-view/hassio-addon-info.ts index 076b7f16a1..c31de13b80 100644 --- a/hassio/src/addon-view/hassio-addon-info.ts +++ b/hassio/src/addon-view/hassio-addon-info.ts @@ -500,6 +500,10 @@ class HassioAddonInfo extends LitElement { .light-color { color: var(--secondary-text-color); } + .addon-header { + font-size: 24px; + color: var(--paper-card-header-color, --primary-text-color); + } .addon-version { float: right; font-size: 15px; From 65994e7280576f4ee7be179c41b1b6dfacf30821 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 29 Jan 2020 00:13:44 +0100 Subject: [PATCH 118/126] Add related items to device info (#4637) * Add related items to device info * Update ha-config-device-page.ts * remove log * Lint * Fix dialog logic showing triggers on script dialog Co-authored-by: Paulus Schoutsen --- src/components/ha-chips.ts | 9 +- src/data/scene.ts | 17 + src/data/script.ts | 17 + .../ha-device-automation-card.ts | 32 +- .../ha-device-automation-dialog.ts | 184 +++++++++ .../devices/device-detail/ha-device-card.ts | 108 +++-- .../device-detail/ha-device-entities-card.ts | 183 ++++---- .../show-dialog-device-automation.ts | 22 + .../config/devices/ha-config-device-page.ts | 390 +++++++++++++----- src/panels/config/scene/ha-scene-editor.ts | 46 ++- src/panels/config/script/ha-script-editor.ts | 5 +- .../lovelace/cards/hui-entities-card.ts | 6 +- src/translations/en.json | 21 +- 13 files changed, 768 insertions(+), 272 deletions(-) create mode 100644 src/panels/config/devices/device-detail/ha-device-automation-dialog.ts create mode 100644 src/panels/config/devices/device-detail/show-dialog-device-automation.ts diff --git a/src/components/ha-chips.ts b/src/components/ha-chips.ts index 132fb1f99d..df2fe418c2 100644 --- a/src/components/ha-chips.ts +++ b/src/components/ha-chips.ts @@ -47,12 +47,9 @@ export class HaChips extends LitElement { } private _handleClick(ev) { - fireEvent( - this, - "chip-clicked", - { index: ev.target.closest("button").index }, - { bubbles: false } - ); + fireEvent(this, "chip-clicked", { + index: ev.target.closest("button").index, + }); } static get styles(): CSSResult { diff --git a/src/data/scene.ts b/src/data/scene.ts index 177b8dfbd8..6e3e65a66f 100644 --- a/src/data/scene.ts +++ b/src/data/scene.ts @@ -4,6 +4,7 @@ import { } from "home-assistant-js-websocket"; import { HomeAssistant, ServiceCallResponse } from "../types"; +import { navigate } from "../common/navigate"; export const SCENE_IGNORED_DOMAINS = [ "sensor", @@ -18,6 +19,22 @@ export const SCENE_IGNORED_DOMAINS = [ "zone", ]; +let inititialSceneEditorData: Partial | undefined; + +export const showSceneEditor = ( + el: HTMLElement, + data?: Partial +) => { + inititialSceneEditorData = data; + navigate(el, "/config/scene/edit/new"); +}; + +export const getSceneEditorInitData = () => { + const data = inititialSceneEditorData; + inititialSceneEditorData = undefined; + return data; +}; + export interface SceneEntity extends HassEntityBase { attributes: HassEntityAttributeBase & { id?: string }; } diff --git a/src/data/script.ts b/src/data/script.ts index 781ed79af5..c52979b664 100644 --- a/src/data/script.ts +++ b/src/data/script.ts @@ -5,6 +5,7 @@ import { HassEntityBase, HassEntityAttributeBase, } from "home-assistant-js-websocket"; +import { navigate } from "../common/navigate"; export interface ScriptEntity extends HassEntityBase { attributes: HassEntityAttributeBase & { @@ -65,3 +66,19 @@ export const triggerScript = ( export const deleteScript = (hass: HomeAssistant, objectId: string) => hass.callApi("DELETE", `config/script/config/${objectId}`); + +let inititialScriptEditorData: Partial | undefined; + +export const showScriptEditor = ( + el: HTMLElement, + data?: Partial +) => { + inititialScriptEditorData = data; + navigate(el, "/config/script/new"); +}; + +export const getScriptEditorInitData = () => { + const data = inititialScriptEditorData; + inititialScriptEditorData = undefined; + return data; +}; diff --git a/src/panels/config/devices/device-detail/ha-device-automation-card.ts b/src/panels/config/devices/device-detail/ha-device-automation-card.ts index 1cdbe5c6b9..1d512b68a6 100644 --- a/src/panels/config/devices/device-detail/ha-device-automation-card.ts +++ b/src/panels/config/devices/device-detail/ha-device-automation-card.ts @@ -5,12 +5,14 @@ import { DeviceAutomation } from "../../../../data/device_automation"; import "../../../../components/ha-card"; import "../../../../components/ha-chips"; import { showAutomationEditor } from "../../../../data/automation"; +import { showScriptEditor } from "../../../../data/script"; export abstract class HaDeviceAutomationCard< T extends DeviceAutomation > extends LitElement { @property() public hass!: HomeAssistant; @property() public deviceId?: string; + @property() public script = false; @property() public automations: T[] = []; protected headerKey = ""; @@ -46,20 +48,18 @@ export abstract class HaDeviceAutomationCard< return html``; } return html` - -
- ${this.hass.localize(this.headerKey)} -
-
- - this._localizeDeviceAutomation(this.hass, automation) - )} - > - -
-
+

+ ${this.hass.localize(this.headerKey)} +

+
+ + this._localizeDeviceAutomation(this.hass, automation) + )} + > + +
`; } @@ -68,6 +68,10 @@ export abstract class HaDeviceAutomationCard< if (!automation) { return; } + if (this.script) { + showScriptEditor(this, { sequence: [automation] }); + return; + } const data = {}; data[this.type] = [automation]; showAutomationEditor(this, data); diff --git a/src/panels/config/devices/device-detail/ha-device-automation-dialog.ts b/src/panels/config/devices/device-detail/ha-device-automation-dialog.ts new file mode 100644 index 0000000000..d7409e3e4a --- /dev/null +++ b/src/panels/config/devices/device-detail/ha-device-automation-dialog.ts @@ -0,0 +1,184 @@ +import { + LitElement, + html, + css, + CSSResult, + TemplateResult, + property, + customElement, +} from "lit-element"; + +import "../../../../components/ha-dialog"; +import "./ha-device-triggers-card"; +import "./ha-device-conditions-card"; +import "./ha-device-actions-card"; +import { DeviceAutomationDialogParams } from "./show-dialog-device-automation"; +import { HomeAssistant } from "../../../../types"; +import { + DeviceTrigger, + DeviceCondition, + DeviceAction, + fetchDeviceTriggers, + fetchDeviceConditions, + fetchDeviceActions, +} from "../../../../data/device_automation"; + +@customElement("dialog-device-automation") +export class DialogDeviceAutomation extends LitElement { + @property() public hass!: HomeAssistant; + @property() private _triggers: DeviceTrigger[] = []; + @property() private _conditions: DeviceCondition[] = []; + @property() private _actions: DeviceAction[] = []; + @property() private _params?: DeviceAutomationDialogParams; + + public async showDialog(params: DeviceAutomationDialogParams): Promise { + this._params = params; + await this.updateComplete; + } + + protected updated(changedProps): void { + super.updated(changedProps); + + if (!changedProps.has("_params")) { + return; + } + + this._triggers = []; + this._conditions = []; + this._actions = []; + + if (!this._params) { + return; + } + + const { deviceId, script } = this._params; + + fetchDeviceActions(this.hass, deviceId).then( + (actions) => (this._actions = actions) + ); + if (script) { + return; + } + fetchDeviceTriggers(this.hass, deviceId).then( + (triggers) => (this._triggers = triggers) + ); + fetchDeviceConditions(this.hass, deviceId).then( + (conditions) => (this._conditions = conditions) + ); + } + + protected render(): TemplateResult | void { + if (!this._params) { + return html``; + } + + return html` + +
+ ${this._triggers.length || + this._conditions.length || + this._actions.length + ? html` + ${this._triggers.length + ? html` + + ` + : ""} + ${this._conditions.length + ? html` + + ` + : ""} + ${this._actions.length + ? html` + + ` + : ""} + ` + : html``} +
+ + Close + +
+ `; + } + + private _close(): void { + this._params = undefined; + } + + static get styles(): CSSResult[] { + return [ + css` + ha-dialog { + --mdc-dialog-title-ink-color: var(--primary-text-color); + --justify-action-buttons: space-between; + } + @media only screen and (min-width: 600px) { + ha-dialog { + --mdc-dialog-min-width: 600px; + } + } + .form { + padding-bottom: 24px; + } + .location { + display: flex; + } + .location > * { + flex-grow: 1; + min-width: 0; + } + .location > *:first-child { + margin-right: 4px; + } + .location > *:last-child { + margin-left: 4px; + } + ha-location-editor { + margin-top: 16px; + } + ha-user-picker { + margin-top: 16px; + } + mwc-button.warning { + --mdc-theme-primary: var(--google-red-500); + } + .error { + color: var(--google-red-500); + } + a { + color: var(--primary-color); + } + p { + color: var(--primary-text-color); + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "dialog-device-automation": DialogDeviceAutomation; + } +} diff --git a/src/panels/config/devices/device-detail/ha-device-card.ts b/src/panels/config/devices/device-detail/ha-device-card.ts index c0a40927e3..141e3389d2 100644 --- a/src/panels/config/devices/device-detail/ha-device-card.ts +++ b/src/panels/config/devices/device-detail/ha-device-card.ts @@ -1,5 +1,3 @@ -import "../../../../components/ha-card"; - import { DeviceRegistryEntry, computeDeviceName, @@ -27,63 +25,63 @@ export class HaDeviceCard extends LitElement { protected render(): TemplateResult { return html` - -
-
-
${this.device.model}
- ${this.device.manufacturer - ? html` -
- ${this.hass.localize( - "ui.panel.config.integrations.config_entry.manuf", - "manufacturer", - this.device.manufacturer - )} -
- ` - : ""} - ${this.device.area_id - ? html` -
-
- ${this.hass.localize( - "ui.panel.config.integrations.config_entry.area", - "area", - this._computeArea(this.areas, this.device) - )} -
-
- ` - : ""} -
- ${this.device.via_device_id - ? html` +
+ ${this.device.model + ? html` +
${this.device.model}
+ ` + : ""} + ${this.device.manufacturer + ? html` +
+ ${this.hass.localize( + "ui.panel.config.integrations.config_entry.manuf", + "manufacturer", + this.device.manufacturer + )} +
+ ` + : ""} + ${this.device.area_id + ? html` +
${this.hass.localize( - "ui.panel.config.integrations.config_entry.via" - )} - ${this._computeDeviceName( - this.devices, - this.device.via_device_id - )} -
- ` - : ""} - ${this.device.sw_version - ? html` -
- ${this.hass.localize( - "ui.panel.config.integrations.config_entry.firmware", - "version", - this.device.sw_version + "ui.panel.config.integrations.config_entry.area", + "area", + this._computeArea(this.areas, this.device) )}
- ` - : ""} -
- +
+ ` + : ""} + ${this.device.via_device_id + ? html` +
+ ${this.hass.localize( + "ui.panel.config.integrations.config_entry.via" + )} + ${this._computeDeviceName( + this.devices, + this.device.via_device_id + )} +
+ ` + : ""} + ${this.device.sw_version + ? html` +
+ ${this.hass.localize( + "ui.panel.config.integrations.config_entry.firmware", + "version", + this.device.sw_version + )} +
+ ` + : ""} +
`; } diff --git a/src/panels/config/devices/device-detail/ha-device-entities-card.ts b/src/panels/config/devices/device-detail/ha-device-entities-card.ts index 878c9ef091..b8540b6309 100644 --- a/src/panels/config/devices/device-detail/ha-device-entities-card.ts +++ b/src/panels/config/devices/device-detail/ha-device-entities-card.ts @@ -6,8 +6,9 @@ import { customElement, css, CSSResult, + queryAll, + PropertyValues, } from "lit-element"; -import { classMap } from "lit-html/directives/class-map"; import { HomeAssistant } from "../../../../types"; @@ -19,15 +20,13 @@ import "@polymer/paper-item/paper-item-body"; import "../../../../components/ha-card"; import "../../../../components/ha-icon"; -import "../../../../components/ha-switch"; import { showEntityRegistryDetailDialog } from "../../entities/show-dialog-entity-registry-detail"; -import { fireEvent } from "../../../../common/dom/fire_event"; import { computeDomain } from "../../../../common/entity/compute_domain"; import { domainIcon } from "../../../../common/entity/domain_icon"; -// tslint:disable-next-line -import { HaSwitch } from "../../../../components/ha-switch"; import { EntityRegistryStateEntry } from "../ha-config-device-page"; import { addEntitiesToLovelaceView } from "../../../lovelace/editor/add-entities-to-view"; +import { createRowElement } from "../../../lovelace/create-element/create-row-element"; +import { LovelaceRow } from "../../../lovelace/entity-rows/types"; @customElement("ha-device-entities-card") export class HaDeviceEntitiesCard extends LitElement { @@ -36,66 +35,61 @@ export class HaDeviceEntitiesCard extends LitElement { @property() public entities!: EntityRegistryStateEntry[]; @property() public narrow!: boolean; @property() private _showDisabled = false; + @queryAll("#entities > *") private _entityRows?: LovelaceRow[]; + + protected updated(changedProps: PropertyValues): void { + super.updated(changedProps); + if (!changedProps.has("hass")) { + return; + } + this._entityRows?.forEach((element) => { + element.hass = this.hass; + }); + } protected render(): TemplateResult { + const disabledEntities: EntityRegistryStateEntry[] = []; return html` - - - ${this.hass.localize( - "ui.panel.config.entities.picker.filter.show_disabled" - )} - - + ${this.entities.length ? html` - ${this.entities.map((entry: EntityRegistryStateEntry) => { - if (!this._showDisabled && entry.disabled_by) { - return ""; - } - const stateObj = this.hass.states[entry.entity_id]; - return html` - - ${stateObj - ? html` - - ` - : html` - - `} - -
${entry.stateName}
-
${entry.entity_id}
-
-
- ${stateObj - ? html` - - ` - : ""} - -
-
- `; - })} +
+ ${this.entities.map((entry: EntityRegistryStateEntry) => { + if (entry.disabled_by) { + disabledEntities.push(entry); + return ""; + } + return this.hass.states[entry.entity_id] + ? this._renderEntity(entry) + : this._renderEntry(entry); + })} +
+ ${disabledEntities.length + ? !this._showDisabled + ? html` + + ` + : html` + ${disabledEntities.map((entry) => + this._renderEntry(entry) + )} + + ` + : ""}
${this.hass.localize( @@ -119,22 +113,48 @@ export class HaDeviceEntitiesCard extends LitElement { `; } - private _showDisabledChanged(ev: Event) { - this._showDisabled = (ev.target as HaSwitch).checked; + private _toggleShowDisabled() { + this._showDisabled = !this._showDisabled; } - private _openEditEntry(ev: MouseEvent): void { - const entry = (ev.currentTarget! as any).closest("paper-icon-item").entry; + private _renderEntity(entry: EntityRegistryStateEntry): TemplateResult { + const element = createRowElement({ entity: entry.entity_id }); + if (this.hass) { + element.hass = this.hass; + } + // @ts-ignore + element.entry = entry; + element.addEventListener("hass-more-info", (ev) => this._openEditEntry(ev)); + + return html` +
${element}
+ `; + } + + private _renderEntry(entry: EntityRegistryStateEntry): TemplateResult { + return html` + + + +
+ ${entry.stateName || entry.entity_id} +
+
+
+ `; + } + + private _openEditEntry(ev: Event): void { + ev.stopPropagation(); + const entry = (ev.currentTarget! as any).entry; showEntityRegistryDetailDialog(this, { entry, }); } - private _openMoreInfo(ev: MouseEvent) { - const entry = (ev.currentTarget! as any).closest("paper-icon-item").entry; - fireEvent(this, "hass-more-info", { entityId: entry.entity_id }); - } - private _addToLovelaceView(): void { addEntitiesToLovelaceView( this, @@ -158,11 +178,32 @@ export class HaDeviceEntitiesCard extends LitElement { .disabled-entry { color: var(--secondary-text-color); } - state-badge { + #entities > * { + margin: 8px; + } + paper-icon-item { + min-height: 40px; + padding: 0 8px; cursor: pointer; } - paper-icon-item:not(.disabled-entry) paper-item-body { + .name { + font-size: 14px; + } + button.show-more { + color: var(--primary-color); + text-align: left; cursor: pointer; + background: none; + border-width: initial; + border-style: none; + border-color: initial; + border-image: initial; + padding: 16px; + font: inherit; + } + button.show-more:focus { + outline: none; + text-decoration: underline; } `; } diff --git a/src/panels/config/devices/device-detail/show-dialog-device-automation.ts b/src/panels/config/devices/device-detail/show-dialog-device-automation.ts new file mode 100644 index 0000000000..5bd92b0a72 --- /dev/null +++ b/src/panels/config/devices/device-detail/show-dialog-device-automation.ts @@ -0,0 +1,22 @@ +import { fireEvent } from "../../../../common/dom/fire_event"; + +export interface DeviceAutomationDialogParams { + deviceId: string; + script?: boolean; +} + +export const loadDeviceAutomationDialog = () => + import( + /* webpackChunkName: "device-automation-dialog" */ "./ha-device-automation-dialog" + ); + +export const showDeviceAutomationDialog = ( + element: HTMLElement, + detailParams: DeviceAutomationDialogParams +): void => { + fireEvent(element, "show-dialog", { + dialogTag: "dialog-device-automation", + dialogImport: loadDeviceAutomationDialog, + dialogParams: detailParams, + }); +}; diff --git a/src/panels/config/devices/ha-config-device-page.ts b/src/panels/config/devices/ha-config-device-page.ts index 336d4642ff..c278250d9c 100644 --- a/src/panels/config/devices/ha-config-device-page.ts +++ b/src/panels/config/devices/ha-config-device-page.ts @@ -9,14 +9,13 @@ import { import memoizeOne from "memoize-one"; +import "@polymer/paper-tooltip/paper-tooltip"; + import "../../../layouts/hass-tabs-subpage"; import "../../../layouts/hass-error-screen"; import "../ha-config-section"; import "./device-detail/ha-device-card"; -import "./device-detail/ha-device-triggers-card"; -import "./device-detail/ha-device-conditions-card"; -import "./device-detail/ha-device-actions-card"; import "./device-detail/ha-device-entities-card"; import { HomeAssistant, Route } from "../../../types"; import { ConfigEntry } from "../../../data/config_entries"; @@ -33,19 +32,15 @@ import { loadDeviceRegistryDetailDialog, showDeviceRegistryDetailDialog, } from "../../../dialogs/device-registry-detail/show-dialog-device-registry-detail"; - -import { - DeviceTrigger, - DeviceAction, - DeviceCondition, - fetchDeviceTriggers, - fetchDeviceConditions, - fetchDeviceActions, -} from "../../../data/device_automation"; +import "../../../components/ha-icon-next"; import { compare } from "../../../common/string/compare"; import { computeStateName } from "../../../common/entity/compute_state_name"; import { createValidEntityId } from "../../../common/entity/valid_entity_id"; import { configSections } from "../ha-panel-config"; +import { RelatedResult, findRelated } from "../../../data/search"; +import { SceneEntities, showSceneEditor } from "../../../data/scene"; +import { navigate } from "../../../common/navigate"; +import { showDeviceAutomationDialog } from "./device-detail/show-dialog-device-automation"; export interface EntityRegistryStateEntry extends EntityRegistryEntry { stateName?: string; @@ -59,12 +54,11 @@ export class HaConfigDevicePage extends LitElement { @property() public entities!: EntityRegistryEntry[]; @property() public areas!: AreaRegistryEntry[]; @property() public deviceId!: string; - @property() public narrow!: boolean; + @property({ type: Boolean, reflect: true }) public narrow!: boolean; + @property() public isWide!: boolean; @property() public showAdvanced!: boolean; @property() public route!: Route; - @property() private _triggers: DeviceTrigger[] = []; - @property() private _conditions: DeviceCondition[] = []; - @property() private _actions: DeviceAction[] = []; + @property() private _related?: RelatedResult; private _device = memoizeOne( ( @@ -97,25 +91,10 @@ export class HaConfigDevicePage extends LitElement { loadDeviceRegistryDetailDialog(); } - protected updated(changedProps): void { + protected updated(changedProps) { super.updated(changedProps); - if (changedProps.has("deviceId")) { - if (this.deviceId) { - fetchDeviceTriggers(this.hass, this.deviceId).then( - (triggers) => (this._triggers = triggers) - ); - fetchDeviceConditions(this.hass, this.deviceId).then( - (conditions) => (this._conditions = conditions) - ); - fetchDeviceActions(this.hass, this.deviceId).then( - (actions) => (this._actions = actions) - ); - } else { - this._triggers = []; - this._conditions = []; - this._actions = []; - } + this._findRelated(); } } @@ -146,70 +125,176 @@ export class HaConfigDevicePage extends LitElement { icon="hass:settings" @click=${this._showSettings} > - - ${this.hass.localize("ui.panel.config.devices.info")} - - ${this.hass.localize("ui.panel.config.devices.details")} - - - ${entities.length - ? html` -
+
+
+
+

${device.name_by_user || device.name}

+ +
+ + ${ + entities.length + ? html` + + + ` + : html`` + } +
+
+
+ ${ + this._related?.automation?.length + ? this._related.automation.map((automation) => { + const state = this.hass.states[automation]; + return state + ? html` +
+ + + ${state.attributes.friendly_name || + automation} + + + + ${!state.attributes.id + ? html` + ${this.hass.localize( + "ui.panel.config.devices.cant_edit" + )} + + ` + : ""} +
+ ` + : ""; + }) + : html` + ${this.hass.localize( + "ui.panel.config.devices.automation.no_automations" + )} + ` + } +
+ ${this.hass.localize( - "ui.panel.config.devices.entities.entities" + "ui.panel.config.devices.automation.create" )} +
- - - ` - : html``} - ${this._triggers.length || - this._conditions.length || - this._actions.length - ? html` -
- ${this.hass.localize("ui.panel.config.devices.automations")} + +
+
+ ${ + this._related?.scene?.length + ? this._related.scene.map((scene) => { + const state = this.hass.states[scene]; + return state + ? html` +
+ + + ${state.attributes.friendly_name || scene} + + + + ${!state.attributes.id + ? html` + ${this.hass.localize( + "ui.panel.config.devices.cant_edit" + )} + + ` + : ""} +
+ ` + : ""; + }) + : html` + ${this.hass.localize( + "ui.panel.config.devices.scene.no_scenes" + )} + ` + } +
+ + ${this.hass.localize( + "ui.panel.config.devices.scene.create" + )}
- ${this._triggers.length - ? html` - - ` - : ""} - ${this._conditions.length - ? html` - - ` - : ""} - ${this._actions.length - ? html` - - ` - : ""} - ` - : html``} +
+ ${ + this._related?.script?.length + ? this._related.script.map((script) => { + const state = this.hass.states[script]; + return state + ? html` + + + ${state.attributes.friendly_name || script} + + + + ` + : ""; + }) + : html` + + ${this.hass.localize( + "ui.panel.config.devices.script.no_scripts" + )} + ` + } +
+ + ${this.hass.localize("ui.panel.config.devices.script.create")} + +
+
+
+
+
- - `; + `; } private _computeEntityName(entity) { @@ -220,6 +305,50 @@ export class HaConfigDevicePage extends LitElement { return state ? computeStateName(state) : null; } + private async _findRelated() { + this._related = await findRelated(this.hass, "device", this.deviceId); + } + + private _createScene() { + const entities: SceneEntities = {}; + this._entities(this.deviceId, this.entities).forEach((entity) => { + entities[entity.entity_id] = ""; + }); + showSceneEditor(this, { + entities, + }); + } + + private _openScene(ev: Event) { + const state = (ev.currentTarget as any).scene; + if (state.attributes.id) { + navigate(this, `/config/scene/edit/${state.attributes.id}`); + } + } + + private _openScript(ev: Event) { + const script = (ev.currentTarget as any).script; + navigate(this, `/config/script/edit/${script}`); + } + + private _openAutomation(ev: Event) { + const state = (ev.currentTarget as any).automation; + if (state.attributes.id) { + navigate(this, `/config/automation/edit/${state.attributes.id}`); + } + } + + private _showScriptDialog() { + showDeviceAutomationDialog(this, { deviceId: this.deviceId, script: true }); + } + + private _showAutomationDialog() { + showDeviceAutomationDialog(this, { + deviceId: this.deviceId, + script: false, + }); + } + private async _showSettings() { const device = this._device(this.deviceId, this.devices)!; showDeviceRegistryDetailDialog(this, { @@ -282,20 +411,81 @@ export class HaConfigDevicePage extends LitElement { static get styles(): CSSResult { return css` - .header { - font-family: var(--paper-font-display1_-_font-family); + .container { + display: flex; + flex-wrap: wrap; + margin: auto; + max-width: 1000px; + margin-top: 32px; + margin-bottom: 32px; + } + + .device-info { + padding: 16px; + } + + .show-more { + } + + h1 { + margin-top: 0; + font-family: var(--paper-font-headline_-_font-family); -webkit-font-smoothing: var( - --paper-font-display1_-_-webkit-font-smoothing + --paper-font-headline_-_-webkit-font-smoothing ); - font-size: var(--paper-font-display1_-_font-size); - font-weight: var(--paper-font-display1_-_font-weight); - letter-spacing: var(--paper-font-display1_-_letter-spacing); - line-height: var(--paper-font-display1_-_line-height); + 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); opacity: var(--dark-primary-opacity); } - ha-config-section *:last-child { - padding-bottom: 24px; + .left, + .column, + .fullwidth { + padding: 8px; + box-sizing: border-box; + } + + .left { + width: 33.33%; + padding-bottom: 0; + } + + .right { + width: 66.66%; + display: flex; + flex-wrap: wrap; + } + + .fullwidth { + width: 100%; + } + + .column { + width: 50%; + } + + .column > *:not(:first-child) { + margin-top: 16px; + } + + :host([narrow]) .left, + :host([narrow]) .right, + :host([narrow]) .column { + width: 100%; + } + + :host([narrow]) .container { + margin-top: 0; + } + + paper-item { + cursor: pointer; + } + + paper-item.no-link { + cursor: default; } `; } diff --git a/src/panels/config/scene/ha-scene-editor.ts b/src/panels/config/scene/ha-scene-editor.ts index d1b10ff3f5..f63dce96b9 100644 --- a/src/panels/config/scene/ha-scene-editor.ts +++ b/src/panels/config/scene/ha-scene-editor.ts @@ -39,6 +39,7 @@ import { SceneEntities, applyScene, activateScene, + getSceneEditorInitData, } from "../../../data/scene"; import { fireEvent } from "../../../common/dom/fire_event"; import { @@ -391,6 +392,7 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) { super.updated(changedProps); const oldscene = changedProps.get("scene") as SceneEntity; + if ( changedProps.has("scene") && this.scene && @@ -403,10 +405,16 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) { if (changedProps.has("creatingNew") && this.creatingNew && this.hass) { this._dirty = false; + const initData = getSceneEditorInitData(); this._config = { name: this.hass.localize("ui.panel.config.scene.editor.default_name"), entities: {}, + ...initData, }; + if (initData) { + this._initEntities(this._config); + this._dirty = true; + } } if (changedProps.has("_entityRegistryEntries")) { @@ -458,24 +466,7 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) { config.entities = {}; } - this._entities = Object.keys(config.entities); - - this._entities.forEach((entity) => { - this._storeState(entity); - }); - - const filteredEntityReg = this._entityRegistryEntries.filter((entityReg) => - this._entities.includes(entityReg.entity_id) - ); - - for (const entityReg of filteredEntityReg) { - if (!entityReg.device_id) { - continue; - } - if (!this._devices.includes(entityReg.device_id)) { - this._devices = [...this._devices, entityReg.device_id]; - } - } + this._initEntities(config); const { context } = await activateScene(this.hass, this.scene!.entity_id); @@ -489,6 +480,25 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) { this._config = config; } + private _initEntities(config: SceneConfig) { + this._entities = Object.keys(config.entities); + this._entities.forEach((entity) => this._storeState(entity)); + + const filteredEntityReg = this._entityRegistryEntries.filter((entityReg) => + this._entities.includes(entityReg.entity_id) + ); + this._devices = []; + + for (const entityReg of filteredEntityReg) { + if (!entityReg.device_id) { + continue; + } + if (!this._devices.includes(entityReg.device_id)) { + this._devices = [...this._devices, entityReg.device_id]; + } + } + } + private _entityPicked(ev: CustomEvent) { const entityId = ev.detail.value; (ev.target as any).value = ""; diff --git a/src/panels/config/script/ha-script-editor.ts b/src/panels/config/script/ha-script-editor.ts index 3dcd615c03..b6134d7de3 100644 --- a/src/panels/config/script/ha-script-editor.ts +++ b/src/panels/config/script/ha-script-editor.ts @@ -20,6 +20,7 @@ import { ScriptEntity, ScriptConfig, deleteScript, + getScriptEditorInitData, } from "../../../data/script"; import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box"; import "../../../layouts/ha-app-layout"; @@ -188,10 +189,12 @@ export class HaScriptEditor extends LitElement { } if (changedProps.has("creatingNew") && this.creatingNew && this.hass) { - this._dirty = false; + const initData = getScriptEditorInitData(); + this._dirty = initData ? true : false; this._config = { alias: this.hass.localize("ui.panel.config.script.editor.default_name"), sequence: [{ service: "" }], + ...initData, }; } } diff --git a/src/panels/lovelace/cards/hui-entities-card.ts b/src/panels/lovelace/cards/hui-entities-card.ts index 688769e54e..2f6dea1af7 100644 --- a/src/panels/lovelace/cards/hui-entities-card.ts +++ b/src/panels/lovelace/cards/hui-entities-card.ts @@ -39,11 +39,11 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard { return { entities: [] }; } - @property() protected _config?: EntitiesCardConfig; + @property() private _config?: EntitiesCardConfig; - protected _hass?: HomeAssistant; + private _hass?: HomeAssistant; - protected _configEntities?: EntitiesCardEntityConfig[]; + private _configEntities?: EntitiesCardEntityConfig[]; set hass(hass: HomeAssistant) { this._hass = hass; diff --git a/src/translations/en.json b/src/translations/en.json index 9a3ca7ac1e..02f233872a 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1271,6 +1271,9 @@ "name": "Name", "update": "Update", "automation": { + "automations": "Automations", + "no_automations": "No automations", + "create": "Create automation with device", "triggers": { "caption": "Do something when..." }, @@ -1281,15 +1284,25 @@ "caption": "When something is triggered..." } }, + "script": { + "scripts": "Scripts", + "no_scripts": "No scripts", + "create": "Create script with device" + }, + "scene": { + "scenes": "Scenes", + "no_scenes": "No scenes", + "create": "Create scene with device" + }, + "cant_edit": "You can only edit items that are created in the UI.", "device_not_found": "Device not found.", - "info": "Device info", - "details": "Here are all the details of your device.", "entities": { "entities": "Entities", - "add_entities_lovelace": "Add all device entities to Lovelace UI", + "add_entities_lovelace": "Add to Lovelace", "none": "This device has no entities" }, - "automations": "Automations", + "scripts": "Scripts", + "scenes": "Scenes", "confirm_rename_entity_ids": "Do you also want to rename the entity id's of your entities?", "data_table": { "device": "Device", From 2fb7a31c7684028217e30203ca1d4e9212a50be2 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Wed, 29 Jan 2020 00:32:22 +0000 Subject: [PATCH 119/126] [ci skip] Translation update --- translations/en.json | 18 +++++++- translations/ko.json | 35 ++++++++++++---- translations/pl.json | 99 +++++++++++++++++++++++++++++++++++++++++--- translations/sv.json | 10 +++++ 4 files changed, 148 insertions(+), 14 deletions(-) diff --git a/translations/en.json b/translations/en.json index e5e7205973..c4d055e577 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1222,14 +1222,18 @@ "actions": { "caption": "When something is triggered..." }, + "automations": "Automations", "conditions": { "caption": "Only do something if..." }, + "create": "Create automation with device", + "no_automations": "No automations", "triggers": { "caption": "Do something when..." } }, "automations": "Automations", + "cant_edit": "You can only edit items that are created in the UI.", "caption": "Devices", "confirm_rename_entity_ids": "Do you also want to rename the entity id's of your entities?", "data_table": { @@ -1244,12 +1248,24 @@ "details": "Here are all the details of your device.", "device_not_found": "Device not found.", "entities": { - "add_entities_lovelace": "Add all device entities to Lovelace UI", + "add_entities_lovelace": "Add to Lovelace", "entities": "Entities", "none": "This device has no entities" }, "info": "Device info", "name": "Name", + "scene": { + "create": "Create scene with device", + "no_scenes": "No scenes", + "scenes": "Scenes" + }, + "scenes": "Scenes", + "script": { + "create": "Create script with device", + "no_scripts": "No scripts", + "scripts": "Scripts" + }, + "scripts": "Scripts", "unknown_error": "Unknown error", "unnamed_device": "Unnamed device", "update": "Update" diff --git a/translations/ko.json b/translations/ko.json index 33e0fee4e8..1bdebb83ce 100644 --- a/translations/ko.json +++ b/translations/ko.json @@ -405,7 +405,7 @@ "arm_home": "재실 경비", "arm_night": "야간 경비", "armed_custom_bypass": "사용자 우회", - "clear_code": "지움", + "clear_code": "지우기", "code": "비밀번호", "disarm": "경비 해제" }, @@ -544,10 +544,24 @@ "yes": "예" }, "components": { + "area-picker": { + "add_dialog": { + "add": "추가", + "failed_create_area": "영역을 만들지 못했습니다.", + "name": "이름", + "text": "새로운 영역의 이름을 입력해주세요.", + "title": "새로운 영역 추가" + }, + "add_new": "새로운 영역 추가...", + "area": "영역", + "clear": "지우기", + "show_areas": "영역 표시" + }, "device-picker": { "clear": "지우기", "device": "기기", - "show_devices": "기기 표시" + "show_devices": "기기 표시", + "toggle": "토글" }, "entity": { "entity-picker": { @@ -562,9 +576,13 @@ }, "related-items": { "area": "영역", + "automation": "다음 자동화의 일부", "device": "기기", "entity": "관련된 구성요소", - "integration": "통합 구성요소" + "group": "다음 그룹의 일부", + "integration": "통합 구성요소", + "scene": "다음 씬의 일부", + "script": "다음 스크립트의 일부" }, "relative_time": { "duration": { @@ -1231,8 +1249,10 @@ "none": "이 기기는 구성요소가 없습니다" }, "info": "기기 정보", + "name": "이름", "unknown_error": "알 수 없는 오류", - "unnamed_device": "이름이 없는 기기" + "unnamed_device": "이름이 없는 기기", + "update": "업데이트" }, "entities": { "caption": "구성요소", @@ -1327,7 +1347,7 @@ }, "failed_create_area": "영역을 만들지 못했습니다.", "finish": "완료", - "name_new_area": "새 영역의 이름?", + "name_new_area": "새로운 영역의 이름?", "not_all_required_fields": "필수 입력란이 모두 채워지지 않았습니다.", "submit": "확인" }, @@ -1644,6 +1664,7 @@ "update": "업데이트" }, "edit_home_zone": "집의 위치는 일반 설정에서 변경할 수 있습니다.", + "introduction": "영역을 사용하면 지구 상의 특정 지역을 지정할 수 있습니다. 구성원이 구역 내에 있으면 구성요소 상태에서 구역의 이름을 가져옵니다. 자동화 설정 내에서 영역을 트리거 또는 조건으로 사용할 수도 있습니다.", "no_zones_created_yet": "아직 생성된 구역이 없는 것 같습니다." }, "zwave": { @@ -2269,11 +2290,11 @@ "title": "고급 모드" }, "change_password": { - "confirm_new_password": "새 비밀번호 확인", + "confirm_new_password": "새로운 비밀번호 확인", "current_password": "현재 비밀번호", "error_required": "필수 요소", "header": "비밀번호 변경", - "new_password": "새 비밀번호", + "new_password": "새로운 비밀번호", "submit": "변경하기" }, "current_user": "현재 {fullName} 로(으로) 로그인 한 상태 입니다.", diff --git a/translations/pl.json b/translations/pl.json index 084a028ef6..985eddb886 100644 --- a/translations/pl.json +++ b/translations/pl.json @@ -478,6 +478,9 @@ "script": { "execute": "Uruchom" }, + "service": { + "run": "Uruchom" + }, "timer": { "actions": { "cancel": "anuluj", @@ -541,10 +544,24 @@ "yes": "Tak" }, "components": { + "area-picker": { + "add_dialog": { + "add": "Dodaj", + "failed_create_area": "Nie udało się utworzyć obszaru.", + "name": "Nazwa", + "text": "Wprowadź nazwę nowego obszaru.", + "title": "Dodaj nowy obszar" + }, + "add_new": "Dodaj nowy obszar...", + "area": "Obszar", + "clear": "Wyczyść", + "show_areas": "Pokaż obszary" + }, "device-picker": { "clear": "Wyczyść", "device": "Urządzenie", - "show_devices": "Wyświetl urządzenia" + "show_devices": "Wyświetl urządzenia", + "toggle": "Przełącz" }, "entity": { "entity-picker": { @@ -557,6 +574,16 @@ "loading_history": "Ładowanie historii...", "no_history_found": "Nie znaleziono historii." }, + "related-items": { + "area": "Obszar", + "automation": "Element następujących automatyzacji", + "device": "Urządzenie", + "entity": "Powiązane encje", + "group": "Element następujących grup", + "integration": "Integracja", + "scene": "Element następujących scen", + "script": "Element następujących skryptów" + }, "relative_time": { "duration": { "day": "{count} {count, plural,\n one {dzień}\n other {dni}\n}", @@ -587,6 +614,29 @@ "domain_toggler": { "title": "Przełączanie domen" }, + "entity_registry": { + "control": "Kontrola", + "dismiss": "Odrzuć", + "editor": { + "confirm_delete": "Na pewno chcesz usunąć ten wpis?", + "delete": "USUŃ", + "enabled_cause": "Wyłączone przez {cause}.", + "enabled_description": "Wyłączone encje nie zostaną dodane do Home Assistant'a.", + "enabled_label": "Włącz encję", + "entity_id": "Identyfikator encji", + "name": "Nadpisanie nazwy", + "note": "Uwaga: może to jeszcze nie działać ze wszystkimi integracjami.", + "unavailable": "Ta encja nie jest obecnie dostępna.", + "update": "AKTUALIZUJ" + }, + "related": "Powiązane", + "settings": "Ustawienia" + }, + "generic": { + "cancel": "Anuluj", + "default_confirmation_title": "Jesteś pewny?", + "ok": "OK" + }, "more_info_control": { "dismiss": "Zamknij okno dialogowe", "edit": "Edytuj encję", @@ -598,7 +648,8 @@ "remove_intro": "Jeśli encja nie jest używana możesz ją usunąć." }, "script": { - "last_action": "Ostatnia akcja" + "last_action": "Ostatnia akcja", + "last_triggered": "Ostatnie uruchomienie" }, "settings": "Ustawienia encji", "sun": { @@ -695,7 +746,7 @@ "create": "UTWÓRZ", "default_name": "Nowy obszar", "delete": "USUŃ", - "update": "UAKTUALNIJ" + "update": "AKTUALIZUJ" }, "no_areas": "Wygląda na to, że jeszcze nie masz zdefiniowanych obszarów!", "picker": { @@ -1198,8 +1249,10 @@ "none": "To urządzenie nie ma żadnych encji" }, "info": "Informacje o urządzeniu", + "name": "Nazwa", "unknown_error": "Nieznany błąd", - "unnamed_device": "Nienazwane urządzenie" + "unnamed_device": "Nienazwane urządzenie", + "update": "Aktualizuj" }, "entities": { "caption": "Rejestr encji", @@ -1212,9 +1265,11 @@ "enabled_cause": "Wyłączone przez {cause}.", "enabled_description": "Wyłączone encje nie będą dodawane do Home Assistant'a.", "enabled_label": "Włącz encję", + "entity_id": "Identyfikator encji", + "name": "Nadpisanie nazwy", "note": "Uwaga: może to jeszcze nie działać ze wszystkimi integracjami.", "unavailable": "Ta encja nie jest obecnie dostępna.", - "update": "UAKTUALNIJ" + "update": "AKTUALIZUJ" }, "picker": { "disable_selected": { @@ -1422,8 +1477,10 @@ "group": "Grupy", "heading": "Ponowne wczytanie konfiguracji", "introduction": "Niektóre części konfiguracji można wczytać od nowa bez konieczności ponownego uruchamiania Home Assistant'a. Naciśnięcie poniższych przycisków wczyta ponownie daną część konfiguracji.", + "person": "Osoby", "scene": "Sceny", - "script": "Skrypty" + "script": "Skrypty", + "zone": "Strefy" }, "server_management": { "confirm_restart": "Na pewno chcesz ponownie uruchomić Home Assistant'a?", @@ -1584,6 +1641,32 @@ }, "title": "Zigbee Home Automation" }, + "zone": { + "add_zone": "Dodaj strefę", + "caption": "Strefy", + "configured_in_yaml": "Stref skonfigurowanych za pomocą pliku configuration.yaml nie można edytować za pomocą interfejsu użytkownika.", + "confirm_delete": "Na pewno chcesz usunąć tę strefę?", + "create_zone": "Utwórz strefę", + "description": "Zarządzaj strefami, w których chcesz śledzić osoby.", + "detail": { + "create": "Utwórz", + "delete": "Usuń", + "icon": "Ikona", + "icon_error_msg": "Ikona powinna mieć format prefiks:nazwa-ikony, na przykład: mdi:home", + "latitude": "Szerokość geograficzna", + "longitude": "Długość geograficzna", + "name": "Nazwa", + "new_zone": "Nowa strefa", + "passive": "Pasywna", + "passive_note": "Strefy pasywne są ukryte w interfejsie użytkownika i nie są używane jako lokalizacje dla śledzonych urządzeń. Są one przydatne, jeśli chcesz w automatyzacjach.", + "radius": "Promień", + "required_error_msg": "To pole jest wymagane", + "update": "Aktualizuj" + }, + "edit_home_zone": "Lokalizację domu można zmienić w konfiguracji ogólnej.", + "introduction": "Strefy pozwalają określić regiony na ziemi. Gdy dana osoba znajduje się w strefie, jej encja pobierze stan z nazwy strefy. Strefy mogą być również używane jako wyzwalacz lub warunek w automatyzacjach.", + "no_zones_created_yet": "Wygląda na to, że nie utworzyłeś jeszcze żadnych stref." + }, "zwave": { "caption": "Z-Wave", "common": { @@ -1776,6 +1859,9 @@ "no_devices": "Ta strona pozwala kontrolować urządzenia, ale wygląda na to, że nie masz jeszcze żadnych skonfigurowanych. Przejdź na stronę integracji, aby rozpocząć.", "title": "Witaj w domu" }, + "entities": { + "never_triggered": "Nigdy nie uruchomiono" + }, "picture-elements": { "call_service": "Wywołaj usługę {name}", "hold": "Przytrzymanie:", @@ -2200,6 +2286,7 @@ "description": "Home Assistant domyślnie ukrywa zaawansowane funkcje i opcje. Możesz włączyć do nich dostęp za pomocą tej opcji. Jest to ustawienie przyporządkowane do użytkownika i nie wpływa na pozostałych użytkowników korzystających z Home Assistant'a.", "hint_enable": "Brakuje opcji konfiguracji? Włącz tryb zaawansowany", "link_profile_page": "strona Twojego profilu", + "link_promo": "Dowiedz się więcej", "title": "Tryb zaawansowany" }, "change_password": { diff --git a/translations/sv.json b/translations/sv.json index 70b7f00493..6d0346d571 100644 --- a/translations/sv.json +++ b/translations/sv.json @@ -556,6 +556,11 @@ "loading_history": "Laddar historik...", "no_history_found": "Ingen historik hittad." }, + "related-items": { + "area": "Område", + "device": "Enhet", + "integration": "Integration" + }, "relative_time": { "duration": { "day": "{count} {count, plural,\none {dag}\nother {dagar}\n}", @@ -586,6 +591,9 @@ "domain_toggler": { "title": "Växla domäner" }, + "generic": { + "default_confirmation_title": "Är du säker?" + }, "more_info_control": { "dismiss": "Avfärda", "edit": "Redigera entitet", @@ -921,6 +929,7 @@ "alexa": { "title": "" }, + "connected": "Ansluten", "fetching_subscription": "Hämtar prenumeration...", "google": { "config_documentation": "Konfigureringsdokumentation", @@ -932,6 +941,7 @@ "integrations_link_all_features": " alla tillgängliga funktioner", "manage_account": "Hantera konto", "nabu_casa_account": "Nabu Casa konto", + "not_connected": "Ej ansluten", "remote": { "link_learn_how_it_works": "Lär dig hur det fungerar" }, From 27ebcc1bda3f0a2baa10b203e32c8679b013c21f Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 29 Jan 2020 17:09:56 +0100 Subject: [PATCH 120/126] Remove balloob from cast page (#4645) --- cast/src/launcher/layout/hc-layout.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cast/src/launcher/layout/hc-layout.ts b/cast/src/launcher/layout/hc-layout.ts index 87ec571fe3..aaf9d8c17a 100644 --- a/cast/src/launcher/layout/hc-layout.ts +++ b/cast/src/launcher/layout/hc-layout.ts @@ -50,13 +50,12 @@ class HcLayout extends LitElement {
`; } From adec2fc2df0797b7b120731985cca7a42a284481 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 29 Jan 2020 17:51:29 +0100 Subject: [PATCH 121/126] Only update disabled for entity reg if it is a user value. (#4646) --- .../config/entities/entity-registry-settings.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/panels/config/entities/entity-registry-settings.ts b/src/panels/config/entities/entity-registry-settings.ts index 0e4fcb95a8..a0090ff6a4 100644 --- a/src/panels/config/entities/entity-registry-settings.ts +++ b/src/panels/config/entities/entity-registry-settings.ts @@ -20,6 +20,7 @@ import { EntityRegistryEntry, removeEntityRegistryEntry, updateEntityRegistryEntry, + EntityRegistryEntryUpdateParams, } from "../../../data/entity_registry"; import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box"; import { PolymerChangedEvent } from "../../../polymer-types"; @@ -159,12 +160,15 @@ export class EntityRegistrySettings extends LitElement { private async _updateEntry(): Promise { this._submitting = true; + const params: Partial = { + name: this._name.trim() || null, + new_entity_id: this._entityId.trim(), + }; + if (this._disabledBy === null || this._disabledBy === "user") { + params.disabled_by = this._disabledBy; + } try { - await updateEntityRegistryEntry(this.hass!, this._origEntityId, { - name: this._name.trim() || null, - disabled_by: this._disabledBy, - new_entity_id: this._entityId.trim(), - }); + await updateEntityRegistryEntry(this.hass!, this._origEntityId, params); fireEvent(this as HTMLElement, "close-dialog"); } catch (err) { this._error = err.message || "Unknown error"; From 7021fd5809a4155cdd1cd3fc3c538ef87290dd49 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 29 Jan 2020 17:59:37 +0100 Subject: [PATCH 122/126] Make dialogs full width on mobile, and modal (#4642) * Make dialogs full width on mobile, and modal * Fix dialog to bottom screen mobile --- src/components/ha-dialog.ts | 3 ++ .../config/person/dialog-person-detail.ts | 32 ++++++++++++++++--- src/panels/config/zone/dialog-zone-detail.ts | 29 +++++++++++++++-- 3 files changed, 56 insertions(+), 8 deletions(-) diff --git a/src/components/ha-dialog.ts b/src/components/ha-dialog.ts index 600ea6c20b..ff770e7bb7 100644 --- a/src/components/ha-dialog.ts +++ b/src/components/ha-dialog.ts @@ -16,6 +16,9 @@ export class HaDialog extends MwcDialog { .mdc-dialog__actions { justify-content: var(--justify-action-buttons, flex-end); } + .mdc-dialog__container { + align-items: var(--vertial-align-dialog, center); + } `, ]; } diff --git a/src/panels/config/person/dialog-person-detail.ts b/src/panels/config/person/dialog-person-detail.ts index b6e1f3fac7..10e995cf8f 100644 --- a/src/panels/config/person/dialog-person-detail.ts +++ b/src/panels/config/person/dialog-person-detail.ts @@ -55,13 +55,26 @@ class DialogPersonDetail extends LitElement { return html``; } const nameInvalid = this._name.trim() === ""; + const title = html` + ${this._params.entry + ? this._params.entry.name + : this.hass!.localize("ui.panel.config.person.detail.new_person")} + + `; return html`
${this._error @@ -225,11 +238,20 @@ class DialogPersonDetail extends LitElement { return [ css` ha-dialog { - min-width: 400px; - max-width: 600px; + --mdc-dialog-min-width: 400px; + --mdc-dialog-max-width: 600px; --mdc-dialog-title-ink-color: var(--primary-text-color); --justify-action-buttons: space-between; } + /* make dialog fullscreen on small screens */ + @media all and (max-width: 450px), all and (max-height: 500px) { + ha-dialog { + --mdc-dialog-min-width: 100vw; + --mdc-dialog-max-height: 100vh; + --mdc-dialog-shape-radius: 0px; + --vertial-align-dialog: flex-end; + } + } .form { padding-bottom: 24px; } diff --git a/src/panels/config/zone/dialog-zone-detail.ts b/src/panels/config/zone/dialog-zone-detail.ts index 1e58e52e4d..a4afc11ec2 100644 --- a/src/panels/config/zone/dialog-zone-detail.ts +++ b/src/panels/config/zone/dialog-zone-detail.ts @@ -62,13 +62,26 @@ class DialogZoneDetail extends LitElement { if (!this._params) { return html``; } + const title = html` + ${this._params.entry + ? this._params.entry.name + : this.hass!.localize("ui.panel.config.zone.detail.new_zone")} + + `; return html`
${this._error @@ -252,6 +265,16 @@ class DialogZoneDetail extends LitElement { --mdc-dialog-min-width: 600px; } } + + /* make dialog fullscreen on small screens */ + @media all and (max-width: 450px), all and (max-height: 500px) { + ha-dialog { + --mdc-dialog-min-width: 100vw; + --mdc-dialog-max-height: 100vh; + --mdc-dialog-shape-radius: 0px; + --vertial-align-dialog: flex-end; + } + } .form { padding-bottom: 24px; } From 7ab9257f5e51cb22a3dced002a027dfb2645164c Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 29 Jan 2020 19:01:35 +0100 Subject: [PATCH 123/126] Fix translation advanced mode (#4647) --- src/panels/config/dashboard/ha-config-dashboard.ts | 4 ++-- src/translations/en.json | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/panels/config/dashboard/ha-config-dashboard.ts b/src/panels/config/dashboard/ha-config-dashboard.ts index 5114a62631..2f3b90ce8b 100644 --- a/src/panels/config/dashboard/ha-config-dashboard.ts +++ b/src/panels/config/dashboard/ha-config-dashboard.ts @@ -88,11 +88,11 @@ class HaConfigDashboard extends LitElement { ? html`
${this.hass.localize( - "ui.panel.profile.advanced_mode.hint_enable" + "ui.panel.config.advanced_mode.hint_enable" )} ${this.hass.localize( - "ui.panel.profile.advanced_mode.link_profile_page" + "ui.panel.config.advanced_mode.link_profile_page" )}.
diff --git a/src/translations/en.json b/src/translations/en.json index 02f233872a..6eaae9ff61 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -712,6 +712,10 @@ "config": { "header": "Configure Home Assistant", "introduction": "Here it is possible to configure your components and Home Assistant. Not everything is possible to configure from the UI yet, but we're working on it.", + "advanced_mode": { + "hint_enable": "Missing config options? Enable advanced mode on", + "link_profile_page": "your profile page" + }, "common": { "editor": { "confirm_unsaved": "You have unsaved changes. Are you sure you want to leave?" @@ -1972,8 +1976,6 @@ "advanced_mode": { "title": "Advanced Mode", "description": "Unlocks advanced features.", - "hint_enable": "Missing config options? Enable advanced mode on", - "link_profile_page": "your profile page", "link_promo": "Learn more" }, "refresh_tokens": { From dd8c568a2cfa3e0b87355ad75c3eed591f41f72f Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 29 Jan 2020 19:03:43 +0100 Subject: [PATCH 124/126] Fix action directive double tab iOS issues (#4639) --- .../directives/action-handler-directive.ts | 103 +++++++++++------- 1 file changed, 61 insertions(+), 42 deletions(-) diff --git a/src/panels/lovelace/common/directives/action-handler-directive.ts b/src/panels/lovelace/common/directives/action-handler-directive.ts index e9f9597b9e..87c5168019 100644 --- a/src/panels/lovelace/common/directives/action-handler-directive.ts +++ b/src/panels/lovelace/common/directives/action-handler-directive.ts @@ -1,5 +1,7 @@ import { directive, PropertyPart } from "lit-html"; import "@material/mwc-ripple"; +// tslint:disable-next-line +import { Ripple } from "@material/mwc-ripple"; import { ActionHandlerOptions, ActionHandlerDetail, @@ -26,22 +28,16 @@ declare global { } class ActionHandler extends HTMLElement implements ActionHandler { - public holdTime: number; - public ripple: any; - protected timer: number | undefined; - protected held: boolean; - protected cooldownStart: boolean; - protected cooldownEnd: boolean; - private dblClickTimeout: number | undefined; + public holdTime = 500; + public ripple: Ripple; + protected timer?: number; + protected held = false; + protected touch?: boolean; + private dblClickTimeout?: number; constructor() { super(); - this.holdTime = 500; this.ripple = document.createElement("mwc-ripple"); - this.timer = undefined; - this.held = false; - this.cooldownStart = false; - this.cooldownEnd = false; } public connectedCallback() { @@ -96,10 +92,23 @@ class ActionHandler extends HTMLElement implements ActionHandler { return false; }); - const clickStart = (ev: Event) => { - if (this.cooldownStart) { + const touchStart = (ev: TouchEvent) => { + if (this.touch === false) { return; } + this.touch = true; + start(ev); + }; + + const clickStart = (ev: MouseEvent) => { + if (this.touch === true) { + return; + } + this.touch = false; + start(ev); + }; + + const start = (ev: Event) => { this.held = false; let x; let y; @@ -115,16 +124,34 @@ class ActionHandler extends HTMLElement implements ActionHandler { this.startAnimation(x, y); this.held = true; }, this.holdTime); - - this.cooldownStart = true; - window.setTimeout(() => (this.cooldownStart = false), 100); }; - const clickEnd = (ev: Event) => { + const touchEnd = (ev: TouchEvent) => { + if (this.touch === false) { + return; + } + end(ev); + }; + + const clickEnd = (ev: MouseEvent) => { + if (this.touch === true) { + return; + } + end(ev); + }; + + const handleEnter = (ev: KeyboardEvent) => { + if (this.touch === true || ev.keyCode !== 13) { + return; + } + this.touch = false; + end(ev); + }; + + const end = (ev: Event) => { if ( - this.cooldownEnd || - (["touchend", "touchcancel"].includes(ev.type) && - this.timer === undefined) + ["touchend", "touchcancel"].includes(ev.type) && + this.timer === undefined ) { return; } @@ -134,41 +161,33 @@ class ActionHandler extends HTMLElement implements ActionHandler { if (this.held) { fireEvent(element, "action", { action: "hold" }); } else if (options.hasDoubleClick) { - if ((ev as MouseEvent).detail === 1 || ev.type === "keyup") { + if ( + (ev.type === "click" && (ev as MouseEvent).detail < 2) || + !this.dblClickTimeout + ) { this.dblClickTimeout = window.setTimeout(() => { + this.dblClickTimeout = undefined; fireEvent(element, "action", { action: "tap" }); }, 250); } else { clearTimeout(this.dblClickTimeout); + this.dblClickTimeout = undefined; fireEvent(element, "action", { action: "double_tap" }); } } else { fireEvent(element, "action", { action: "tap" }); } - this.cooldownEnd = true; - window.setTimeout(() => (this.cooldownEnd = false), 100); + window.setTimeout(() => (this.touch = undefined), 100); }; - const handleEnter = (ev: Event) => { - if ((ev as KeyboardEvent).keyCode === 13) { - return clickEnd(ev); - } - }; + element.addEventListener("touchstart", touchStart, { passive: true }); + element.addEventListener("touchend", touchEnd); + element.addEventListener("touchcancel", touchEnd); + + element.addEventListener("mousedown", clickStart, { passive: true }); + element.addEventListener("click", clickEnd); - element.addEventListener("touchstart", clickStart, { passive: true }); - element.addEventListener("touchend", clickEnd); - element.addEventListener("touchcancel", clickEnd); element.addEventListener("keyup", handleEnter); - - // iOS 13 sends a complete normal touchstart-touchend series of events followed by a mousedown-click series. - // That might be a bug, but until it's fixed, this should make action-handler work. - // If it's not a bug that is fixed, this might need updating with the next iOS version. - // Note that all events (both touch and mouse) must be listened for in order to work on computers with both mouse and touchscreen. - const isIOS13 = /iPhone OS 13_/.test(window.navigator.userAgent); - if (!isIOS13) { - element.addEventListener("mousedown", clickStart, { passive: true }); - element.addEventListener("click", clickEnd); - } } private startAnimation(x: number, y: number) { From 4e8bf434f189b0c97a4b3ba1aeff7731351465ee Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 29 Jan 2020 19:04:12 +0100 Subject: [PATCH 125/126] Add settings button to map panel (#4641) --- src/panels/map/ha-panel-map.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/panels/map/ha-panel-map.js b/src/panels/map/ha-panel-map.js index 8d40a9bfd3..bf62930e59 100644 --- a/src/panels/map/ha-panel-map.js +++ b/src/panels/map/ha-panel-map.js @@ -4,6 +4,7 @@ import { PolymerElement } from "@polymer/polymer/polymer-element"; import "../../components/ha-menu-button"; import "../../components/ha-icon"; +import { navigate } from "../../common/navigate"; import "./ha-entity-marker"; @@ -33,6 +34,10 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) {
[[localize('panel.map')]]
+
@@ -67,6 +72,10 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) { } } + openZonesEditor() { + navigate(this, "/config/zone"); + } + fitMap() { var bounds; From c1a29e809159c4595f9343d44f35cde7ff6f3fdb Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 29 Jan 2020 19:28:09 +0100 Subject: [PATCH 126/126] Bumped version to 20200129.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 82d26187a0..274e7bceda 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup( name="home-assistant-frontend", - version="20200108.0", + version="20200129.0", description="The Home Assistant frontend", url="https://github.com/home-assistant/home-assistant-polymer", author="The Home Assistant Authors",