mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-29 20:26:39 +00:00
commit
5f5bf17df0
@ -76,6 +76,55 @@ const CONFIGS = [
|
||||
left: 35%
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Card with header",
|
||||
config: `
|
||||
- type: picture-elements
|
||||
image: /images/floorplan.png
|
||||
title: My House
|
||||
elements:
|
||||
- type: service-button
|
||||
title: Lights Off
|
||||
style:
|
||||
top: 97%
|
||||
left: 90%
|
||||
padding: 0px
|
||||
service: light.turn_off
|
||||
service_data:
|
||||
entity_id: group.all_lights
|
||||
- type: icon
|
||||
icon: mdi:cctv
|
||||
entity: camera.demo_camera
|
||||
style:
|
||||
top: 12%
|
||||
left: 6%
|
||||
transform: rotate(-60deg) scaleX(-1)
|
||||
--iron-icon-height: 30px
|
||||
--iron-icon-width: 30px
|
||||
--iron-icon-stroke-color: black
|
||||
--iron-icon-fill-color: rgba(50, 50, 50, .75)
|
||||
- type: image
|
||||
entity: light.bed_light
|
||||
tap_action:
|
||||
action: toggle
|
||||
image: /images/light_bulb_off.png
|
||||
state_image:
|
||||
'on': /images/light_bulb_on.png
|
||||
state_filter:
|
||||
'on': brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)
|
||||
'off': brightness(80%) saturate(0.8)
|
||||
style:
|
||||
top: 35%
|
||||
left: 65%
|
||||
width: 7%
|
||||
padding: 50px 50px 100px 50px
|
||||
- type: state-icon
|
||||
entity: binary_sensor.movement_backyard
|
||||
style:
|
||||
top: 8%
|
||||
left: 35%
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
class DemoPicElements extends PolymerElement {
|
||||
|
2
setup.py
2
setup.py
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20181211.0",
|
||||
version="20181211.1",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
|
@ -119,7 +119,7 @@ class HaPlantCard extends EventsMixin(PolymerElement) {
|
||||
}
|
||||
|
||||
computeTitle(stateObj) {
|
||||
return this.config.name || computeStateName(stateObj);
|
||||
return (this.config && this.config.name) || computeStateName(stateObj);
|
||||
}
|
||||
|
||||
computeAttributes(data) {
|
||||
|
@ -39,6 +39,7 @@ class HaWeatherCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
opacity: var(--dark-primary-opacity);
|
||||
padding: 24px 16px 16px;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.name {
|
||||
@ -195,6 +196,7 @@ class HaWeatherCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
config: Object,
|
||||
stateObj: Object,
|
||||
forecast: {
|
||||
type: Array,
|
||||
@ -274,7 +276,7 @@ class HaWeatherCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
}
|
||||
|
||||
computeName(stateObj) {
|
||||
return this.config.name || computeStateName(stateObj);
|
||||
return (this.config && this.config.name) || computeStateName(stateObj);
|
||||
}
|
||||
|
||||
showWeatherIcon(condition) {
|
||||
|
@ -1,24 +1,30 @@
|
||||
export default function parseAspectRatio(input) {
|
||||
// Handle 16x9, 16:9, 1.78x1, 1.78:1, 1.78
|
||||
// Ignore everything else
|
||||
function parseOrThrow(num) {
|
||||
const parsed = parseFloat(num);
|
||||
if (isNaN(parsed)) {
|
||||
throw new Error(`${num} is not a number`);
|
||||
}
|
||||
return parsed;
|
||||
// Handle 16x9, 16:9, 1.78x1, 1.78:1, 1.78
|
||||
// Ignore everything else
|
||||
const parseOrThrow = (num) => {
|
||||
const parsed = parseFloat(num);
|
||||
if (isNaN(parsed)) {
|
||||
throw new Error(`${num} is not a number`);
|
||||
}
|
||||
return parsed;
|
||||
};
|
||||
|
||||
export default function parseAspectRatio(input: string) {
|
||||
if (!input) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
if (input) {
|
||||
const arr = input.replace(":", "x").split("x");
|
||||
if (arr.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return arr.length === 1
|
||||
? { w: parseOrThrow(arr[0]), h: 1 }
|
||||
: { w: parseOrThrow(arr[0]), h: parseOrThrow(arr[1]) };
|
||||
if (input.endsWith("%")) {
|
||||
return { w: 100, h: parseOrThrow(input.substr(0, input.length - 1)) };
|
||||
}
|
||||
|
||||
const arr = input.replace(":", "x").split("x");
|
||||
if (arr.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return arr.length === 1
|
||||
? { w: parseOrThrow(arr[0]), h: 1 }
|
||||
: { w: parseOrThrow(arr[0]), h: parseOrThrow(arr[1]) };
|
||||
} catch (err) {
|
||||
// Ignore the error
|
||||
}
|
||||
|
@ -56,6 +56,10 @@ export default (superClass) =>
|
||||
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);
|
||||
|
||||
@ -91,6 +95,15 @@ export default (superClass) =>
|
||||
}
|
||||
this.fire("hass-notification", { message });
|
||||
} catch (err) {
|
||||
if (__DEV__) {
|
||||
// eslint-disable-next-line
|
||||
console.error(
|
||||
"Error calling service",
|
||||
domain,
|
||||
service,
|
||||
serviceData
|
||||
);
|
||||
}
|
||||
const message = this.localize(
|
||||
"ui.notification_toast.service_call_failed",
|
||||
"service",
|
||||
@ -106,21 +119,25 @@ export default (superClass) =>
|
||||
fetchWithAuth(auth, `${auth.data.hassUrl}${path}`, init),
|
||||
// For messages that do not get a response
|
||||
sendWS: (msg) => {
|
||||
// eslint-disable-next-line
|
||||
if (__DEV__) console.log("Sending", msg);
|
||||
if (__DEV__) {
|
||||
// eslint-disable-next-line
|
||||
console.log("Sending", msg);
|
||||
}
|
||||
conn.sendMessage(msg);
|
||||
},
|
||||
// For messages that expect a response
|
||||
callWS: (msg) => {
|
||||
/* eslint-disable no-console */
|
||||
if (__DEV__) console.log("Sending", 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.log("Error", err)
|
||||
(err) => console.error("Error", err)
|
||||
);
|
||||
}
|
||||
return resp;
|
||||
|
@ -70,6 +70,7 @@ export class CloudExposedEntities extends LitElement {
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
super.updated(changedProperties);
|
||||
if (
|
||||
changedProperties.has("filter") &&
|
||||
changedProperties.get("filter") !== this.filter
|
||||
|
@ -79,6 +79,7 @@ export class CloudWebhooks extends LitElement {
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
if (changedProps.has("cloudStatus") && this.cloudStatus) {
|
||||
this._cloudHooks = this.cloudStatus.prefs.cloudhooks || {};
|
||||
}
|
||||
|
@ -88,7 +88,8 @@ class HuiEntitiesCard extends hassLocalizeLitMixin(LitElement)
|
||||
this._configEntities = entities;
|
||||
}
|
||||
|
||||
protected updated(_changedProperties: PropertyValues): void {
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
super.updated(changedProperties);
|
||||
if (this._hass && this._config) {
|
||||
applyThemesOnElement(this, this._hass.themes, this._config.theme);
|
||||
}
|
||||
|
@ -116,6 +116,7 @@ class HuiEntityButtonCard extends hassLocalizeLitMixin(LitElement)
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
super.updated(changedProps);
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
|
@ -110,11 +110,8 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
if (
|
||||
!this._config ||
|
||||
!this.hass ||
|
||||
!this.shadowRoot!.getElementById("gauge")
|
||||
) {
|
||||
super.updated(changedProps);
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
const stateObj = this.hass.states[this._config.entity];
|
||||
|
@ -135,6 +135,7 @@ export class HuiGlanceCard extends hassLocalizeLitMixin(LitElement)
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
super.updated(changedProperties);
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ const lightConfig = {
|
||||
lineCap: "round",
|
||||
handleSize: "+12",
|
||||
showTooltip: false,
|
||||
animation: false,
|
||||
};
|
||||
|
||||
interface Config extends LovelaceCardConfig {
|
||||
@ -154,6 +155,7 @@ export class HuiLightCard extends hassLocalizeLitMixin(LitElement)
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
super.updated(changedProps);
|
||||
if (!this._config || !this.hass || !this._jQuery) {
|
||||
return;
|
||||
}
|
||||
|
@ -63,15 +63,15 @@ class HuiPictureElementsCard extends LitElement implements LovelaceCard {
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
<ha-card .header="${this._config.title}">
|
||||
<hui-image
|
||||
.hass="${this._hass}"
|
||||
.image="${this._config.image}"
|
||||
.stateImage="${this._config.state_image}"
|
||||
.cameraImage="${this._config.camera_image}"
|
||||
.entity="${this._config.entity}"
|
||||
.aspectRatio="${this._config.aspect_ratio}"
|
||||
></hui-image>
|
||||
<div id="root">
|
||||
<hui-image
|
||||
.hass="${this._hass}"
|
||||
.image="${this._config.image}"
|
||||
.stateImage="${this._config.state_image}"
|
||||
.cameraImage="${this._config.camera_image}"
|
||||
.entity="${this._config.entity}"
|
||||
.aspectRatio="${this._config.aspect_ratio}"
|
||||
></hui-image>
|
||||
${
|
||||
this._config.elements.map((elementConfig: LovelaceElementConfig) =>
|
||||
this._createHuiElement(elementConfig)
|
||||
@ -85,9 +85,9 @@ class HuiPictureElementsCard extends LitElement implements LovelaceCard {
|
||||
private renderStyle(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
ha-card {
|
||||
overflow: hidden;
|
||||
#root {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.element {
|
||||
position: absolute;
|
||||
|
@ -265,6 +265,7 @@ class HuiSensorCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
if (!this._config || this._config.graph !== "line" || !this.hass) {
|
||||
return;
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ const thermostatConfig = {
|
||||
lineCap: "round",
|
||||
handleSize: "+10",
|
||||
showTooltip: false,
|
||||
animation: false,
|
||||
};
|
||||
|
||||
const modeIcons = {
|
||||
|
@ -25,6 +25,7 @@ class HuiEntitiesToggle extends LitElement {
|
||||
}
|
||||
|
||||
public updated(changedProperties: PropertyValues) {
|
||||
super.updated(changedProperties);
|
||||
if (changedProperties.has("entities")) {
|
||||
this._toggleEntities = this.entities!.filter(
|
||||
(entityId) =>
|
||||
|
@ -78,6 +78,7 @@ class HuiTimestampDisplay extends hassLocalizeLitMixin(LitElement) {
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
super.updated(changedProperties);
|
||||
if (!changedProperties.has("format") || !this._connected) {
|
||||
return;
|
||||
}
|
||||
|
@ -58,6 +58,7 @@ export class HuiDialogEditCard extends LitElement {
|
||||
<hui-dialog-pick-card
|
||||
.hass="${this.hass}"
|
||||
.cardPicked="${this._cardPicked}"
|
||||
.closeDialog="${this._cancel}"
|
||||
></hui-dialog-pick-card>
|
||||
`;
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import { LovelaceCardConfig } from "../../../../data/lovelace";
|
||||
export class HuiDialogPickCard extends hassLocalizeLitMixin(LitElement) {
|
||||
public hass?: HomeAssistant;
|
||||
public cardPicked?: (cardConf: LovelaceCardConfig) => void;
|
||||
public closeDialog?: () => void;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {};
|
||||
@ -18,7 +19,11 @@ export class HuiDialogPickCard extends hassLocalizeLitMixin(LitElement) {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<paper-dialog with-backdrop opened>
|
||||
<paper-dialog
|
||||
with-backdrop
|
||||
opened
|
||||
@opened-changed="${this._openedChanged}"
|
||||
>
|
||||
<h2>${this.localize("ui.panel.lovelace.editor.edit_card.header")}</h2>
|
||||
<paper-dialog-scrollable>
|
||||
<hui-card-picker
|
||||
@ -33,6 +38,12 @@ export class HuiDialogPickCard extends hassLocalizeLitMixin(LitElement) {
|
||||
`;
|
||||
}
|
||||
|
||||
private _openedChanged(ev) {
|
||||
if (!ev.detail.value) {
|
||||
this.closeDialog!();
|
||||
}
|
||||
}
|
||||
|
||||
private _skipPick() {
|
||||
this.cardPicked!({ type: "" });
|
||||
}
|
||||
|
@ -132,7 +132,11 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
|
||||
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
<paper-dialog with-backdrop opened>
|
||||
<paper-dialog
|
||||
with-backdrop
|
||||
opened
|
||||
@opened-changed="${this._openedChanged}"
|
||||
>
|
||||
<h2>${this.localize("ui.panel.lovelace.editor.edit_card.header")}</h2>
|
||||
<paper-spinner
|
||||
?active="${this._loading}"
|
||||
@ -436,6 +440,12 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
|
||||
private get _creatingCard(): boolean {
|
||||
return this.path!.length === 1;
|
||||
}
|
||||
|
||||
private _openedChanged(ev) {
|
||||
if (!ev.detail.value) {
|
||||
this.closeDialog!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@ -48,7 +48,7 @@ export class HuiServiceButtonElement extends LitElement
|
||||
.hass="${this.hass}"
|
||||
.domain="${this._domain}"
|
||||
.service="${this._service}"
|
||||
.service-data="${this._config.service_data}"
|
||||
.serviceData="${this._config.service_data}"
|
||||
>${this._config.title}</ha-call-service-button
|
||||
>
|
||||
`;
|
||||
|
@ -105,6 +105,7 @@ class LovelacePanel extends hassLocalizeLitMixin(LitElement) {
|
||||
}
|
||||
|
||||
public updated(changedProps: PropertyValues): void {
|
||||
super.updated(changedProps);
|
||||
if (changedProps.has("narrow") || changedProps.has("showMenu")) {
|
||||
this._updateColumns();
|
||||
}
|
||||
|
@ -31,9 +31,9 @@ describe("parseAspectRatio", () => {
|
||||
assert.deepEqual(r, ratio178);
|
||||
});
|
||||
|
||||
it("Skips null states", () => {
|
||||
const r = parseAspectRatio(null);
|
||||
assert.equal(r, null);
|
||||
it("Parses 23%", () => {
|
||||
const r = parseAspectRatio("23%");
|
||||
assert.deepEqual(r, { w: 100, h: 23 });
|
||||
});
|
||||
|
||||
it("Skips empty states", () => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user