diff --git a/src/data/lovelace.ts b/src/data/lovelace.ts
index fa638acb85..32bebb98ff 100644
--- a/src/data/lovelace.ts
+++ b/src/data/lovelace.ts
@@ -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");
diff --git a/src/managers/notification-manager.ts b/src/managers/notification-manager.ts
index 8ff0afb521..0cc3dda81d 100644
--- a/src/managers/notification-manager.ts
+++ b/src/managers/notification-manager.ts
@@ -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`
-
+
+ ${this._action
+ ? html`
+
+ `
+ : ""}
+
+ `;
+ }
+
+ 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;
+ }
+}
diff --git a/src/panels/lovelace/ha-panel-lovelace.ts b/src/panels/lovelace/ha-panel-lovelace.ts
index 8cf00fd008..e8765cfcc3 100644
--- a/src/panels/lovelace/ha-panel-lovelace.ts
+++ b/src/panels/lovelace/ha-panel-lovelace.ts
@@ -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`
- Reload Lovelace
+ ${this.hass!.localize(
+ "ui.panel.lovelace.reload_lovelace"
+ )}
`;
}
@@ -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
diff --git a/src/polymer-types.ts b/src/polymer-types.ts
index 4ffbf77df6..b00de62941 100644
--- a/src/polymer-types.ts
+++ b/src/polymer-types.ts
@@ -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;
diff --git a/src/state/disconnect-toast-mixin.ts b/src/state/disconnect-toast-mixin.ts
index 853fa3f392..a36ec359ff 100644
--- a/src/state/disconnect-toast-mixin.ts
+++ b/src/state/disconnect-toast-mixin.ts
@@ -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) =>
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,
+ });
}
};
diff --git a/src/translations/en.json b/src/translations/en.json
index e8fddbcaaa..1c160b0482 100644
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -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",
diff --git a/src/util/register-service-worker.ts b/src/util/register-service-worker.ts
index e48c2dc437..1d9870019d 100644
--- a/src/util/register-service-worker.ts
+++ b/src/util/register-service-worker.ts
@@ -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,
+ });
}
});
});
diff --git a/src/util/show-new-frontend-toast.js b/src/util/show-new-frontend-toast.js
deleted file mode 100644
index a089245bda..0000000000
--- a/src/util/show-new-frontend-toast.js
+++ /dev/null
@@ -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);
-};