diff --git a/src/panels/lovelace/common/compute-notifications.js b/src/panels/lovelace/common/compute-notifications.js new file mode 100644 index 0000000000..802805cebb --- /dev/null +++ b/src/panels/lovelace/common/compute-notifications.js @@ -0,0 +1,12 @@ +import computeDomain from '../../../common/entity/compute_domain.js'; + +const NOTIFICATION_DOMAINS = [ + 'configurator', + 'persistent_notification' +]; + +export default function computeNotifications(states) { + return Object.keys(states) + .filter(entityId => NOTIFICATION_DOMAINS.includes(computeDomain(entityId))) + .map(entityId => states[entityId]); +} diff --git a/src/panels/lovelace/components/notifications/hui-configurator-notification-item.js b/src/panels/lovelace/components/notifications/hui-configurator-notification-item.js new file mode 100644 index 0000000000..25c2612bf5 --- /dev/null +++ b/src/panels/lovelace/components/notifications/hui-configurator-notification-item.js @@ -0,0 +1,53 @@ +import '@polymer/paper-button/paper-button.js'; +import '@polymer/paper-icon-button/paper-icon-button.js'; + +import { html } from '@polymer/polymer/lib/utils/html-tag.js'; +import { PolymerElement } from '@polymer/polymer/polymer-element.js'; + +import './hui-notification-item-template.js'; + +import EventsMixin from '../../../../mixins/events-mixin.js'; +import LocalizeMixin from '../../../../mixins/localize-mixin.js'; + +/* + * @appliesMixin EventsMixin + * @appliesMixin LocalizeMixin + */ +export class HuiConfiguratorNotificationItem extends EventsMixin(LocalizeMixin(PolymerElement)) { + static get template() { + return html` + + [[localize('domain.configurator')]] + +
[[_getMessage(stateObj)]]
+ + [[_localizeState(stateObj.state)]] +
+ `; + } + + static get properties() { + return { + hass: Object, + stateObj: Object + }; + } + + _handleClick() { + this.fire('hass-more-info', { entityId: this.stateObj.entity_id }); + } + + _localizeState(state) { + return this.localize(`state.configurator.${state}`); + } + + _getMessage(stateObj) { + const friendlyName = stateObj.attributes.friendly_name; + return this.localize('ui.notification_drawer.click_to_configure', 'entity', friendlyName); + } +} +customElements.define('hui-configurator-notification-item', HuiConfiguratorNotificationItem); diff --git a/src/panels/lovelace/components/notifications/hui-notification-drawer.js b/src/panels/lovelace/components/notifications/hui-notification-drawer.js new file mode 100644 index 0000000000..d641b280ba --- /dev/null +++ b/src/panels/lovelace/components/notifications/hui-notification-drawer.js @@ -0,0 +1,179 @@ +import '@polymer/paper-button/paper-button.js'; +import '@polymer/paper-icon-button/paper-icon-button.js'; +import '@polymer/app-layout/app-toolbar/app-toolbar.js'; + +import { html } from '@polymer/polymer/lib/utils/html-tag.js'; +import { PolymerElement } from '@polymer/polymer/polymer-element.js'; + +import './hui-notification-item.js'; + +import computeNotifications from '../../common/compute-notifications.js'; + +import EventsMixin from '../../../../mixins/events-mixin.js'; +import LocalizeMixin from '../../../../mixins/localize-mixin.js'; + +/* + * @appliesMixin EventsMixin + * @appliesMixin LocalizeMixin + */ +export class HuiNotificationDrawer extends EventsMixin(LocalizeMixin(PolymerElement)) { + static get template() { + return html` + +
+
+ +
[[localize('ui.notification_drawer.title')]]
+ +
+
+ + +
+
+ `; + } + + static get properties() { + return { + hass: Object, + _entities: { + type: Array, + computed: '_getEntities(hass.states, hidden)' + }, + narrow: { + type: Boolean, + reflectToAttribute: true + }, + open: { + type: Boolean, + notify: true, + observer: '_openChanged' + }, + hidden: { + type: Boolean, + value: true, + reflectToAttribute: true + } + }; + } + + _getEntities(states, hidden) { + return (states && !hidden) ? computeNotifications(states) : []; + } + + _closeDrawer(ev) { + ev.stopPropagation(); + this.open = false; + } + + _empty(entities) { + return entities.length === 0; + } + + _openChanged(open) { + clearTimeout(this._openTimer); + if (open) { + // Render closed then animate open + this.hidden = false; + this._openTimer = setTimeout(() => { + this.classList.add('open'); + }, 50); + } else { + // Animate closed then hide + this.classList.remove('open'); + this._openTimer = setTimeout(() => { + this.hidden = true; + }, 250); + } + } +} +customElements.define('hui-notification-drawer', HuiNotificationDrawer); diff --git a/src/panels/lovelace/components/notifications/hui-notification-item-template.js b/src/panels/lovelace/components/notifications/hui-notification-item-template.js new file mode 100644 index 0000000000..a4188778db --- /dev/null +++ b/src/panels/lovelace/components/notifications/hui-notification-item-template.js @@ -0,0 +1,46 @@ +import '@polymer/paper-button/paper-button.js'; +import '@polymer/paper-icon-button/paper-icon-button.js'; + +import { html } from '@polymer/polymer/lib/utils/html-tag.js'; +import { PolymerElement } from '@polymer/polymer/polymer-element.js'; + +import '../../../../components/ha-card.js'; + +export class HuiNotificationItemTemplate extends PolymerElement { + static get template() { + return html` + + +
+ +
+
+ +
+
+ +
+
+ `; + } +} +customElements.define('hui-notification-item-template', HuiNotificationItemTemplate); diff --git a/src/panels/lovelace/components/notifications/hui-notification-item.js b/src/panels/lovelace/components/notifications/hui-notification-item.js new file mode 100644 index 0000000000..9fcab42428 --- /dev/null +++ b/src/panels/lovelace/components/notifications/hui-notification-item.js @@ -0,0 +1,33 @@ +import { PolymerElement } from '@polymer/polymer/polymer-element.js'; +import computeDomain from '../../../../common/entity/compute_domain.js'; + +import './hui-configurator-notification-item.js'; +import './hui-persistent-notification-item.js'; + +export class HuiNotificationItem extends PolymerElement { + static get properties() { + return { + hass: Object, + stateObj: { + type: Object, + observer: '_stateChanged' + } + }; + } + + _stateChanged(stateObj) { + if (this.lastChild) { + this.removeChild(this.lastChild); + } + + if (!stateObj) return; + + const domain = computeDomain(stateObj.entity_id); + const tag = `hui-${domain}-notification-item`; + const el = document.createElement(tag); + el.hass = this.hass; + el.stateObj = stateObj; + this.appendChild(el); + } +} +customElements.define('hui-notification-item', HuiNotificationItem); diff --git a/src/panels/lovelace/components/notifications/hui-notifications-button.js b/src/panels/lovelace/components/notifications/hui-notifications-button.js new file mode 100644 index 0000000000..5d9048cb64 --- /dev/null +++ b/src/panels/lovelace/components/notifications/hui-notifications-button.js @@ -0,0 +1,61 @@ +import '@polymer/paper-button/paper-button.js'; +import '@polymer/paper-icon-button/paper-icon-button.js'; +import '@polymer/app-layout/app-toolbar/app-toolbar.js'; + +import { html } from '@polymer/polymer/lib/utils/html-tag.js'; +import { PolymerElement } from '@polymer/polymer/polymer-element.js'; + +import computeNotifications from '../../common/compute-notifications.js'; + +import EventsMixin from '../../../../mixins/events-mixin.js'; + +/* + * @appliesMixin EventsMixin + */ +export class HuiNotificationsButton extends EventsMixin(PolymerElement) { + static get template() { + return html` + + + + `; + } + + static get properties() { + return { + hass: Object, + notificationsOpen: { + type: Boolean, + notify: true + } + }; + } + + _clicked() { + this.notificationsOpen = true; + } + + _hasNotifications(states) { + return computeNotifications(states).length > 0; + } +} +customElements.define('hui-notifications-button', HuiNotificationsButton); diff --git a/src/panels/lovelace/components/notifications/hui-persistent-notification-item.js b/src/panels/lovelace/components/notifications/hui-persistent-notification-item.js new file mode 100644 index 0000000000..dfe511b9c3 --- /dev/null +++ b/src/panels/lovelace/components/notifications/hui-persistent-notification-item.js @@ -0,0 +1,52 @@ +import '@polymer/paper-button/paper-button.js'; +import '@polymer/paper-icon-button/paper-icon-button.js'; + +import { html } from '@polymer/polymer/lib/utils/html-tag.js'; +import { PolymerElement } from '@polymer/polymer/polymer-element.js'; + +import computeStateName from '../../../../common/entity/compute_state_name.js'; + +import '../../../../components/ha-markdown.js'; +import './hui-notification-item-template.js'; + +import LocalizeMixin from '../../../../mixins/localize-mixin.js'; + +/* + * @appliesMixin LocalizeMixin + */ +export class HuiPersistentNotificationItem extends LocalizeMixin(PolymerElement) { + static get template() { + return html` + + [[_computeTitle(stateObj)]] + + + + [[localize('ui.card.persistent_notification.dismiss')]] + + `; + } + + static get properties() { + return { + hass: Object, + stateObj: Object + }; + } + + _handleDismiss() { + this.hass.callApi('DELETE', `states/${this.stateObj.entity_id}`); + } + + _computeTitle(stateObj) { + return (stateObj.attributes.title || computeStateName(stateObj)); + } +} +customElements.define( + 'hui-persistent_notification-notification-item', + HuiPersistentNotificationItem +); diff --git a/src/panels/lovelace/hui-root.js b/src/panels/lovelace/hui-root.js index 2679bf85bb..f4374f0344 100644 --- a/src/panels/lovelace/hui-root.js +++ b/src/panels/lovelace/hui-root.js @@ -22,6 +22,8 @@ import '../../layouts/ha-app-layout.js'; import '../../components/ha-start-voice-button.js'; import '../../components/ha-icon.js'; import { loadModule, loadCSS, loadJS } from '../../common/dom/load_resource.js'; +import './components/notifications/hui-notification-drawer.js'; +import './components/notifications/hui-notifications-button.js'; import './hui-unused-entities.js'; import './hui-view.js'; import debounce from '../../common/util/debounce.js'; @@ -72,11 +74,20 @@ class HUIRoot extends NavigateMixin(EventsMixin(PolymerElement)) { } +
[[_computeTitle(config)]]
+