Lovelace cleanups (#3427)

* Improvements

* Add types CAF

* Fix demo switching

* Do not set background color in hui-view
This commit is contained in:
Paulus Schoutsen 2019-07-26 11:06:16 -07:00 committed by GitHub
parent dae0ecce6a
commit 6abbe72e4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 233 additions and 107 deletions

View File

@ -1,4 +1,4 @@
// Run HA develop mode // Run demo develop mode
const gulp = require("gulp"); const gulp = require("gulp");
require("./clean.js"); require("./clean.js");

View File

@ -84,12 +84,12 @@ gulp.task("webpack-dev-server-demo", () => {
open: true, open: true,
watchContentBase: true, watchContentBase: true,
contentBase: path.resolve(paths.demo_dir, "dist"), contentBase: path.resolve(paths.demo_dir, "dist"),
}).listen(8080, "localhost", function(err) { }).listen(8090, "localhost", function(err) {
if (err) { if (err) {
throw err; throw err;
} }
// Server listening // Server listening
log("[webpack-dev-server]", "http://localhost:8080"); log("[webpack-dev-server]", "http://localhost:8090");
}); });
}); });

View File

@ -96,7 +96,7 @@ const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
// Create an object mapping browser urls to their paths during build // Create an object mapping browser urls to their paths during build
const translationMetadata = require("../build-translations/translationMetadata.json"); const translationMetadata = require("../build-translations/translationMetadata.json");
const workBoxTranslationsTemplatedURLs = {}; const workBoxTranslationsTemplatedURLs = {};
const englishFP = translationMetadata["translations"]["en"]["fingerprints"]; const englishFP = translationMetadata.translations.en.fingerprints;
Object.keys(englishFP).forEach((key) => { Object.keys(englishFP).forEach((key) => {
workBoxTranslationsTemplatedURLs[ workBoxTranslationsTemplatedURLs[
`/static/translations/${englishFP[key]}` `/static/translations/${englishFP[key]}`
@ -192,7 +192,7 @@ const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
new webpack.DefinePlugin({ new webpack.DefinePlugin({
__DEV__: !isProdBuild, __DEV__: !isProdBuild,
__BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"), __BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"),
__VERSION__: JSON.stringify("DEMO"), __VERSION__: JSON.stringify(`DEMO-${version}`),
__DEMO__: true, __DEMO__: true,
__STATIC_PATH__: "/static/", __STATIC_PATH__: "/static/",
"process.env.NODE_ENV": JSON.stringify( "process.env.NODE_ENV": JSON.stringify(

View File

@ -16,6 +16,7 @@ import { mockEvents } from "./stubs/events";
import { mockMediaPlayer } from "./stubs/media_player"; import { mockMediaPlayer } from "./stubs/media_player";
import { HomeAssistant } from "../../src/types"; import { HomeAssistant } from "../../src/types";
import { mockFrontend } from "./stubs/frontend"; import { mockFrontend } from "./stubs/frontend";
import { mockPersistentNotification } from "./stubs/persistent_notification";
class HaDemo extends HomeAssistantAppEl { class HaDemo extends HomeAssistantAppEl {
protected async _initialize() { protected async _initialize() {
@ -43,6 +44,7 @@ class HaDemo extends HomeAssistantAppEl {
mockEvents(hass); mockEvents(hass);
mockMediaPlayer(hass); mockMediaPlayer(hass);
mockFrontend(hass); mockFrontend(hass);
mockPersistentNotification(hass);
// Once config is loaded AND localize, set entities and apply theme. // Once config is loaded AND localize, set entities and apply theme.
Promise.all([selectedDemoConfig, localizePromise]).then( Promise.all([selectedDemoConfig, localizePromise]).then(

View File

@ -0,0 +1,16 @@
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
import { PersistentNotification } from "../../../src/data/persistent_notification";
export const mockPersistentNotification = (hass: MockHomeAssistant) => {
hass.mockWS("persistent_notification/get", () =>
Promise.resolve([
{
created_at: new Date().toISOString(),
message: "There was motion detected in the backyard.",
notification_id: "demo-1",
title: "Motion Detected!",
status: "unread",
},
] as PersistentNotification[])
);
};

View File

@ -78,7 +78,7 @@
"fuse.js": "^3.4.4", "fuse.js": "^3.4.4",
"google-timezones-json": "^1.0.2", "google-timezones-json": "^1.0.2",
"hls.js": "^0.12.4", "hls.js": "^0.12.4",
"home-assistant-js-websocket": "^4.2.2", "home-assistant-js-websocket": "4.3.1",
"intl-messageformat": "^2.2.0", "intl-messageformat": "^2.2.0",
"jquery": "^3.3.1", "jquery": "^3.3.1",
"js-yaml": "^3.13.0", "js-yaml": "^3.13.0",
@ -112,6 +112,8 @@
"@babel/preset-typescript": "^7.3.3", "@babel/preset-typescript": "^7.3.3",
"@gfx/zopfli": "^1.0.11", "@gfx/zopfli": "^1.0.11",
"@types/chai": "^4.1.7", "@types/chai": "^4.1.7",
"@types/chromecast-caf-receiver": "^3.0.12",
"@types/chromecast-caf-sender": "^1.0.1",
"@types/hls.js": "^0.12.3", "@types/hls.js": "^0.12.3",
"@types/leaflet": "^1.4.3", "@types/leaflet": "^1.4.3",
"@types/memoize-one": "4.1.0", "@types/memoize-one": "4.1.0",

View File

@ -199,3 +199,9 @@ class HaEntityPicker extends LitElement {
} }
customElements.define("ha-entity-picker", HaEntityPicker); customElements.define("ha-entity-picker", HaEntityPicker);
declare global {
interface HTMLElementTagNameMap {
"ha-entity-picker": HaEntityPicker;
}
}

View File

@ -15,8 +15,10 @@ import { HassEntity } from "home-assistant-js-websocket";
// Not duplicate, this is for typing. // Not duplicate, this is for typing.
// tslint:disable-next-line // tslint:disable-next-line
import { HaIcon } from "../ha-icon"; import { HaIcon } from "../ha-icon";
import { HomeAssistant } from "../../types";
class StateBadge extends LitElement { class StateBadge extends LitElement {
public hass?: HomeAssistant;
@property() public stateObj?: HassEntity; @property() public stateObj?: HassEntity;
@property() public overrideIcon?: string; @property() public overrideIcon?: string;
@query("ha-icon") private _icon!: HaIcon; @query("ha-icon") private _icon!: HaIcon;
@ -54,8 +56,11 @@ class StateBadge extends LitElement {
if (stateObj) { if (stateObj) {
// hide icon if we have entity picture // hide icon if we have entity picture
if (stateObj.attributes.entity_picture && !this.overrideIcon) { if (stateObj.attributes.entity_picture && !this.overrideIcon) {
hostStyle.backgroundImage = let imageUrl = stateObj.attributes.entity_picture;
"url(" + stateObj.attributes.entity_picture + ")"; if (this.hass) {
imageUrl = this.hass.hassUrl(imageUrl);
}
hostStyle.backgroundImage = `url(${imageUrl})`;
iconStyle.display = "none"; iconStyle.display = "none";
} else { } else {
if (stateObj.attributes.hs_color) { if (stateObj.attributes.hs_color) {

View File

@ -35,8 +35,13 @@ export const fetchThumbnailUrlWithCache = (
entityId entityId
); );
export const fetchThumbnailUrl = (hass: HomeAssistant, entityId: string) => export const fetchThumbnailUrl = async (
getSignedPath(hass, `/api/camera_proxy/${entityId}`).then(({ path }) => path); hass: HomeAssistant,
entityId: string
) => {
const path = await getSignedPath(hass, `/api/camera_proxy/${entityId}`);
return hass.hassUrl(path.path);
};
export const fetchThumbnail = (hass: HomeAssistant, entityId: string) => { export const fetchThumbnail = (hass: HomeAssistant, entityId: string) => {
// tslint:disable-next-line: no-console // tslint:disable-next-line: no-console
@ -47,7 +52,7 @@ export const fetchThumbnail = (hass: HomeAssistant, entityId: string) => {
}); });
}; };
export const fetchStreamUrl = ( export const fetchStreamUrl = async (
hass: HomeAssistant, hass: HomeAssistant,
entityId: string, entityId: string,
format?: "hls" format?: "hls"
@ -60,7 +65,9 @@ export const fetchStreamUrl = (
// @ts-ignore // @ts-ignore
data.format = format; data.format = format;
} }
return hass.callWS<Stream>(data); const stream = await hass.callWS<Stream>(data);
stream.url = hass.hassUrl(stream.url);
return stream;
}; };
export const fetchCameraPrefs = (hass: HomeAssistant, entityId: string) => export const fetchCameraPrefs = (hass: HomeAssistant, entityId: string) =>

View File

@ -1,5 +1,5 @@
import { HomeAssistant } from "../types"; import { HomeAssistant } from "../types";
import { Connection } from "home-assistant-js-websocket"; import { Connection, getCollection } from "home-assistant-js-websocket";
export interface LovelaceConfig { export interface LovelaceConfig {
title?: string; title?: string;
@ -83,6 +83,17 @@ export const subscribeLovelaceUpdates = (
onChange: () => void onChange: () => void
) => conn.subscribeEvents(onChange, "lovelace_updated"); ) => conn.subscribeEvents(onChange, "lovelace_updated");
export const getLovelaceCollection = (conn: Connection) =>
getCollection(
conn,
"_lovelace",
(conn2) => fetchConfig(conn2, false),
(_conn, store) =>
subscribeLovelaceUpdates(conn, () =>
fetchConfig(conn, false).then((config) => store.setState(config, true))
)
);
export interface WindowWithLovelaceProm extends Window { export interface WindowWithLovelaceProm extends Window {
llConfProm?: Promise<LovelaceConfig>; llConfProm?: Promise<LovelaceConfig>;
} }

View File

@ -203,6 +203,25 @@ class CoverEntity extends Entity {
} }
} }
class InputNumberEntity extends Entity {
public async handleService(
domain,
service,
// @ts-ignore
data
) {
if (domain !== this.domain) {
return;
}
if (service === "set_value") {
this.update("" + data.value);
} else {
super.handleService(domain, service, data);
}
}
}
class ClimateEntity extends Entity { class ClimateEntity extends Entity {
public async handleService(domain, service, data) { public async handleService(domain, service, data) {
if (domain !== this.domain) { if (domain !== this.domain) {
@ -256,6 +275,7 @@ const TYPES = {
cover: CoverEntity, cover: CoverEntity,
group: GroupEntity, group: GroupEntity,
input_boolean: ToggleEntity, input_boolean: ToggleEntity,
input_number: InputNumberEntity,
light: LightEntity, light: LightEntity,
lock: LockEntity, lock: LockEntity,
media_player: MediaPlayerEntity, media_player: MediaPlayerEntity,

View File

@ -90,7 +90,11 @@ export const provideHass = (
const hassObj: MockHomeAssistant = { const hassObj: MockHomeAssistant = {
// Home Assistant properties // Home Assistant properties
auth: {} as any, auth: {
data: {
hassUrl: "",
},
} as any,
connection: { connection: {
addEventListener: () => undefined, addEventListener: () => undefined,
removeEventListener: () => undefined, removeEventListener: () => undefined,
@ -182,6 +186,7 @@ export const provideHass = (
? response[1](hass(), method, path, parameters) ? response[1](hass(), method, path, parameters)
: Promise.reject(`API Mock for ${path} is not implemented`); : Promise.reject(`API Mock for ${path} is not implemented`);
}, },
hassUrl: (path?) => path,
fetchWithAuth: () => Promise.reject("Not implemented"), fetchWithAuth: () => Promise.reject("Not implemented"),
sendWS: (msg) => hassObj.connection.sendMessage(msg), sendWS: (msg) => hassObj.connection.sendMessage(msg),
callWS: (msg) => hassObj.connection.sendMessagePromise(msg), callWS: (msg) => hassObj.connection.sendMessagePromise(msg),

View File

@ -1,15 +1,4 @@
<meta charset="utf-8"> <meta charset="utf-8">
<link rel='manifest' href='/manifest.json' crossorigin="use-credentials"> <link rel='manifest' href='/manifest.json' crossorigin="use-credentials">
<link rel='icon' href='/static/icons/favicon.ico'> <link rel='icon' href='/static/icons/favicon.ico'>
<meta name='viewport' content='width=device-width, user-scalable=no'> <%= renderTemplate('_style_base') %>
<style>
body {
font-family: Roboto, sans-serif;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
font-weight: 400;
margin: 0;
padding: 0;
height: 100vh;
}
</style>

View File

@ -0,0 +1,12 @@
<meta name='viewport' content='width=device-width, user-scalable=no'>
<style>
body {
font-family: Roboto, sans-serif;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
font-weight: 400;
margin: 0;
padding: 0;
height: 100vh;
}
</style>

View File

@ -198,8 +198,9 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
${this._config!.show_icon !== false ${this._config!.show_icon !== false
? html` ? html`
<state-badge <state-badge
.stateObj="${stateObj}" .hass=${this.hass}
.overrideIcon="${entityConf.icon}" .stateObj=${stateObj}
.overrideIcon=${entityConf.icon}
></state-badge> ></state-badge>
` `
: ""} : ""}

View File

@ -148,6 +148,27 @@ class HuiMapCard extends LitElement implements LovelaceCard {
`; `;
} }
protected shouldUpdate(changedProps) {
if (!changedProps.has("hass") || changedProps.size > 1) {
return true;
}
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (!oldHass || !this._configEntities) {
return true;
}
// Check if any state has changed
for (const entity of this._configEntities) {
if (oldHass.states[entity.entity] !== this.hass!.states[entity.entity]) {
return true;
}
}
return false;
}
protected firstUpdated(changedProps: PropertyValues): void { protected firstUpdated(changedProps: PropertyValues): void {
super.firstUpdated(changedProps); super.firstUpdated(changedProps);
this.loadMap(); this.loadMap();

View File

@ -59,15 +59,6 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard {
static get styles(): CSSResult { static get styles(): CSSResult {
return css` return css`
:host {
/* start paper-font-body1 style */
font-family: "Roboto", "Noto", sans-serif;
-webkit-font-smoothing: antialiased; /* OS X subpixel AA bleed bug */
font-size: 14px;
font-weight: 400;
line-height: 20px;
/* end paper-font-body1 style */
}
ha-markdown { ha-markdown {
display: block; display: block;
padding: 0 16px 16px; padding: 0 16px 16px;

View File

@ -64,7 +64,7 @@ export class HuiPictureCard extends LitElement implements LovelaceCard {
), ),
})}" })}"
> >
<img src="${this._config.image}" /> <img src="${this.hass.hassUrl(this._config.image)}" />
</ha-card> </ha-card>
`; `;
} }

View File

@ -492,7 +492,8 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
} }
#thermostat .rs-handle { #thermostat .rs-handle {
background-color: var(--paper-card-background-color, white); background-color: var(--paper-card-background-color, white);
padding: 7px; padding: 10px;
margin: -10px 0 0 -8px !important;
border: 2px solid var(--disabled-text-color); border: 2px solid var(--disabled-text-color);
} }
#thermostat .rs-handle.rs-focus { #thermostat .rs-handle.rs-focus {

View File

@ -30,6 +30,7 @@ export interface EntitiesCardEntityConfig extends EntityConfig {
} }
export interface EntitiesCardConfig extends LovelaceCardConfig { export interface EntitiesCardConfig extends LovelaceCardConfig {
type: "entities";
show_header_toggle?: boolean; show_header_toggle?: boolean;
title?: string; title?: string;
entities: EntitiesCardEntityConfig[]; entities: EntitiesCardEntityConfig[];
@ -104,6 +105,7 @@ export interface LightCardConfig extends LovelaceCardConfig {
} }
export interface MapCardConfig extends LovelaceCardConfig { export interface MapCardConfig extends LovelaceCardConfig {
type: "map";
title: string; title: string;
aspect_ratio: string; aspect_ratio: string;
default_zoom?: number; default_zoom?: number;
@ -113,6 +115,7 @@ export interface MapCardConfig extends LovelaceCardConfig {
} }
export interface MarkdownCardConfig extends LovelaceCardConfig { export interface MarkdownCardConfig extends LovelaceCardConfig {
type: "markdown";
content: string; content: string;
title?: string; title?: string;
} }

View File

@ -129,6 +129,10 @@ export const createRowElement = (
return element; return element;
} }
if (!config.entity) {
return _createErrorElement("Invalid config given.", config);
}
const domain = config.entity.split(".", 1)[0]; const domain = config.entity.split(".", 1)[0];
tag = `hui-${DOMAIN_TO_ELEMENT_TYPE[domain] || "text"}-entity-row`; tag = `hui-${DOMAIN_TO_ELEMENT_TYPE[domain] || "text"}-entity-row`;

View File

@ -0,0 +1,44 @@
import { loadModule, loadCSS, loadJS } from "../../../common/dom/load_resource";
import { LovelaceConfig } from "../../../data/lovelace";
// CSS and JS should only be imported once. Modules and HTML are safe.
const CSS_CACHE = {};
const JS_CACHE = {};
export const loadLovelaceResources = (
resources: NonNullable<LovelaceConfig["resources"]>,
hassUrl: string
) =>
resources.forEach((resource) => {
const normalizedUrl = new URL(resource.url, hassUrl).toString();
switch (resource.type) {
case "css":
if (normalizedUrl in CSS_CACHE) {
break;
}
CSS_CACHE[normalizedUrl] = loadCSS(normalizedUrl);
break;
case "js":
if (normalizedUrl in JS_CACHE) {
break;
}
JS_CACHE[normalizedUrl] = loadJS(normalizedUrl);
break;
case "module":
loadModule(normalizedUrl);
break;
case "html":
import(/* webpackChunkName: "import-href-polyfill" */ "../../../resources/html-import/import-href").then(
({ importHref }) => importHref(normalizedUrl)
);
break;
default:
// tslint:disable-next-line
console.warn(`Unknown resource type specified: ${resource.type}`);
}
});

View File

@ -47,8 +47,9 @@ class HuiGenericEntityRow extends LitElement {
return html` return html`
<state-badge <state-badge
.stateObj="${stateObj}" .hass=${this.hass}
.overrideIcon="${this.config.icon}" .stateObj=${stateObj}
.overrideIcon=${this.config.icon}
></state-badge> ></state-badge>
<div class="flex"> <div class="flex">
<div class="info"> <div class="info">
@ -63,8 +64,8 @@ class HuiGenericEntityRow extends LitElement {
: this.config.secondary_info === "last-changed" : this.config.secondary_info === "last-changed"
? html` ? html`
<ha-relative-time <ha-relative-time
.hass="${this.hass}" .hass=${this.hass}
.datetime="${stateObj.last_changed}" .datetime=${stateObj.last_changed}
></ha-relative-time> ></ha-relative-time>
` `
: ""} : ""}

View File

@ -104,6 +104,10 @@ export class HuiImage extends LitElement {
imageSrc = this.image; imageSrc = this.image;
} }
if (imageSrc) {
imageSrc = this.hass!.hassUrl(imageSrc);
}
// Figure out filter to use // Figure out filter to use
let filter = this.filter || ""; let filter = this.filter || "";

View File

@ -7,20 +7,21 @@ export interface EntityConfig {
icon?: string; icon?: string;
} }
export interface DividerConfig { export interface DividerConfig {
type: string; type: "divider";
style: string; style: string;
} }
export interface SectionConfig { export interface SectionConfig {
type: string; type: "section";
label: string; label: string;
} }
export interface WeblinkConfig { export interface WeblinkConfig {
type: string; type: "weblink";
name?: string; name?: string;
icon?: string; icon?: string;
url: string; url: string;
} }
export interface CallServiceConfig extends EntityConfig { export interface CallServiceConfig extends EntityConfig {
type: "call-service";
action_name?: string; action_name?: string;
service: string; service: string;
service_data?: { [key: string]: any }; service_data?: { [key: string]: any };

View File

@ -28,7 +28,6 @@ import "../../components/ha-start-voice-button";
import "../../components/ha-paper-icon-button-arrow-next"; import "../../components/ha-paper-icon-button-arrow-next";
import "../../components/ha-paper-icon-button-arrow-prev"; import "../../components/ha-paper-icon-button-arrow-prev";
import "../../components/ha-icon"; import "../../components/ha-icon";
import { loadModule, loadCSS, loadJS } from "../../common/dom/load_resource";
import { debounce } from "../../common/util/debounce"; import { debounce } from "../../common/util/debounce";
import { HomeAssistant } from "../../types"; import { HomeAssistant } from "../../types";
import { LovelaceConfig } from "../../data/lovelace"; import { LovelaceConfig } from "../../data/lovelace";
@ -47,10 +46,7 @@ import { Lovelace } from "./types";
import { afterNextRender } from "../../common/util/render-status"; import { afterNextRender } from "../../common/util/render-status";
import { haStyle } from "../../resources/styles"; import { haStyle } from "../../resources/styles";
import { computeRTLDirection } from "../../common/util/compute_rtl"; import { computeRTLDirection } from "../../common/util/compute_rtl";
import { loadLovelaceResources } from "./common/load-resources";
// CSS and JS should only be imported once. Modules and HTML are safe.
const CSS_CACHE = {};
const JS_CACHE = {};
class HUIRoot extends LitElement { class HUIRoot extends LitElement {
@property() public hass?: HomeAssistant; @property() public hass?: HomeAssistant;
@ -349,10 +345,14 @@ class HUIRoot extends LitElement {
* https://www.w3.org/TR/CSS2/visudet.html#the-height-property * https://www.w3.org/TR/CSS2/visudet.html#the-height-property
*/ */
position: relative; position: relative;
display: flex;
} }
#view.tabs-hidden { #view.tabs-hidden {
min-height: calc(100vh - 64px); min-height: calc(100vh - 64px);
} }
#view > * {
flex: 1;
}
paper-item { paper-item {
cursor: pointer; cursor: pointer;
} }
@ -408,7 +408,12 @@ class HUIRoot extends LitElement {
| undefined; | undefined;
if (!oldLovelace || oldLovelace.config !== this.lovelace!.config) { if (!oldLovelace || oldLovelace.config !== this.lovelace!.config) {
this._loadResources(this.lovelace!.config.resources || []); if (this.lovelace!.config.resources) {
loadLovelaceResources(
this.lovelace!.config.resources,
this.hass!.auth.data.hassUrl
);
}
// On config change, recreate the current view from scratch. // On config change, recreate the current view from scratch.
force = true; force = true;
// Recalculate to see if we need to adjust content area for tab bar // Recalculate to see if we need to adjust content area for tab bar
@ -595,40 +600,6 @@ class HUIRoot extends LitElement {
viewConfig.background || this.config.background || ""; viewConfig.background || this.config.background || "";
root.append(view); root.append(view);
} }
private _loadResources(resources) {
resources.forEach((resource) => {
switch (resource.type) {
case "css":
if (resource.url in CSS_CACHE) {
break;
}
CSS_CACHE[resource.url] = loadCSS(resource.url);
break;
case "js":
if (resource.url in JS_CACHE) {
break;
}
JS_CACHE[resource.url] = loadJS(resource.url);
break;
case "module":
loadModule(resource.url);
break;
case "html":
import(/* webpackChunkName: "import-href-polyfill" */ "../../resources/html-import/import-href").then(
({ importHref }) => importHref(resource.url)
);
break;
default:
// tslint:disable-next-line
console.warn(`Unknown resource type specified: ${resource.type}`);
}
});
}
} }
declare global { declare global {

View File

@ -57,8 +57,8 @@ export class HUIView extends LitElement {
return { return {
hass: {}, hass: {},
lovelace: {}, lovelace: {},
columns: {}, columns: { type: Number },
index: {}, index: { type: Number },
_cards: {}, _cards: {},
_badges: {}, _badges: {},
}; };
@ -116,10 +116,10 @@ export class HUIView extends LitElement {
<style> <style>
:host { :host {
display: block; display: block;
box-sizing: border-box;
padding: 4px 4px 0; padding: 4px 4px 0;
transform: translateZ(0); transform: translateZ(0);
position: relative; position: relative;
min-height: calc(100vh - 155px);
} }
#badges { #badges {
@ -194,7 +194,9 @@ export class HUIView extends LitElement {
let editModeChanged = false; let editModeChanged = false;
let configChanged = false; let configChanged = false;
if (changedProperties.has("lovelace")) { if (changedProperties.has("index")) {
configChanged = true;
} else if (changedProperties.has("lovelace")) {
const oldLovelace = changedProperties.get("lovelace") as Lovelace; const oldLovelace = changedProperties.get("lovelace") as Lovelace;
editModeChanged = editModeChanged =
!oldLovelace || lovelace.editMode !== oldLovelace.editMode; !oldLovelace || lovelace.editMode !== oldLovelace.editMode;
@ -310,10 +312,8 @@ export class HUIView extends LitElement {
this._cards = elements; this._cards = elements;
if ("theme" in config) {
applyThemesOnElement(root, this.hass!.themes, config.theme); applyThemesOnElement(root, this.hass!.themes, config.theme);
} }
}
private _rebuildCard( private _rebuildCard(
cardElToReplace: LovelaceCard, cardElToReplace: LovelaceCard,

View File

@ -46,6 +46,7 @@ export const connectionMixin = (
translationMetadata, translationMetadata,
dockedSidebar: "docked", dockedSidebar: "docked",
moreInfoEntityId: null, moreInfoEntityId: null,
hassUrl: (path = "") => new URL(path, auth.data.hassUrl).toString(),
callService: async (domain, service, serviceData = {}) => { callService: async (domain, service, serviceData = {}) => {
if (__DEV__) { if (__DEV__) {
// tslint:disable-next-line: no-console // tslint:disable-next-line: no-console

View File

@ -142,22 +142,20 @@ export interface HomeAssistant {
dockedSidebar: "docked" | "always_hidden" | "auto"; dockedSidebar: "docked" | "always_hidden" | "auto";
moreInfoEntityId: string | null; moreInfoEntityId: string | null;
user?: CurrentUser; user?: CurrentUser;
callService: ( hassUrl(path?): string;
callService(
domain: string, domain: string,
service: string, service: string,
serviceData?: { [key: string]: any } serviceData?: { [key: string]: any }
) => Promise<void>; ): Promise<void>;
callApi: <T>( callApi<T>(
method: "GET" | "POST" | "PUT" | "DELETE", method: "GET" | "POST" | "PUT" | "DELETE",
path: string, path: string,
parameters?: { [key: string]: any } parameters?: { [key: string]: any }
) => Promise<T>; ): Promise<T>;
fetchWithAuth: ( fetchWithAuth(path: string, init?: { [key: string]: any }): Promise<Response>;
path: string, sendWS(msg: MessageBase): void;
init?: { [key: string]: any } callWS<T>(msg: MessageBase): Promise<T>;
) => Promise<Response>;
sendWS: (msg: MessageBase) => void;
callWS: <T>(msg: MessageBase) => Promise<T>;
} }
export type LightEntity = HassEntityBase & { export type LightEntity = HassEntityBase & {

View File

@ -1570,6 +1570,16 @@
resolved "https://registry.yarnpkg.com/@types/chalk/-/chalk-0.4.31.tgz#a31d74241a6b1edbb973cf36d97a2896834a51f9" resolved "https://registry.yarnpkg.com/@types/chalk/-/chalk-0.4.31.tgz#a31d74241a6b1edbb973cf36d97a2896834a51f9"
integrity sha1-ox10JBprHtu5c8822XooloNKUfk= integrity sha1-ox10JBprHtu5c8822XooloNKUfk=
"@types/chromecast-caf-receiver@^3.0.12":
version "3.0.12"
resolved "https://registry.yarnpkg.com/@types/chromecast-caf-receiver/-/chromecast-caf-receiver-3.0.12.tgz#0172edc5e43a0b4f426b21a614a58e04e3df009d"
integrity sha512-GdR9nGOENDWYhF40FasB0Xnsy3c+e68K90sGVBZx1W1N3LP1NGOmCtaxgUpxk4IuHYmzGrW7I57zWZIbT3D5BQ==
"@types/chromecast-caf-sender@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@types/chromecast-caf-sender/-/chromecast-caf-sender-1.0.1.tgz#da0047c41c2a7ecf2d5348715b27c4542ed9b579"
integrity sha512-/JuG+zrS+KCPwEiOrK9O7WrIMyiUEF7Ev9ywbzXcCOPkXin9tLX7w9zxCmxtnOPdgH9lZbtOvgo5IA4cEJknRg==
"@types/clean-css@*": "@types/clean-css@*":
version "4.2.1" version "4.2.1"
resolved "https://registry.yarnpkg.com/@types/clean-css/-/clean-css-4.2.1.tgz#cb0134241ec5e6ede1b5344bc829668fd9871a8d" resolved "https://registry.yarnpkg.com/@types/clean-css/-/clean-css-4.2.1.tgz#cb0134241ec5e6ede1b5344bc829668fd9871a8d"
@ -7303,10 +7313,10 @@ hoek@6.x.x:
resolved "https://registry.yarnpkg.com/hoek/-/hoek-6.1.3.tgz#73b7d33952e01fe27a38b0457294b79dd8da242c" resolved "https://registry.yarnpkg.com/hoek/-/hoek-6.1.3.tgz#73b7d33952e01fe27a38b0457294b79dd8da242c"
integrity sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ== integrity sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==
home-assistant-js-websocket@^4.2.2: home-assistant-js-websocket@4.3.1:
version "4.2.2" version "4.3.1"
resolved "https://registry.yarnpkg.com/home-assistant-js-websocket/-/home-assistant-js-websocket-4.2.2.tgz#e13b058a9e200bc56080e1b48fdeaaf1ed2e4e5f" resolved "https://registry.yarnpkg.com/home-assistant-js-websocket/-/home-assistant-js-websocket-4.3.1.tgz#be320672b070cb4fcae2f1709a0eba1845ef7b31"
integrity sha512-4mXYbn2DCiDVBYGZROUSWLBDerSoDRJulw1GiQbhKEyrDhzFs5KQkcLdIu6k3CSDYQiiKQez5uAhOfb0Hr/M0A== integrity sha512-eVIRdisSmcIzYKNSgB3gqUCrZpQkSUKlluYTsM0NqpUc4W0hHmF2vd8bShl3URWJXPOI5XPdeHuAPPqc0gUj+Q==
homedir-polyfill@^1.0.0, homedir-polyfill@^1.0.1: homedir-polyfill@^1.0.0, homedir-polyfill@^1.0.1:
version "1.0.3" version "1.0.3"