mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-14 04:46:34 +00:00
Lovelace cleanups (#3427)
* Improvements * Add types CAF * Fix demo switching * Do not set background color in hui-view
This commit is contained in:
parent
dae0ecce6a
commit
6abbe72e4d
@ -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");
|
||||||
|
@ -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");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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(
|
||||||
|
@ -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(
|
||||||
|
16
demo/src/stubs/persistent_notification.ts
Normal file
16
demo/src/stubs/persistent_notification.ts
Normal 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[])
|
||||||
|
);
|
||||||
|
};
|
@ -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",
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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) =>
|
||||||
|
@ -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>;
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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),
|
||||||
|
@ -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>
|
|
||||||
|
12
src/html/_style_base.html.template
Normal file
12
src/html/_style_base.html.template
Normal 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>
|
@ -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>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
|
@ -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();
|
||||||
|
@ -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;
|
||||||
|
@ -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>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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`;
|
||||||
|
|
||||||
|
44
src/panels/lovelace/common/load-resources.ts
Normal file
44
src/panels/lovelace/common/load-resources.ts
Normal 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}`);
|
||||||
|
}
|
||||||
|
});
|
@ -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>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
|
@ -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 || "";
|
||||||
|
|
||||||
|
@ -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 };
|
||||||
|
@ -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 {
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
18
src/types.ts
18
src/types.ts
@ -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 & {
|
||||||
|
18
yarn.lock
18
yarn.lock
@ -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"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user