mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-27 19:26: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 { HomeAssistant } from "../types";
|
||||||
|
import { Connection } from "home-assistant-js-websocket";
|
||||||
|
|
||||||
export interface LovelaceConfig {
|
export interface LovelaceConfig {
|
||||||
title?: string;
|
title?: string;
|
||||||
@ -76,3 +77,8 @@ export const saveConfig = (
|
|||||||
type: "lovelace/config/save",
|
type: "lovelace/config/save",
|
||||||
config,
|
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 { computeRTL } from "../common/util/compute_rtl";
|
||||||
import "../components/ha-toast";
|
|
||||||
import { LitElement, query, property, TemplateResult, html } from "lit-element";
|
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
|
import "@material/mwc-button";
|
||||||
|
import "../components/ha-toast";
|
||||||
// Typing
|
// Typing
|
||||||
// tslint:disable-next-line: no-duplicate-imports
|
// tslint:disable-next-line: no-duplicate-imports
|
||||||
import { HaToast } from "../components/ha-toast";
|
import { HaToast } from "../components/ha-toast";
|
||||||
|
|
||||||
export interface ShowToastParams {
|
export interface ShowToastParams {
|
||||||
message: string;
|
message: string;
|
||||||
|
action?: ToastActionParams;
|
||||||
|
duration?: number;
|
||||||
|
dismissable?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToastActionParams {
|
||||||
|
action: () => void;
|
||||||
|
text: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class NotificationManager extends LitElement {
|
class NotificationManager extends LitElement {
|
||||||
@property() public hass!: HomeAssistant;
|
@property() public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() private _action?: ToastActionParams;
|
||||||
|
@property() private _noCancelOnOutsideClick: boolean = false;
|
||||||
|
|
||||||
@query("ha-toast") private _toast!: HaToast;
|
@query("ha-toast") private _toast!: HaToast;
|
||||||
|
|
||||||
public showDialog({ message }: ShowToastParams) {
|
public showDialog({
|
||||||
|
message,
|
||||||
|
action,
|
||||||
|
duration,
|
||||||
|
dismissable,
|
||||||
|
}: ShowToastParams) {
|
||||||
const toast = this._toast;
|
const toast = this._toast;
|
||||||
toast.setAttribute("dir", computeRTL(this.hass) ? "rtl" : "ltr");
|
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 {
|
protected render(): TemplateResult | void {
|
||||||
return html`
|
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);
|
customElements.define("notification-manager", NotificationManager);
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
// for fire event
|
||||||
|
interface HASSDomEvents {
|
||||||
|
"hass-notification": ShowToastParams;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
import "@material/mwc-button";
|
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-loading-screen";
|
||||||
import "../../layouts/hass-error-screen";
|
import "../../layouts/hass-error-screen";
|
||||||
import "./hui-root";
|
import "./hui-root";
|
||||||
@ -15,6 +20,7 @@ import {
|
|||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { showSaveDialog } from "./editor/show-save-config-dialog";
|
import { showSaveDialog } from "./editor/show-save-config-dialog";
|
||||||
import { generateLovelaceConfig } from "./common/generate-lovelace-config";
|
import { generateLovelaceConfig } from "./common/generate-lovelace-config";
|
||||||
|
import { showToast } from "../../util/toast";
|
||||||
|
|
||||||
interface LovelacePanelConfig {
|
interface LovelacePanelConfig {
|
||||||
mode: "yaml" | "storage";
|
mode: "yaml" | "storage";
|
||||||
@ -42,6 +48,8 @@ class LovelacePanel extends LitElement {
|
|||||||
|
|
||||||
private mqls?: MediaQueryList[];
|
private mqls?: MediaQueryList[];
|
||||||
|
|
||||||
|
private _saving: boolean = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this._closeEditor = this._closeEditor.bind(this);
|
this._closeEditor = this._closeEditor.bind(this);
|
||||||
@ -66,7 +74,11 @@ class LovelacePanel extends LitElement {
|
|||||||
if (state === "error") {
|
if (state === "error") {
|
||||||
return html`
|
return html`
|
||||||
<hass-error-screen title="Lovelace" .error="${this._errorMsg}">
|
<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>
|
</hass-error-screen>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -107,6 +119,16 @@ class LovelacePanel extends LitElement {
|
|||||||
|
|
||||||
public firstUpdated() {
|
public firstUpdated() {
|
||||||
this._fetchConfig(false);
|
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._updateColumns = this._updateColumns.bind(this);
|
||||||
this.mqls = [300, 600, 900, 1200].map((width) => {
|
this.mqls = [300, 600, 900, 1200].map((width) => {
|
||||||
const mql = matchMedia(`(min-width: ${width}px)`);
|
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() {
|
private _forceFetchConfig() {
|
||||||
this._fetchConfig(true);
|
this._fetchConfig(true);
|
||||||
}
|
}
|
||||||
@ -211,6 +249,7 @@ class LovelacePanel extends LitElement {
|
|||||||
config: newConfig,
|
config: newConfig,
|
||||||
mode: "storage",
|
mode: "storage",
|
||||||
});
|
});
|
||||||
|
this._saving = true;
|
||||||
await saveConfig(this.hass!, newConfig);
|
await saveConfig(this.hass!, newConfig);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
|
@ -31,9 +31,6 @@ declare global {
|
|||||||
"iron-resize": undefined;
|
"iron-resize": undefined;
|
||||||
"config-refresh": undefined;
|
"config-refresh": undefined;
|
||||||
"ha-refresh-cloud-status": undefined;
|
"ha-refresh-cloud-status": undefined;
|
||||||
"hass-notification": {
|
|
||||||
message: string;
|
|
||||||
};
|
|
||||||
"hass-api-called": {
|
"hass-api-called": {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
response: unknown;
|
response: unknown;
|
||||||
|
@ -1,37 +1,31 @@
|
|||||||
import { Constructor, LitElement } from "lit-element";
|
import { Constructor, LitElement } from "lit-element";
|
||||||
import { HassBaseEl } from "./hass-base-mixin";
|
import { HassBaseEl } from "./hass-base-mixin";
|
||||||
import { HaToast } from "../components/ha-toast";
|
import { showToast } from "../util/toast";
|
||||||
import { computeRTL } from "../common/util/compute_rtl";
|
|
||||||
|
|
||||||
export default (superClass: Constructor<LitElement & HassBaseEl>) =>
|
export default (superClass: Constructor<LitElement & HassBaseEl>) =>
|
||||||
class extends superClass {
|
class extends superClass {
|
||||||
private _discToast?: HaToast;
|
|
||||||
|
|
||||||
protected firstUpdated(changedProps) {
|
protected firstUpdated(changedProps) {
|
||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
// Need to load in advance because when disconnected, can't dynamically load code.
|
// 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() {
|
protected hassReconnected() {
|
||||||
super.hassReconnected();
|
super.hassReconnected();
|
||||||
if (this._discToast) {
|
|
||||||
this._discToast.opened = false;
|
showToast(this, {
|
||||||
}
|
message: "",
|
||||||
|
duration: 1,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected hassDisconnected() {
|
protected hassDisconnected() {
|
||||||
super.hassDisconnected();
|
super.hassDisconnected();
|
||||||
if (!this._discToast) {
|
|
||||||
const el = document.createElement("ha-toast");
|
showToast(this, {
|
||||||
el.duration = 0;
|
message: this.hass!.localize("ui.notification_toast.connection_lost"),
|
||||||
this._discToast = el;
|
duration: 0,
|
||||||
this.shadowRoot!.appendChild(el as any);
|
dismissable: false,
|
||||||
}
|
});
|
||||||
this._discToast.dir = computeRTL(this.hass!);
|
|
||||||
this._discToast.text = this.hass!.localize(
|
|
||||||
"ui.notification_toast.connection_lost"
|
|
||||||
);
|
|
||||||
this._discToast.opened = true;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -999,7 +999,12 @@
|
|||||||
"warning": {
|
"warning": {
|
||||||
"entity_not_found": "Entity not available: {entity}",
|
"entity_not_found": "Entity not available: {entity}",
|
||||||
"entity_non_numeric": "Entity is non-numeric: {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": {
|
"mailbox": {
|
||||||
"empty": "You do not have any messages",
|
"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) => {
|
export const registerServiceWorker = (notifyUpdate = true) => {
|
||||||
if (
|
if (
|
||||||
!("serviceWorker" in navigator) ||
|
!("serviceWorker" in navigator) ||
|
||||||
@ -20,9 +23,19 @@ export const registerServiceWorker = (notifyUpdate = true) => {
|
|||||||
!__DEMO__
|
!__DEMO__
|
||||||
) {
|
) {
|
||||||
// Notify users here of a new frontend being available.
|
// Notify users here of a new frontend being available.
|
||||||
import(/* webpackChunkName: "show-new-frontend-toast" */ "./show-new-frontend-toast").then(
|
const haElement = window.document.querySelector(
|
||||||
(mod) => mod.default(installingWorker)
|
"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