mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-09 10:26:35 +00:00
Isolate hass state from base el (#3157)
This commit is contained in:
parent
8729410dce
commit
fcdb1b48a2
@ -1,4 +1,4 @@
|
|||||||
import { HomeAssistantAppEl } from "../../src/layouts/app/home-assistant";
|
import { HomeAssistantAppEl } from "../../src/layouts/home-assistant";
|
||||||
import {
|
import {
|
||||||
provideHass,
|
provideHass,
|
||||||
MockHomeAssistant,
|
MockHomeAssistant,
|
||||||
@ -18,7 +18,7 @@ import { HomeAssistant } from "../../src/types";
|
|||||||
import { mockFrontend } from "./stubs/frontend";
|
import { mockFrontend } from "./stubs/frontend";
|
||||||
|
|
||||||
class HaDemo extends HomeAssistantAppEl {
|
class HaDemo extends HomeAssistantAppEl {
|
||||||
protected async _handleConnProm() {
|
protected async _initialize() {
|
||||||
const initial: Partial<MockHomeAssistant> = {
|
const initial: Partial<MockHomeAssistant> = {
|
||||||
panelUrl: (this as any).panelUrl,
|
panelUrl: (this as any).panelUrl,
|
||||||
// Override updateHass so that the correct hass lifecycle methods are called
|
// Override updateHass so that the correct hass lifecycle methods are called
|
||||||
|
@ -14,7 +14,7 @@ import {
|
|||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { forwardHaptic } from "../../util/haptics";
|
import { forwardHaptic } from "../../data/haptics";
|
||||||
|
|
||||||
const isOn = (stateObj?: HassEntity) =>
|
const isOn = (stateObj?: HassEntity) =>
|
||||||
stateObj !== undefined && !STATES_OFF.includes(stateObj.state);
|
stateObj !== undefined && !STATES_OFF.includes(stateObj.state);
|
||||||
@ -90,7 +90,7 @@ class HaEntityToggle extends LitElement {
|
|||||||
if (!this.hass || !this.stateObj) {
|
if (!this.hass || !this.stateObj) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
forwardHaptic(this, "light");
|
forwardHaptic("light");
|
||||||
const stateDomain = computeStateDomain(this.stateObj);
|
const stateDomain = computeStateDomain(this.stateObj);
|
||||||
let serviceDomain;
|
let serviceDomain;
|
||||||
let service;
|
let service;
|
||||||
|
22
src/data/connection-status.ts
Normal file
22
src/data/connection-status.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Broadcast connection status updates
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { fireEvent, HASSDomEvent } from "../common/dom/fire_event";
|
||||||
|
|
||||||
|
export type ConnectionStatus = "connected" | "auth-invalid" | "disconnected";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
// for fire event
|
||||||
|
interface HASSDomEvents {
|
||||||
|
"connection-status": ConnectionStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GlobalEventHandlersEventMap {
|
||||||
|
"connection-status": HASSDomEvent<ConnectionStatus>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const broadcastConnectionStatus = (status: ConnectionStatus) => {
|
||||||
|
fireEvent(window, "connection-status", status);
|
||||||
|
};
|
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Utility function that enables haptic feedback
|
* Broadcast haptic feedback requests
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { fireEvent, HASSDomEvent } from "../common/dom/fire_event";
|
import { fireEvent, HASSDomEvent } from "../common/dom/fire_event";
|
||||||
@ -27,6 +27,6 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const forwardHaptic = (el: HTMLElement, hapticType: HapticType) => {
|
export const forwardHaptic = (hapticType: HapticType) => {
|
||||||
fireEvent(el, "haptic", hapticType);
|
fireEvent(window, "haptic", hapticType);
|
||||||
};
|
};
|
@ -11,7 +11,7 @@ import "../resources/roboto";
|
|||||||
// properly into iron-meta, which is used to transfer iconsets to iron-icon.
|
// properly into iron-meta, which is used to transfer iconsets to iron-icon.
|
||||||
import "../components/ha-iconset-svg";
|
import "../components/ha-iconset-svg";
|
||||||
|
|
||||||
import "../layouts/app/home-assistant";
|
import "../layouts/home-assistant";
|
||||||
|
|
||||||
setPassiveTouchGestures(true);
|
setPassiveTouchGestures(true);
|
||||||
/* LastPass createElement workaround. See #428 */
|
/* LastPass createElement workaround. See #428 */
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { ExternalMessaging } from "./external_messaging";
|
import { ExternalMessaging } from "./external_messaging";
|
||||||
|
|
||||||
export const externalForwardConnectionEvents = (bus: ExternalMessaging) => {
|
export const externalForwardConnectionEvents = (bus: ExternalMessaging) => {
|
||||||
document.addEventListener("connection-status", (ev) =>
|
window.addEventListener("connection-status", (ev) =>
|
||||||
bus.fireMessage({
|
bus.fireMessage({
|
||||||
type: "connection-status",
|
type: "connection-status",
|
||||||
payload: { event: ev.detail },
|
payload: { event: ev.detail },
|
||||||
@ -10,6 +10,6 @@ export const externalForwardConnectionEvents = (bus: ExternalMessaging) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const externalForwardHaptics = (bus: ExternalMessaging) =>
|
export const externalForwardHaptics = (bus: ExternalMessaging) =>
|
||||||
document.addEventListener("haptic", (ev) =>
|
window.addEventListener("haptic", (ev) =>
|
||||||
bus.fireMessage({ type: "haptic", payload: { hapticType: ev.detail } })
|
bus.fireMessage({ type: "haptic", payload: { hapticType: ev.detail } })
|
||||||
);
|
);
|
||||||
|
@ -1,161 +0,0 @@
|
|||||||
import {
|
|
||||||
ERR_INVALID_AUTH,
|
|
||||||
subscribeEntities,
|
|
||||||
subscribeConfig,
|
|
||||||
subscribeServices,
|
|
||||||
callService,
|
|
||||||
} from "home-assistant-js-websocket";
|
|
||||||
|
|
||||||
import { translationMetadata } from "../../resources/translations-metadata";
|
|
||||||
|
|
||||||
import LocalizeMixin from "../../mixins/localize-mixin";
|
|
||||||
import EventsMixin from "../../mixins/events-mixin";
|
|
||||||
|
|
||||||
import { getState } from "../../util/ha-pref-storage";
|
|
||||||
import { getLocalLanguage } from "../../util/hass-translation";
|
|
||||||
import { fetchWithAuth } from "../../util/fetch-with-auth";
|
|
||||||
import hassCallApi from "../../util/hass-call-api";
|
|
||||||
import { subscribePanels } from "../../data/ws-panels";
|
|
||||||
import { forwardHaptic } from "../../util/haptics";
|
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
|
||||||
|
|
||||||
export default (superClass) =>
|
|
||||||
class extends EventsMixin(LocalizeMixin(superClass)) {
|
|
||||||
firstUpdated(changedProps) {
|
|
||||||
super.firstUpdated(changedProps);
|
|
||||||
this._handleConnProm();
|
|
||||||
}
|
|
||||||
|
|
||||||
async _handleConnProm() {
|
|
||||||
let auth;
|
|
||||||
let conn;
|
|
||||||
try {
|
|
||||||
const result = await window.hassConnection;
|
|
||||||
auth = result.auth;
|
|
||||||
conn = result.conn;
|
|
||||||
} catch (err) {
|
|
||||||
this._error = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hass = Object.assign(
|
|
||||||
{
|
|
||||||
auth,
|
|
||||||
connection: conn,
|
|
||||||
connected: true,
|
|
||||||
states: null,
|
|
||||||
config: null,
|
|
||||||
themes: null,
|
|
||||||
panels: null,
|
|
||||||
services: null,
|
|
||||||
user: null,
|
|
||||||
panelUrl: this._panelUrl,
|
|
||||||
|
|
||||||
language: getLocalLanguage(),
|
|
||||||
// If resources are already loaded, don't discard them
|
|
||||||
resources: (this.hass && this.hass.resources) || null,
|
|
||||||
localize: () => "",
|
|
||||||
|
|
||||||
translationMetadata: translationMetadata,
|
|
||||||
dockedSidebar: false,
|
|
||||||
moreInfoEntityId: null,
|
|
||||||
callService: async (domain, service, serviceData = {}) => {
|
|
||||||
if (__DEV__) {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
console.log("Calling service", domain, service, serviceData);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await callService(conn, domain, service, serviceData);
|
|
||||||
} catch (err) {
|
|
||||||
if (__DEV__) {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
console.error(
|
|
||||||
"Error calling service",
|
|
||||||
domain,
|
|
||||||
service,
|
|
||||||
serviceData,
|
|
||||||
err
|
|
||||||
);
|
|
||||||
}
|
|
||||||
forwardHaptic(this, "error");
|
|
||||||
const message =
|
|
||||||
this.hass.localize(
|
|
||||||
"ui.notification_toast.service_call_failed",
|
|
||||||
"service",
|
|
||||||
`${domain}/${service}`
|
|
||||||
) + ` ${err.message}`;
|
|
||||||
this.fire("hass-notification", { message });
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
callApi: async (method, path, parameters) =>
|
|
||||||
hassCallApi(auth, method, path, parameters),
|
|
||||||
fetchWithAuth: (path, init) =>
|
|
||||||
fetchWithAuth(auth, `${auth.data.hassUrl}${path}`, init),
|
|
||||||
// For messages that do not get a response
|
|
||||||
sendWS: (msg) => {
|
|
||||||
if (__DEV__) {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
console.log("Sending", msg);
|
|
||||||
}
|
|
||||||
conn.sendMessage(msg);
|
|
||||||
},
|
|
||||||
// For messages that expect a response
|
|
||||||
callWS: (msg) => {
|
|
||||||
if (__DEV__) {
|
|
||||||
/* eslint-disable no-console */
|
|
||||||
console.log("Sending", msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
const resp = conn.sendMessagePromise(msg);
|
|
||||||
|
|
||||||
if (__DEV__) {
|
|
||||||
resp.then(
|
|
||||||
(result) => console.log("Received", result),
|
|
||||||
(err) => console.error("Error", err)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return resp;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
getState()
|
|
||||||
);
|
|
||||||
|
|
||||||
this.hassConnected();
|
|
||||||
}
|
|
||||||
|
|
||||||
hassConnected() {
|
|
||||||
super.hassConnected();
|
|
||||||
|
|
||||||
const conn = this.hass.connection;
|
|
||||||
|
|
||||||
fireEvent(document, "connection-status", "connected");
|
|
||||||
|
|
||||||
conn.addEventListener("ready", () => this.hassReconnected());
|
|
||||||
conn.addEventListener("disconnected", () => this.hassDisconnected());
|
|
||||||
// If we reconnect after losing connection and auth is no longer valid.
|
|
||||||
conn.addEventListener("reconnect-error", (_conn, err) => {
|
|
||||||
if (err === ERR_INVALID_AUTH) {
|
|
||||||
fireEvent(document, "connection-status", "auth-invalid");
|
|
||||||
location.reload();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
subscribeEntities(conn, (states) => this._updateHass({ states }));
|
|
||||||
subscribeConfig(conn, (config) => this._updateHass({ config }));
|
|
||||||
subscribeServices(conn, (services) => this._updateHass({ services }));
|
|
||||||
subscribePanels(conn, (panels) => this._updateHass({ panels }));
|
|
||||||
}
|
|
||||||
|
|
||||||
hassReconnected() {
|
|
||||||
super.hassReconnected();
|
|
||||||
this._updateHass({ connected: true });
|
|
||||||
fireEvent(document, "connection-status", "connected");
|
|
||||||
}
|
|
||||||
|
|
||||||
hassDisconnected() {
|
|
||||||
super.hassDisconnected();
|
|
||||||
this._updateHass({ connected: false });
|
|
||||||
fireEvent(document, "connection-status", "disconnected");
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,45 +1,20 @@
|
|||||||
import "@polymer/app-route/app-location";
|
import "@polymer/app-route/app-location";
|
||||||
import { html, LitElement, PropertyValues, css, property } from "lit-element";
|
import { html, LitElement, PropertyValues, css, property } from "lit-element";
|
||||||
|
|
||||||
import "../home-assistant-main";
|
import "./home-assistant-main";
|
||||||
import "../ha-init-page";
|
import "./ha-init-page";
|
||||||
import "../../resources/ha-style";
|
import "../resources/ha-style";
|
||||||
import { registerServiceWorker } from "../../util/register-service-worker";
|
import { registerServiceWorker } from "../util/register-service-worker";
|
||||||
import { DEFAULT_PANEL } from "../../common/const";
|
import { DEFAULT_PANEL } from "../common/const";
|
||||||
|
|
||||||
import HassBaseMixin from "./hass-base-mixin";
|
import { Route, HomeAssistant } from "../types";
|
||||||
import AuthMixin from "./auth-mixin";
|
import { navigate } from "../common/navigate";
|
||||||
import TranslationsMixin from "./translations-mixin";
|
import { HassElement } from "../state/hass-element";
|
||||||
import ThemesMixin from "./themes-mixin";
|
|
||||||
import MoreInfoMixin from "./more-info-mixin";
|
|
||||||
import SidebarMixin from "./sidebar-mixin";
|
|
||||||
import { dialogManagerMixin } from "./dialog-manager-mixin";
|
|
||||||
import ConnectionMixin from "./connection-mixin";
|
|
||||||
import NotificationMixin from "./notification-mixin";
|
|
||||||
import DisconnectToastMixin from "./disconnect-toast-mixin";
|
|
||||||
import { urlSyncMixin } from "./url-sync-mixin";
|
|
||||||
|
|
||||||
import { Route, HomeAssistant } from "../../types";
|
|
||||||
import { navigate } from "../../common/navigate";
|
|
||||||
|
|
||||||
(LitElement.prototype as any).html = html;
|
(LitElement.prototype as any).html = html;
|
||||||
(LitElement.prototype as any).css = css;
|
(LitElement.prototype as any).css = css;
|
||||||
|
|
||||||
const ext = <T>(baseClass: T, mixins): T =>
|
export class HomeAssistantAppEl extends HassElement {
|
||||||
mixins.reduceRight((base, mixin) => mixin(base), baseClass);
|
|
||||||
|
|
||||||
export class HomeAssistantAppEl extends ext(HassBaseMixin(LitElement), [
|
|
||||||
AuthMixin,
|
|
||||||
ThemesMixin,
|
|
||||||
TranslationsMixin,
|
|
||||||
MoreInfoMixin,
|
|
||||||
SidebarMixin,
|
|
||||||
DisconnectToastMixin,
|
|
||||||
ConnectionMixin,
|
|
||||||
NotificationMixin,
|
|
||||||
dialogManagerMixin,
|
|
||||||
urlSyncMixin,
|
|
||||||
]) {
|
|
||||||
@property() private _route?: Route;
|
@property() private _route?: Route;
|
||||||
@property() private _error?: boolean;
|
@property() private _error?: boolean;
|
||||||
@property() private _panelUrl?: string;
|
@property() private _panelUrl?: string;
|
||||||
@ -69,6 +44,7 @@ export class HomeAssistantAppEl extends ext(HassBaseMixin(LitElement), [
|
|||||||
|
|
||||||
protected firstUpdated(changedProps) {
|
protected firstUpdated(changedProps) {
|
||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
|
this._initialize();
|
||||||
setTimeout(registerServiceWorker, 1000);
|
setTimeout(registerServiceWorker, 1000);
|
||||||
/* polyfill for paper-dropdown */
|
/* polyfill for paper-dropdown */
|
||||||
import(/* webpackChunkName: "polyfill-web-animations-next" */ "web-animations-js/web-animations-next-lite.min");
|
import(/* webpackChunkName: "polyfill-web-animations-next" */ "web-animations-js/web-animations-next-lite.min");
|
||||||
@ -86,6 +62,16 @@ export class HomeAssistantAppEl extends ext(HassBaseMixin(LitElement), [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected async _initialize() {
|
||||||
|
try {
|
||||||
|
const { auth, conn } = await window.hassConnection;
|
||||||
|
this.initializeHass(auth, conn);
|
||||||
|
} catch (err) {
|
||||||
|
this._error = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _routeChanged(ev) {
|
private _routeChanged(ev) {
|
||||||
const route = ev.detail.value as Route;
|
const route = ev.detail.value as Route;
|
||||||
// If it's the first route that we process,
|
// If it's the first route that we process,
|
@ -13,7 +13,7 @@ import { PaperToggleButtonElement } from "@polymer/paper-toggle-button/paper-tog
|
|||||||
import { DOMAINS_TOGGLE } from "../../../common/const";
|
import { DOMAINS_TOGGLE } from "../../../common/const";
|
||||||
import { turnOnOffEntities } from "../common/entity/turn-on-off-entities";
|
import { turnOnOffEntities } from "../common/entity/turn-on-off-entities";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { forwardHaptic } from "../../../util/haptics";
|
import { forwardHaptic } from "../../../data/haptics";
|
||||||
|
|
||||||
@customElement("hui-entities-toggle")
|
@customElement("hui-entities-toggle")
|
||||||
class HuiEntitiesToggle extends LitElement {
|
class HuiEntitiesToggle extends LitElement {
|
||||||
@ -66,7 +66,7 @@ class HuiEntitiesToggle extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _callService(ev: MouseEvent): void {
|
private _callService(ev: MouseEvent): void {
|
||||||
forwardHaptic(this, "light");
|
forwardHaptic("light");
|
||||||
const turnOn = (ev.target as PaperToggleButtonElement).checked;
|
const turnOn = (ev.target as PaperToggleButtonElement).checked;
|
||||||
turnOnOffEntities(this.hass!, this._toggleEntities!, turnOn!);
|
turnOnOffEntities(this.hass!, this._toggleEntities!, turnOn!);
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ import { HomeAssistant, InputSelectEntity } from "../../../types";
|
|||||||
import { EntityRow, EntityConfig } from "./types";
|
import { EntityRow, EntityConfig } from "./types";
|
||||||
import { setInputSelectOption } from "../../../data/input-select";
|
import { setInputSelectOption } from "../../../data/input-select";
|
||||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||||
import { forwardHaptic } from "../../../util/haptics";
|
import { forwardHaptic } from "../../../data/haptics";
|
||||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||||
|
|
||||||
@customElement("hui-input-select-entity-row")
|
@customElement("hui-input-select-entity-row")
|
||||||
@ -128,7 +128,7 @@ class HuiInputSelectEntityRow extends LitElement implements EntityRow {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
forwardHaptic(this, "light");
|
forwardHaptic("light");
|
||||||
|
|
||||||
setInputSelectOption(
|
setInputSelectOption(
|
||||||
this.hass!,
|
this.hass!,
|
||||||
|
@ -1,16 +1,5 @@
|
|||||||
import * as translationMetadata_ from "../../build-translations/translationMetadata.json";
|
import * as translationMetadata_ from "../../build-translations/translationMetadata.json";
|
||||||
|
import { TranslationMetadata } from "../types.js";
|
||||||
interface TranslationMetadata {
|
|
||||||
fragments: string[];
|
|
||||||
translations: {
|
|
||||||
[language: string]: {
|
|
||||||
nativeName: string;
|
|
||||||
fingerprints: {
|
|
||||||
[filename: string]: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const translationMetadata = (translationMetadata_ as any)
|
export const translationMetadata = (translationMetadata_ as any)
|
||||||
.default as TranslationMetadata;
|
.default as TranslationMetadata;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { clearState } from "../../util/ha-pref-storage";
|
import { clearState } from "../util/ha-pref-storage";
|
||||||
import { askWrite } from "../../common/auth/token_storage";
|
import { askWrite } from "../common/auth/token_storage";
|
||||||
import { subscribeUser, userCollection } from "../../data/ws-user";
|
import { subscribeUser, userCollection } from "../data/ws-user";
|
||||||
import { Constructor, LitElement } from "lit-element";
|
import { Constructor, LitElement } from "lit-element";
|
||||||
import { HassBaseEl } from "./hass-base-mixin";
|
import { HassBaseEl } from "./hass-base-mixin";
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ export default (superClass: Constructor<LitElement & HassBaseEl>) =>
|
|||||||
if (askWrite()) {
|
if (askWrite()) {
|
||||||
this.updateComplete
|
this.updateComplete
|
||||||
.then(() =>
|
.then(() =>
|
||||||
import(/* webpackChunkName: "ha-store-auth-card" */ "../../dialogs/ha-store-auth-card")
|
import(/* webpackChunkName: "ha-store-auth-card" */ "../dialogs/ha-store-auth-card")
|
||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const el = document.createElement("ha-store-auth-card");
|
const el = document.createElement("ha-store-auth-card");
|
149
src/state/connection-mixin.ts
Normal file
149
src/state/connection-mixin.ts
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
import {
|
||||||
|
ERR_INVALID_AUTH,
|
||||||
|
subscribeEntities,
|
||||||
|
subscribeConfig,
|
||||||
|
subscribeServices,
|
||||||
|
callService,
|
||||||
|
Auth,
|
||||||
|
Connection,
|
||||||
|
} from "home-assistant-js-websocket";
|
||||||
|
|
||||||
|
import { translationMetadata } from "../resources/translations-metadata";
|
||||||
|
|
||||||
|
import { getState } from "../util/ha-pref-storage";
|
||||||
|
import { getLocalLanguage } from "../util/hass-translation";
|
||||||
|
import { fetchWithAuth } from "../util/fetch-with-auth";
|
||||||
|
import hassCallApi from "../util/hass-call-api";
|
||||||
|
import { subscribePanels } from "../data/ws-panels";
|
||||||
|
import { forwardHaptic } from "../data/haptics";
|
||||||
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
import { Constructor, LitElement } from "lit-element";
|
||||||
|
import { HassBaseEl } from "./hass-base-mixin";
|
||||||
|
import { broadcastConnectionStatus } from "../data/connection-status";
|
||||||
|
|
||||||
|
export const connectionMixin = (
|
||||||
|
superClass: Constructor<LitElement & HassBaseEl>
|
||||||
|
) =>
|
||||||
|
class extends superClass {
|
||||||
|
protected initializeHass(auth: Auth, conn: Connection) {
|
||||||
|
this.hass = {
|
||||||
|
auth,
|
||||||
|
connection: conn,
|
||||||
|
connected: true,
|
||||||
|
states: null as any,
|
||||||
|
config: null as any,
|
||||||
|
themes: null as any,
|
||||||
|
panels: null as any,
|
||||||
|
services: null as any,
|
||||||
|
user: null as any,
|
||||||
|
panelUrl: (this as any)._panelUrl,
|
||||||
|
|
||||||
|
language: getLocalLanguage(),
|
||||||
|
selectedLanguage: null,
|
||||||
|
resources: null as any,
|
||||||
|
localize: () => "",
|
||||||
|
|
||||||
|
translationMetadata,
|
||||||
|
dockedSidebar: false,
|
||||||
|
moreInfoEntityId: null,
|
||||||
|
callService: async (domain, service, serviceData = {}) => {
|
||||||
|
if (__DEV__) {
|
||||||
|
// tslint:disable-next-line: no-console
|
||||||
|
console.log("Calling service", domain, service, serviceData);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await callService(conn, domain, service, serviceData);
|
||||||
|
} catch (err) {
|
||||||
|
if (__DEV__) {
|
||||||
|
// tslint:disable-next-line: no-console
|
||||||
|
console.error(
|
||||||
|
"Error calling service",
|
||||||
|
domain,
|
||||||
|
service,
|
||||||
|
serviceData,
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
forwardHaptic("failure");
|
||||||
|
const message =
|
||||||
|
(this as any).hass.localize(
|
||||||
|
"ui.notification_toast.service_call_failed",
|
||||||
|
"service",
|
||||||
|
`${domain}/${service}`
|
||||||
|
) + ` ${err.message}`;
|
||||||
|
fireEvent(this as any, "hass-notification", { message });
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
callApi: async (method, path, parameters) =>
|
||||||
|
hassCallApi(auth, method, path, parameters),
|
||||||
|
fetchWithAuth: (path, init) =>
|
||||||
|
fetchWithAuth(auth, `${auth.data.hassUrl}${path}`, init),
|
||||||
|
// For messages that do not get a response
|
||||||
|
sendWS: (msg) => {
|
||||||
|
if (__DEV__) {
|
||||||
|
// tslint:disable-next-line: no-console
|
||||||
|
console.log("Sending", msg);
|
||||||
|
}
|
||||||
|
conn.sendMessage(msg);
|
||||||
|
},
|
||||||
|
// For messages that expect a response
|
||||||
|
callWS: <T>(msg) => {
|
||||||
|
if (__DEV__) {
|
||||||
|
// tslint:disable-next-line: no-console
|
||||||
|
console.log("Sending", msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
const resp = conn.sendMessagePromise<T>(msg);
|
||||||
|
|
||||||
|
if (__DEV__) {
|
||||||
|
resp.then(
|
||||||
|
// tslint:disable-next-line: no-console
|
||||||
|
(result) => console.log("Received", result),
|
||||||
|
// tslint:disable-next-line: no-console
|
||||||
|
(err) => console.error("Error", err)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return resp;
|
||||||
|
},
|
||||||
|
...getState(),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.hassConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected hassConnected() {
|
||||||
|
super.hassConnected();
|
||||||
|
|
||||||
|
const conn = this.hass!.connection;
|
||||||
|
|
||||||
|
broadcastConnectionStatus("connected");
|
||||||
|
|
||||||
|
conn.addEventListener("ready", () => this.hassReconnected());
|
||||||
|
conn.addEventListener("disconnected", () => this.hassDisconnected());
|
||||||
|
// If we reconnect after losing connection and auth is no longer valid.
|
||||||
|
conn.addEventListener("reconnect-error", (_conn, err) => {
|
||||||
|
if (err === ERR_INVALID_AUTH) {
|
||||||
|
broadcastConnectionStatus("auth-invalid");
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
subscribeEntities(conn, (states) => this._updateHass({ states }));
|
||||||
|
subscribeConfig(conn, (config) => this._updateHass({ config }));
|
||||||
|
subscribeServices(conn, (services) => this._updateHass({ services }));
|
||||||
|
subscribePanels(conn, (panels) => this._updateHass({ panels }));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected hassReconnected() {
|
||||||
|
super.hassReconnected();
|
||||||
|
this._updateHass({ connected: true });
|
||||||
|
broadcastConnectionStatus("connected");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected hassDisconnected() {
|
||||||
|
super.hassDisconnected();
|
||||||
|
this._updateHass({ connected: false });
|
||||||
|
broadcastConnectionStatus("disconnected");
|
||||||
|
}
|
||||||
|
};
|
@ -1,10 +1,7 @@
|
|||||||
import { Constructor, LitElement } from "lit-element";
|
import { Constructor, LitElement } from "lit-element";
|
||||||
import { HASSDomEvent } from "../../common/dom/fire_event";
|
import { HASSDomEvent } from "../common/dom/fire_event";
|
||||||
import { HassBaseEl } from "./hass-base-mixin";
|
import { HassBaseEl } from "./hass-base-mixin";
|
||||||
import {
|
import { makeDialogManager, showDialog } from "../dialogs/make-dialog-manager";
|
||||||
makeDialogManager,
|
|
||||||
showDialog,
|
|
||||||
} from "../../dialogs/make-dialog-manager";
|
|
||||||
|
|
||||||
interface RegisterDialogParams {
|
interface RegisterDialogParams {
|
||||||
dialogShowEvent: keyof HASSDomEvents;
|
dialogShowEvent: keyof HASSDomEvents;
|
@ -1,7 +1,7 @@
|
|||||||
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 { HaToast } from "../components/ha-toast";
|
||||||
import { computeRTL } from "../../common/util/compute_rtl";
|
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 {
|
||||||
@ -10,7 +10,7 @@ export default (superClass: Constructor<LitElement & HassBaseEl>) =>
|
|||||||
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: "ha-toast" */ "../components/ha-toast");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected hassReconnected() {
|
protected hassReconnected() {
|
@ -3,12 +3,14 @@ import {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
property,
|
property,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { HomeAssistant } from "../../types";
|
import { Auth, Connection } from "home-assistant-js-websocket";
|
||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
|
|
||||||
export class HassBaseEl {
|
export class HassBaseEl {
|
||||||
protected hass?: HomeAssistant;
|
protected hass?: HomeAssistant;
|
||||||
|
protected initializeHass(_auth: Auth, _conn: Connection) {}
|
||||||
protected hassConnected() {}
|
protected hassConnected() {}
|
||||||
protected hassReconnected() {}
|
protected hassReconnected() {}
|
||||||
protected hassDisconnected() {}
|
protected hassDisconnected() {}
|
28
src/state/hass-element.ts
Normal file
28
src/state/hass-element.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import HassBaseMixin from "./hass-base-mixin";
|
||||||
|
import AuthMixin from "./auth-mixin";
|
||||||
|
import TranslationsMixin from "./translations-mixin";
|
||||||
|
import ThemesMixin from "./themes-mixin";
|
||||||
|
import MoreInfoMixin from "./more-info-mixin";
|
||||||
|
import SidebarMixin from "./sidebar-mixin";
|
||||||
|
import { dialogManagerMixin } from "./dialog-manager-mixin";
|
||||||
|
import { connectionMixin } from "./connection-mixin";
|
||||||
|
import NotificationMixin from "./notification-mixin";
|
||||||
|
import DisconnectToastMixin from "./disconnect-toast-mixin";
|
||||||
|
import { urlSyncMixin } from "./url-sync-mixin";
|
||||||
|
import { LitElement } from "lit-element";
|
||||||
|
|
||||||
|
const ext = <T>(baseClass: T, mixins): T =>
|
||||||
|
mixins.reduceRight((base, mixin) => mixin(base), baseClass);
|
||||||
|
|
||||||
|
export class HassElement extends ext(HassBaseMixin(LitElement), [
|
||||||
|
AuthMixin,
|
||||||
|
ThemesMixin,
|
||||||
|
TranslationsMixin,
|
||||||
|
MoreInfoMixin,
|
||||||
|
SidebarMixin,
|
||||||
|
DisconnectToastMixin,
|
||||||
|
connectionMixin,
|
||||||
|
NotificationMixin,
|
||||||
|
dialogManagerMixin,
|
||||||
|
urlSyncMixin,
|
||||||
|
]) {}
|
@ -20,7 +20,7 @@ export default (superClass: Constructor<LitElement & HassBaseEl>) =>
|
|||||||
this.addEventListener("hass-more-info", (e) => this._handleMoreInfo(e));
|
this.addEventListener("hass-more-info", (e) => this._handleMoreInfo(e));
|
||||||
|
|
||||||
// Load it once we are having the initial rendering done.
|
// Load it once we are having the initial rendering done.
|
||||||
import(/* webpackChunkName: "more-info-dialog" */ "../../dialogs/ha-more-info-dialog");
|
import(/* webpackChunkName: "more-info-dialog" */ "../dialogs/ha-more-info-dialog");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _handleMoreInfo(ev) {
|
private async _handleMoreInfo(ev) {
|
@ -6,7 +6,7 @@ export default (superClass) =>
|
|||||||
dialogShowEvent: "hass-notification",
|
dialogShowEvent: "hass-notification",
|
||||||
dialogTag: "notification-manager",
|
dialogTag: "notification-manager",
|
||||||
dialogImport: () =>
|
dialogImport: () =>
|
||||||
import(/* webpackChunkName: "notification-manager" */ "../../managers/notification-manager"),
|
import(/* webpackChunkName: "notification-manager" */ "../managers/notification-manager"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
@ -1,7 +1,7 @@
|
|||||||
import { storeState } from "../../util/ha-pref-storage";
|
import { storeState } from "../util/ha-pref-storage";
|
||||||
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 { HASSDomEvent } from "../../common/dom/fire_event";
|
import { HASSDomEvent } from "../common/dom/fire_event";
|
||||||
|
|
||||||
interface DockSidebarParams {
|
interface DockSidebarParams {
|
||||||
dock: boolean;
|
dock: boolean;
|
@ -1,9 +1,9 @@
|
|||||||
import applyThemesOnElement from "../../common/dom/apply_themes_on_element";
|
import applyThemesOnElement from "../common/dom/apply_themes_on_element";
|
||||||
import { storeState } from "../../util/ha-pref-storage";
|
import { storeState } from "../util/ha-pref-storage";
|
||||||
import { subscribeThemes } from "../../data/ws-themes";
|
import { subscribeThemes } from "../data/ws-themes";
|
||||||
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 { HASSDomEvent } from "../../common/dom/fire_event";
|
import { HASSDomEvent } from "../common/dom/fire_event";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// for add event listener
|
// for add event listener
|
@ -1,17 +1,17 @@
|
|||||||
import { translationMetadata } from "../../resources/translations-metadata";
|
import { translationMetadata } from "../resources/translations-metadata";
|
||||||
import {
|
import {
|
||||||
getTranslation,
|
getTranslation,
|
||||||
getLocalLanguage,
|
getLocalLanguage,
|
||||||
getUserLanguage,
|
getUserLanguage,
|
||||||
} from "../../util/hass-translation";
|
} from "../util/hass-translation";
|
||||||
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 { computeLocalize } from "../../common/translations/localize";
|
import { computeLocalize } from "../common/translations/localize";
|
||||||
import { computeRTL } from "../../common/util/compute_rtl";
|
import { computeRTL } from "../common/util/compute_rtl";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../types";
|
||||||
import { saveFrontendUserData } from "../../data/frontend";
|
import { saveFrontendUserData } from "../data/frontend";
|
||||||
import { storeState } from "../../util/ha-pref-storage";
|
import { storeState } from "../util/ha-pref-storage";
|
||||||
import { getHassTranslations } from "../../data/translation";
|
import { getHassTranslations } from "../data/translation";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* superClass needs to contain `this.hass` and `this._updateHass`.
|
* superClass needs to contain `this.hass` and `this._updateHass`.
|
@ -1,6 +1,6 @@
|
|||||||
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 { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
|
||||||
/* tslint:disable:no-console */
|
/* tslint:disable:no-console */
|
||||||
const DEBUG = false;
|
const DEBUG = false;
|
28
src/types.ts
28
src/types.ts
@ -10,7 +10,6 @@ import {
|
|||||||
} from "home-assistant-js-websocket";
|
} from "home-assistant-js-websocket";
|
||||||
import { LocalizeFunc } from "./common/translations/localize";
|
import { LocalizeFunc } from "./common/translations/localize";
|
||||||
import { ExternalMessaging } from "./external_app/external_messaging";
|
import { ExternalMessaging } from "./external_app/external_messaging";
|
||||||
import { HASSDomEvent } from "./common/dom/fire_event";
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
var __DEV__: boolean;
|
var __DEV__: boolean;
|
||||||
@ -38,16 +37,9 @@ declare global {
|
|||||||
value: unknown;
|
value: unknown;
|
||||||
};
|
};
|
||||||
change: undefined;
|
change: undefined;
|
||||||
"connection-status": ConnectionStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GlobalEventHandlersEventMap {
|
|
||||||
"connection-status": HASSDomEvent<ConnectionStatus>;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConnectionStatus = "connected" | "auth-invalid" | "disconnected";
|
|
||||||
|
|
||||||
export interface WebhookError {
|
export interface WebhookError {
|
||||||
code: number;
|
code: number;
|
||||||
message: string;
|
message: string;
|
||||||
@ -103,6 +95,13 @@ export interface Translation {
|
|||||||
fingerprints: { [fragment: string]: string };
|
fingerprints: { [fragment: string]: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TranslationMetadata {
|
||||||
|
fragments: string[];
|
||||||
|
translations: {
|
||||||
|
[lang: string]: Translation;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export interface Notification {
|
export interface Notification {
|
||||||
notification_id: string;
|
notification_id: string;
|
||||||
message: string;
|
message: string;
|
||||||
@ -135,18 +134,13 @@ export interface HomeAssistant {
|
|||||||
// - english (en)
|
// - english (en)
|
||||||
language: string;
|
language: string;
|
||||||
// local stored language, keep that name for backward compability
|
// local stored language, keep that name for backward compability
|
||||||
selectedLanguage: string;
|
selectedLanguage: string | null;
|
||||||
resources: Resources;
|
resources: Resources;
|
||||||
localize: LocalizeFunc;
|
localize: LocalizeFunc;
|
||||||
translationMetadata: {
|
translationMetadata: TranslationMetadata;
|
||||||
fragments: string[];
|
|
||||||
translations: {
|
|
||||||
[lang: string]: Translation;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
dockedSidebar: boolean;
|
dockedSidebar: boolean;
|
||||||
moreInfoEntityId: string;
|
moreInfoEntityId: string | null;
|
||||||
user?: CurrentUser;
|
user?: CurrentUser;
|
||||||
callService: (
|
callService: (
|
||||||
domain: string,
|
domain: string,
|
||||||
@ -162,7 +156,7 @@ export interface HomeAssistant {
|
|||||||
path: string,
|
path: string,
|
||||||
init?: { [key: string]: any }
|
init?: { [key: string]: any }
|
||||||
) => Promise<Response>;
|
) => Promise<Response>;
|
||||||
sendWS: (msg: MessageBase) => Promise<void>;
|
sendWS: (msg: MessageBase) => void;
|
||||||
callWS: <T>(msg: MessageBase) => Promise<T>;
|
callWS: <T>(msg: MessageBase) => Promise<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user