mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-24 21:37:21 +00:00
Move notifications to the sidebar (#3317)
* Move notifications to the sidebar * Close when navigating * Lint
This commit is contained in:
parent
58e6be12af
commit
42e75e7cdf
@ -21,6 +21,11 @@ import {
|
|||||||
getExternalConfig,
|
getExternalConfig,
|
||||||
ExternalConfig,
|
ExternalConfig,
|
||||||
} from "../external_app/external_config";
|
} from "../external_app/external_config";
|
||||||
|
import {
|
||||||
|
PersistentNotification,
|
||||||
|
subscribeNotifications,
|
||||||
|
} from "../data/persistent_notification";
|
||||||
|
import computeDomain from "../common/entity/compute_domain";
|
||||||
|
|
||||||
const SHOW_AFTER_SPACER = ["config", "developer-tools"];
|
const SHOW_AFTER_SPACER = ["config", "developer-tools"];
|
||||||
|
|
||||||
@ -102,12 +107,14 @@ const renderPanel = (hass, panel) => html`
|
|||||||
* @appliesMixin LocalizeMixin
|
* @appliesMixin LocalizeMixin
|
||||||
*/
|
*/
|
||||||
class HaSidebar extends LitElement {
|
class HaSidebar extends LitElement {
|
||||||
@property() public hass?: HomeAssistant;
|
@property() public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ type: Boolean }) public alwaysExpand = false;
|
@property({ type: Boolean }) public alwaysExpand = false;
|
||||||
@property({ type: Boolean, reflect: true }) public expanded = false;
|
@property({ type: Boolean, reflect: true }) public expanded = false;
|
||||||
@property() public _defaultPage?: string =
|
@property() public _defaultPage?: string =
|
||||||
localStorage.defaultPage || DEFAULT_PANEL;
|
localStorage.defaultPage || DEFAULT_PANEL;
|
||||||
@property() private _externalConfig?: ExternalConfig;
|
@property() private _externalConfig?: ExternalConfig;
|
||||||
|
@property() private _notifications?: PersistentNotification[];
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
const hass = this.hass;
|
const hass = this.hass;
|
||||||
@ -118,6 +125,15 @@ class HaSidebar extends LitElement {
|
|||||||
|
|
||||||
const [beforeSpacer, afterSpacer] = computePanels(hass);
|
const [beforeSpacer, afterSpacer] = computePanels(hass);
|
||||||
|
|
||||||
|
let notificationCount = this._notifications
|
||||||
|
? this._notifications.length
|
||||||
|
: 0;
|
||||||
|
for (const entityId in hass.states) {
|
||||||
|
if (computeDomain(entityId) === "configurator") {
|
||||||
|
notificationCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${this.expanded
|
${this.expanded
|
||||||
? html`
|
? html`
|
||||||
@ -167,57 +183,60 @@ class HaSidebar extends LitElement {
|
|||||||
slot="item-icon"
|
slot="item-icon"
|
||||||
icon="hass:cellphone-settings-variant"
|
icon="hass:cellphone-settings-variant"
|
||||||
></ha-icon>
|
></ha-icon>
|
||||||
<span class="item-text"
|
|
||||||
>${hass.localize(
|
|
||||||
"ui.sidebar.external_app_configuration"
|
|
||||||
)}</span
|
|
||||||
>
|
|
||||||
</paper-icon-item>
|
|
||||||
</a>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
${hass.user
|
|
||||||
? html`
|
|
||||||
<a
|
|
||||||
href="/profile"
|
|
||||||
data-panel="panel"
|
|
||||||
tabindex="-1"
|
|
||||||
aria-role="option"
|
|
||||||
aria-label=${hass.localize("panel.profile")}
|
|
||||||
>
|
|
||||||
<paper-icon-item class="profile">
|
|
||||||
<ha-user-badge
|
|
||||||
slot="item-icon"
|
|
||||||
.user=${hass.user}
|
|
||||||
></ha-user-badge>
|
|
||||||
|
|
||||||
<span class="item-text">
|
<span class="item-text">
|
||||||
${hass.user.name}
|
${hass.localize("ui.sidebar.external_app_configuration")}
|
||||||
</span>
|
</span>
|
||||||
</paper-icon-item>
|
</paper-icon-item>
|
||||||
</a>
|
</a>
|
||||||
`
|
`
|
||||||
: html`
|
: ""}
|
||||||
<paper-icon-item
|
|
||||||
@click=${this._handleLogOut}
|
<div disabled class="divider sticky-el"></div>
|
||||||
class="logout"
|
|
||||||
aria-role="option"
|
<paper-icon-item
|
||||||
>
|
class="notifications sticky-el"
|
||||||
<ha-icon slot="item-icon" icon="hass:exit-to-app"></ha-icon>
|
aria-role="option"
|
||||||
<span class="item-text"
|
@click=${this._handleShowNotificationDrawer}
|
||||||
>${hass.localize("ui.sidebar.log_out")}</span
|
>
|
||||||
>
|
<ha-icon slot="item-icon" icon="hass:bell"></ha-icon>
|
||||||
</paper-icon-item>
|
${notificationCount > 0
|
||||||
`}
|
? html`
|
||||||
|
<span class="notification-badge" slot="item-icon">
|
||||||
|
${notificationCount}
|
||||||
|
</span>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
<span class="item-text">
|
||||||
|
${hass.localize("ui.notification_drawer.title")}
|
||||||
|
</span>
|
||||||
|
</paper-icon-item>
|
||||||
|
|
||||||
|
<a
|
||||||
|
class="profile sticky-el"
|
||||||
|
href="/profile"
|
||||||
|
data-panel="panel"
|
||||||
|
tabindex="-1"
|
||||||
|
aria-role="option"
|
||||||
|
aria-label=${hass.localize("panel.profile")}
|
||||||
|
>
|
||||||
|
<paper-icon-item>
|
||||||
|
<ha-user-badge slot="item-icon" .user=${hass.user}></ha-user-badge>
|
||||||
|
|
||||||
|
<span class="item-text">
|
||||||
|
${hass.user ? hass.user.name : ""}
|
||||||
|
</span>
|
||||||
|
</paper-icon-item>
|
||||||
|
</a>
|
||||||
</paper-listbox>
|
</paper-listbox>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||||
if (
|
if (
|
||||||
changedProps.has("_externalConfig") ||
|
|
||||||
changedProps.has("expanded") ||
|
changedProps.has("expanded") ||
|
||||||
changedProps.has("alwaysExpand")
|
changedProps.has("alwaysExpand") ||
|
||||||
|
changedProps.has("_externalConfig") ||
|
||||||
|
changedProps.has("_notifications")
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -233,7 +252,8 @@ class HaSidebar extends LitElement {
|
|||||||
hass.panels !== oldHass.panels ||
|
hass.panels !== oldHass.panels ||
|
||||||
hass.panelUrl !== oldHass.panelUrl ||
|
hass.panelUrl !== oldHass.panelUrl ||
|
||||||
hass.user !== oldHass.user ||
|
hass.user !== oldHass.user ||
|
||||||
hass.localize !== oldHass.localize
|
hass.localize !== oldHass.localize ||
|
||||||
|
hass.states !== oldHass.states
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,6 +273,17 @@ class HaSidebar extends LitElement {
|
|||||||
this.addEventListener("mouseleave", () => {
|
this.addEventListener("mouseleave", () => {
|
||||||
this._contract();
|
this._contract();
|
||||||
});
|
});
|
||||||
|
subscribeNotifications(this.hass.connection, (notifications) => {
|
||||||
|
this._notifications = notifications;
|
||||||
|
});
|
||||||
|
// Deal with configurator
|
||||||
|
// private _updateNotifications(
|
||||||
|
// states: HassEntities,
|
||||||
|
// persistent: unknown[]
|
||||||
|
// ): unknown[] {
|
||||||
|
// const configurator = computeNotifications(states);
|
||||||
|
// return persistent.concat(configurator);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updated(changedProps) {
|
protected updated(changedProps) {
|
||||||
@ -266,13 +297,13 @@ class HaSidebar extends LitElement {
|
|||||||
this.expanded = this.alwaysExpand || false;
|
this.expanded = this.alwaysExpand || false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleLogOut() {
|
private _handleShowNotificationDrawer() {
|
||||||
fireEvent(this, "hass-logout");
|
fireEvent(this, "hass-show-notifications");
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleExternalAppConfiguration(ev: Event) {
|
private _handleExternalAppConfiguration(ev: Event) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
this.hass!.auth.external!.fireMessage({
|
this.hass.auth.external!.fireMessage({
|
||||||
type: "config_screen/show",
|
type: "config_screen/show",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -386,24 +417,64 @@ class HaSidebar extends LitElement {
|
|||||||
color: var(--sidebar-selected-text-color);
|
color: var(--sidebar-selected-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
a .item-text {
|
paper-icon-item .item-text {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
:host([expanded]) a .item-text {
|
:host([expanded]) paper-icon-item .item-text {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
paper-icon-item.logout {
|
.divider {
|
||||||
margin-top: 16px;
|
bottom: 88px;
|
||||||
|
padding: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
paper-icon-item.profile {
|
.divider::before {
|
||||||
|
content: " ";
|
||||||
|
display: block;
|
||||||
|
height: 1px;
|
||||||
|
background-color: var(--divider-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
bottom: 48px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.profile {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
.profile paper-icon-item {
|
||||||
padding-left: 4px;
|
padding-left: 4px;
|
||||||
}
|
}
|
||||||
.profile .item-text {
|
.profile .item-text {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sticky-el {
|
||||||
|
position: sticky;
|
||||||
|
background-color: var(
|
||||||
|
--sidebar-background-color,
|
||||||
|
var(--primary-background-color)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-badge {
|
||||||
|
position: absolute;
|
||||||
|
font-weight: 400;
|
||||||
|
bottom: 14px;
|
||||||
|
left: 26px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
height: 20px;
|
||||||
|
line-height: 20px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0px 6px;
|
||||||
|
font-size: 0.65em;
|
||||||
|
color: var(--text-primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
.spacer {
|
.spacer {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
43
src/data/persistent_notification.ts
Normal file
43
src/data/persistent_notification.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import {
|
||||||
|
createCollection,
|
||||||
|
Connection,
|
||||||
|
HassEntity,
|
||||||
|
} from "home-assistant-js-websocket";
|
||||||
|
|
||||||
|
export interface PersitentNotificationEntity extends HassEntity {
|
||||||
|
notification_id?: string;
|
||||||
|
created_at?: string;
|
||||||
|
title?: string;
|
||||||
|
message?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PersistentNotification {
|
||||||
|
created_at: string;
|
||||||
|
message: string;
|
||||||
|
notification_id: string;
|
||||||
|
title: string;
|
||||||
|
status: "read" | "unread";
|
||||||
|
}
|
||||||
|
|
||||||
|
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: Connection,
|
||||||
|
onChange: (notifications: PersistentNotification[]) => void
|
||||||
|
) =>
|
||||||
|
createCollection<PersistentNotification[]>(
|
||||||
|
"_ntf",
|
||||||
|
fetchNotifications,
|
||||||
|
subscribeUpdates,
|
||||||
|
conn,
|
||||||
|
onChange
|
||||||
|
);
|
@ -1,24 +0,0 @@
|
|||||||
import { createCollection, Connection } 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: Connection,
|
|
||||||
onChange: (notifications: Notification[]) => void
|
|
||||||
) =>
|
|
||||||
createCollection<Notification[]>(
|
|
||||||
"_ntf",
|
|
||||||
fetchNotifications,
|
|
||||||
subscribeUpdates,
|
|
||||||
conn,
|
|
||||||
onChange
|
|
||||||
);
|
|
@ -7,17 +7,17 @@ import {
|
|||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
|
|
||||||
import "./hui-notification-item-template";
|
import "./notification-item-template";
|
||||||
|
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { HassNotification } from "./types";
|
import { PersitentNotificationEntity } from "../../data/persistent_notification";
|
||||||
|
|
||||||
@customElement("hui-configurator-notification-item")
|
@customElement("configurator-notification-item")
|
||||||
export class HuiConfiguratorNotificationItem extends LitElement {
|
export class HuiConfiguratorNotificationItem extends LitElement {
|
||||||
@property() public hass?: HomeAssistant;
|
@property() public hass?: HomeAssistant;
|
||||||
|
|
||||||
@property() public notification?: HassNotification;
|
@property() public notification?: PersitentNotificationEntity;
|
||||||
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
if (!this.hass || !this.notification) {
|
if (!this.hass || !this.notification) {
|
||||||
@ -25,7 +25,7 @@ export class HuiConfiguratorNotificationItem extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<hui-notification-item-template>
|
<notification-item-template>
|
||||||
<span slot="header">${this.hass.localize("domain.configurator")}</span>
|
<span slot="header">${this.hass.localize("domain.configurator")}</span>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@ -41,7 +41,7 @@ export class HuiConfiguratorNotificationItem extends LitElement {
|
|||||||
`state.configurator.${this.notification.state}`
|
`state.configurator.${this.notification.state}`
|
||||||
)}</mwc-button
|
)}</mwc-button
|
||||||
>
|
>
|
||||||
</hui-notification-item-template>
|
</notification-item-template>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,6 +54,6 @@ export class HuiConfiguratorNotificationItem extends LitElement {
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"hui-configurator-notification-item": HuiConfiguratorNotificationItem;
|
"configurator-notification-item": HuiConfiguratorNotificationItem;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,13 +5,14 @@ import "@polymer/app-layout/app-toolbar/app-toolbar";
|
|||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||||
|
|
||||||
import "./hui-notification-item";
|
import "./notification-item";
|
||||||
import "../../../../components/ha-paper-icon-button-next";
|
import "../../components/ha-paper-icon-button-next";
|
||||||
|
|
||||||
import { EventsMixin } from "../../../../mixins/events-mixin";
|
|
||||||
import LocalizeMixin from "../../../../mixins/localize-mixin";
|
|
||||||
import { computeRTL } from "../../../../common/util/compute_rtl";
|
|
||||||
|
|
||||||
|
import { EventsMixin } from "../../mixins/events-mixin";
|
||||||
|
import LocalizeMixin from "../../mixins/localize-mixin";
|
||||||
|
import { computeRTL } from "../../common/util/compute_rtl";
|
||||||
|
import { subscribeNotifications } from "../../data/persistent_notification";
|
||||||
|
import computeDomain from "../../common/entity/compute_domain";
|
||||||
/*
|
/*
|
||||||
* @appliesMixin EventsMixin
|
* @appliesMixin EventsMixin
|
||||||
* @appliesMixin LocalizeMixin
|
* @appliesMixin LocalizeMixin
|
||||||
@ -129,7 +130,7 @@ export class HuiNotificationDrawer extends EventsMixin(
|
|||||||
<dom-repeat items="[[notifications]]">
|
<dom-repeat items="[[notifications]]">
|
||||||
<template>
|
<template>
|
||||||
<div class="notification">
|
<div class="notification">
|
||||||
<hui-notification-item hass="[[hass]]" notification="[[item]]"></hui-notification-item>
|
<notification-item hass="[[hass]]" notification="[[item]]"></notification-item>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</dom-repeat>
|
</dom-repeat>
|
||||||
@ -160,6 +161,10 @@ export class HuiNotificationDrawer extends EventsMixin(
|
|||||||
reflectToAttribute: true,
|
reflectToAttribute: true,
|
||||||
},
|
},
|
||||||
notifications: {
|
notifications: {
|
||||||
|
type: Array,
|
||||||
|
computed: "_computeNotifications(open, hass, _notificationsBackend)",
|
||||||
|
},
|
||||||
|
_notificationsBackend: {
|
||||||
type: Array,
|
type: Array,
|
||||||
value: [],
|
value: [],
|
||||||
},
|
},
|
||||||
@ -171,6 +176,16 @@ export class HuiNotificationDrawer extends EventsMixin(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ready() {
|
||||||
|
super.ready();
|
||||||
|
window.addEventListener("location-changed", () => {
|
||||||
|
// close drawer when we navigate away.
|
||||||
|
if (this.open) {
|
||||||
|
this.open = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
_closeDrawer(ev) {
|
_closeDrawer(ev) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this.open = false;
|
this.open = false;
|
||||||
@ -188,17 +203,44 @@ export class HuiNotificationDrawer extends EventsMixin(
|
|||||||
this._openTimer = setTimeout(() => {
|
this._openTimer = setTimeout(() => {
|
||||||
this.classList.add("open");
|
this.classList.add("open");
|
||||||
}, 50);
|
}, 50);
|
||||||
|
this._unsubNotifications = subscribeNotifications(
|
||||||
|
this.hass.connection,
|
||||||
|
(notifications) => {
|
||||||
|
this._notificationsBackend = notifications;
|
||||||
|
}
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// Animate closed then hide
|
// Animate closed then hide
|
||||||
this.classList.remove("open");
|
this.classList.remove("open");
|
||||||
this._openTimer = setTimeout(() => {
|
this._openTimer = setTimeout(() => {
|
||||||
this.hidden = true;
|
this.hidden = true;
|
||||||
}, 250);
|
}, 250);
|
||||||
|
if (this._unsubNotifications) {
|
||||||
|
this._unsubNotifications();
|
||||||
|
this._unsubNotifications = undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_computeRTL(hass) {
|
_computeRTL(hass) {
|
||||||
return computeRTL(hass);
|
return computeRTL(hass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_computeNotifications(open, hass, notificationsBackend) {
|
||||||
|
if (!open) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const configuratorEntities = Object.keys(hass.states)
|
||||||
|
.filter((entityId) => computeDomain(entityId) === "configurator")
|
||||||
|
.map((entityId) => hass.states[entityId]);
|
||||||
|
|
||||||
|
return notificationsBackend.concat(configuratorEntities);
|
||||||
|
}
|
||||||
|
|
||||||
|
showDialog({ narrow }) {
|
||||||
|
this.open = true;
|
||||||
|
this.narrow = narrow;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
customElements.define("hui-notification-drawer", HuiNotificationDrawer);
|
customElements.define("notification-drawer", HuiNotificationDrawer);
|
@ -7,9 +7,9 @@ import {
|
|||||||
CSSResult,
|
CSSResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
|
|
||||||
import "../../../../components/ha-card";
|
import "../../components/ha-card";
|
||||||
|
|
||||||
@customElement("hui-notification-item-template")
|
@customElement("notification-item-template")
|
||||||
export class HuiNotificationItemTemplate extends LitElement {
|
export class HuiNotificationItemTemplate extends LitElement {
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
return html`
|
return html`
|
||||||
@ -60,6 +60,6 @@ export class HuiNotificationItemTemplate extends LitElement {
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"hui-notification-item-template": HuiNotificationItemTemplate;
|
"notification-item-template": HuiNotificationItemTemplate;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -6,18 +6,19 @@ import {
|
|||||||
TemplateResult,
|
TemplateResult,
|
||||||
html,
|
html,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
|
|
||||||
import "./hui-configurator-notification-item";
|
import "./configurator-notification-item";
|
||||||
import "./hui-persistent-notification-item";
|
import "./persistent-notification-item";
|
||||||
|
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import { HassNotification } from "./types";
|
import { PersistentNotification } from "../../data/persistent_notification";
|
||||||
|
|
||||||
@customElement("hui-notification-item")
|
@customElement("notification-item")
|
||||||
export class HuiNotificationItem extends LitElement {
|
export class HuiNotificationItem extends LitElement {
|
||||||
@property() public hass?: HomeAssistant;
|
@property() public hass?: HomeAssistant;
|
||||||
|
|
||||||
@property() public notification?: HassNotification;
|
@property() public notification?: HassEntity | PersistentNotification;
|
||||||
|
|
||||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||||
if (!this.hass || !this.notification || changedProps.has("notification")) {
|
if (!this.hass || !this.notification || changedProps.has("notification")) {
|
||||||
@ -32,24 +33,24 @@ export class HuiNotificationItem extends LitElement {
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.notification.entity_id
|
return "entity_id" in this.notification
|
||||||
? html`
|
? html`
|
||||||
<hui-configurator-notification-item
|
<configurator-notification-item
|
||||||
.hass="${this.hass}"
|
.hass="${this.hass}"
|
||||||
.notification="${this.notification}"
|
.notification="${this.notification}"
|
||||||
></hui-configurator-notification-item>
|
></configurator-notification-item>
|
||||||
`
|
`
|
||||||
: html`
|
: html`
|
||||||
<hui-persistent-notification-item
|
<persistent-notification-item
|
||||||
.hass="${this.hass}"
|
.hass="${this.hass}"
|
||||||
.notification="${this.notification}"
|
.notification="${this.notification}"
|
||||||
></hui-persistent-notification-item>
|
></persistent-notification-item>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"hui-notification-item": HuiNotificationItem;
|
"notification-item": HuiNotificationItem;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -10,18 +10,18 @@ import {
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import "@polymer/paper-tooltip/paper-tooltip";
|
import "@polymer/paper-tooltip/paper-tooltip";
|
||||||
|
|
||||||
import "../../../../components/ha-relative-time";
|
import "../../components/ha-relative-time";
|
||||||
import "../../../../components/ha-markdown";
|
import "../../components/ha-markdown";
|
||||||
import "./hui-notification-item-template";
|
import "./notification-item-template";
|
||||||
|
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import { HassNotification } from "./types";
|
import { PersistentNotification } from "../../data/persistent_notification";
|
||||||
|
|
||||||
@customElement("hui-persistent-notification-item")
|
@customElement("persistent-notification-item")
|
||||||
export class HuiPersistentNotificationItem extends LitElement {
|
export class HuiPersistentNotificationItem extends LitElement {
|
||||||
@property() public hass?: HomeAssistant;
|
@property() public hass?: HomeAssistant;
|
||||||
|
|
||||||
@property() public notification?: HassNotification;
|
@property() public notification?: PersistentNotification;
|
||||||
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
if (!this.hass || !this.notification) {
|
if (!this.hass || !this.notification) {
|
||||||
@ -29,8 +29,10 @@ export class HuiPersistentNotificationItem extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<hui-notification-item-template>
|
<notification-item-template>
|
||||||
<span slot="header">${this._computeTitle(this.notification)}</span>
|
<span slot="header">
|
||||||
|
${this.notification.title || this.notification.notification_id}
|
||||||
|
</span>
|
||||||
|
|
||||||
<ha-markdown content="${this.notification.message}"></ha-markdown>
|
<ha-markdown content="${this.notification.message}"></ha-markdown>
|
||||||
|
|
||||||
@ -54,7 +56,7 @@ export class HuiPersistentNotificationItem extends LitElement {
|
|||||||
"ui.card.persistent_notification.dismiss"
|
"ui.card.persistent_notification.dismiss"
|
||||||
)}</mwc-button
|
)}</mwc-button
|
||||||
>
|
>
|
||||||
</hui-notification-item-template>
|
</notification-item-template>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,13 +82,9 @@ export class HuiPersistentNotificationItem extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _computeTitle(notification: HassNotification): string | undefined {
|
|
||||||
return notification.title || notification.notification_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _computeTooltip(
|
private _computeTooltip(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
notification: HassNotification
|
notification: PersistentNotification
|
||||||
): string | undefined {
|
): string | undefined {
|
||||||
if (!hass || !notification) {
|
if (!hass || !notification) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -105,6 +103,6 @@ export class HuiPersistentNotificationItem extends LitElement {
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"hui-persistent-notification-item": HuiPersistentNotificationItem;
|
"persistent-notification-item": HuiPersistentNotificationItem;
|
||||||
}
|
}
|
||||||
}
|
}
|
16
src/dialogs/notifications/show-notification-drawer.ts
Normal file
16
src/dialogs/notifications/show-notification-drawer.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
|
||||||
|
export interface NotificationDrawerParams {
|
||||||
|
narrow: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const showNotificationDrawer = (
|
||||||
|
element: HTMLElement,
|
||||||
|
dialogParams: NotificationDrawerParams
|
||||||
|
): void => {
|
||||||
|
fireEvent(element, "show-dialog", {
|
||||||
|
dialogTag: "notification-drawer" as any, // Not in TS yet
|
||||||
|
dialogImport: () => import("./notification-drawer"),
|
||||||
|
dialogParams,
|
||||||
|
});
|
||||||
|
};
|
@ -20,6 +20,7 @@ import { fireEvent } from "../common/dom/fire_event";
|
|||||||
import { PolymerChangedEvent } from "../polymer-types";
|
import { PolymerChangedEvent } from "../polymer-types";
|
||||||
// tslint:disable-next-line: no-duplicate-imports
|
// tslint:disable-next-line: no-duplicate-imports
|
||||||
import { AppDrawerLayoutElement } from "@polymer/app-layout/app-drawer-layout/app-drawer-layout";
|
import { AppDrawerLayoutElement } from "@polymer/app-layout/app-drawer-layout/app-drawer-layout";
|
||||||
|
import { showNotificationDrawer } from "../dialogs/notifications/show-notification-drawer";
|
||||||
|
|
||||||
const NON_SWIPABLE_PANELS = ["kiosk", "map"];
|
const NON_SWIPABLE_PANELS = ["kiosk", "map"];
|
||||||
|
|
||||||
@ -27,6 +28,7 @@ declare global {
|
|||||||
// for fire event
|
// for fire event
|
||||||
interface HASSDomEvents {
|
interface HASSDomEvents {
|
||||||
"hass-toggle-menu": undefined;
|
"hass-toggle-menu": undefined;
|
||||||
|
"hass-show-notifications": undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,6 +68,7 @@ class HomeAssistantMain extends LitElement {
|
|||||||
>
|
>
|
||||||
<ha-sidebar
|
<ha-sidebar
|
||||||
.hass=${hass}
|
.hass=${hass}
|
||||||
|
.narrow=${this.narrow}
|
||||||
.alwaysExpand=${this.narrow || hass.dockedSidebar}
|
.alwaysExpand=${this.narrow || hass.dockedSidebar}
|
||||||
></ha-sidebar>
|
></ha-sidebar>
|
||||||
</app-drawer>
|
</app-drawer>
|
||||||
@ -96,6 +99,12 @@ class HomeAssistantMain extends LitElement {
|
|||||||
setTimeout(() => this.appLayout.resetLayout());
|
setTimeout(() => this.appLayout.resetLayout());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.addEventListener("hass-show-notifications", () => {
|
||||||
|
showNotificationDrawer(this, {
|
||||||
|
narrow: this.narrow!,
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updated(changedProps: PropertyValues) {
|
protected updated(changedProps: PropertyValues) {
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
import { HassEntities, HassEntity } from "home-assistant-js-websocket";
|
|
||||||
|
|
||||||
import computeDomain from "../../../common/entity/compute_domain";
|
|
||||||
|
|
||||||
export const computeNotifications = (states: HassEntities): HassEntity[] => {
|
|
||||||
return Object.keys(states)
|
|
||||||
.filter((entityId) => computeDomain(entityId) === "configurator")
|
|
||||||
.map((entityId) => states[entityId]);
|
|
||||||
};
|
|
@ -1,81 +0,0 @@
|
|||||||
import {
|
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
TemplateResult,
|
|
||||||
css,
|
|
||||||
CSSResult,
|
|
||||||
property,
|
|
||||||
} from "lit-element";
|
|
||||||
import "@polymer/paper-icon-button/paper-icon-button";
|
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
// tslint:disable-next-line
|
|
||||||
interface HASSDomEvents {
|
|
||||||
"opened-changed": { value: boolean };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class HuiNotificationsButton extends LitElement {
|
|
||||||
@property() public notifications?: string[];
|
|
||||||
@property() public opened?: boolean;
|
|
||||||
|
|
||||||
protected render(): TemplateResult | void {
|
|
||||||
return html`
|
|
||||||
<paper-icon-button
|
|
||||||
aria-label="Show Notifications"
|
|
||||||
icon="hass:bell"
|
|
||||||
@click="${this._clicked}"
|
|
||||||
></paper-icon-button>
|
|
||||||
${this.notifications && this.notifications.length > 0
|
|
||||||
? html`
|
|
||||||
<span class="indicator">
|
|
||||||
<div>${this.notifications.length}</div>
|
|
||||||
</span>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
|
||||||
return [
|
|
||||||
css`
|
|
||||||
:host {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.indicator {
|
|
||||||
position: absolute;
|
|
||||||
top: 0px;
|
|
||||||
right: -3px;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: var(--accent-color);
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.indicator > div {
|
|
||||||
right: 7px;
|
|
||||||
top: 3px;
|
|
||||||
position: absolute;
|
|
||||||
font-size: 0.55em;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private _clicked() {
|
|
||||||
this.opened = true;
|
|
||||||
fireEvent(this, "opened-changed", { value: this.opened });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"hui-notifications-button": HuiNotificationsButton;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("hui-notifications-button", HuiNotificationsButton);
|
|
@ -1,8 +0,0 @@
|
|||||||
import { HassEntity } from "home-assistant-js-websocket";
|
|
||||||
|
|
||||||
export declare type HassNotification = HassEntity & {
|
|
||||||
notification_id?: string;
|
|
||||||
created_at?: string;
|
|
||||||
title?: string;
|
|
||||||
message?: string;
|
|
||||||
};
|
|
@ -20,7 +20,6 @@ import "@polymer/paper-listbox/paper-listbox";
|
|||||||
import "@polymer/paper-menu-button/paper-menu-button";
|
import "@polymer/paper-menu-button/paper-menu-button";
|
||||||
import "@polymer/paper-tabs/paper-tab";
|
import "@polymer/paper-tabs/paper-tab";
|
||||||
import "@polymer/paper-tabs/paper-tabs";
|
import "@polymer/paper-tabs/paper-tabs";
|
||||||
import { HassEntities } from "home-assistant-js-websocket";
|
|
||||||
|
|
||||||
import scrollToTarget from "../../common/dom/scroll-to-target";
|
import scrollToTarget from "../../common/dom/scroll-to-target";
|
||||||
|
|
||||||
@ -30,17 +29,13 @@ import "../../components/ha-paper-icon-button-arrow-next";
|
|||||||
import "../../components/ha-paper-icon-button-arrow-prev";
|
import "../../components/ha-paper-icon-button-arrow-prev";
|
||||||
import "../../components/ha-icon";
|
import "../../components/ha-icon";
|
||||||
import { loadModule, loadCSS, loadJS } from "../../common/dom/load_resource";
|
import { loadModule, loadCSS, loadJS } from "../../common/dom/load_resource";
|
||||||
import { subscribeNotifications } from "../../data/ws-notifications";
|
|
||||||
import { debounce } from "../../common/util/debounce";
|
import { debounce } from "../../common/util/debounce";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import { LovelaceConfig } from "../../data/lovelace";
|
import { LovelaceConfig } from "../../data/lovelace";
|
||||||
import { navigate } from "../../common/navigate";
|
import { navigate } from "../../common/navigate";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { computeNotifications } from "./common/compute-notifications";
|
|
||||||
import { swapView } from "./editor/config-util";
|
import { swapView } from "./editor/config-util";
|
||||||
|
|
||||||
import "./components/notifications/hui-notification-drawer";
|
|
||||||
import "./components/notifications/hui-notifications-button";
|
|
||||||
import "./hui-view";
|
import "./hui-view";
|
||||||
// Not a duplicate import, this one is for type
|
// Not a duplicate import, this one is for type
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
@ -65,12 +60,9 @@ class HUIRoot extends LitElement {
|
|||||||
@property() public route?: { path: string; prefix: string };
|
@property() public route?: { path: string; prefix: string };
|
||||||
@property() private _routeData?: { view: string };
|
@property() private _routeData?: { view: string };
|
||||||
@property() private _curView?: number | "hass-unused-entities";
|
@property() private _curView?: number | "hass-unused-entities";
|
||||||
@property() private _notificationsOpen = false;
|
|
||||||
@property() private _persistentNotifications?: Notification[];
|
|
||||||
private _viewCache?: { [viewId: string]: HUIView };
|
private _viewCache?: { [viewId: string]: HUIView };
|
||||||
|
|
||||||
private _debouncedConfigChanged: () => void;
|
private _debouncedConfigChanged: () => void;
|
||||||
private _unsubNotifications?: () => void;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@ -83,35 +75,11 @@ class HUIRoot extends LitElement {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public connectedCallback(): void {
|
|
||||||
super.connectedCallback();
|
|
||||||
this._unsubNotifications = subscribeNotifications(
|
|
||||||
this.hass!.connection,
|
|
||||||
(notifications) => {
|
|
||||||
this._persistentNotifications = notifications;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public disconnectedCallback(): void {
|
|
||||||
super.disconnectedCallback();
|
|
||||||
if (this._unsubNotifications) {
|
|
||||||
this._unsubNotifications();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
return html`
|
return html`
|
||||||
<app-route .route="${this.route}" pattern="/:view" data="${
|
<app-route .route="${this.route}" pattern="/:view" data="${
|
||||||
this._routeData
|
this._routeData
|
||||||
}" @data-changed="${this._routeDataChanged}"></app-route>
|
}" @data-changed="${this._routeDataChanged}"></app-route>
|
||||||
<hui-notification-drawer
|
|
||||||
.hass="${this.hass}"
|
|
||||||
.notifications="${this._notifications}"
|
|
||||||
.open="${this._notificationsOpen}"
|
|
||||||
@open-changed="${this._handleNotificationsOpenChanged}"
|
|
||||||
.narrow="${this.narrow}"
|
|
||||||
></hui-notification-drawer>
|
|
||||||
<ha-app-layout id="layout">
|
<ha-app-layout id="layout">
|
||||||
<app-header slot="header" effects="waterfall" class="${classMap({
|
<app-header slot="header" effects="waterfall" class="${classMap({
|
||||||
"edit-mode": this._editMode,
|
"edit-mode": this._editMode,
|
||||||
@ -165,12 +133,6 @@ class HUIRoot extends LitElement {
|
|||||||
<app-toolbar>
|
<app-toolbar>
|
||||||
<ha-menu-button></ha-menu-button>
|
<ha-menu-button></ha-menu-button>
|
||||||
<div main-title>${this.config.title || "Home Assistant"}</div>
|
<div main-title>${this.config.title || "Home Assistant"}</div>
|
||||||
<hui-notifications-button
|
|
||||||
.hass="${this.hass}"
|
|
||||||
.opened="${this._notificationsOpen}"
|
|
||||||
@opened-changed="${this._handleNotificationsOpenChanged}"
|
|
||||||
.notifications="${this._notifications}"
|
|
||||||
></hui-notifications-button>
|
|
||||||
<ha-start-voice-button
|
<ha-start-voice-button
|
||||||
.hass="${this.hass}"
|
.hass="${this.hass}"
|
||||||
></ha-start-voice-button>
|
></ha-start-voice-button>
|
||||||
@ -449,13 +411,6 @@ class HUIRoot extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _notifications() {
|
|
||||||
return this._updateNotifications(
|
|
||||||
this.hass!.states,
|
|
||||||
this._persistentNotifications! || []
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private get config(): LovelaceConfig {
|
private get config(): LovelaceConfig {
|
||||||
return this.lovelace!.config;
|
return this.lovelace!.config;
|
||||||
}
|
}
|
||||||
@ -480,18 +435,6 @@ class HUIRoot extends LitElement {
|
|||||||
this._routeData = ev.detail.value;
|
this._routeData = ev.detail.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleNotificationsOpenChanged(ev): void {
|
|
||||||
this._notificationsOpen = ev.detail.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _updateNotifications(
|
|
||||||
states: HassEntities,
|
|
||||||
persistent: unknown[]
|
|
||||||
): unknown[] {
|
|
||||||
const configurator = computeNotifications(states);
|
|
||||||
return persistent.concat(configurator);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _handleRefresh(): void {
|
private _handleRefresh(): void {
|
||||||
fireEvent(this, "config-refresh");
|
fireEvent(this, "config-refresh");
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user