diff --git a/src/panels/lovelace/common/compute-unused-entities.js b/src/panels/lovelace/common/compute-unused-entities.js new file mode 100644 index 0000000000..ceb0b0782c --- /dev/null +++ b/src/panels/lovelace/common/compute-unused-entities.js @@ -0,0 +1,29 @@ +const EXCLUDED_DOMAINS = [ + 'group', + 'zone' +]; + +function computeUsedEntities(config) { + const entities = new Set(); + + function addEntityId(entity) { + entities.add(typeof entity === 'string' ? entity : entity.entity); + } + + function addEntities(obj) { + if (obj.entity) addEntityId(obj.entity); + if (obj.entities) obj.entities.forEach(entity => addEntityId(entity)); + if (obj.card) addEntities(obj.card); + if (obj.cards) obj.cards.forEach(card => addEntities(card)); + } + + config.views.forEach(view => addEntities(view)); + return entities; +} + +export default function computeUnusedEntities(hass, config) { + const usedEntities = computeUsedEntities(config); + return Object.keys(hass.states).filter(entity => !usedEntities.has(entity) && + !(config.excluded_entities && config.excluded_entities.includes(entity)) && + !EXCLUDED_DOMAINS.includes(entity.split('.', 1)[0])).sort(); +} diff --git a/src/panels/lovelace/hui-root.js b/src/panels/lovelace/hui-root.js index 946db854eb..27b8b11c6e 100644 --- a/src/panels/lovelace/hui-root.js +++ b/src/panels/lovelace/hui-root.js @@ -3,6 +3,9 @@ import '@polymer/app-layout/app-header/app-header.js'; import '@polymer/app-layout/app-toolbar/app-toolbar.js'; import '@polymer/app-route/app-route.js'; import '@polymer/paper-icon-button/paper-icon-button.js'; +import '@polymer/paper-item/paper-item.js'; +import '@polymer/paper-listbox/paper-listbox.js'; +import '@polymer/paper-menu-button/paper-menu-button.js'; import '@polymer/paper-tabs/paper-tab.js'; import '@polymer/paper-tabs/paper-tabs.js'; @@ -18,6 +21,7 @@ import '../../layouts/ha-app-layout.js'; import '../../components/ha-start-voice-button.js'; import '../../components/ha-icon.js'; import { loadModule, loadJS } from '../../common/dom/load_resource.js'; +import './hui-unused-entities.js'; import './hui-view.js'; import createCardElement from './common/create-card-element.js'; @@ -53,11 +57,19 @@ class HUIRoot extends NavigateMixin(EventsMixin(PolymerElement)) {
[[_computeTitle(config)]]
- - - - + + + + Refresh + Unused entities + Help + +
@@ -148,6 +160,18 @@ class HUIRoot extends NavigateMixin(EventsMixin(PolymerElement)) { this.fire('config-refresh'); } + _handleUnusedEntities() { + this._selectView('unused'); + } + + _deselect(ev) { + ev.target.selected = null; + } + + _handleHelp() { + window.open('https://developers.home-assistant.io/docs/en/lovelace_index.html', '_blank'); + } + _handleViewSelected(ev) { const index = ev.detail.selected; if (index !== this._curView) { @@ -166,16 +190,20 @@ class HUIRoot extends NavigateMixin(EventsMixin(PolymerElement)) { root.removeChild(root.lastChild); } - const viewConfig = this.config.views[this._curView]; - let view; - if (viewConfig.panel) { - view = createCardElement(viewConfig.cards[0]); + if (viewIndex === 'unused') { + view = document.createElement('hui-unused-entities'); + view.config = this.config; } else { - view = document.createElement('hui-view'); - view.config = viewConfig; - view.columns = this.columns; + const viewConfig = this.config.views[this._curView]; + if (viewConfig.panel) { + view = createCardElement(viewConfig.cards[0]); + } else { + view = document.createElement('hui-view'); + view.config = viewConfig; + view.columns = this.columns; + } } view.hass = this.hass; diff --git a/src/panels/lovelace/hui-unused-entities.js b/src/panels/lovelace/hui-unused-entities.js new file mode 100644 index 0000000000..27abdb7320 --- /dev/null +++ b/src/panels/lovelace/hui-unused-entities.js @@ -0,0 +1,57 @@ +import { html } from '@polymer/polymer/lib/utils/html-tag.js'; +import { PolymerElement } from '@polymer/polymer/polymer-element.js'; + +import computeUnusedEntities from './common/compute-unused-entities.js'; +import createCardElement from './common/create-card-element.js'; + +import './cards/hui-entities-card.js'; + +class HuiUnusedEntities extends PolymerElement { + static get template() { + return html` + +
+ `; + } + + static get properties() { + return { + hass: { + type: Object, + observer: '_hassChanged' + }, + config: { + type: Object, + observer: '_configChanged' + }, + }; + } + + _configChanged(config) { + const root = this.$.root; + if (root.lastChild) root.removeChild(root.lastChild); + + const entities = computeUnusedEntities(this.hass, config); + const cardConfig = { + type: 'entities', + title: 'Unused entities', + entities, + show_header_toggle: false + }; + const element = createCardElement(cardConfig); + element.hass = this.hass; + root.appendChild(element); + } + + _hassChanged(hass) { + const root = this.$.root; + if (!root.lastChild) return; + root.lastChild.hass = hass; + } +} +customElements.define('hui-unused-entities', HuiUnusedEntities);