mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 09:46:36 +00:00
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:
parent
d10a0b3b6c
commit
e595637a10
@ -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");
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -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",
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user