Get persistent_notifications for lovelace from websocket. (#1649)

* Get persistent_notifications for lovelace from websocket.

* Only fetch notifications on event.

* Use collection for notifications.
This commit is contained in:
Jerad Meisner 2018-09-17 00:53:14 -07:00 committed by Paulus Schoutsen
parent 443e083a79
commit 5187f3b84f
9 changed files with 101 additions and 51 deletions

View File

@ -8,6 +8,7 @@ import '../components/ha-markdown.js';
import computeStateName from '../common/entity/compute_state_name.js'; import computeStateName from '../common/entity/compute_state_name.js';
import LocalizeMixin from '../mixins/localize-mixin.js'; import LocalizeMixin from '../mixins/localize-mixin.js';
import computeObjectId from '../common/entity/compute_object_id';
/* /*
* @appliesMixin LocalizeMixin * @appliesMixin LocalizeMixin
@ -65,7 +66,9 @@ class HaPersistentNotificationCard extends LocalizeMixin(PolymerElement) {
dismissTap(ev) { dismissTap(ev) {
ev.preventDefault(); ev.preventDefault();
this.hass.callApi('DELETE', 'states/' + this.stateObj.entity_id); this.hass.callService('persistent_notification', 'dismiss', {
notification_id: computeObjectId(this.stateObj.entity_id)
});
} }
} }
customElements.define('ha-persistent_notification-card', HaPersistentNotificationCard); customElements.define('ha-persistent_notification-card', HaPersistentNotificationCard);

View File

@ -0,0 +1,20 @@
import { createCollection } from 'home-assistant-js-websocket';
const fetchNotifications = conn => conn.sendMessagePromise({
type: 'persistent_notification/get'
});
const subscribeUpdates = (conn, store) =>
conn.subscribeEvents(
() => fetchNotifications(conn).then(ntf => store.setState(ntf, true)),
'persistent_notifications_updated'
);
export const subscribeNotifications = (conn, onChange) =>
createCollection(
'_ntf',
fetchNotifications,
subscribeUpdates,
conn,
onChange
);

View File

@ -1,12 +1,7 @@
import computeDomain from '../../../common/entity/compute_domain.js'; import computeDomain from '../../../common/entity/compute_domain.js';
const NOTIFICATION_DOMAINS = [
'configurator',
'persistent_notification'
];
export default function computeNotifications(states) { export default function computeNotifications(states) {
return Object.keys(states) return Object.keys(states)
.filter(entityId => NOTIFICATION_DOMAINS.includes(computeDomain(entityId))) .filter(entityId => computeDomain(entityId) === 'configurator')
.map(entityId => states[entityId]); .map(entityId => states[entityId]);
} }

View File

@ -19,13 +19,13 @@ export class HuiConfiguratorNotificationItem extends EventsMixin(LocalizeMixin(P
<hui-notification-item-template> <hui-notification-item-template>
<span slot="header">[[localize('domain.configurator')]]</span> <span slot="header">[[localize('domain.configurator')]]</span>
<div>[[_getMessage(stateObj)]]</div> <div>[[_getMessage(notification)]]</div>
<paper-button <paper-button
slot="actions" slot="actions"
class="primary" class="primary"
on-click="_handleClick" on-click="_handleClick"
>[[_localizeState(stateObj.state)]]</paper-button> >[[_localizeState(notification.state)]]</paper-button>
</hui-notification-item-template> </hui-notification-item-template>
`; `;
} }
@ -33,20 +33,20 @@ export class HuiConfiguratorNotificationItem extends EventsMixin(LocalizeMixin(P
static get properties() { static get properties() {
return { return {
hass: Object, hass: Object,
stateObj: Object notification: Object
}; };
} }
_handleClick() { _handleClick() {
this.fire('hass-more-info', { entityId: this.stateObj.entity_id }); this.fire('hass-more-info', { entityId: this.notification.entity_id });
} }
_localizeState(state) { _localizeState(state) {
return this.localize(`state.configurator.${state}`); return this.localize(`state.configurator.${state}`);
} }
_getMessage(stateObj) { _getMessage(notification) {
const friendlyName = stateObj.attributes.friendly_name; const friendlyName = notification.attributes.friendly_name;
return this.localize('ui.notification_drawer.click_to_configure', 'entity', friendlyName); return this.localize('ui.notification_drawer.click_to_configure', 'entity', friendlyName);
} }
} }

View File

@ -7,8 +7,6 @@ import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import './hui-notification-item.js'; import './hui-notification-item.js';
import computeNotifications from '../../common/compute-notifications.js';
import EventsMixin from '../../../../mixins/events-mixin.js'; import EventsMixin from '../../../../mixins/events-mixin.js';
import LocalizeMixin from '../../../../mixins/localize-mixin.js'; import LocalizeMixin from '../../../../mixins/localize-mixin.js';
@ -105,16 +103,16 @@ export class HuiNotificationDrawer extends EventsMixin(LocalizeMixin(PolymerElem
<paper-icon-button icon="hass:chevron-right" on-click="_closeDrawer"></paper-icon-button> <paper-icon-button icon="hass:chevron-right" on-click="_closeDrawer"></paper-icon-button>
</app-toolbar> </app-toolbar>
<div class="notifications"> <div class="notifications">
<template is="dom-if" if="[[!_empty(_entities)]]"> <template is="dom-if" if="[[!_empty(notifications)]]">
<dom-repeat items="[[_entities]]"> <dom-repeat items="[[notifications]]">
<template> <template>
<div class="notification"> <div class="notification">
<hui-notification-item hass="[[hass]]" state-obj="[[item]]"></hui-notification-item> <hui-notification-item hass="[[hass]]" notification="[[item]]"></hui-notification-item>
</div> </div>
</template> </template>
</dom-repeat> </dom-repeat>
</template> </template>
<template is="dom-if" if="[[_empty(_entities)]]"> <template is="dom-if" if="[[_empty(notifications)]]">
<div class="empty">[[localize('ui.notification_drawer.empty')]]<div> <div class="empty">[[localize('ui.notification_drawer.empty')]]<div>
</template> </template>
</div> </div>
@ -125,10 +123,6 @@ export class HuiNotificationDrawer extends EventsMixin(LocalizeMixin(PolymerElem
static get properties() { static get properties() {
return { return {
hass: Object, hass: Object,
_entities: {
type: Array,
computed: '_getEntities(hass.states, hidden)'
},
narrow: { narrow: {
type: Boolean, type: Boolean,
reflectToAttribute: true reflectToAttribute: true
@ -142,21 +136,21 @@ export class HuiNotificationDrawer extends EventsMixin(LocalizeMixin(PolymerElem
type: Boolean, type: Boolean,
value: true, value: true,
reflectToAttribute: true reflectToAttribute: true
},
notifications: {
type: Array,
value: []
} }
}; };
} }
_getEntities(states, hidden) {
return (states && !hidden) ? computeNotifications(states) : [];
}
_closeDrawer(ev) { _closeDrawer(ev) {
ev.stopPropagation(); ev.stopPropagation();
this.open = false; this.open = false;
} }
_empty(entities) { _empty(notifications) {
return entities.length === 0; return notifications.length === 0;
} }
_openChanged(open) { _openChanged(open) {

View File

@ -8,25 +8,27 @@ export class HuiNotificationItem extends PolymerElement {
static get properties() { static get properties() {
return { return {
hass: Object, hass: Object,
stateObj: { notification: {
type: Object, type: Object,
observer: '_stateChanged' observer: '_stateChanged'
} }
}; };
} }
_stateChanged(stateObj) { _stateChanged(notification) {
if (this.lastChild) { if (this.lastChild) {
this.removeChild(this.lastChild); this.removeChild(this.lastChild);
} }
if (!stateObj) return; if (!notification) return;
const domain = computeDomain(stateObj.entity_id); const domain = notification.entity_id
? computeDomain(notification.entity_id)
: 'persistent_notification';
const tag = `hui-${domain}-notification-item`; const tag = `hui-${domain}-notification-item`;
const el = document.createElement(tag); const el = document.createElement(tag);
el.hass = this.hass; el.hass = this.hass;
el.stateObj = stateObj; el.notification = notification;
this.appendChild(el); this.appendChild(el);
} }
} }

View File

@ -5,8 +5,6 @@ import '@polymer/app-layout/app-toolbar/app-toolbar.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js'; import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js'; import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import computeNotifications from '../../common/compute-notifications.js';
import EventsMixin from '../../../../mixins/events-mixin.js'; import EventsMixin from '../../../../mixins/events-mixin.js';
/* /*
@ -36,16 +34,19 @@ export class HuiNotificationsButton extends EventsMixin(PolymerElement) {
} }
</style> </style>
<paper-icon-button icon="hass:bell" on-click="_clicked"></paper-icon-button> <paper-icon-button icon="hass:bell" on-click="_clicked"></paper-icon-button>
<span class="indicator" hidden$="[[!_hasNotifications(hass.states)]]"></span> <span class="indicator" hidden$="[[!_hasNotifications(notifications)]]"></span>
`; `;
} }
static get properties() { static get properties() {
return { return {
hass: Object,
notificationsOpen: { notificationsOpen: {
type: Boolean, type: Boolean,
notify: true notify: true
},
notifications: {
type: Array,
value: []
} }
}; };
} }
@ -54,8 +55,8 @@ export class HuiNotificationsButton extends EventsMixin(PolymerElement) {
this.notificationsOpen = true; this.notificationsOpen = true;
} }
_hasNotifications(states) { _hasNotifications(notifications) {
return computeNotifications(states).length > 0; return notifications.length > 0;
} }
} }
customElements.define('hui-notifications-button', HuiNotificationsButton); customElements.define('hui-notifications-button', HuiNotificationsButton);

View File

@ -4,8 +4,6 @@ import '@polymer/paper-icon-button/paper-icon-button.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js'; import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js'; import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import computeStateName from '../../../../common/entity/compute_state_name.js';
import '../../../../components/ha-markdown.js'; import '../../../../components/ha-markdown.js';
import './hui-notification-item-template.js'; import './hui-notification-item-template.js';
@ -18,13 +16,13 @@ export class HuiPersistentNotificationItem extends LocalizeMixin(PolymerElement)
static get template() { static get template() {
return html` return html`
<hui-notification-item-template> <hui-notification-item-template>
<span slot="header">[[_computeTitle(stateObj)]]</span> <span slot="header">[[_computeTitle(notification)]]</span>
<ha-markdown content="[[stateObj.attributes.message]]"></ha-markdown> <ha-markdown content="[[notification.message]]"></ha-markdown>
<paper-button <paper-button
slot="actions" slot="actions"
class="primary" class="primary"
on-click="_handleDismiss" on-click="_handleDismiss"
>[[localize('ui.card.persistent_notification.dismiss')]]</paper-button> >[[localize('ui.card.persistent_notification.dismiss')]]</paper-button>
</hui-notification-item-template> </hui-notification-item-template>
@ -34,16 +32,18 @@ export class HuiPersistentNotificationItem extends LocalizeMixin(PolymerElement)
static get properties() { static get properties() {
return { return {
hass: Object, hass: Object,
stateObj: Object notification: Object
}; };
} }
_handleDismiss() { _handleDismiss() {
this.hass.callApi('DELETE', `states/${this.stateObj.entity_id}`); this.hass.callService('persistent_notification', 'dismiss', {
notification_id: this.notification.notification_id
});
} }
_computeTitle(stateObj) { _computeTitle(notification) {
return (stateObj.attributes.title || computeStateName(stateObj)); return notification.title || notification.notification_id;
} }
} }
customElements.define( customElements.define(

View File

@ -22,6 +22,7 @@ import '../../layouts/ha-app-layout.js';
import '../../components/ha-start-voice-button.js'; import '../../components/ha-start-voice-button.js';
import '../../components/ha-icon.js'; import '../../components/ha-icon.js';
import { loadModule, loadCSS, loadJS } from '../../common/dom/load_resource.js'; import { loadModule, loadCSS, loadJS } from '../../common/dom/load_resource.js';
import { subscribeNotifications } from '../../data/ws-notifications';
import './components/notifications/hui-notification-drawer.js'; import './components/notifications/hui-notification-drawer.js';
import './components/notifications/hui-notifications-button.js'; import './components/notifications/hui-notifications-button.js';
import './hui-unused-entities.js'; import './hui-unused-entities.js';
@ -29,6 +30,7 @@ import './hui-view.js';
import debounce from '../../common/util/debounce.js'; import debounce from '../../common/util/debounce.js';
import createCardElement from './common/create-card-element.js'; import createCardElement from './common/create-card-element.js';
import computeNotifications from './common/compute-notifications';
// CSS and JS should only be imported once. Modules and HTML are safe. // CSS and JS should only be imported once. Modules and HTML are safe.
const CSS_CACHE = {}; const CSS_CACHE = {};
@ -76,6 +78,7 @@ class HUIRoot extends NavigateMixin(EventsMixin(PolymerElement)) {
<app-route route="[[route]]" pattern="/:view" data="{{routeData}}"></app-route> <app-route route="[[route]]" pattern="/:view" data="{{routeData}}"></app-route>
<hui-notification-drawer <hui-notification-drawer
hass="[[hass]]" hass="[[hass]]"
notifications="[[_notifications]]"
open="{{notificationsOpen}}" open="{{notificationsOpen}}"
narrow="[[narrow]]" narrow="[[narrow]]"
></hui-notification-drawer> ></hui-notification-drawer>
@ -87,6 +90,7 @@ class HUIRoot extends NavigateMixin(EventsMixin(PolymerElement)) {
<hui-notifications-button <hui-notifications-button
hass="[[hass]]" hass="[[hass]]"
notifications-open="{{notificationsOpen}}" notifications-open="{{notificationsOpen}}"
notifications="[[_notifications]]"
></hui-notifications-button> ></hui-notifications-button>
<ha-start-voice-button hass="[[hass]]"></ha-start-voice-button> <ha-start-voice-button hass="[[hass]]"></ha-start-voice-button>
<paper-menu-button <paper-menu-button
@ -156,6 +160,16 @@ class HUIRoot extends NavigateMixin(EventsMixin(PolymerElement)) {
value: false, value: false,
}, },
_persistentNotifications: {
type: Array,
value: []
},
_notifications: {
type: Array,
computed: '_updateNotifications(hass.states, _persistentNotifications)'
},
routeData: Object, routeData: Object,
}; };
} }
@ -165,6 +179,27 @@ class HUIRoot extends NavigateMixin(EventsMixin(PolymerElement)) {
this._debouncedConfigChanged = debounce(() => this._selectView(this._curView), 100); this._debouncedConfigChanged = debounce(() => this._selectView(this._curView), 100);
} }
connectedCallback() {
super.connectedCallback();
this._unsubNotifications = subscribeNotifications(this.hass.connection, (notifications) => {
this._persistentNotifications = notifications;
});
}
disconnectedCallback() {
super.disconnectedCallback();
if (typeof this._unsubNotifications === 'function') {
this._unsubNotifications();
}
}
_updateNotifications(states, persistent) {
if (!states) return persistent;
const configurator = computeNotifications(states);
return persistent.concat(configurator);
}
_routeChanged(route) { _routeChanged(route) {
const views = this.config && this.config.views; const views = this.config && this.config.views;
if (route.path === '' && route.prefix === '/lovelace' && views) { if (route.path === '' && route.prefix === '/lovelace' && views) {