Show toast when Lovelace config was updated from a different place (#3218)

* Refresh other lovelace UI's when making a change

* Move to toast with refresh button

* Change to `hass-notification`

* Reload on reconnect

- Fix for duration = 0
- Reload on reconnect

* Listen to ready of connection

* Update src/managers/notification-manager.ts

Co-Authored-By: Paulus Schoutsen <paulus@home-assistant.io>

* use showToast, listen connection-status,  noCancelOnOutsideClick -> option

* Remove unused import
This commit is contained in:
Bram Kragten 2019-05-27 23:54:14 +02:00 committed by Paulus Schoutsen
parent d10a0b3b6c
commit e595637a10
8 changed files with 152 additions and 53 deletions

View File

@ -1,4 +1,5 @@
import { HomeAssistant } from "../types";
import { Connection } from "home-assistant-js-websocket";
export interface LovelaceConfig {
title?: string;
@ -76,3 +77,8 @@ export const saveConfig = (
type: "lovelace/config/save",
config,
});
export const subscribeLovelaceUpdates = (
conn: Connection,
onChange: () => void
) => conn.subscribeEvents(onChange, "lovelace_updated");

View File

@ -1,30 +1,95 @@
import {
LitElement,
query,
property,
TemplateResult,
html,
css,
CSSResult,
} from "lit-element";
import { computeRTL } from "../common/util/compute_rtl";
import "../components/ha-toast";
import { LitElement, query, property, TemplateResult, html } from "lit-element";
import { HomeAssistant } from "../types";
import "@material/mwc-button";
import "../components/ha-toast";
// Typing
// tslint:disable-next-line: no-duplicate-imports
import { HaToast } from "../components/ha-toast";
export interface ShowToastParams {
message: string;
action?: ToastActionParams;
duration?: number;
dismissable?: boolean;
}
export interface ToastActionParams {
action: () => void;
text: string;
}
class NotificationManager extends LitElement {
@property() public hass!: HomeAssistant;
@property() private _action?: ToastActionParams;
@property() private _noCancelOnOutsideClick: boolean = false;
@query("ha-toast") private _toast!: HaToast;
public showDialog({ message }: ShowToastParams) {
public showDialog({
message,
action,
duration,
dismissable,
}: ShowToastParams) {
const toast = this._toast;
toast.setAttribute("dir", computeRTL(this.hass) ? "rtl" : "ltr");
toast.show(message);
this._action = action || undefined;
this._noCancelOnOutsideClick =
dismissable === undefined ? false : !dismissable;
toast.hide();
toast.show({
text: message,
duration: duration === undefined ? 3000 : duration,
});
}
protected render(): TemplateResult | void {
return html`
<ha-toast dir="[[_rtl]]" noCancelOnOutsideClick=${false}></ha-toast>
<ha-toast .noCancelOnOutsideClick=${this._noCancelOnOutsideClick}>
${this._action
? html`
<mwc-button
.label=${this._action.text}
@click=${this.buttonClicked}
></mwc-button>
`
: ""}
</ha-toast>
`;
}
private buttonClicked() {
this._toast.hide();
if (this._action) {
this._action.action();
}
}
static get styles(): CSSResult {
return css`
mwc-button {
color: var(--primary-color);
font-weight: bold;
}
`;
}
}
customElements.define("notification-manager", NotificationManager);
declare global {
// for fire event
interface HASSDomEvents {
"hass-notification": ShowToastParams;
}
}

View File

@ -1,6 +1,11 @@
import "@material/mwc-button";
import { fetchConfig, LovelaceConfig, saveConfig } from "../../data/lovelace";
import {
fetchConfig,
LovelaceConfig,
saveConfig,
subscribeLovelaceUpdates,
} from "../../data/lovelace";
import "../../layouts/hass-loading-screen";
import "../../layouts/hass-error-screen";
import "./hui-root";
@ -15,6 +20,7 @@ import {
} from "lit-element";
import { showSaveDialog } from "./editor/show-save-config-dialog";
import { generateLovelaceConfig } from "./common/generate-lovelace-config";
import { showToast } from "../../util/toast";
interface LovelacePanelConfig {
mode: "yaml" | "storage";
@ -42,6 +48,8 @@ class LovelacePanel extends LitElement {
private mqls?: MediaQueryList[];
private _saving: boolean = false;
constructor() {
super();
this._closeEditor = this._closeEditor.bind(this);
@ -66,7 +74,11 @@ class LovelacePanel extends LitElement {
if (state === "error") {
return html`
<hass-error-screen title="Lovelace" .error="${this._errorMsg}">
<mwc-button on-click="_forceFetchConfig">Reload Lovelace</mwc-button>
<mwc-button on-click="_forceFetchConfig"
>${this.hass!.localize(
"ui.panel.lovelace.reload_lovelace"
)}</mwc-button
>
</hass-error-screen>
`;
}
@ -107,6 +119,16 @@ class LovelacePanel extends LitElement {
public firstUpdated() {
this._fetchConfig(false);
// we don't want to unsub as we want to stay informed of updates
subscribeLovelaceUpdates(this.hass!.connection, () =>
this._lovelaceChanged()
);
// reload lovelace on reconnect so we are sure we have the latest config
window.addEventListener("connection-status", (ev) => {
if (ev.detail === "connected") {
this._fetchConfig(false);
}
});
this._updateColumns = this._updateColumns.bind(this);
this.mqls = [300, 600, 900, 1200].map((width) => {
const mql = matchMedia(`(min-width: ${width}px)`);
@ -155,6 +177,22 @@ class LovelacePanel extends LitElement {
);
}
private _lovelaceChanged() {
if (this._saving) {
this._saving = false;
} else {
showToast(this, {
message: this.hass!.localize("ui.panel.lovelace.changed_toast.message"),
action: {
action: () => this._fetchConfig(false),
text: this.hass!.localize("ui.panel.lovelace.changed_toast.refresh"),
},
duration: 0,
dismissable: false,
});
}
}
private _forceFetchConfig() {
this._fetchConfig(true);
}
@ -211,6 +249,7 @@ class LovelacePanel extends LitElement {
config: newConfig,
mode: "storage",
});
this._saving = true;
await saveConfig(this.hass!, newConfig);
} catch (err) {
// tslint:disable-next-line

View File

@ -31,9 +31,6 @@ declare global {
"iron-resize": undefined;
"config-refresh": undefined;
"ha-refresh-cloud-status": undefined;
"hass-notification": {
message: string;
};
"hass-api-called": {
success: boolean;
response: unknown;

View File

@ -1,37 +1,31 @@
import { Constructor, LitElement } from "lit-element";
import { HassBaseEl } from "./hass-base-mixin";
import { HaToast } from "../components/ha-toast";
import { computeRTL } from "../common/util/compute_rtl";
import { showToast } from "../util/toast";
export default (superClass: Constructor<LitElement & HassBaseEl>) =>
class extends superClass {
private _discToast?: HaToast;
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
// Need to load in advance because when disconnected, can't dynamically load code.
import(/* webpackChunkName: "ha-toast" */ "../components/ha-toast");
import(/* webpackChunkName: "notification-manager" */ "../managers/notification-manager");
}
protected hassReconnected() {
super.hassReconnected();
if (this._discToast) {
this._discToast.opened = false;
}
showToast(this, {
message: "",
duration: 1,
});
}
protected hassDisconnected() {
super.hassDisconnected();
if (!this._discToast) {
const el = document.createElement("ha-toast");
el.duration = 0;
this._discToast = el;
this.shadowRoot!.appendChild(el as any);
}
this._discToast.dir = computeRTL(this.hass!);
this._discToast.text = this.hass!.localize(
"ui.notification_toast.connection_lost"
);
this._discToast.opened = true;
showToast(this, {
message: this.hass!.localize("ui.notification_toast.connection_lost"),
duration: 0,
dismissable: false,
});
}
};

View File

@ -999,7 +999,12 @@
"warning": {
"entity_not_found": "Entity not available: {entity}",
"entity_non_numeric": "Entity is non-numeric: {entity}"
}
},
"changed_toast": {
"message": "The Lovelace config was updated, would you like to refresh?",
"refresh": "Refresh"
},
"reload_lovelace": "Reload Lovelace"
},
"mailbox": {
"empty": "You do not have any messages",

View File

@ -1,3 +1,6 @@
import { HassElement } from "../state/hass-element";
import { showToast } from "./toast";
export const registerServiceWorker = (notifyUpdate = true) => {
if (
!("serviceWorker" in navigator) ||
@ -20,9 +23,19 @@ export const registerServiceWorker = (notifyUpdate = true) => {
!__DEMO__
) {
// Notify users here of a new frontend being available.
import(/* webpackChunkName: "show-new-frontend-toast" */ "./show-new-frontend-toast").then(
(mod) => mod.default(installingWorker)
);
const haElement = window.document.querySelector(
"home-assistant, ha-onboarding"
)! as HassElement;
showToast(haElement, {
message: "A new version of the frontend is available.",
action: {
action: () =>
installingWorker.postMessage({ type: "skipWaiting" }),
text: "reload",
},
duration: 0,
dismissable: false,
});
}
});
});

View File

@ -1,20 +0,0 @@
import "@material/mwc-button";
import "../components/ha-toast";
export default (installingWorker) => {
const toast = document.createElement("ha-toast");
toast.opened = true;
toast.text = "A new version of the frontend is available.";
toast.duration = 0;
const button = document.createElement("mwc-button");
button.addEventListener("click", () =>
installingWorker.postMessage({ type: "skipWaiting" })
);
button.style.color = "var(--primary-color)";
button.style.fontWeight = "bold";
button.label = "reload";
toast.appendChild(button);
document.body.appendChild(toast);
};