mirror of
https://github.com/home-assistant/frontend.git
synced 2025-09-17 17:09:43 +00:00
Compare commits
124 Commits
20121207.0
...
20190109.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
67c032c85a | ||
![]() |
417ffde3e8 | ||
![]() |
f3064f0071 | ||
![]() |
e9d912cc87 | ||
![]() |
2517e5ba60 | ||
![]() |
64b405dd4d | ||
![]() |
ddb050d1fd | ||
![]() |
3f6a8cac80 | ||
![]() |
ad113367e6 | ||
![]() |
f4f08ab0d1 | ||
![]() |
c2a57099d3 | ||
![]() |
adf0c6d891 | ||
![]() |
38a2627227 | ||
![]() |
5a90edc893 | ||
![]() |
88473581c2 | ||
![]() |
88d23eb9dd | ||
![]() |
25c788871f | ||
![]() |
f272801253 | ||
![]() |
2e750dc1e2 | ||
![]() |
3c5fb6d1ad | ||
![]() |
32cd683b8a | ||
![]() |
6c029b39e0 | ||
![]() |
7efad04e42 | ||
![]() |
b6f7781a87 | ||
![]() |
16a147f389 | ||
![]() |
79b71ed753 | ||
![]() |
49fa74cc07 | ||
![]() |
0a2eaec884 | ||
![]() |
4c5d3138c1 | ||
![]() |
5e1cd389b3 | ||
![]() |
7ced08a899 | ||
![]() |
603cf7ba0f | ||
![]() |
c47ba65c3b | ||
![]() |
849ed80e78 | ||
![]() |
b78c48ecec | ||
![]() |
8d2da9c5a6 | ||
![]() |
9664e8258c | ||
![]() |
5f5bf17df0 | ||
![]() |
ca7674cd15 | ||
![]() |
3f5f5bb1ee | ||
![]() |
e7ee9c7054 | ||
![]() |
4f6ecf5c21 | ||
![]() |
87eac4cdee | ||
![]() |
d267196bff | ||
![]() |
f683337cbe | ||
![]() |
1a6226270f | ||
![]() |
64714c64c7 | ||
![]() |
b7c34c483a | ||
![]() |
e5bf842801 | ||
![]() |
d1a56d6acc | ||
![]() |
cac7f8d1ab | ||
![]() |
9d2b37c9f2 | ||
![]() |
e20a02c52c | ||
![]() |
c46d04eaa6 | ||
![]() |
2ec8b97378 | ||
![]() |
b3b9ca9c3f | ||
![]() |
71ed83ef07 | ||
![]() |
47635055d0 | ||
![]() |
0dfca2f33b | ||
![]() |
18de427145 | ||
![]() |
118f28285e | ||
![]() |
6a9cfbfa1c | ||
![]() |
8c61624a9c | ||
![]() |
d277571735 | ||
![]() |
a6f3684846 | ||
![]() |
edef4ba2f5 | ||
![]() |
7cd6619a79 | ||
![]() |
2059e36dd6 | ||
![]() |
4a455e9147 | ||
![]() |
fe0b131480 | ||
![]() |
b1b78c2bb7 | ||
![]() |
99395360c7 | ||
![]() |
bd46e3b8e0 | ||
![]() |
80dd15306e | ||
![]() |
88f0ebf75d | ||
![]() |
8679f10f10 | ||
![]() |
db4c1e45f5 | ||
![]() |
65cf2feb7a | ||
![]() |
97da26eba7 | ||
![]() |
8e7d7c5188 | ||
![]() |
767307ef47 | ||
![]() |
ccc6262026 | ||
![]() |
2cdb542112 | ||
![]() |
4e232e58ce | ||
![]() |
27bb175624 | ||
![]() |
5a5a7dad1e | ||
![]() |
2d1cf421ef | ||
![]() |
8be25f2020 | ||
![]() |
0a8f853a8e | ||
![]() |
a46f5e3d4e | ||
![]() |
5de36f9579 | ||
![]() |
9b5e79f42a | ||
![]() |
a824599a37 | ||
![]() |
884b24da0e | ||
![]() |
76325a384c | ||
![]() |
e2218f1e6e | ||
![]() |
758b686684 | ||
![]() |
3a50d47dd2 | ||
![]() |
b4d4591273 | ||
![]() |
432fdd628c | ||
![]() |
bc23dd37be | ||
![]() |
0319fd23c5 | ||
![]() |
d7e5993501 | ||
![]() |
46a9b90ed0 | ||
![]() |
b0c68e58c5 | ||
![]() |
7063ced7fd | ||
![]() |
7d2444868d | ||
![]() |
0899d42967 | ||
![]() |
d57bcc2701 | ||
![]() |
d9d92c8766 | ||
![]() |
7a16ed5400 | ||
![]() |
07e35ff81a | ||
![]() |
0398944cab | ||
![]() |
a33ff7479a | ||
![]() |
f9182e5453 | ||
![]() |
4f0a965573 | ||
![]() |
2a23487163 | ||
![]() |
6b9ba7367d | ||
![]() |
a17ae5546f | ||
![]() |
47de0e9156 | ||
![]() |
baeda622de | ||
![]() |
48220b67ed | ||
![]() |
9ba232249b | ||
![]() |
b0580e70d2 |
@@ -7,7 +7,7 @@ import { demoConfig } from "../data/demo_config";
|
||||
import { demoServices } from "../data/demo_services";
|
||||
import demoResources from "../data/demo_resources";
|
||||
import demoStates from "../data/demo_states";
|
||||
import createCardElement from "../../../src/panels/lovelace/common/create-card-element";
|
||||
import { createCardElement } from "../../../src/panels/lovelace/common/create-card-element";
|
||||
|
||||
class DemoCard extends PolymerElement {
|
||||
static get template() {
|
||||
@@ -78,6 +78,10 @@ class DemoCard extends PolymerElement {
|
||||
hass.resources = demoResources;
|
||||
hass.language = "en";
|
||||
hass.states = demoStates;
|
||||
hass.themes = {
|
||||
default_theme: "default",
|
||||
themes: {},
|
||||
};
|
||||
el.hass = hass;
|
||||
}
|
||||
|
||||
|
@@ -29,6 +29,31 @@ const ENTITIES = [
|
||||
friendly_name: "Home",
|
||||
icon: "mdi:home",
|
||||
}),
|
||||
getEntity("zone", "bushfire", "zoning", {
|
||||
latitude: -33.8611,
|
||||
longitude: 151.203,
|
||||
radius: 35000,
|
||||
friendly_name: "Bushfire Zone",
|
||||
icon: "mdi:home",
|
||||
}),
|
||||
getEntity("geo_location", "nelsons_creek", "15", {
|
||||
source: "bushfire_demo",
|
||||
latitude: -34.07792,
|
||||
longitude: 151.03219,
|
||||
friendly_name: "Nelsons Creek",
|
||||
}),
|
||||
getEntity("geo_location", "forest_rd_nowra_hill", "8", {
|
||||
source: "bushfire_demo",
|
||||
latitude: -33.69452,
|
||||
longitude: 151.19577,
|
||||
friendly_name: "Forest Rd, Nowra Hill",
|
||||
}),
|
||||
getEntity("geo_location", "stoney_ridge_rd_kremnos", "20", {
|
||||
source: "bushfire_demo",
|
||||
latitude: -33.66584,
|
||||
longitude: 150.97209,
|
||||
friendly_name: "Stoney Ridge Rd, Kremnos",
|
||||
}),
|
||||
];
|
||||
|
||||
const CONFIGS = [
|
||||
@@ -116,6 +141,24 @@ const CONFIGS = [
|
||||
- light.bed_light
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Geo Location Entities",
|
||||
config: `
|
||||
- type: map
|
||||
geo_location_sources:
|
||||
- bushfire_demo
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Geo Location Entities with Home Zone",
|
||||
config: `
|
||||
- type: map
|
||||
geo_location_sources:
|
||||
- bushfire_demo
|
||||
entities:
|
||||
- zone.bushfire
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
class DemoMap extends PolymerElement {
|
||||
|
@@ -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 {
|
||||
|
@@ -9,6 +9,9 @@ const ENTITIES = [
|
||||
getEntity("light", "kitchen_lights", "on", {
|
||||
friendly_name: "Kitchen Lights",
|
||||
}),
|
||||
getEntity("light", "bed_light", "on", {
|
||||
friendly_name: "Bed Lights",
|
||||
}),
|
||||
getEntity("device_tracker", "demo_paulus", "work", {
|
||||
source_type: "gps",
|
||||
latitude: 32.877105,
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 27 KiB |
2
setup.py
2
setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20181207.0",
|
||||
version="2019109.0",
|
||||
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) {
|
||||
|
@@ -8,6 +8,7 @@ import "../components/ha-icon";
|
||||
|
||||
import EventsMixin from "../mixins/events-mixin";
|
||||
import LocalizeMixin from "../mixins/localize-mixin";
|
||||
import { computeRTL } from "../common/util/compute_rtl";
|
||||
|
||||
/*
|
||||
* @appliesMixin LocalizeMixin
|
||||
@@ -30,15 +31,20 @@ class HaWeatherCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
|
||||
.header {
|
||||
font-family: var(--paper-font-headline_-_font-family);
|
||||
-webkit-font-smoothing: var(--paper-font-headline_-_-webkit-font-smoothing);
|
||||
-webkit-font-smoothing: var(
|
||||
--paper-font-headline_-_-webkit-font-smoothing
|
||||
);
|
||||
font-size: var(--paper-font-headline_-_font-size);
|
||||
font-weight: var(--paper-font-headline_-_font-weight);
|
||||
letter-spacing: var(--paper-font-headline_-_letter-spacing);
|
||||
line-height: var(--paper-font-headline_-_line-height);
|
||||
text-rendering: var(--paper-font-common-expensive-kerning_-_text-rendering);
|
||||
text-rendering: var(
|
||||
--paper-font-common-expensive-kerning_-_text-rendering
|
||||
);
|
||||
opacity: var(--dark-primary-opacity);
|
||||
padding: 24px 16px 16px;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.name {
|
||||
@@ -47,6 +53,11 @@ class HaWeatherCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
:host([rtl]) .name {
|
||||
margin-left: 0px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.now {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -60,18 +71,31 @@ class HaWeatherCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
margin-right: 32px;
|
||||
}
|
||||
|
||||
:host([rtl]) .main {
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.main ha-icon {
|
||||
--iron-icon-height: 72px;
|
||||
--iron-icon-width: 72px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
:host([rtl]) .main ha-icon {
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.main .temp {
|
||||
font-size: 52px;
|
||||
line-height: 1em;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
:host([rtl]) .main .temp {
|
||||
direction: ltr;
|
||||
margin-right: 28px;
|
||||
}
|
||||
|
||||
.main .temp span {
|
||||
font-size: 24px;
|
||||
line-height: 1em;
|
||||
@@ -79,6 +103,14 @@ class HaWeatherCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
.measurand {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
:host([rtl]) .measurand {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.forecast {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
@@ -95,13 +127,17 @@ class HaWeatherCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:host([rtl]) .forecast .temp {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.weekday {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.attributes,
|
||||
.templow,
|
||||
.precipitation { {
|
||||
.precipitation {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
</style>
|
||||
@@ -129,7 +165,9 @@ class HaWeatherCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
>
|
||||
<div>
|
||||
[[localize('ui.card.weather.attributes.air_pressure')]]:
|
||||
[[stateObj.attributes.pressure]] [[getUnit('air_pressure')]]
|
||||
<span class="measurand">
|
||||
[[stateObj.attributes.pressure]] [[getUnit('air_pressure')]]
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<template
|
||||
@@ -138,7 +176,9 @@ class HaWeatherCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
>
|
||||
<div>
|
||||
[[localize('ui.card.weather.attributes.humidity')]]:
|
||||
[[stateObj.attributes.humidity]] %
|
||||
<span class="measurand"
|
||||
>[[stateObj.attributes.humidity]] %</span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
<template
|
||||
@@ -147,8 +187,10 @@ class HaWeatherCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
>
|
||||
<div>
|
||||
[[localize('ui.card.weather.attributes.wind_speed')]]:
|
||||
[[getWind(stateObj.attributes.wind_speed,
|
||||
stateObj.attributes.wind_bearing, localize)]]
|
||||
<span class="measurand">
|
||||
[[getWindSpeed(stateObj.attributes.wind_speed)]]
|
||||
</span>
|
||||
[[getWindBearing(stateObj.attributes.wind_bearing, localize)]]
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@@ -195,11 +237,17 @@ class HaWeatherCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
config: Object,
|
||||
stateObj: Object,
|
||||
forecast: {
|
||||
type: Array,
|
||||
computed: "computeForecast(stateObj.attributes.forecast)",
|
||||
},
|
||||
rtl: {
|
||||
type: Boolean,
|
||||
reflectToAttribute: true,
|
||||
computed: "_computeRTL(hass)",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -274,7 +322,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) {
|
||||
@@ -293,14 +341,18 @@ class HaWeatherCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
return degree;
|
||||
}
|
||||
|
||||
getWind(speed, bearing, localize) {
|
||||
getWindSpeed(speed) {
|
||||
return `${speed} ${this.getUnit("length")}/h`;
|
||||
}
|
||||
|
||||
getWindBearing(bearing, localize) {
|
||||
if (bearing != null) {
|
||||
const cardinalDirection = this.windBearingToText(bearing);
|
||||
return `${speed} ${this.getUnit("length")}/h (${localize(
|
||||
return `(${localize(
|
||||
`ui.card.weather.cardinal_direction.${cardinalDirection.toLowerCase()}`
|
||||
) || cardinalDirection})`;
|
||||
}
|
||||
return `${speed} ${this.getUnit("length")}/h`;
|
||||
return ``;
|
||||
}
|
||||
|
||||
_showValue(item) {
|
||||
@@ -322,5 +374,9 @@ class HaWeatherCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
{ hour: "numeric" }
|
||||
);
|
||||
}
|
||||
|
||||
_computeRTL(hass) {
|
||||
return computeRTL(hass);
|
||||
}
|
||||
}
|
||||
customElements.define("ha-weather-card", HaWeatherCard);
|
||||
|
@@ -18,8 +18,8 @@ interface RefreshTokenResponse {
|
||||
declare global {
|
||||
interface Window {
|
||||
externalApp?: {
|
||||
getExternalAuth(payload: BasePayload);
|
||||
revokeExternalAuth(payload: BasePayload);
|
||||
getExternalAuth(payload: string);
|
||||
revokeExternalAuth(payload: string);
|
||||
};
|
||||
webkit?: {
|
||||
messageHandlers: {
|
||||
@@ -67,7 +67,7 @@ export default class ExternalAuth extends Auth {
|
||||
const callbackPayload = { callback: CALLBACK_SET_TOKEN };
|
||||
|
||||
if (window.externalApp) {
|
||||
window.externalApp.getExternalAuth(callbackPayload);
|
||||
window.externalApp.getExternalAuth(JSON.stringify(callbackPayload));
|
||||
} else {
|
||||
window.webkit!.messageHandlers.getExternalAuth.postMessage(
|
||||
callbackPayload
|
||||
@@ -92,7 +92,7 @@ export default class ExternalAuth extends Auth {
|
||||
const callbackPayload = { callback: CALLBACK_REVOKE_TOKEN };
|
||||
|
||||
if (window.externalApp) {
|
||||
window.externalApp.revokeExternalAuth(callbackPayload);
|
||||
window.externalApp.revokeExternalAuth(JSON.stringify(callbackPayload));
|
||||
} else {
|
||||
window.webkit!.messageHandlers.revokeExternalAuth.postMessage(
|
||||
callbackPayload
|
||||
|
@@ -7,6 +7,9 @@
|
||||
/** Icon to use when no icon specified for domain. */
|
||||
export const DEFAULT_DOMAIN_ICON = "hass:bookmark";
|
||||
|
||||
/** Panel to show when no panel is picked. */
|
||||
export const DEFAULT_PANEL = "states";
|
||||
|
||||
/** Domains that have a state card. */
|
||||
export const DOMAINS_WITH_CARD = [
|
||||
"climate",
|
||||
|
@@ -5,87 +5,82 @@ import formatDate from "../datetime/format_date";
|
||||
import formatTime from "../datetime/format_time";
|
||||
import { LocalizeFunc } from "../../mixins/localize-base-mixin";
|
||||
|
||||
type CachedDisplayEntity = HassEntity & {
|
||||
_stateDisplay?: string;
|
||||
};
|
||||
|
||||
export default function computeStateDisplay(
|
||||
export default (
|
||||
localize: LocalizeFunc,
|
||||
stateObj: HassEntity,
|
||||
language: string
|
||||
) {
|
||||
const state = stateObj as CachedDisplayEntity;
|
||||
if (!state._stateDisplay) {
|
||||
const domain = computeStateDomain(state);
|
||||
if (domain === "binary_sensor") {
|
||||
// Try device class translation, then default binary sensor translation
|
||||
if (state.attributes.device_class) {
|
||||
state._stateDisplay = localize(
|
||||
`state.${domain}.${state.attributes.device_class}.${state.state}`
|
||||
);
|
||||
}
|
||||
if (!state._stateDisplay) {
|
||||
state._stateDisplay = localize(
|
||||
`state.${domain}.default.${state.state}`
|
||||
);
|
||||
}
|
||||
} else if (
|
||||
state.attributes.unit_of_measurement &&
|
||||
!["unknown", "unavailable"].includes(state.state)
|
||||
) {
|
||||
state._stateDisplay =
|
||||
state.state + " " + state.attributes.unit_of_measurement;
|
||||
} else if (domain === "input_datetime") {
|
||||
let date: Date;
|
||||
if (!state.attributes.has_time) {
|
||||
date = new Date(
|
||||
state.attributes.year,
|
||||
state.attributes.month - 1,
|
||||
state.attributes.day
|
||||
);
|
||||
state._stateDisplay = formatDate(date, language);
|
||||
} else if (!state.attributes.has_date) {
|
||||
const now = new Date();
|
||||
date = new Date(
|
||||
// Due to bugs.chromium.org/p/chromium/issues/detail?id=797548
|
||||
// don't use artificial 1970 year.
|
||||
now.getFullYear(),
|
||||
now.getMonth(),
|
||||
now.getDay(),
|
||||
state.attributes.hour,
|
||||
state.attributes.minute
|
||||
);
|
||||
state._stateDisplay = formatTime(date, language);
|
||||
} else {
|
||||
date = new Date(
|
||||
state.attributes.year,
|
||||
state.attributes.month - 1,
|
||||
state.attributes.day,
|
||||
state.attributes.hour,
|
||||
state.attributes.minute
|
||||
);
|
||||
state._stateDisplay = formatDateTime(date, language);
|
||||
}
|
||||
} else if (domain === "zwave") {
|
||||
if (["initializing", "dead"].includes(state.state)) {
|
||||
state._stateDisplay = localize(
|
||||
`state.zwave.query_stage.${state.state}`,
|
||||
"query_stage",
|
||||
state.attributes.query_stage
|
||||
);
|
||||
} else {
|
||||
state._stateDisplay = localize(`state.zwave.default.${state.state}`);
|
||||
}
|
||||
} else {
|
||||
state._stateDisplay = localize(`state.${domain}.${state.state}`);
|
||||
): string => {
|
||||
let display: string | undefined;
|
||||
const domain = computeStateDomain(stateObj);
|
||||
|
||||
if (domain === "binary_sensor") {
|
||||
// Try device class translation, then default binary sensor translation
|
||||
if (stateObj.attributes.device_class) {
|
||||
display = localize(
|
||||
`state.${domain}.${stateObj.attributes.device_class}.${stateObj.state}`
|
||||
);
|
||||
}
|
||||
// Fall back to default, component backend translation, or raw state if nothing else matches.
|
||||
state._stateDisplay =
|
||||
state._stateDisplay ||
|
||||
localize(`state.default.${state.state}`) ||
|
||||
localize(`component.${domain}.state.${state.state}`) ||
|
||||
state.state;
|
||||
|
||||
if (!display) {
|
||||
display = localize(`state.${domain}.default.${stateObj.state}`);
|
||||
}
|
||||
} else if (
|
||||
stateObj.attributes.unit_of_measurement &&
|
||||
!["unknown", "unavailable"].includes(stateObj.state)
|
||||
) {
|
||||
display = stateObj.state + " " + stateObj.attributes.unit_of_measurement;
|
||||
} else if (domain === "input_datetime") {
|
||||
let date: Date;
|
||||
if (!stateObj.attributes.has_time) {
|
||||
date = new Date(
|
||||
stateObj.attributes.year,
|
||||
stateObj.attributes.month - 1,
|
||||
stateObj.attributes.day
|
||||
);
|
||||
display = formatDate(date, language);
|
||||
} else if (!stateObj.attributes.has_date) {
|
||||
const now = new Date();
|
||||
date = new Date(
|
||||
// Due to bugs.chromium.org/p/chromium/issues/detail?id=797548
|
||||
// don't use artificial 1970 year.
|
||||
now.getFullYear(),
|
||||
now.getMonth(),
|
||||
now.getDay(),
|
||||
stateObj.attributes.hour,
|
||||
stateObj.attributes.minute
|
||||
);
|
||||
display = formatTime(date, language);
|
||||
} else {
|
||||
date = new Date(
|
||||
stateObj.attributes.year,
|
||||
stateObj.attributes.month - 1,
|
||||
stateObj.attributes.day,
|
||||
stateObj.attributes.hour,
|
||||
stateObj.attributes.minute
|
||||
);
|
||||
display = formatDateTime(date, language);
|
||||
}
|
||||
} else if (domain === "zwave") {
|
||||
if (["initializing", "dead"].includes(stateObj.state)) {
|
||||
display = localize(
|
||||
`state.zwave.query_stage.${stateObj.state}`,
|
||||
"query_stage",
|
||||
stateObj.attributes.query_stage
|
||||
);
|
||||
} else {
|
||||
display = localize(`state.zwave.default.${stateObj.state}`);
|
||||
}
|
||||
} else {
|
||||
display = localize(`state.${domain}.${stateObj.state}`);
|
||||
}
|
||||
|
||||
return state._stateDisplay;
|
||||
}
|
||||
// Fall back to default, component backend translation, or raw state if nothing else matches.
|
||||
if (!display) {
|
||||
display =
|
||||
localize(`state.default.${stateObj.state}`) ||
|
||||
localize(`component.${domain}.state.${stateObj.state}`) ||
|
||||
stateObj.state;
|
||||
}
|
||||
|
||||
return display;
|
||||
};
|
||||
|
@@ -1,18 +1,7 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import computeObjectId from "./compute_object_id";
|
||||
|
||||
type CachedDisplayEntity = HassEntity & {
|
||||
_entityDisplay?: string;
|
||||
};
|
||||
|
||||
export default function computeStateName(stateObj: HassEntity) {
|
||||
const state = stateObj as CachedDisplayEntity;
|
||||
|
||||
if (state._entityDisplay === undefined) {
|
||||
state._entityDisplay =
|
||||
state.attributes.friendly_name ||
|
||||
computeObjectId(state.entity_id).replace(/_/g, " ");
|
||||
}
|
||||
|
||||
return state._entityDisplay;
|
||||
}
|
||||
export default (stateObj: HassEntity): string =>
|
||||
stateObj.attributes.friendly_name === undefined
|
||||
? computeObjectId(stateObj.entity_id).replace(/_/g, " ")
|
||||
: stateObj.attributes.friendly_name || "";
|
||||
|
@@ -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
|
||||
}
|
||||
|
3
src/common/util/render-status.ts
Normal file
3
src/common/util/render-status.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const afterNextRender = (cb: () => void): void => {
|
||||
requestAnimationFrame(() => setTimeout(cb, 0));
|
||||
};
|
@@ -1,25 +1,208 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import "../ha-label-badge";
|
||||
import {
|
||||
LitElement,
|
||||
html,
|
||||
PropertyValues,
|
||||
PropertyDeclarations,
|
||||
} from "@polymer/lit-element";
|
||||
import { TemplateResult } from "lit-html";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { classMap } from "lit-html/directives/classMap";
|
||||
|
||||
import computeStateDomain from "../../common/entity/compute_state_domain";
|
||||
import computeStateName from "../../common/entity/compute_state_name";
|
||||
import domainIcon from "../../common/entity/domain_icon";
|
||||
import stateIcon from "../../common/entity/state_icon";
|
||||
import timerTimeRemaining from "../../common/entity/timer_time_remaining";
|
||||
import attributeClassNames from "../../common/entity/attribute_class_names";
|
||||
import secondsToDuration from "../../common/datetime/seconds_to_duration";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { hassLocalizeLitMixin } from "../../mixins/lit-localize-mixin";
|
||||
|
||||
import EventsMixin from "../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../mixins/localize-mixin";
|
||||
import "../ha-label-badge";
|
||||
|
||||
/*
|
||||
* @appliesMixin LocalizeMixin
|
||||
* @appliesMixin EventsMixin
|
||||
*/
|
||||
class HaStateLabelBadge extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
static get template() {
|
||||
export class HaStateLabelBadge extends hassLocalizeLitMixin(LitElement) {
|
||||
public state?: HassEntity;
|
||||
private _connected?: boolean;
|
||||
private _updateRemaining?: number;
|
||||
private _timerTimeRemaining?: number;
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this._connected = true;
|
||||
this.startInterval(this.state);
|
||||
}
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this._connected = false;
|
||||
this.clearInterval();
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const state = this.state;
|
||||
|
||||
if (!state) {
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
<ha-label-badge label="not found"></ha-label-badge>
|
||||
`;
|
||||
}
|
||||
|
||||
const domain = computeStateDomain(state);
|
||||
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
<ha-label-badge
|
||||
class="${
|
||||
classMap({
|
||||
[domain]: true,
|
||||
"has-unit_of_measurement":
|
||||
"unit_of_measurement" in state.attributes,
|
||||
})
|
||||
}"
|
||||
.value="${this._computeValue(domain, state)}"
|
||||
.icon="${this._computeIcon(domain, state)}"
|
||||
.image="${state.attributes.entity_picture}"
|
||||
.label="${this._computeLabel(domain, state, this._timerTimeRemaining)}"
|
||||
.description="${computeStateName(state)}"
|
||||
></ha-label-badge>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
hass: {},
|
||||
state: {},
|
||||
_timerTimeRemaining: {},
|
||||
};
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues): void {
|
||||
super.firstUpdated(changedProperties);
|
||||
this.addEventListener("click", (ev) => {
|
||||
ev.stopPropagation();
|
||||
if (this.state) {
|
||||
fireEvent(this, "hass-more-info", { entityId: this.state.entity_id });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
super.updated(changedProperties);
|
||||
|
||||
if (this._connected && changedProperties.has("state")) {
|
||||
this.startInterval(this.state);
|
||||
}
|
||||
}
|
||||
|
||||
private _computeValue(domain: string, state: HassEntity) {
|
||||
switch (domain) {
|
||||
case "binary_sensor":
|
||||
case "device_tracker":
|
||||
case "updater":
|
||||
case "sun":
|
||||
case "alarm_control_panel":
|
||||
case "timer":
|
||||
return null;
|
||||
case "sensor":
|
||||
default:
|
||||
return state.state === "unknown"
|
||||
? "-"
|
||||
: this.localize(`component.${domain}.state.${state.state}`) ||
|
||||
state.state;
|
||||
}
|
||||
}
|
||||
|
||||
private _computeIcon(domain: string, state: HassEntity) {
|
||||
if (state.state === "unavailable") {
|
||||
return null;
|
||||
}
|
||||
switch (domain) {
|
||||
case "alarm_control_panel":
|
||||
if (state.state === "pending") {
|
||||
return "hass:clock-fast";
|
||||
}
|
||||
if (state.state === "armed_away") {
|
||||
return "hass:nature";
|
||||
}
|
||||
if (state.state === "armed_home") {
|
||||
return "hass:home-variant";
|
||||
}
|
||||
if (state.state === "armed_night") {
|
||||
return "hass:weather-night";
|
||||
}
|
||||
if (state.state === "armed_custom_bypass") {
|
||||
return "hass:security-home";
|
||||
}
|
||||
if (state.state === "triggered") {
|
||||
return "hass:alert-circle";
|
||||
}
|
||||
// state == 'disarmed'
|
||||
return domainIcon(domain, state.state);
|
||||
case "binary_sensor":
|
||||
case "device_tracker":
|
||||
case "updater":
|
||||
return stateIcon(state);
|
||||
case "sun":
|
||||
return state.state === "above_horizon"
|
||||
? domainIcon(domain)
|
||||
: "hass:brightness-3";
|
||||
case "timer":
|
||||
return state.state === "active" ? "hass:timer" : "hass:timer-off";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private _computeLabel(domain, state, _timerTimeRemaining) {
|
||||
if (
|
||||
state.state === "unavailable" ||
|
||||
["device_tracker", "alarm_control_panel"].includes(domain)
|
||||
) {
|
||||
// Localize the state with a special state_badge namespace, which has variations of
|
||||
// the state translations that are truncated to fit within the badge label. Translations
|
||||
// are only added for device_tracker and alarm_control_panel.
|
||||
return (
|
||||
this.localize(`state_badge.${domain}.${state.state}`) ||
|
||||
this.localize(`state_badge.default.${state.state}`) ||
|
||||
state.state
|
||||
);
|
||||
}
|
||||
if (domain === "timer") {
|
||||
return secondsToDuration(_timerTimeRemaining);
|
||||
}
|
||||
return state.attributes.unit_of_measurement || null;
|
||||
}
|
||||
|
||||
private clearInterval() {
|
||||
if (this._updateRemaining) {
|
||||
clearInterval(this._updateRemaining);
|
||||
this._updateRemaining = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private startInterval(stateObj) {
|
||||
this.clearInterval();
|
||||
if (stateObj && computeStateDomain(stateObj) === "timer") {
|
||||
this.calculateTimerRemaining(stateObj);
|
||||
|
||||
if (stateObj.state === "active") {
|
||||
this._updateRemaining = window.setInterval(
|
||||
() => this.calculateTimerRemaining(this.state),
|
||||
1000
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private calculateTimerRemaining(stateObj) {
|
||||
this._timerTimeRemaining = timerTimeRemaining(stateObj);
|
||||
}
|
||||
|
||||
private renderStyle(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
:host {
|
||||
@@ -61,175 +244,13 @@ class HaStateLabelBadge extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
);
|
||||
}
|
||||
</style>
|
||||
|
||||
<ha-label-badge
|
||||
class$="[[computeClassNames(state)]]"
|
||||
value="[[computeValue(localize, state)]]"
|
||||
icon="[[computeIcon(state)]]"
|
||||
image="[[computeImage(state)]]"
|
||||
label="[[computeLabel(localize, state, _timerTimeRemaining)]]"
|
||||
description="[[computeDescription(state)]]"
|
||||
></ha-label-badge>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
state: {
|
||||
type: Object,
|
||||
observer: "stateChanged",
|
||||
},
|
||||
_timerTimeRemaining: {
|
||||
type: Number,
|
||||
value: 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.startInterval(this.state);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this.clearInterval();
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
this.addEventListener("click", (ev) => this.badgeTap(ev));
|
||||
}
|
||||
|
||||
badgeTap(ev) {
|
||||
ev.stopPropagation();
|
||||
this.fire("hass-more-info", { entityId: this.state.entity_id });
|
||||
}
|
||||
|
||||
computeClassNames(state) {
|
||||
const classes = [computeStateDomain(state)];
|
||||
classes.push(attributeClassNames(state, ["unit_of_measurement"]));
|
||||
return classes.join(" ");
|
||||
}
|
||||
|
||||
computeValue(localize, state) {
|
||||
const domain = computeStateDomain(state);
|
||||
switch (domain) {
|
||||
case "binary_sensor":
|
||||
case "device_tracker":
|
||||
case "updater":
|
||||
case "sun":
|
||||
case "alarm_control_panel":
|
||||
case "timer":
|
||||
return null;
|
||||
case "sensor":
|
||||
default:
|
||||
return state.state === "unknown"
|
||||
? "-"
|
||||
: localize(`component.${domain}.state.${state.state}`) || state.state;
|
||||
}
|
||||
}
|
||||
|
||||
computeIcon(state) {
|
||||
if (state.state === "unavailable") {
|
||||
return null;
|
||||
}
|
||||
const domain = computeStateDomain(state);
|
||||
switch (domain) {
|
||||
case "alarm_control_panel":
|
||||
if (state.state === "pending") {
|
||||
return "hass:clock-fast";
|
||||
}
|
||||
if (state.state === "armed_away") {
|
||||
return "hass:nature";
|
||||
}
|
||||
if (state.state === "armed_home") {
|
||||
return "hass:home-variant";
|
||||
}
|
||||
if (state.state === "armed_night") {
|
||||
return "hass:weather-night";
|
||||
}
|
||||
if (state.state === "armed_custom_bypass") {
|
||||
return "hass:security-home";
|
||||
}
|
||||
if (state.state === "triggered") {
|
||||
return "hass:alert-circle";
|
||||
}
|
||||
// state == 'disarmed'
|
||||
return domainIcon(domain, state.state);
|
||||
case "binary_sensor":
|
||||
case "device_tracker":
|
||||
case "updater":
|
||||
return stateIcon(state);
|
||||
case "sun":
|
||||
return state.state === "above_horizon"
|
||||
? domainIcon(domain)
|
||||
: "hass:brightness-3";
|
||||
case "timer":
|
||||
return state.state === "active" ? "hass:timer" : "hass:timer-off";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
computeImage(state) {
|
||||
return state.attributes.entity_picture || null;
|
||||
}
|
||||
|
||||
computeLabel(localize, state, _timerTimeRemaining) {
|
||||
const domain = computeStateDomain(state);
|
||||
if (
|
||||
state.state === "unavailable" ||
|
||||
["device_tracker", "alarm_control_panel"].includes(domain)
|
||||
) {
|
||||
// Localize the state with a special state_badge namespace, which has variations of
|
||||
// the state translations that are truncated to fit within the badge label. Translations
|
||||
// are only added for device_tracker and alarm_control_panel.
|
||||
return (
|
||||
localize(`state_badge.${domain}.${state.state}`) ||
|
||||
localize(`state_badge.default.${state.state}`) ||
|
||||
state.state
|
||||
);
|
||||
}
|
||||
if (domain === "timer") {
|
||||
return secondsToDuration(_timerTimeRemaining);
|
||||
}
|
||||
return state.attributes.unit_of_measurement || null;
|
||||
}
|
||||
|
||||
computeDescription(state) {
|
||||
return computeStateName(state);
|
||||
}
|
||||
|
||||
stateChanged(stateObj) {
|
||||
this.updateStyles();
|
||||
this.startInterval(stateObj);
|
||||
}
|
||||
|
||||
clearInterval() {
|
||||
if (this._updateRemaining) {
|
||||
clearInterval(this._updateRemaining);
|
||||
this._updateRemaining = null;
|
||||
}
|
||||
}
|
||||
|
||||
startInterval(stateObj) {
|
||||
this.clearInterval();
|
||||
if (computeStateDomain(stateObj) === "timer") {
|
||||
this.calculateTimerRemaining(stateObj);
|
||||
|
||||
if (stateObj.state === "active") {
|
||||
this._updateRemaining = setInterval(
|
||||
() => this.calculateTimerRemaining(this.state),
|
||||
1000
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
calculateTimerRemaining(stateObj) {
|
||||
this._timerTimeRemaining = timerTimeRemaining(stateObj);
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-state-label-badge": HaStateLabelBadge;
|
||||
}
|
||||
}
|
||||
|
@@ -81,8 +81,15 @@ class HaClimateControl extends EventsMixin(PolymerElement) {
|
||||
this.$.target_temperature.classList.toggle("in-flux", inFlux);
|
||||
}
|
||||
|
||||
_round(val) {
|
||||
// round value to precision derived from step
|
||||
// insired by https://github.com/soundar24/roundSlider/blob/master/src/roundslider.js
|
||||
const s = this.step.toString().split(".");
|
||||
return s[1] ? parseFloat(val.toFixed(s[1].length)) : Math.round(val);
|
||||
}
|
||||
|
||||
incrementValue() {
|
||||
const newval = this.value + this.step;
|
||||
const newval = this._round(this.value + this.step);
|
||||
if (this.value < this.max) {
|
||||
this.last_changed = Date.now();
|
||||
this.temperatureStateInFlux(true);
|
||||
@@ -102,7 +109,7 @@ class HaClimateControl extends EventsMixin(PolymerElement) {
|
||||
}
|
||||
|
||||
decrementValue() {
|
||||
const newval = this.value - this.step;
|
||||
const newval = this._round(this.value - this.step);
|
||||
if (this.value > this.min) {
|
||||
this.last_changed = Date.now();
|
||||
this.temperatureStateInFlux(true);
|
||||
|
@@ -1,9 +1,83 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import {
|
||||
LitElement,
|
||||
PropertyDeclarations,
|
||||
PropertyValues,
|
||||
} from "@polymer/lit-element";
|
||||
import { TemplateResult, html } from "lit-html";
|
||||
import { classMap } from "lit-html/directives/classMap";
|
||||
import "./ha-icon";
|
||||
|
||||
class HaLabelBadge extends PolymerElement {
|
||||
static get template() {
|
||||
class HaLabelBadge extends LitElement {
|
||||
public value?: string;
|
||||
public icon?: string;
|
||||
public label?: string;
|
||||
public description?: string;
|
||||
public image?: string;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
value: {},
|
||||
icon: {},
|
||||
label: {},
|
||||
description: {},
|
||||
image: {},
|
||||
};
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
<div class="badge-container">
|
||||
<div class="label-badge" id="badge">
|
||||
<div
|
||||
class="${
|
||||
classMap({
|
||||
value: true,
|
||||
big: Boolean(this.value && this.value.length > 4),
|
||||
})
|
||||
}"
|
||||
>
|
||||
${
|
||||
this.icon && !this.value && !this.image
|
||||
? html`
|
||||
<ha-icon .icon="${this.icon}"></ha-icon>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
this.value && !this.image
|
||||
? html`
|
||||
<span>${this.value}</span>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
${
|
||||
this.label
|
||||
? html`
|
||||
<div
|
||||
class="${
|
||||
classMap({ label: true, big: this.label.length > 5 })
|
||||
}"
|
||||
>
|
||||
<span>${this.label}</span>
|
||||
</div>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
${
|
||||
this.description
|
||||
? html`
|
||||
<div class="title">${this.description}</div>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected renderStyle(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
.badge-container {
|
||||
@@ -74,69 +148,25 @@ class HaLabelBadge extends PolymerElement {
|
||||
text-overflow: ellipsis;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="badge-container">
|
||||
<div class="label-badge" id="badge">
|
||||
<div class$="[[computeValueClasses(value)]]">
|
||||
<ha-icon
|
||||
icon="[[icon]]"
|
||||
hidden$="[[computeHideIcon(icon, value, image)]]"
|
||||
></ha-icon>
|
||||
<span hidden$="[[computeHideValue(value, image)]]">[[value]]</span>
|
||||
</div>
|
||||
<div
|
||||
hidden$="[[computeHideLabel(label)]]"
|
||||
class$="[[computeLabelClasses(label)]]"
|
||||
>
|
||||
<span>[[label]]</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="title" hidden$="[[!description]]">[[description]]</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
value: String,
|
||||
icon: String,
|
||||
label: String,
|
||||
description: String,
|
||||
|
||||
image: {
|
||||
type: String,
|
||||
observer: "imageChanged",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
computeValueClasses(value) {
|
||||
return value && value.length > 4 ? "value big" : "value";
|
||||
}
|
||||
|
||||
computeLabelClasses(label) {
|
||||
return label && label.length > 5 ? "label big" : "label";
|
||||
}
|
||||
|
||||
computeHideLabel(label) {
|
||||
return !label || !label.trim();
|
||||
}
|
||||
|
||||
computeHideIcon(icon, value, image) {
|
||||
return !icon || value || image;
|
||||
}
|
||||
|
||||
computeHideValue(value, image) {
|
||||
return !value || image;
|
||||
}
|
||||
|
||||
imageChanged(newVal) {
|
||||
this.$.badge.style.backgroundImage = newVal ? "url(" + newVal + ")" : "";
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
super.updated(changedProperties);
|
||||
if (changedProperties.has("image")) {
|
||||
this.shadowRoot!.getElementById("badge")!.style.backgroundImage = this
|
||||
.image
|
||||
? `url(${this.image})`
|
||||
: "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-label-badge": HaLabelBadge;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-label-badge", HaLabelBadge);
|
18
src/data/alarm_control_panel.ts
Normal file
18
src/data/alarm_control_panel.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export const callAlarmAction = (
|
||||
hass: HomeAssistant,
|
||||
entity: string,
|
||||
action:
|
||||
| "arm_away"
|
||||
| "arm_home"
|
||||
| "arm_night"
|
||||
| "arm_custom_bypass"
|
||||
| "disarm",
|
||||
code: string
|
||||
) => {
|
||||
hass!.callService("alarm_control_panel", "alarm_" + action, {
|
||||
entity_id: entity,
|
||||
code,
|
||||
});
|
||||
};
|
@@ -1,22 +1,28 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface LovelaceConfig {
|
||||
_frontendAuto: boolean;
|
||||
title?: string;
|
||||
views: LovelaceViewConfig[];
|
||||
background?: string;
|
||||
resources?: Array<{ type: "css" | "js" | "module" | "html"; url: string }>;
|
||||
excluded_entities?: string[];
|
||||
}
|
||||
|
||||
export interface LovelaceViewConfig {
|
||||
index?: number;
|
||||
title?: string;
|
||||
badges?: string[];
|
||||
cards?: LovelaceCardConfig[];
|
||||
id?: string;
|
||||
path?: string;
|
||||
icon?: string;
|
||||
theme?: string;
|
||||
panel?: boolean;
|
||||
background?: string;
|
||||
}
|
||||
|
||||
export interface LovelaceCardConfig {
|
||||
id?: string;
|
||||
index?: number;
|
||||
view_index?: number;
|
||||
type: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
@@ -60,95 +66,11 @@ export const fetchConfig = (
|
||||
force,
|
||||
});
|
||||
|
||||
export const migrateConfig = (hass: HomeAssistant): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "lovelace/config/migrate",
|
||||
});
|
||||
|
||||
export const saveConfig = (
|
||||
hass: HomeAssistant,
|
||||
config: LovelaceConfig | string,
|
||||
format: "json" | "yaml"
|
||||
config: LovelaceConfig
|
||||
): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "lovelace/config/save",
|
||||
config,
|
||||
format,
|
||||
});
|
||||
|
||||
export const getCardConfig = (
|
||||
hass: HomeAssistant,
|
||||
cardId: string
|
||||
): Promise<string> =>
|
||||
hass.callWS({
|
||||
type: "lovelace/config/card/get",
|
||||
card_id: cardId,
|
||||
});
|
||||
|
||||
export const updateCardConfig = (
|
||||
hass: HomeAssistant,
|
||||
cardId: string,
|
||||
config: LovelaceCardConfig | string,
|
||||
format: "json" | "yaml"
|
||||
): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "lovelace/config/card/update",
|
||||
card_id: cardId,
|
||||
card_config: config,
|
||||
format,
|
||||
});
|
||||
|
||||
export const deleteCard = (
|
||||
hass: HomeAssistant,
|
||||
cardId: string
|
||||
): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "lovelace/config/card/delete",
|
||||
card_id: cardId,
|
||||
});
|
||||
|
||||
export const addCard = (
|
||||
hass: HomeAssistant,
|
||||
viewId: string,
|
||||
config: LovelaceCardConfig | string,
|
||||
format: "json" | "yaml"
|
||||
): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "lovelace/config/card/add",
|
||||
view_id: viewId,
|
||||
card_config: config,
|
||||
format,
|
||||
});
|
||||
|
||||
export const updateViewConfig = (
|
||||
hass: HomeAssistant,
|
||||
viewId: string,
|
||||
config: LovelaceViewConfig | string,
|
||||
format: "json" | "yaml"
|
||||
): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "lovelace/config/view/update",
|
||||
view_id: viewId,
|
||||
view_config: config,
|
||||
format,
|
||||
});
|
||||
|
||||
export const deleteView = (
|
||||
hass: HomeAssistant,
|
||||
viewId: string
|
||||
): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "lovelace/config/view/delete",
|
||||
view_id: viewId,
|
||||
});
|
||||
|
||||
export const addView = (
|
||||
hass: HomeAssistant,
|
||||
config: LovelaceViewConfig | string,
|
||||
format: "json" | "yaml"
|
||||
): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "lovelace/config/view/add",
|
||||
view_config: config,
|
||||
format,
|
||||
});
|
||||
|
6
src/data/websocket_api.ts
Normal file
6
src/data/websocket_api.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export const ERR_ID_REUSE = "id_reuse";
|
||||
export const ERR_INVALID_FORMAT = "invalid_format";
|
||||
export const ERR_NOT_FOUND = "not_found";
|
||||
export const ERR_UNKNOWN_COMMAND = "unknown_command";
|
||||
export const ERR_UNKNOWN_ERROR = "unknown_error";
|
||||
export const ERR_UNAUTHORIZED = "unauthorized";
|
@@ -1,15 +1,18 @@
|
||||
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
|
||||
class MoreInfoScript extends PolymerElement {
|
||||
class MoreInfoScript extends LocalizeMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="iron-flex iron-flex-alignment"></style>
|
||||
|
||||
<div class="layout vertical">
|
||||
<div class="data-entry layout justified horizontal">
|
||||
<div class="key">Last Action</div>
|
||||
<div class="key">
|
||||
[[localize('ui.dialogs.more_info_control.script.last_action')]]
|
||||
</div>
|
||||
<div class="value">[[stateObj.attributes.last_action]]</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -28,7 +28,9 @@ class MoreInfoSun extends LocalizeMixin(PolymerElement) {
|
||||
</div>
|
||||
</template>
|
||||
<div class="data-entry layout justified horizontal">
|
||||
<div class="key">Elevation</div>
|
||||
<div class="key">
|
||||
[[localize('ui.dialogs.more_info_control.sun.elevation')]]
|
||||
</div>
|
||||
<div class="value">[[stateObj.attributes.elevation]]</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -63,7 +65,10 @@ class MoreInfoSun extends LocalizeMixin(PolymerElement) {
|
||||
}
|
||||
|
||||
itemCaption(type) {
|
||||
return type === "ris" ? "Rising " : "Setting ";
|
||||
if (type === "ris") {
|
||||
return this.localize("ui.dialogs.more_info_control.sun.rising");
|
||||
}
|
||||
return this.localize("ui.dialogs.more_info_control.sun.setting");
|
||||
}
|
||||
|
||||
itemDate(type) {
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
|
||||
class MoreInfoUpdater extends PolymerElement {
|
||||
class MoreInfoUpdater extends LocalizeMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
@@ -15,7 +16,7 @@ class MoreInfoUpdater extends PolymerElement {
|
||||
class="link"
|
||||
href="https://www.home-assistant.io/docs/installation/updating/"
|
||||
target="_blank"
|
||||
>Update Instructions</a
|
||||
>[[localize('ui.dialogs.more_info_control.updater.title')]]</a
|
||||
>
|
||||
</div>
|
||||
`;
|
||||
|
@@ -26,7 +26,7 @@ const isExternal = location.search.includes("external_auth=1");
|
||||
|
||||
const authProm = isExternal
|
||||
? () =>
|
||||
import("../common/auth/external_auth").then(
|
||||
import(/* webpackChunkName: "external_auth" */ "../common/auth/external_auth").then(
|
||||
(mod) => new mod.default(hassUrl)
|
||||
)
|
||||
: () =>
|
||||
|
@@ -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;
|
||||
|
@@ -10,6 +10,7 @@ import "../home-assistant-main";
|
||||
import "../ha-init-page";
|
||||
import "../../resources/ha-style";
|
||||
import registerServiceWorker from "../../util/register-service-worker";
|
||||
import { DEFAULT_PANEL } from "../../common/const";
|
||||
|
||||
import HassBaseMixin from "./hass-base-mixin";
|
||||
import AuthMixin from "./auth-mixin";
|
||||
@@ -94,7 +95,7 @@ class HomeAssistant extends ext(PolymerElement, [
|
||||
}
|
||||
|
||||
computePanelUrl(routeData) {
|
||||
return (routeData && routeData.panel) || "lovelace";
|
||||
return (routeData && routeData.panel) || DEFAULT_PANEL;
|
||||
}
|
||||
|
||||
panelUrlChanged(newPanelUrl) {
|
||||
|
@@ -11,6 +11,7 @@ import "./partial-panel-resolver";
|
||||
import EventsMixin from "../mixins/events-mixin";
|
||||
import NavigateMixin from "../mixins/navigate-mixin";
|
||||
import { computeRTL } from "../common/util/compute_rtl";
|
||||
import { DEFAULT_PANEL } from "../common/const";
|
||||
|
||||
import(/* webpackChunkName: "ha-sidebar" */ "../components/ha-sidebar");
|
||||
import(/* webpackChunkName: "voice-command-dialog" */ "../dialogs/ha-voice-command-dialog");
|
||||
@@ -98,7 +99,7 @@ class HomeAssistantMain extends NavigateMixin(EventsMixin(PolymerElement)) {
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
this._defaultPage = localStorage.defaultPage || "lovelace";
|
||||
this._defaultPage = localStorage.defaultPage || DEFAULT_PANEL;
|
||||
this.addEventListener("hass-open-menu", () => this.handleOpenMenu());
|
||||
this.addEventListener("hass-close-menu", () => this.handleCloseMenu());
|
||||
this.addEventListener("hass-start-voice", (ev) =>
|
||||
@@ -135,7 +136,7 @@ class HomeAssistantMain extends NavigateMixin(EventsMixin(PolymerElement)) {
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
if (document.location.pathname === "/") {
|
||||
this.navigate(`/${localStorage.defaultPage || "lovelace"}`, true);
|
||||
this.navigate(`/${localStorage.defaultPage || DEFAULT_PANEL}`, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -37,13 +37,15 @@ export const hassLocalizeLitMixin = <T extends LitElement>(
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
|
||||
let language;
|
||||
let resources;
|
||||
if (this.hass) {
|
||||
language = this.hass.language;
|
||||
resources = this.hass.resources;
|
||||
if (this.localize === empty) {
|
||||
let language;
|
||||
let resources;
|
||||
if (this.hass) {
|
||||
language = this.hass.language;
|
||||
resources = this.hass.resources;
|
||||
}
|
||||
this.localize = this.__computeLocalize(language, resources);
|
||||
}
|
||||
this.localize = this.__computeLocalize(language, resources);
|
||||
}
|
||||
|
||||
public updated(changedProperties: PropertyValues) {
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import IntlMessageFormat from "intl-messageformat/src/main";
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
/**
|
||||
* Adapted from Polymer app-localize-behavior.
|
||||
@@ -32,6 +33,7 @@ export interface FormatsType {
|
||||
export type LocalizeFunc = (key: string, ...args: any[]) => string;
|
||||
|
||||
export interface LocalizeMixin {
|
||||
hass?: HomeAssistant;
|
||||
localize: LocalizeFunc;
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -20,6 +20,7 @@ import {
|
||||
deleteCloudhook,
|
||||
CloudWebhook,
|
||||
} from "../../../data/cloud";
|
||||
import { ERR_UNKNOWN_COMMAND } from "../../../data/websocket_api";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
@@ -78,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 || {};
|
||||
}
|
||||
@@ -86,7 +88,17 @@ export class CloudWebhooks extends LitElement {
|
||||
private _renderBody() {
|
||||
if (!this.cloudStatus || !this._localHooks || !this._cloudHooks) {
|
||||
return html`
|
||||
<div class="loading">Loading…</div>
|
||||
<div class="body-text">Loading…</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (this._localHooks.length === 0) {
|
||||
return html`
|
||||
<div class="body-text">
|
||||
Looks like you have no webhooks yet. Get started by configuring a
|
||||
<a href="/config/integrations">webhook-based integration</a> or by
|
||||
creating a <a href="/config/automation/new">webhook automation</a>.
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -188,7 +200,15 @@ export class CloudWebhooks extends LitElement {
|
||||
}
|
||||
|
||||
private async _fetchData() {
|
||||
this._localHooks = await fetchWebhooks(this.hass!);
|
||||
try {
|
||||
this._localHooks = await fetchWebhooks(this.hass!);
|
||||
} catch (err) {
|
||||
if (err.code === ERR_UNKNOWN_COMMAND) {
|
||||
this._localHooks = [];
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private renderStyle() {
|
||||
@@ -197,7 +217,7 @@ export class CloudWebhooks extends LitElement {
|
||||
.body {
|
||||
padding: 0 16px 8px;
|
||||
}
|
||||
.loading {
|
||||
.body-text {
|
||||
padding: 0 16px;
|
||||
}
|
||||
.webhook {
|
||||
@@ -217,6 +237,7 @@ export class CloudWebhooks extends LitElement {
|
||||
.footer {
|
||||
padding: 16px;
|
||||
}
|
||||
.body-text a,
|
||||
.footer a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
@@ -169,7 +169,8 @@ class HaConfigCloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
fireEvent(this, "register-dialog", {
|
||||
dialogShowEvent: "manage-cloud-webhook",
|
||||
dialogTag: "cloud-webhook-manage-dialog",
|
||||
dialogImport: () => import("./cloud-webhook-manage-dialog"),
|
||||
dialogImport: () =>
|
||||
import(/* webpackChunkName: "cloud-webhook-manage-dialog" */ "./cloud-webhook-manage-dialog"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -172,7 +172,8 @@ class HaConfigManagerDashboard extends LocalizeMixin(
|
||||
this.fire("register-dialog", {
|
||||
dialogShowEvent: "show-config-flow",
|
||||
dialogTag: "ha-config-flow",
|
||||
dialogImport: () => import("./ha-config-flow"),
|
||||
dialogImport: () =>
|
||||
import(/* webpackChunkName: "ha-config-flow" */ "./ha-config-flow"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -50,7 +50,7 @@ class HaConfigNavigation extends LocalizeMixin(NavigateMixin(PolymerElement)) {
|
||||
|
||||
pages: {
|
||||
type: Array,
|
||||
value: ["core", "customize", "automation", "script", "zwave"],
|
||||
value: ["core", "customize", "automation", "script", "zha", "zwave"],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@@ -17,6 +17,7 @@ import(/* webpackChunkName: "panel-config-customize" */ "./customize/ha-config-c
|
||||
import(/* webpackChunkName: "panel-config-dashboard" */ "./dashboard/ha-config-dashboard");
|
||||
import(/* webpackChunkName: "panel-config-script" */ "./script/ha-config-script");
|
||||
import(/* webpackChunkName: "panel-config-users" */ "./users/ha-config-users");
|
||||
import(/* webpackChunkName: "panel-config-zha" */ "./zha/ha-config-zha");
|
||||
import(/* webpackChunkName: "panel-config-zwave" */ "./zwave/ha-config-zwave");
|
||||
|
||||
/*
|
||||
@@ -106,6 +107,18 @@ class HaPanelConfig extends EventsMixin(NavigateMixin(PolymerElement)) {
|
||||
></ha-config-script>
|
||||
</template>
|
||||
|
||||
<template
|
||||
is="dom-if"
|
||||
if="[[_equals(_routeData.page, "zha")]]"
|
||||
restamp
|
||||
>
|
||||
<ha-config-zha
|
||||
page-name="zha"
|
||||
hass="[[hass]]"
|
||||
is-wide="[[isWide]]"
|
||||
></ha-config-zha>
|
||||
</template>
|
||||
|
||||
<template
|
||||
is="dom-if"
|
||||
if="[[_equals(_routeData.page, "zwave")]]"
|
||||
|
@@ -12,6 +12,7 @@ import StateTrigger from "./state";
|
||||
import SunTrigger from "./sun";
|
||||
import TemplateTrigger from "./template";
|
||||
import TimeTrigger from "./time";
|
||||
import WebhookTrigger from "./webhook";
|
||||
import ZoneTrigger from "./zone";
|
||||
|
||||
const TYPES = {
|
||||
@@ -23,6 +24,7 @@ const TYPES = {
|
||||
sun: SunTrigger,
|
||||
template: TemplateTrigger,
|
||||
time: TimeTrigger,
|
||||
webhook: WebhookTrigger,
|
||||
zone: ZoneTrigger,
|
||||
};
|
||||
|
||||
|
32
src/panels/config/js/trigger/webhook.js
Normal file
32
src/panels/config/js/trigger/webhook.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { h, Component } from "preact";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
|
||||
import { onChangeEvent } from "../../../../common/preact/event";
|
||||
|
||||
export default class WebhookTrigger extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.onChange = onChangeEvent.bind(this, "trigger");
|
||||
}
|
||||
|
||||
render({ trigger, localize }) {
|
||||
const { webhook_id: webhookId } = trigger;
|
||||
return (
|
||||
<div>
|
||||
<paper-input
|
||||
label={localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.webhook.webhook_id"
|
||||
)}
|
||||
name="webhook_id"
|
||||
value={webhookId}
|
||||
onvalue-changed={this.onChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
WebhookTrigger.defaultConfig = {
|
||||
webhook_id: "",
|
||||
};
|
@@ -90,7 +90,8 @@ class HaUserPicker extends EventsMixin(
|
||||
this.fire("register-dialog", {
|
||||
dialogShowEvent: "show-add-user",
|
||||
dialogTag: "ha-dialog-add-user",
|
||||
dialogImport: () => import("./ha-dialog-add-user"),
|
||||
dialogImport: () =>
|
||||
import(/* webpackChunkName: "ha-dialog-add-user" */ "./ha-dialog-add-user"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
80
src/panels/config/zha/ha-config-zha.ts
Executable file
80
src/panels/config/zha/ha-config-zha.ts
Executable file
@@ -0,0 +1,80 @@
|
||||
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
|
||||
import { TemplateResult } from "lit-html";
|
||||
import "@polymer/app-layout/app-header/app-header";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
|
||||
import "../../../layouts/ha-app-layout";
|
||||
import "../../../resources/ha-style";
|
||||
|
||||
import "./zha-network";
|
||||
|
||||
export class HaConfigZha extends LitElement {
|
||||
public hass?: HomeAssistant;
|
||||
public isWide?: boolean;
|
||||
private _haStyle?: DocumentFragment;
|
||||
private _ironFlex?: DocumentFragment;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
hass: {},
|
||||
isWide: {},
|
||||
};
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
<ha-app-layout has-scrolling-region="">
|
||||
<app-header slot="header" fixed="">
|
||||
<app-toolbar>
|
||||
<paper-icon-button
|
||||
icon="hass:arrow-left"
|
||||
@click="${this._onBackTapped}"
|
||||
></paper-icon-button>
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
|
||||
<zha-network
|
||||
id="zha-network"
|
||||
.is-wide="${this.isWide}"
|
||||
.hass="${this.hass}"
|
||||
></zha-network>
|
||||
</ha-app-layout>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderStyle(): TemplateResult {
|
||||
if (!this._haStyle) {
|
||||
this._haStyle = document.importNode(
|
||||
(document.getElementById("ha-style")!
|
||||
.children[0] as HTMLTemplateElement).content,
|
||||
true
|
||||
);
|
||||
}
|
||||
if (!this._ironFlex) {
|
||||
this._ironFlex = document.importNode(
|
||||
(document.getElementById("iron-flex")!
|
||||
.children[0] as HTMLTemplateElement).content,
|
||||
true
|
||||
);
|
||||
}
|
||||
return html`
|
||||
${this._ironFlex} ${this._haStyle}
|
||||
`;
|
||||
}
|
||||
|
||||
private _onBackTapped(): void {
|
||||
history.back();
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-config-zha": HaConfigZha;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-config-zha", HaConfigZha);
|
129
src/panels/config/zha/zha-network.ts
Normal file
129
src/panels/config/zha/zha-network.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
|
||||
import { TemplateResult } from "lit-html";
|
||||
import "@polymer/paper-button/paper-button";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
||||
import "../../../components/buttons/ha-call-service-button";
|
||||
import "../../../components/ha-service-description";
|
||||
import "../ha-config-section";
|
||||
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import "../../../resources/ha-style";
|
||||
|
||||
export class ZHANetwork extends LitElement {
|
||||
public hass?: HomeAssistant;
|
||||
public isWide?: boolean;
|
||||
public showDescription: boolean;
|
||||
private _haStyle?: DocumentFragment;
|
||||
private _ironFlex?: DocumentFragment;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.showDescription = false;
|
||||
}
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
hass: {},
|
||||
isWide: {},
|
||||
showDescription: {},
|
||||
};
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
<ha-config-section .is-wide="${this.isWide}">
|
||||
<div style="position: relative" slot="header">
|
||||
<span>Zigbee Home Automation network management</span>
|
||||
<paper-icon-button class="toggle-help-icon" @click="${
|
||||
this._onHelpTap
|
||||
}" icon="hass:help-circle"></paper-icon-button>
|
||||
</div>
|
||||
<span slot="introduction">Commands that affect entire network</span>
|
||||
|
||||
<paper-card class="content">
|
||||
<div class="card-actions">
|
||||
<ha-call-service-button .hass="${
|
||||
this.hass
|
||||
}" domain="zha" service="permit">Permit</ha-call-service-button>
|
||||
${
|
||||
this.showDescription
|
||||
? html`
|
||||
<ha-service-description
|
||||
.hass="${this.hass}"
|
||||
domain="zha"
|
||||
service="permit"
|
||||
/>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
</paper-card>
|
||||
</ha-config-section>
|
||||
`;
|
||||
}
|
||||
|
||||
private _onHelpTap(): void {
|
||||
this.showDescription = !this.showDescription;
|
||||
}
|
||||
|
||||
private renderStyle(): TemplateResult {
|
||||
if (!this._haStyle) {
|
||||
this._haStyle = document.importNode(
|
||||
(document.getElementById("ha-style")!
|
||||
.children[0] as HTMLTemplateElement).content,
|
||||
true
|
||||
);
|
||||
}
|
||||
if (!this._ironFlex) {
|
||||
this._ironFlex = document.importNode(
|
||||
(document.getElementById("iron-flex")!
|
||||
.children[0] as HTMLTemplateElement).content,
|
||||
true
|
||||
);
|
||||
}
|
||||
return html`
|
||||
${this._ironFlex} ${this._haStyle}
|
||||
<style>
|
||||
.content {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
paper-card {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.card-actions.warning ha-call-service-button {
|
||||
color: var(--google-red-500);
|
||||
}
|
||||
|
||||
.toggle-help-icon {
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
right: 0;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
ha-service-description {
|
||||
display: block;
|
||||
color: grey;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"zha-network": ZHANetwork;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("zha-network", ZHANetwork);
|
@@ -108,6 +108,16 @@ class ZwaveGroups extends PolymerElement {
|
||||
Remove From Group
|
||||
</ha-call-service-button>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_isBroadcastNodeInGroup]]">
|
||||
<ha-call-service-button
|
||||
hass="[[hass]]"
|
||||
domain="zwave"
|
||||
service="change_association"
|
||||
service-data="[[_removeBroadcastNodeServiceData]]"
|
||||
>
|
||||
Remove Broadcast
|
||||
</ha-call-service-button>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</paper-card>
|
||||
@@ -165,6 +175,16 @@ class ZwaveGroups extends PolymerElement {
|
||||
type: String,
|
||||
value: "",
|
||||
},
|
||||
|
||||
_removeBroadcastNodeServiceData: {
|
||||
type: String,
|
||||
value: "",
|
||||
},
|
||||
|
||||
_isBroadcastNodeInGroup: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -201,6 +221,7 @@ class ZwaveGroups extends PolymerElement {
|
||||
|
||||
_computeOtherGroupNodes(selectedGroup) {
|
||||
if (selectedGroup === -1) return -1;
|
||||
this.setProperties({ _isBroadcastNodeInGroup: false });
|
||||
const associations = Object.values(
|
||||
this.groups[selectedGroup].value.association_instances
|
||||
);
|
||||
@@ -212,6 +233,17 @@ class ZwaveGroups extends PolymerElement {
|
||||
const id = assoc[0];
|
||||
const instance = assoc[1];
|
||||
const node = this.nodes.find((n) => n.attributes.node_id === id);
|
||||
if (id === 255) {
|
||||
this.setProperties({
|
||||
_isBroadcastNodeInGroup: true,
|
||||
_removeBroadcastNodeServiceData: {
|
||||
node_id: this.nodes[this.selectedNode].attributes.node_id,
|
||||
association: "remove",
|
||||
target_node_id: 255,
|
||||
group: this.groups[selectedGroup].key,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (!node) {
|
||||
return `Unknown Node (${id}: (${instance} ? ${id}.${instance} : ${id}))`;
|
||||
}
|
||||
@@ -288,6 +320,7 @@ class ZwaveGroups extends PolymerElement {
|
||||
_otherGroupNodes: Object.values(
|
||||
groupData[this._selectedGroup].value.associations
|
||||
),
|
||||
_isBroadcastNodeInGroup: false,
|
||||
});
|
||||
const oldGroup = this._selectedGroup;
|
||||
this.setProperties({ _selectedGroup: -1 });
|
||||
|
@@ -133,7 +133,8 @@ class OzwLog extends EventsMixin(PolymerElement) {
|
||||
this.fire("register-dialog", {
|
||||
dialogShowEvent: "show-ozwlog-dialog",
|
||||
dialogTag: "zwave-log-dialog",
|
||||
dialogImport: () => import("./zwave-log-dialog"),
|
||||
dialogImport: () =>
|
||||
import(/* webpackChunkName: "zwave-log-dialog" */ "./zwave-log-dialog"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -20,6 +20,7 @@ import formatTime from "../../common/datetime/format_time";
|
||||
import EventsMixin from "../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../mixins/localize-mixin";
|
||||
|
||||
const OPT_IN_PANEL = "lovelace";
|
||||
let registeredDialog = false;
|
||||
|
||||
class HaPanelDevInfo extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
@@ -164,7 +165,7 @@ class HaPanelDevInfo extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
</template>
|
||||
</p>
|
||||
<p>
|
||||
<a href='/states'>Go back to the old states page</a>
|
||||
<a href='/lovelace'>Try out the new Lovelace UI</a>
|
||||
<div id="love" style="cursor:pointer;" on-click="_toggleDefaultPage">[[_defaultPageText()]]</div
|
||||
</p>
|
||||
</div>
|
||||
@@ -311,7 +312,8 @@ class HaPanelDevInfo extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
this.fire("register-dialog", {
|
||||
dialogShowEvent: "show-loaded-components",
|
||||
dialogTag: "ha-loaded-components",
|
||||
dialogImport: () => import("./ha-loaded-components"),
|
||||
dialogImport: () =>
|
||||
import(/* webpackChunkName: "ha-loaded-components" */ "./ha-loaded-components"),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -364,15 +366,15 @@ class HaPanelDevInfo extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
|
||||
_defaultPageText() {
|
||||
return `>> ${
|
||||
localStorage.defaultPage === "states" ? "Remove" : "Set"
|
||||
} the old states as default page on this device <<`;
|
||||
localStorage.defaultPage === OPT_IN_PANEL ? "Remove" : "Set"
|
||||
} ${OPT_IN_PANEL} as default page on this device <<`;
|
||||
}
|
||||
|
||||
_toggleDefaultPage() {
|
||||
if (localStorage.defaultPage === "states") {
|
||||
if (localStorage.defaultPage === OPT_IN_PANEL) {
|
||||
delete localStorage.defaultPage;
|
||||
} else {
|
||||
localStorage.defaultPage = "states";
|
||||
localStorage.defaultPage = OPT_IN_PANEL;
|
||||
}
|
||||
this.$.love.innerText = this._defaultPageText();
|
||||
}
|
||||
|
@@ -28,6 +28,7 @@ class HaPanelDevState extends EventsMixin(PolymerElement) {
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
ha-entity-picker,
|
||||
|
@@ -1,258 +0,0 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import "../../../components/ha-card";
|
||||
|
||||
import EventsMixin from "../../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
import "../../../components/ha-label-badge";
|
||||
|
||||
/*
|
||||
* @appliesMixin EventsMixin
|
||||
*/
|
||||
|
||||
const Icons = {
|
||||
armed_away: "hass:security-lock",
|
||||
armed_custom_bypass: "hass:security",
|
||||
armed_home: "hass:security-home",
|
||||
armed_night: "hass:security-home",
|
||||
disarmed: "hass:verified",
|
||||
pending: "hass:shield-outline",
|
||||
triggered: "hass:bell-ring",
|
||||
};
|
||||
|
||||
class HuiAlarmPanelCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
ha-card {
|
||||
padding-bottom: 16px;
|
||||
position: relative;
|
||||
--alarm-color-disarmed: var(--label-badge-green);
|
||||
--alarm-color-pending: var(--label-badge-yellow);
|
||||
--alarm-color-triggered: var(--label-badge-red);
|
||||
--alarm-color-armed: var(--label-badge-red);
|
||||
--alarm-color-autoarm: rgba(0, 153, 255, .1);
|
||||
--alarm-state-color: var(--alarm-color-armed);
|
||||
--base-unit: 15px;
|
||||
font-size: calc(var(--base-unit));
|
||||
}
|
||||
ha-label-badge {
|
||||
--ha-label-badge-color: var(--alarm-state-color);
|
||||
--label-badge-text-color: var(--alarm-state-color);
|
||||
color: var(--alarm-state-color);
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 12px;
|
||||
}
|
||||
.disarmed {
|
||||
--alarm-state-color: var(--alarm-color-disarmed);
|
||||
}
|
||||
.triggered {
|
||||
--alarm-state-color: var(--alarm-color-triggered);
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
.arming {
|
||||
--alarm-state-color: var(--alarm-color-pending);
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
.pending {
|
||||
--alarm-state-color: var(--alarm-color-pending);
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
--ha-label-badge-color: var(--alarm-state-color);
|
||||
}
|
||||
100% {
|
||||
--ha-label-badge-color: rgba(255, 153, 0, 0.3);
|
||||
}
|
||||
}
|
||||
paper-input {
|
||||
margin: auto;
|
||||
max-width: 200px;
|
||||
font-size: calc(var(--base-unit));
|
||||
}
|
||||
.state {
|
||||
margin-left: 16px;
|
||||
font-size: calc(var(--base-unit) * 0.9);
|
||||
position: relative;
|
||||
bottom: 16px;
|
||||
color: var(--alarm-state-color);
|
||||
animation: none;
|
||||
}
|
||||
#keypad {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
#keypad div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
#keypad paper-button {
|
||||
margin-bottom: 10%;
|
||||
position: relative;
|
||||
padding: calc(var(--base-unit));
|
||||
font-size: calc(var(--base-unit) * 1.1);
|
||||
}
|
||||
.actions {
|
||||
margin: 0 8px;
|
||||
padding-top: 20px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
font-size: calc(var(--base-unit) * 1);
|
||||
}
|
||||
.actions paper-button {
|
||||
min-width: calc(var(--base-unit) * 9);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
paper-button#disarm {
|
||||
color: var(--google-red-500);
|
||||
}
|
||||
.not-found {
|
||||
flex: 1;
|
||||
background-color: yellow;
|
||||
padding: 8px;
|
||||
}
|
||||
</style>
|
||||
<ha-card
|
||||
header$="[[_computeHeader(localize, _stateObj)]]"
|
||||
class$="[[_computeClassName(_stateObj)]]"
|
||||
>
|
||||
<template is="dom-if" if="[[_stateObj]]">
|
||||
<ha-label-badge
|
||||
class$="[[_stateObj.state]]"
|
||||
icon="[[_computeIcon(_stateObj)]]"
|
||||
label="[[_stateIconLabel(_stateObj.state)]]"
|
||||
></ha-label-badge>
|
||||
<template is="dom-if" if="[[_showActionToggle(_stateObj.state)]]">
|
||||
<div id="armActions" class="actions">
|
||||
<template is="dom-repeat" items="[[_config.states]]">
|
||||
<paper-button noink raised id="[[item]]" on-click="_handleActionClick">[[_label(localize, item)]]</paper-button>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template is="dom-if" if="[[!_showActionToggle(_stateObj.state)]]">
|
||||
<div id="disarmActions" class="actions">
|
||||
<paper-button noink raised id="disarm" on-click="_handleActionClick">[[_label(localize, "disarm")]]</paper-button>
|
||||
</div>
|
||||
</template>
|
||||
<paper-input label="Alarm Code" type="password" value="[[_value]]"></paper-input>
|
||||
<div id="keypad">
|
||||
<div>
|
||||
<paper-button noink raised value="1" on-click="_handlePadClick">1</paper-button>
|
||||
<paper-button noink raised value="4" on-click="_handlePadClick">4</paper-button>
|
||||
<paper-button noink raised value="7" on-click="_handlePadClick">7</paper-button>
|
||||
</div>
|
||||
<div>
|
||||
<paper-button noink raised value="2" on-click="_handlePadClick">2</paper-button>
|
||||
<paper-button noink raised value="5" on-click="_handlePadClick">5</paper-button>
|
||||
<paper-button noink raised value="8" on-click="_handlePadClick">8</paper-button>
|
||||
<paper-button noink raised value="0" on-click="_handlePadClick">0</paper-button>
|
||||
</div>
|
||||
<div>
|
||||
<paper-button noink raised value="3" on-click="_handlePadClick">3</paper-button>
|
||||
<paper-button noink raised value="6" on-click="_handlePadClick">6</paper-button>
|
||||
<paper-button noink raised value="9" on-click="_handlePadClick">9</paper-button>
|
||||
<paper-button noink raised value="clear" on-click="_handlePadClick">[[_label(localize, "clear_code")]]</paper-button>
|
||||
</div>
|
||||
</template>
|
||||
<template is="dom-if" if="[[!_stateObj]]">
|
||||
<div>Entity not available: [[_config.entity]]</div>
|
||||
</template>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: {
|
||||
type: Object,
|
||||
},
|
||||
_config: Object,
|
||||
_stateObj: {
|
||||
type: Object,
|
||||
computed: "_computeStateObj(hass.states, _config.entity)",
|
||||
},
|
||||
_value: {
|
||||
type: String,
|
||||
value: "",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
getCardSize() {
|
||||
return 4;
|
||||
}
|
||||
|
||||
setConfig(config) {
|
||||
if (
|
||||
!config ||
|
||||
!config.entity ||
|
||||
config.entity.split(".")[0] !== "alarm_control_panel"
|
||||
) {
|
||||
throw new Error("Invalid card configuration");
|
||||
}
|
||||
|
||||
const defaults = {
|
||||
states: ["arm_away", "arm_home"],
|
||||
};
|
||||
|
||||
this._config = { ...defaults, ...config };
|
||||
this._icons = Icons;
|
||||
}
|
||||
|
||||
_computeStateObj(states, entityId) {
|
||||
return states && entityId in states ? states[entityId] : null;
|
||||
}
|
||||
|
||||
_computeHeader(localize, stateObj) {
|
||||
if (!stateObj) return "";
|
||||
return this._config.name
|
||||
? this._config.name
|
||||
: this._label(localize, stateObj.state);
|
||||
}
|
||||
|
||||
_computeIcon(stateObj) {
|
||||
return this._icons[stateObj.state] || "hass:shield-outline";
|
||||
}
|
||||
|
||||
_label(localize, state) {
|
||||
return (
|
||||
localize(`state.alarm_control_panel.${state}`) ||
|
||||
localize(`ui.card.alarm_control_panel.${state}`)
|
||||
);
|
||||
}
|
||||
|
||||
_stateIconLabel(state) {
|
||||
const stateLabel = state.split("_").pop();
|
||||
return stateLabel === "disarmed" || stateLabel === "triggered"
|
||||
? ""
|
||||
: stateLabel;
|
||||
}
|
||||
|
||||
_showActionToggle(state) {
|
||||
return state === "disarmed";
|
||||
}
|
||||
|
||||
_computeClassName(stateObj) {
|
||||
if (!stateObj) return "not-found";
|
||||
return "";
|
||||
}
|
||||
|
||||
_handlePadClick(e) {
|
||||
const val = e.target.getAttribute("value");
|
||||
this._value = val === "clear" ? "" : this._value + val;
|
||||
}
|
||||
|
||||
_handleActionClick(e) {
|
||||
this.hass.callService("alarm_control_panel", "alarm_" + e.target.id, {
|
||||
entity_id: this._stateObj.entity_id,
|
||||
code: this._value,
|
||||
});
|
||||
this._value = "";
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-alarm-panel-card", HuiAlarmPanelCard);
|
316
src/panels/lovelace/cards/hui-alarm-panel-card.ts
Normal file
316
src/panels/lovelace/cards/hui-alarm-panel-card.ts
Normal file
@@ -0,0 +1,316 @@
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
PropertyDeclarations,
|
||||
} from "@polymer/lit-element";
|
||||
import { TemplateResult } from "lit-html";
|
||||
import { classMap } from "lit-html/directives/classMap";
|
||||
|
||||
import { LovelaceCard } from "../types";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { LovelaceCardConfig } from "../../../data/lovelace";
|
||||
import { callAlarmAction } from "../../../data/alarm_control_panel";
|
||||
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
|
||||
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-label-badge";
|
||||
import {
|
||||
createErrorCardConfig,
|
||||
createErrorCardElement,
|
||||
} from "./hui-error-card";
|
||||
|
||||
const ICONS = {
|
||||
armed_away: "hass:security-lock",
|
||||
armed_custom_bypass: "hass:security",
|
||||
armed_home: "hass:security-home",
|
||||
armed_night: "hass:security-home",
|
||||
disarmed: "hass:verified",
|
||||
pending: "hass:shield-outline",
|
||||
triggered: "hass:bell-ring",
|
||||
};
|
||||
|
||||
const BUTTONS = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "", "0", "clear"];
|
||||
|
||||
export interface Config extends LovelaceCardConfig {
|
||||
entity: string;
|
||||
name?: string;
|
||||
states?: string[];
|
||||
}
|
||||
|
||||
class HuiAlarmPanelCard extends hassLocalizeLitMixin(LitElement)
|
||||
implements LovelaceCard {
|
||||
public static async getConfigElement() {
|
||||
await import(/* webpackChunkName: "hui-alarm-panel-card-editor" */ "../editor/config-elements/hui-alarm-panel-card-editor");
|
||||
return document.createElement("hui-alarm-panel-card-editor");
|
||||
}
|
||||
|
||||
public static getStubConfig() {
|
||||
return { states: ["arm_home", "arm_away"] };
|
||||
}
|
||||
|
||||
public hass?: HomeAssistant;
|
||||
private _config?: Config;
|
||||
private _code?: string;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
hass: {},
|
||||
_config: {},
|
||||
_code: {},
|
||||
};
|
||||
}
|
||||
|
||||
public getCardSize(): number {
|
||||
return 4;
|
||||
}
|
||||
|
||||
public setConfig(config: Config): void {
|
||||
if (
|
||||
!config ||
|
||||
!config.entity ||
|
||||
config.entity.split(".")[0] !== "alarm_control_panel"
|
||||
) {
|
||||
throw new Error("Invalid card configuration");
|
||||
}
|
||||
|
||||
const defaults = {
|
||||
states: ["arm_away", "arm_home"],
|
||||
};
|
||||
|
||||
this._config = { ...defaults, ...config };
|
||||
this._code = "";
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
if (changedProps.has("_config") || changedProps.has("_code")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
if (oldHass) {
|
||||
return (
|
||||
oldHass.states[this._config!.entity] !==
|
||||
this.hass!.states[this._config!.entity]
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._config || !this.hass) {
|
||||
return html``;
|
||||
}
|
||||
const stateObj = this.hass.states[this._config.entity];
|
||||
|
||||
if (!stateObj) {
|
||||
const element = createErrorCardElement(
|
||||
createErrorCardConfig("Entity not Found!", this._config)
|
||||
);
|
||||
return html`
|
||||
${element}
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
<ha-card .header="${this._config.name || this._label(stateObj.state)}">
|
||||
<ha-label-badge
|
||||
class="${classMap({ [stateObj.state]: true })}"
|
||||
.icon="${ICONS[stateObj.state] || "hass:shield-outline"}"
|
||||
.label="${this._stateIconLabel(stateObj.state)}"
|
||||
></ha-label-badge>
|
||||
<div id="armActions" class="actions">
|
||||
${
|
||||
(stateObj.state === "disarmed"
|
||||
? this._config.states!
|
||||
: ["disarm"]
|
||||
).map((state) => {
|
||||
return html`
|
||||
<paper-button
|
||||
noink
|
||||
raised
|
||||
.action="${state}"
|
||||
@click="${this._handleActionClick}"
|
||||
>${this._label(state)}</paper-button
|
||||
>
|
||||
`;
|
||||
})
|
||||
}
|
||||
</div>
|
||||
${
|
||||
!stateObj.attributes.code_format
|
||||
? html``
|
||||
: html`
|
||||
<paper-input
|
||||
label="Alarm Code"
|
||||
type="password"
|
||||
.value="${this._code}"
|
||||
></paper-input>
|
||||
`
|
||||
}
|
||||
${
|
||||
stateObj.attributes.code_format !== "Number"
|
||||
? html``
|
||||
: html`
|
||||
<div id="keypad">
|
||||
${
|
||||
BUTTONS.map((value) => {
|
||||
return value === ""
|
||||
? html`
|
||||
<paper-button disabled></paper-button>
|
||||
`
|
||||
: html`
|
||||
<paper-button
|
||||
noink
|
||||
raised
|
||||
.value="${value}"
|
||||
@click="${this._handlePadClick}"
|
||||
>${
|
||||
value === "clear"
|
||||
? this._label("clear_code")
|
||||
: value
|
||||
}</paper-button
|
||||
>
|
||||
`;
|
||||
})
|
||||
}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private _stateIconLabel(state: string): string {
|
||||
const stateLabel = state.split("_").pop();
|
||||
return stateLabel === "disarmed" ||
|
||||
stateLabel === "triggered" ||
|
||||
!stateLabel
|
||||
? ""
|
||||
: stateLabel;
|
||||
}
|
||||
|
||||
private _label(state: string): string {
|
||||
return (
|
||||
this.localize(`state.alarm_control_panel.${state}`) ||
|
||||
this.localize(`ui.card.alarm_control_panel.${state}`)
|
||||
);
|
||||
}
|
||||
|
||||
private _handlePadClick(e: MouseEvent): void {
|
||||
const val = (e.currentTarget! as any).value;
|
||||
this._code = val === "clear" ? "" : this._code + val;
|
||||
}
|
||||
|
||||
private _handleActionClick(e: MouseEvent): void {
|
||||
callAlarmAction(
|
||||
this.hass!,
|
||||
this._config!.entity_id,
|
||||
(e.currentTarget! as any).action,
|
||||
this._code!
|
||||
);
|
||||
this._code = "";
|
||||
}
|
||||
|
||||
private renderStyle(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
ha-card {
|
||||
padding-bottom: 16px;
|
||||
position: relative;
|
||||
--alarm-color-disarmed: var(--label-badge-green);
|
||||
--alarm-color-pending: var(--label-badge-yellow);
|
||||
--alarm-color-triggered: var(--label-badge-red);
|
||||
--alarm-color-armed: var(--label-badge-red);
|
||||
--alarm-color-autoarm: rgba(0, 153, 255, 0.1);
|
||||
--alarm-state-color: var(--alarm-color-armed);
|
||||
--base-unit: 15px;
|
||||
font-size: calc(var(--base-unit));
|
||||
}
|
||||
ha-label-badge {
|
||||
--ha-label-badge-color: var(--alarm-state-color);
|
||||
--label-badge-text-color: var(--alarm-state-color);
|
||||
--label-badge-background-color: var(--paper-card-background-color);
|
||||
color: var(--alarm-state-color);
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 12px;
|
||||
}
|
||||
.disarmed {
|
||||
--alarm-state-color: var(--alarm-color-disarmed);
|
||||
}
|
||||
.triggered {
|
||||
--alarm-state-color: var(--alarm-color-triggered);
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
.arming {
|
||||
--alarm-state-color: var(--alarm-color-pending);
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
.pending {
|
||||
--alarm-state-color: var(--alarm-color-pending);
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
--ha-label-badge-color: var(--alarm-state-color);
|
||||
}
|
||||
100% {
|
||||
--ha-label-badge-color: rgba(255, 153, 0, 0.3);
|
||||
}
|
||||
}
|
||||
paper-input {
|
||||
margin: 0 auto 8px;
|
||||
max-width: 150px;
|
||||
font-size: calc(var(--base-unit));
|
||||
text-align: center;
|
||||
}
|
||||
.state {
|
||||
margin-left: 16px;
|
||||
font-size: calc(var(--base-unit) * 0.9);
|
||||
position: relative;
|
||||
bottom: 16px;
|
||||
color: var(--alarm-state-color);
|
||||
animation: none;
|
||||
}
|
||||
#keypad {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
margin: auto;
|
||||
width: 300px;
|
||||
}
|
||||
#keypad paper-button {
|
||||
margin-bottom: 5%;
|
||||
width: 30%;
|
||||
padding: calc(var(--base-unit));
|
||||
font-size: calc(var(--base-unit) * 1.1);
|
||||
}
|
||||
.actions {
|
||||
margin: 0 8px;
|
||||
padding-top: 20px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
font-size: calc(var(--base-unit) * 1);
|
||||
}
|
||||
.actions paper-button {
|
||||
min-width: calc(var(--base-unit) * 9);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
paper-button#disarm {
|
||||
color: var(--google-red-500);
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-alarm-panel-card": HuiAlarmPanelCard;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-alarm-panel-card", HuiAlarmPanelCard);
|
@@ -1,4 +1,4 @@
|
||||
import createCardElement from "../common/create-card-element";
|
||||
import { createCardElement } from "../common/create-card-element";
|
||||
import { computeCardSize } from "../common/compute-card-size";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { LovelaceCard } from "../types";
|
||||
|
@@ -17,7 +17,7 @@ import { EntityConfig, EntityRow } from "../entity-rows/types";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import { LovelaceCardConfig } from "../../../data/lovelace";
|
||||
import { processConfigEntities } from "../common/process-config-entities";
|
||||
import createRowElement from "../common/create-row-element";
|
||||
import { createRowElement } from "../common/create-row-element";
|
||||
import computeDomain from "../../../common/entity/compute_domain";
|
||||
import applyThemesOnElement from "../../../common/dom/apply_themes_on_element";
|
||||
|
||||
@@ -40,7 +40,7 @@ export interface Config extends LovelaceCardConfig {
|
||||
class HuiEntitiesCard extends hassLocalizeLitMixin(LitElement)
|
||||
implements LovelaceCard {
|
||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||
await import("../editor/config-elements/hui-entities-card-editor");
|
||||
await import(/* webpackChunkName: "hui-entities-card-editor" */ "../editor/config-elements/hui-entities-card-editor");
|
||||
return document.createElement("hui-entities-card-editor");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
@@ -17,12 +17,12 @@ import computeStateName from "../../../common/entity/compute_state_name";
|
||||
import applyThemesOnElement from "../../../common/dom/apply_themes_on_element";
|
||||
import { HomeAssistant, LightEntity } from "../../../types";
|
||||
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
|
||||
import { LovelaceCard } from "../types";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import { LovelaceCardConfig, ActionConfig } from "../../../data/lovelace";
|
||||
import { longPress } from "../common/directives/long-press-directive";
|
||||
import { handleClick } from "../common/handle-click";
|
||||
|
||||
interface Config extends LovelaceCardConfig {
|
||||
export interface Config extends LovelaceCardConfig {
|
||||
entity: string;
|
||||
name?: string;
|
||||
icon?: string;
|
||||
@@ -33,6 +33,18 @@ interface Config extends LovelaceCardConfig {
|
||||
|
||||
class HuiEntityButtonCard extends hassLocalizeLitMixin(LitElement)
|
||||
implements LovelaceCard {
|
||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||
await import(/* webpackChunkName: "hui-entity-button-card-editor" */ "../editor/config-elements/hui-entity-button-card-editor");
|
||||
return document.createElement("hui-entity-button-card-editor");
|
||||
}
|
||||
|
||||
public static getStubConfig(): object {
|
||||
return {
|
||||
tap_action: { action: "more-info" },
|
||||
hold_action: { action: "none" },
|
||||
};
|
||||
}
|
||||
|
||||
public hass?: HomeAssistant;
|
||||
private _config?: Config;
|
||||
|
||||
@@ -116,6 +128,7 @@ class HuiEntityButtonCard extends hassLocalizeLitMixin(LitElement)
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
super.updated(changedProps);
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import createCardElement from "../common/create-card-element";
|
||||
import { createCardElement } from "../common/create-card-element";
|
||||
import { processConfigEntities } from "../common/process-config-entities";
|
||||
|
||||
function getEntities(hass, filterState, entities) {
|
||||
|
@@ -3,13 +3,27 @@ import { html, LitElement } from "@polymer/lit-element";
|
||||
import { LovelaceCard } from "../types";
|
||||
import { LovelaceCardConfig } from "../../../data/lovelace";
|
||||
import { TemplateResult } from "lit-html";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
|
||||
interface Config extends LovelaceCardConfig {
|
||||
error: string;
|
||||
origConfig: LovelaceCardConfig;
|
||||
}
|
||||
|
||||
class HuiErrorCard extends LitElement implements LovelaceCard {
|
||||
export const createErrorCardElement = (config) => {
|
||||
const el = document.createElement("hui-error-card");
|
||||
el.setConfig(config);
|
||||
return el;
|
||||
};
|
||||
|
||||
export const createErrorCardConfig = (error, origConfig) => ({
|
||||
type: "error",
|
||||
error,
|
||||
origConfig,
|
||||
});
|
||||
|
||||
export class HuiErrorCard extends LitElement implements LovelaceCard {
|
||||
public hass?: HomeAssistant;
|
||||
private _config?: Config;
|
||||
|
||||
static get properties() {
|
||||
|
@@ -5,30 +5,40 @@ import {
|
||||
PropertyValues,
|
||||
} from "@polymer/lit-element";
|
||||
import { TemplateResult } from "lit-html";
|
||||
import { styleMap } from "lit-html/directives/styleMap";
|
||||
|
||||
import { LovelaceCard } from "../types";
|
||||
import "../../../components/ha-card";
|
||||
import { LovelaceCardConfig } from "../../../data/lovelace";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
|
||||
import isValidEntityId from "../../../common/entity/valid_entity_id";
|
||||
import applyThemesOnElement from "../../../common/dom/apply_themes_on_element";
|
||||
import computeStateName from "../../../common/entity/compute_state_name";
|
||||
|
||||
import "../../../components/ha-card";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import {
|
||||
createErrorCardConfig,
|
||||
createErrorCardElement,
|
||||
} from "./hui-error-card";
|
||||
|
||||
interface Config extends LovelaceCardConfig {
|
||||
export interface SeverityConfig {
|
||||
green?: number;
|
||||
yellow?: number;
|
||||
red?: number;
|
||||
}
|
||||
|
||||
export interface Config extends LovelaceCardConfig {
|
||||
entity: string;
|
||||
name?: string;
|
||||
unit?: string;
|
||||
min?: number;
|
||||
max?: number;
|
||||
severity?: object;
|
||||
severity?: SeverityConfig;
|
||||
theme?: string;
|
||||
}
|
||||
|
||||
const severityMap = {
|
||||
export const severityMap = {
|
||||
red: "var(--label-badge-red)",
|
||||
green: "var(--label-badge-green)",
|
||||
yellow: "var(--label-badge-yellow)",
|
||||
@@ -36,6 +46,14 @@ const severityMap = {
|
||||
};
|
||||
|
||||
class HuiGaugeCard extends LitElement implements LovelaceCard {
|
||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||
await import(/* webpackChunkName: "hui-gauge-card-editor" */ "../editor/config-elements/hui-gauge-card-editor");
|
||||
return document.createElement("hui-gauge-card-editor");
|
||||
}
|
||||
public static getStubConfig(): object {
|
||||
return {};
|
||||
}
|
||||
|
||||
public hass?: HomeAssistant;
|
||||
private _config?: Config;
|
||||
|
||||
@@ -65,11 +83,23 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
|
||||
return html``;
|
||||
}
|
||||
const stateObj = this.hass.states[this._config.entity];
|
||||
let state;
|
||||
let error;
|
||||
|
||||
if (!stateObj) {
|
||||
error = "Entity not available: " + this._config.entity;
|
||||
} else if (isNaN(Number(stateObj.state))) {
|
||||
error = "Entity is non-numeric: " + this._config.entity;
|
||||
} else {
|
||||
state = Number(stateObj.state);
|
||||
|
||||
if (isNaN(state)) {
|
||||
error = "Entity is non-numeric: " + this._config.entity;
|
||||
}
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return html`
|
||||
${createErrorCardElement(createErrorCardConfig(error, this._config))}
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
@@ -84,7 +114,15 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
|
||||
<div class="container">
|
||||
<div class="gauge-a"></div>
|
||||
<div class="gauge-b"></div>
|
||||
<div class="gauge-c" id="gauge"></div>
|
||||
<div
|
||||
class="gauge-c"
|
||||
style="${
|
||||
styleMap({
|
||||
transform: `rotate(${this._translateTurn(state)}turn)`,
|
||||
"background-color": this._computeSeverity(state),
|
||||
})
|
||||
}"
|
||||
></div>
|
||||
<div class="gauge-data">
|
||||
<div id="percent">
|
||||
${stateObj.state}
|
||||
@@ -109,41 +147,74 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
|
||||
return hasConfigOrEntityChanged(this, changedProps);
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
if (
|
||||
!this._config ||
|
||||
!this.hass ||
|
||||
!this.shadowRoot!.getElementById("gauge")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const stateObj = this.hass.states[this._config.entity];
|
||||
if (isNaN(Number(stateObj.state))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const turn = this._translateTurn(Number(stateObj.state), this._config);
|
||||
|
||||
this.shadowRoot!.getElementById(
|
||||
"gauge"
|
||||
)!.style.cssText = `transform: rotate(${turn}turn); background-color: ${this._computeSeverity(
|
||||
stateObj.state,
|
||||
this._config.severity!
|
||||
)}`;
|
||||
|
||||
protected firstUpdated(): void {
|
||||
(this.shadowRoot!.querySelector(
|
||||
"ha-card"
|
||||
)! as HTMLElement).style.setProperty(
|
||||
"--base-unit",
|
||||
this._computeBaseUnit()
|
||||
);
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
super.updated(changedProps);
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
|
||||
if (!oldHass || oldHass.themes !== this.hass.themes) {
|
||||
applyThemesOnElement(this, this.hass.themes, this._config.theme);
|
||||
}
|
||||
}
|
||||
|
||||
private _computeSeverity(numberValue: number): string {
|
||||
const sections = this._config!.severity;
|
||||
|
||||
if (!sections) {
|
||||
return severityMap.normal;
|
||||
}
|
||||
|
||||
const sectionsArray = Object.keys(sections);
|
||||
const sortable = sectionsArray.map((severity) => [
|
||||
severity,
|
||||
sections[severity],
|
||||
]);
|
||||
|
||||
for (const severity of sortable) {
|
||||
if (severityMap[severity[0]] == null || isNaN(severity[1])) {
|
||||
return severityMap.normal;
|
||||
}
|
||||
}
|
||||
sortable.sort((a, b) => a[1] - b[1]);
|
||||
|
||||
if (numberValue >= sortable[0][1] && numberValue < sortable[1][1]) {
|
||||
return severityMap[sortable[0][0]];
|
||||
}
|
||||
if (numberValue >= sortable[1][1] && numberValue < sortable[2][1]) {
|
||||
return severityMap[sortable[1][0]];
|
||||
}
|
||||
if (numberValue >= sortable[2][1]) {
|
||||
return severityMap[sortable[2][0]];
|
||||
}
|
||||
return severityMap.normal;
|
||||
}
|
||||
|
||||
private _translateTurn(value: number): number {
|
||||
const { min, max } = this._config!;
|
||||
const maxTurnValue = Math.min(Math.max(value, min!), max!);
|
||||
return (5 * (maxTurnValue - min!)) / (max! - min!) / 10;
|
||||
}
|
||||
|
||||
private _computeBaseUnit(): string {
|
||||
return this.clientWidth < 200 ? this.clientWidth / 5 + "px" : "50px";
|
||||
}
|
||||
|
||||
private _handleClick(): void {
|
||||
fireEvent(this, "hass-more-info", { entityId: this._config!.entity });
|
||||
}
|
||||
|
||||
private renderStyle(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
@@ -226,53 +297,6 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
|
||||
</style>
|
||||
`;
|
||||
}
|
||||
|
||||
private _computeSeverity(stateValue: string, sections: object): string {
|
||||
const numberValue = Number(stateValue);
|
||||
|
||||
if (!sections) {
|
||||
return severityMap.normal;
|
||||
}
|
||||
|
||||
const sectionsArray = Object.keys(sections);
|
||||
const sortable = sectionsArray.map((severity) => [
|
||||
severity,
|
||||
sections[severity],
|
||||
]);
|
||||
|
||||
for (const severity of sortable) {
|
||||
if (severityMap[severity[0]] == null || isNaN(severity[1])) {
|
||||
return severityMap.normal;
|
||||
}
|
||||
}
|
||||
sortable.sort((a, b) => a[1] - b[1]);
|
||||
|
||||
if (numberValue >= sortable[0][1] && numberValue < sortable[1][1]) {
|
||||
return severityMap[sortable[0][0]];
|
||||
}
|
||||
if (numberValue >= sortable[1][1] && numberValue < sortable[2][1]) {
|
||||
return severityMap[sortable[1][0]];
|
||||
}
|
||||
if (numberValue >= sortable[2][1]) {
|
||||
return severityMap[sortable[2][0]];
|
||||
}
|
||||
return severityMap.normal;
|
||||
}
|
||||
|
||||
private _translateTurn(value: number, config: Config): number {
|
||||
const maxTurnValue = Math.min(Math.max(value, config.min!), config.max!);
|
||||
return (
|
||||
(5 * (maxTurnValue - config.min!)) / (config.max! - config.min!) / 10
|
||||
);
|
||||
}
|
||||
|
||||
private _computeBaseUnit(): string {
|
||||
return this.clientWidth < 200 ? this.clientWidth / 5 + "px" : "50px";
|
||||
}
|
||||
|
||||
private _handleClick(): void {
|
||||
fireEvent(this, "hass-more-info", { entityId: this._config!.entity });
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -41,7 +41,7 @@ export interface Config extends LovelaceCardConfig {
|
||||
export class HuiGlanceCard extends hassLocalizeLitMixin(LitElement)
|
||||
implements LovelaceCard {
|
||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||
await import("../editor/config-elements/hui-glance-card-editor");
|
||||
await import(/* webpackChunkName: "hui-glance-card-editor" */ "../editor/config-elements/hui-glance-card-editor");
|
||||
return document.createElement("hui-glance-card-editor");
|
||||
}
|
||||
public static getStubConfig(): object {
|
||||
@@ -135,6 +135,7 @@ export class HuiGlanceCard extends hassLocalizeLitMixin(LitElement)
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
super.updated(changedProperties);
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
|
@@ -2,18 +2,26 @@ import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
|
||||
|
||||
import "../../../components/ha-card";
|
||||
|
||||
import { LovelaceCard } from "../types";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import { LovelaceCardConfig } from "../../../data/lovelace";
|
||||
import { TemplateResult } from "lit-html";
|
||||
import { styleMap } from "lit-html/directives/styleMap";
|
||||
|
||||
interface Config extends LovelaceCardConfig {
|
||||
export interface Config extends LovelaceCardConfig {
|
||||
aspect_ratio?: string;
|
||||
title?: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export class HuiIframeCard extends LitElement implements LovelaceCard {
|
||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||
await import(/* webpackChunkName: "hui-iframe-card-editor" */ "../editor/config-elements/hui-iframe-card-editor");
|
||||
return document.createElement("hui-iframe-card-editor");
|
||||
}
|
||||
public static getStubConfig(): object {
|
||||
return { url: "https://www.home-assistant.io", aspect_ratio: "50%" };
|
||||
}
|
||||
|
||||
protected _config?: Config;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import createErrorCardConfig from "../common/create-error-card-config";
|
||||
import { createErrorCardConfig } from "./hui-error-card";
|
||||
import computeDomain from "../../../common/entity/compute_domain";
|
||||
|
||||
export default class LegacyWrapperCard extends HTMLElement {
|
||||
|
@@ -10,7 +10,7 @@ import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { styleMap } from "lit-html/directives/styleMap";
|
||||
import { HomeAssistant, LightEntity } from "../../../types";
|
||||
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
|
||||
import { LovelaceCard } from "../types";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import { LovelaceCardConfig } from "../../../data/lovelace";
|
||||
import { longPress } from "../common/directives/long-press-directive";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
@@ -36,9 +36,10 @@ const lightConfig = {
|
||||
lineCap: "round",
|
||||
handleSize: "+12",
|
||||
showTooltip: false,
|
||||
animation: false,
|
||||
};
|
||||
|
||||
interface Config extends LovelaceCardConfig {
|
||||
export interface Config extends LovelaceCardConfig {
|
||||
entity: string;
|
||||
name?: string;
|
||||
theme?: string;
|
||||
@@ -46,6 +47,14 @@ interface Config extends LovelaceCardConfig {
|
||||
|
||||
export class HuiLightCard extends hassLocalizeLitMixin(LitElement)
|
||||
implements LovelaceCard {
|
||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||
await import(/* webpackChunkName: "hui-light-card-editor" */ "../editor/config-elements/hui-light-card-editor");
|
||||
return document.createElement("hui-light-card-editor");
|
||||
}
|
||||
public static getStubConfig(): object {
|
||||
return {};
|
||||
}
|
||||
|
||||
public hass?: HomeAssistant;
|
||||
private _config?: Config;
|
||||
private _brightnessTimout?: number;
|
||||
@@ -134,8 +143,14 @@ export class HuiLightCard extends hassLocalizeLitMixin(LitElement)
|
||||
this._roundSliderStyle = loaded.roundSliderStyle;
|
||||
this._jQuery = loaded.jQuery;
|
||||
|
||||
const brightness = this.hass!.states[this._config!.entity].attributes
|
||||
.brightness;
|
||||
const stateObj = this.hass!.states[this._config!.entity] as LightEntity;
|
||||
|
||||
if (!stateObj) {
|
||||
return;
|
||||
}
|
||||
|
||||
const brightness = stateObj.attributes.brightness || 0;
|
||||
|
||||
this._jQuery("#light", this.shadowRoot).roundSlider({
|
||||
...lightConfig,
|
||||
change: (value) => this._setBrightness(value),
|
||||
@@ -148,11 +163,18 @@ export class HuiLightCard extends hassLocalizeLitMixin(LitElement)
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
super.updated(changedProps);
|
||||
if (!this._config || !this.hass || !this._jQuery) {
|
||||
return;
|
||||
}
|
||||
|
||||
const attrs = this.hass!.states[this._config!.entity].attributes;
|
||||
const stateObj = this.hass!.states[this._config!.entity];
|
||||
|
||||
if (!stateObj) {
|
||||
return;
|
||||
}
|
||||
|
||||
const attrs = stateObj.attributes;
|
||||
|
||||
this._jQuery("#light", this.shadowRoot).roundSlider({
|
||||
value: Math.round((attrs.brightness / 254) * 100) || 0,
|
||||
|
@@ -11,7 +11,24 @@ import computeStateName from "../../../common/entity/compute_state_name";
|
||||
import debounce from "../../../common/util/debounce";
|
||||
import parseAspectRatio from "../../../common/util/parse-aspect-ratio";
|
||||
|
||||
// should be interface when converted to TS
|
||||
export const Config = {
|
||||
title: "",
|
||||
aspect_ratio: "",
|
||||
default_zoom: 14,
|
||||
entities: [],
|
||||
};
|
||||
|
||||
class HuiMapCard extends PolymerElement {
|
||||
static async getConfigElement() {
|
||||
await import(/* webpackChunkName: "hui-map-card-editor" */ "../editor/config-elements/hui-map-card-editor");
|
||||
return document.createElement("hui-map-card-editor");
|
||||
}
|
||||
|
||||
static getStubConfig() {
|
||||
return { entities: [] };
|
||||
}
|
||||
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
@@ -111,8 +128,24 @@ class HuiMapCard extends PolymerElement {
|
||||
throw new Error("Error in card configuration.");
|
||||
}
|
||||
|
||||
this._configEntities = processConfigEntities(config.entities);
|
||||
if (!config.entities && !config.geo_location_sources) {
|
||||
throw new Error(
|
||||
"Either entities or geo_location_sources must be defined"
|
||||
);
|
||||
}
|
||||
if (config.entities && !Array.isArray(config.entities)) {
|
||||
throw new Error("Entities need to be an array");
|
||||
}
|
||||
if (
|
||||
config.geo_location_sources &&
|
||||
!Array.isArray(config.geo_location_sources)
|
||||
) {
|
||||
throw new Error("Geo_location_sources needs to be an array");
|
||||
}
|
||||
|
||||
this._config = config;
|
||||
this._configGeoLocationSources = config.geo_location_sources;
|
||||
this._configEntities = config.entities;
|
||||
}
|
||||
|
||||
getCardSize() {
|
||||
@@ -205,7 +238,24 @@ class HuiMapCard extends PolymerElement {
|
||||
}
|
||||
const mapItems = (this._mapItems = []);
|
||||
|
||||
this._configEntities.forEach((entity) => {
|
||||
let allEntities = [];
|
||||
if (this._configEntities) {
|
||||
allEntities = allEntities.concat(this._configEntities);
|
||||
}
|
||||
if (this._configGeoLocationSources) {
|
||||
Object.keys(this.hass.states).forEach((entityId) => {
|
||||
const stateObj = this.hass.states[entityId];
|
||||
if (
|
||||
computeStateDomain(stateObj) === "geo_location" &&
|
||||
this._configGeoLocationSources.includes(stateObj.attributes.source)
|
||||
) {
|
||||
allEntities.push(entityId);
|
||||
}
|
||||
});
|
||||
}
|
||||
allEntities = processConfigEntities(allEntities);
|
||||
|
||||
allEntities.forEach((entity) => {
|
||||
const entityId = entity.entity;
|
||||
if (!(entityId in hass.states)) {
|
||||
return;
|
||||
|
@@ -4,16 +4,24 @@ import { classMap } from "lit-html/directives/classMap";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-markdown";
|
||||
|
||||
import { LovelaceCard } from "../types";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import { LovelaceCardConfig } from "../../../data/lovelace";
|
||||
import { TemplateResult } from "lit-html";
|
||||
|
||||
interface Config extends LovelaceCardConfig {
|
||||
export interface Config extends LovelaceCardConfig {
|
||||
content: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export class HuiMarkdownCard extends LitElement implements LovelaceCard {
|
||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||
await import(/* webpackChunkName: "hui-markdown-card-editor" */ "../editor/config-elements/hui-markdown-card-editor");
|
||||
return document.createElement("hui-markdown-card-editor");
|
||||
}
|
||||
public static getStubConfig(): object {
|
||||
return { content: " " };
|
||||
}
|
||||
|
||||
private _config?: Config;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
|
@@ -2,7 +2,21 @@ import "../../../cards/ha-media_player-card";
|
||||
|
||||
import LegacyWrapperCard from "./hui-legacy-wrapper-card";
|
||||
|
||||
// should be interface when converted to TS
|
||||
export const Config = {
|
||||
entity: "",
|
||||
};
|
||||
|
||||
class HuiMediaControlCard extends LegacyWrapperCard {
|
||||
static async getConfigElement() {
|
||||
await import(/* webpackChunkName: "hui-media-control-card-editor" */ "../editor/config-elements/hui-media-control-card-editor");
|
||||
return document.createElement("hui-media-control-card-editor");
|
||||
}
|
||||
|
||||
static getStubConfig() {
|
||||
return {};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super("ha-media_player-card", "media_player");
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@ import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
|
||||
|
||||
import "../../../components/ha-card";
|
||||
|
||||
import { LovelaceCard } from "../types";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import { LovelaceCardConfig, ActionConfig } from "../../../data/lovelace";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { TemplateResult } from "lit-html";
|
||||
@@ -10,20 +10,31 @@ import { classMap } from "lit-html/directives/classMap";
|
||||
import { handleClick } from "../common/handle-click";
|
||||
import { longPress } from "../common/directives/long-press-directive";
|
||||
|
||||
interface Config extends LovelaceCardConfig {
|
||||
export interface Config extends LovelaceCardConfig {
|
||||
image?: string;
|
||||
tap_action?: ActionConfig;
|
||||
hold_action?: ActionConfig;
|
||||
}
|
||||
|
||||
export class HuiPictureCard extends LitElement implements LovelaceCard {
|
||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||
await import(/* webpackChunkName: "hui-picture-card-editor" */ "../editor/config-elements/hui-picture-card-editor");
|
||||
return document.createElement("hui-picture-card-editor");
|
||||
}
|
||||
public static getStubConfig(): object {
|
||||
return {
|
||||
image:
|
||||
"https://www.home-assistant.io/images/merchandise/shirt-frontpage.png",
|
||||
tap_action: { action: "none" },
|
||||
hold_action: { action: "none" },
|
||||
};
|
||||
}
|
||||
|
||||
public hass?: HomeAssistant;
|
||||
protected _config?: Config;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
_config: {},
|
||||
};
|
||||
return { _config: {} };
|
||||
}
|
||||
|
||||
public getCardSize(): number {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { html, LitElement } from "@polymer/lit-element";
|
||||
import { TemplateResult } from "lit-html";
|
||||
|
||||
import createHuiElement from "../common/create-hui-element";
|
||||
import { createHuiElement } from "../common/create-hui-element";
|
||||
|
||||
import { LovelaceCard } from "../types";
|
||||
import { LovelaceCardConfig } from "../../../data/lovelace";
|
||||
@@ -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;
|
||||
|
@@ -16,6 +16,10 @@ import { LovelaceCardConfig, ActionConfig } from "../../../data/lovelace";
|
||||
import { LovelaceCard } from "../types";
|
||||
import { handleClick } from "../common/handle-click";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
import {
|
||||
createErrorCardElement,
|
||||
createErrorCardConfig,
|
||||
} from "./hui-error-card";
|
||||
|
||||
interface Config extends LovelaceCardConfig {
|
||||
entity: string;
|
||||
@@ -62,11 +66,25 @@ class HuiPictureEntityCard extends hassLocalizeLitMixin(LitElement)
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._config || !this.hass || !this.hass.states[this._config.entity]) {
|
||||
if (!this._config || !this.hass) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const stateObj = this.hass.states[this._config.entity];
|
||||
|
||||
if (!stateObj) {
|
||||
return html`
|
||||
${
|
||||
createErrorCardElement(
|
||||
createErrorCardConfig(
|
||||
`Entity not found: ${this._config.entity}`,
|
||||
this._config
|
||||
)
|
||||
)
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
const name = this._config.name || computeStateName(stateObj);
|
||||
const state = computeStateDisplay(
|
||||
this.localize,
|
||||
|
@@ -2,7 +2,22 @@ import "../../../cards/ha-plant-card";
|
||||
|
||||
import LegacyWrapperCard from "./hui-legacy-wrapper-card";
|
||||
|
||||
// should be interface when converted to TS
|
||||
export const Config = {
|
||||
name: "",
|
||||
entity: "",
|
||||
};
|
||||
|
||||
class HuiPlantStatusCard extends LegacyWrapperCard {
|
||||
static async getConfigElement() {
|
||||
await import(/* webpackChunkName: "hui-plant-status-card-editor" */ "../editor/config-elements/hui-plant-status-card-editor");
|
||||
return document.createElement("hui-plant-status-card-editor");
|
||||
}
|
||||
|
||||
static getStubConfig() {
|
||||
return {};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super("ha-plant-card", "plant");
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@ import {
|
||||
import { TemplateResult } from "lit-html";
|
||||
import "@polymer/paper-spinner/paper-spinner";
|
||||
|
||||
import { LovelaceCard } from "../types";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import { LovelaceCardConfig } from "../../../data/lovelace";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
@@ -133,7 +133,7 @@ const coordinates = (
|
||||
return calcPoints(history, hours, width, detail, min, max);
|
||||
};
|
||||
|
||||
interface Config extends LovelaceCardConfig {
|
||||
export interface Config extends LovelaceCardConfig {
|
||||
entity: string;
|
||||
name?: string;
|
||||
icon?: string;
|
||||
@@ -145,6 +145,15 @@ interface Config extends LovelaceCardConfig {
|
||||
}
|
||||
|
||||
class HuiSensorCard extends LitElement implements LovelaceCard {
|
||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||
await import(/* webpackChunkName: "hui-sensor-card-editor" */ "../editor/config-elements/hui-sensor-card-editor");
|
||||
return document.createElement("hui-sensor-card-editor");
|
||||
}
|
||||
|
||||
public static getStubConfig(): object {
|
||||
return {};
|
||||
}
|
||||
|
||||
public hass?: HomeAssistant;
|
||||
private _config?: Config;
|
||||
private _history?: any;
|
||||
@@ -265,6 +274,7 @@ class HuiSensorCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
if (!this._config || this._config.graph !== "line" || !this.hass) {
|
||||
return;
|
||||
}
|
||||
|
@@ -9,7 +9,7 @@ import "../../../components/ha-icon";
|
||||
|
||||
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { LovelaceCard } from "../types";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import { LovelaceCardConfig } from "../../../data/lovelace";
|
||||
import {
|
||||
fetchItems,
|
||||
@@ -19,12 +19,20 @@ import {
|
||||
addItem,
|
||||
} from "../../../data/shopping-list";
|
||||
|
||||
interface Config extends LovelaceCardConfig {
|
||||
export interface Config extends LovelaceCardConfig {
|
||||
title?: string;
|
||||
}
|
||||
|
||||
class HuiShoppingListCard extends hassLocalizeLitMixin(LitElement)
|
||||
implements LovelaceCard {
|
||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||
await import(/* webpackChunkName: "hui-shopping-list-editor" */ "../editor/config-elements/hui-shopping-list-editor");
|
||||
return document.createElement("hui-shopping-list-card-editor");
|
||||
}
|
||||
public static getStubConfig(): object {
|
||||
return {};
|
||||
}
|
||||
|
||||
public hass?: HomeAssistant;
|
||||
private _config?: Config;
|
||||
private _uncheckedItems?: ShoppingListItem[];
|
||||
@@ -33,6 +41,7 @@ class HuiShoppingListCard extends hassLocalizeLitMixin(LitElement)
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: {},
|
||||
_config: {},
|
||||
_uncheckedItems: {},
|
||||
_checkedItems: {},
|
||||
@@ -117,7 +126,7 @@ class HuiShoppingListCard extends hassLocalizeLitMixin(LitElement)
|
||||
<paper-item-body>
|
||||
<paper-input
|
||||
no-label-float
|
||||
value="${item.name}"
|
||||
.value="${item.name}"
|
||||
.itemId="${item.id}"
|
||||
@change="${this._saveEdit}"
|
||||
></paper-input>
|
||||
@@ -168,7 +177,7 @@ class HuiShoppingListCard extends hassLocalizeLitMixin(LitElement)
|
||||
<paper-item-body>
|
||||
<paper-input
|
||||
no-label-float
|
||||
value="${item.name}"
|
||||
.value="${item.name}"
|
||||
.itemId="${item.id}"
|
||||
@change="${this._saveEdit}"
|
||||
></paper-input>
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { html, LitElement } from "@polymer/lit-element";
|
||||
import { TemplateResult } from "lit-html";
|
||||
|
||||
import createCardElement from "../common/create-card-element";
|
||||
import { createCardElement } from "../common/create-card-element";
|
||||
|
||||
import { LovelaceCard } from "../types";
|
||||
import { LovelaceCardConfig } from "../../../data/lovelace";
|
||||
|
@@ -7,28 +7,30 @@ import {
|
||||
import { classMap } from "lit-html/directives/classMap";
|
||||
import { TemplateResult } from "lit-html";
|
||||
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon";
|
||||
|
||||
import applyThemesOnElement from "../../../common/dom/apply_themes_on_element";
|
||||
import computeStateName from "../../../common/entity/compute_state_name";
|
||||
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import { HomeAssistant, ClimateEntity } from "../../../types";
|
||||
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
|
||||
import { LovelaceCard } from "../types";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import { LovelaceCardConfig } from "../../../data/lovelace";
|
||||
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon";
|
||||
import { loadRoundslider } from "../../../resources/jquery.roundslider.ondemand";
|
||||
import { afterNextRender } from "../../../common/util/render-status";
|
||||
import { UNIT_F } from "../../../common/const";
|
||||
|
||||
const thermostatConfig = {
|
||||
radius: 150,
|
||||
step: 1,
|
||||
circleShape: "pie",
|
||||
startAngle: 315,
|
||||
width: 5,
|
||||
lineCap: "round",
|
||||
handleSize: "+10",
|
||||
showTooltip: false,
|
||||
animation: false,
|
||||
};
|
||||
|
||||
const modeIcons = {
|
||||
@@ -43,7 +45,7 @@ const modeIcons = {
|
||||
idle: "hass:power-sleep",
|
||||
};
|
||||
|
||||
interface Config extends LovelaceCardConfig {
|
||||
export interface Config extends LovelaceCardConfig {
|
||||
entity: string;
|
||||
theme?: string;
|
||||
name?: string;
|
||||
@@ -53,12 +55,30 @@ function formatTemp(temps: string[]): string {
|
||||
return temps.filter(Boolean).join("-");
|
||||
}
|
||||
|
||||
function computeTemperatureStepSize(hass: HomeAssistant, config: Config) {
|
||||
const stateObj = hass.states[config.entity];
|
||||
if (stateObj.attributes.target_temp_step) {
|
||||
return stateObj.attributes.target_temp_step;
|
||||
}
|
||||
return hass.config.unit_system.temperature === UNIT_F ? 1 : 0.5;
|
||||
}
|
||||
|
||||
export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
|
||||
implements LovelaceCard {
|
||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||
await import(/* webpackChunkName: "hui-thermostat-card-editor" */ "../editor/config-elements/hui-thermostat-card-editor");
|
||||
return document.createElement("hui-thermostat-card-editor");
|
||||
}
|
||||
|
||||
public static getStubConfig(): object {
|
||||
return { entity: "" };
|
||||
}
|
||||
|
||||
public hass?: HomeAssistant;
|
||||
private _config?: Config;
|
||||
private _roundSliderStyle?: TemplateResult;
|
||||
private _jQuery?: any;
|
||||
private _broadCard?: boolean;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
@@ -86,7 +106,6 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
|
||||
return html``;
|
||||
}
|
||||
const stateObj = this.hass.states[this._config.entity] as ClimateEntity;
|
||||
const broadCard = this.clientWidth > 390;
|
||||
const mode = modeIcons[stateObj.attributes.operation_mode || ""]
|
||||
? stateObj.attributes.operation_mode!
|
||||
: "unknown-mode";
|
||||
@@ -95,8 +114,8 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
|
||||
<ha-card
|
||||
class="${classMap({
|
||||
[mode]: true,
|
||||
large: broadCard,
|
||||
small: !broadCard,
|
||||
large: this._broadCard!,
|
||||
small: !this._broadCard,
|
||||
})}">
|
||||
<div id="root">
|
||||
<div id="thermostat"></div>
|
||||
@@ -137,8 +156,49 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
|
||||
return hasConfigOrEntityChanged(this, changedProps);
|
||||
}
|
||||
|
||||
protected async firstUpdated(): Promise<void> {
|
||||
protected firstUpdated(): void {
|
||||
this._initialLoad();
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
super.updated(changedProps);
|
||||
if (!this._config || !this.hass || !changedProps.has("hass")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
|
||||
if (!oldHass || oldHass.themes !== this.hass.themes) {
|
||||
applyThemesOnElement(this, this.hass.themes, this._config.theme);
|
||||
}
|
||||
|
||||
const stateObj = this.hass.states[this._config.entity] as ClimateEntity;
|
||||
|
||||
if (
|
||||
this._jQuery &&
|
||||
// If jQuery changed, we just rendered in firstUpdated
|
||||
!changedProps.has("_jQuery") &&
|
||||
(!oldHass || oldHass.states[this._config.entity] !== stateObj)
|
||||
) {
|
||||
const [sliderValue, uiValue] = this._genSliderValue(stateObj);
|
||||
|
||||
this._jQuery("#thermostat", this.shadowRoot).roundSlider({
|
||||
value: sliderValue,
|
||||
});
|
||||
this._updateSetTemp(uiValue);
|
||||
}
|
||||
}
|
||||
|
||||
private async _initialLoad(): Promise<void> {
|
||||
const radius = this.clientWidth / 3;
|
||||
this._broadCard = this.clientWidth > 390;
|
||||
|
||||
(this.shadowRoot!.querySelector(
|
||||
"#thermostat"
|
||||
)! as HTMLElement).style.minHeight = radius * 2 + "px";
|
||||
|
||||
const loaded = await loadRoundslider();
|
||||
await new Promise((resolve) => afterNextRender(resolve));
|
||||
|
||||
this._roundSliderStyle = loaded.roundSliderStyle;
|
||||
this._jQuery = loaded.jQuery;
|
||||
@@ -151,26 +211,27 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
|
||||
? "range"
|
||||
: "min-range";
|
||||
|
||||
const [sliderValue, uiValue] = this._genSliderValue(stateObj);
|
||||
const step = computeTemperatureStepSize(this.hass!, this._config!);
|
||||
|
||||
this._jQuery("#thermostat", this.shadowRoot).roundSlider({
|
||||
...thermostatConfig,
|
||||
radius: this.clientWidth / 3,
|
||||
radius,
|
||||
min: stateObj.attributes.min_temp,
|
||||
max: stateObj.attributes.max_temp,
|
||||
sliderType: _sliderType,
|
||||
create: () => this._loaded(),
|
||||
change: (value) => this._setTemperature(value),
|
||||
drag: (value) => this._dragEvent(value),
|
||||
value: sliderValue,
|
||||
step,
|
||||
});
|
||||
this._updateSetTemp(uiValue);
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
if (!this._config || !this.hass || !this._jQuery) {
|
||||
return;
|
||||
}
|
||||
|
||||
const stateObj = this.hass.states[this._config.entity] as ClimateEntity;
|
||||
|
||||
let sliderValue;
|
||||
let uiValue;
|
||||
private _genSliderValue(stateObj: ClimateEntity): [string | number, string] {
|
||||
let sliderValue: string | number;
|
||||
let uiValue: string;
|
||||
|
||||
if (
|
||||
stateObj.attributes.target_temp_low &&
|
||||
@@ -184,18 +245,73 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
|
||||
String(stateObj.attributes.target_temp_high),
|
||||
]);
|
||||
} else {
|
||||
sliderValue = uiValue = stateObj.attributes.temperature;
|
||||
sliderValue = stateObj.attributes.temperature;
|
||||
uiValue = "" + stateObj.attributes.temperature;
|
||||
}
|
||||
|
||||
this._jQuery("#thermostat", this.shadowRoot).roundSlider({
|
||||
value: sliderValue,
|
||||
return [sliderValue, uiValue];
|
||||
}
|
||||
|
||||
private _loaded(): void {
|
||||
(this.shadowRoot!.querySelector(
|
||||
"#thermostat"
|
||||
)! as HTMLElement).style.minHeight = null;
|
||||
}
|
||||
|
||||
private _updateSetTemp(value: string): void {
|
||||
this.shadowRoot!.querySelector("#set-temperature")!.innerHTML = value;
|
||||
}
|
||||
|
||||
private _dragEvent(e): void {
|
||||
this._updateSetTemp(formatTemp(String(e.value).split(",")));
|
||||
}
|
||||
|
||||
private _setTemperature(e): void {
|
||||
const stateObj = this.hass!.states[this._config!.entity] as ClimateEntity;
|
||||
if (
|
||||
stateObj.attributes.target_temp_low &&
|
||||
stateObj.attributes.target_temp_high
|
||||
) {
|
||||
if (e.handle.index === 1) {
|
||||
this.hass!.callService("climate", "set_temperature", {
|
||||
entity_id: this._config!.entity,
|
||||
target_temp_low: e.handle.value,
|
||||
target_temp_high: stateObj.attributes.target_temp_high,
|
||||
});
|
||||
} else {
|
||||
this.hass!.callService("climate", "set_temperature", {
|
||||
entity_id: this._config!.entity,
|
||||
target_temp_low: stateObj.attributes.target_temp_low,
|
||||
target_temp_high: e.handle.value,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.hass!.callService("climate", "set_temperature", {
|
||||
entity_id: this._config!.entity,
|
||||
temperature: e.value,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _renderIcon(mode: string, currentMode: string): TemplateResult {
|
||||
if (!modeIcons[mode]) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<ha-icon
|
||||
class="${classMap({ "selected-icon": currentMode === mode })}"
|
||||
.mode="${mode}"
|
||||
.icon="${modeIcons[mode]}"
|
||||
@click="${this._handleModeClick}"
|
||||
></ha-icon>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleModeClick(e: MouseEvent): void {
|
||||
this.hass!.callService("climate", "set_operation_mode", {
|
||||
entity_id: this._config!.entity,
|
||||
operation_mode: (e.currentTarget as any).mode,
|
||||
});
|
||||
this.shadowRoot!.querySelector("#set-temperature")!.innerHTML = uiValue;
|
||||
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
if (!oldHass || oldHass.themes !== this.hass.themes) {
|
||||
applyThemesOnElement(this, this.hass.themes, this._config.theme);
|
||||
}
|
||||
}
|
||||
|
||||
private renderStyle(): TemplateResult {
|
||||
@@ -373,60 +489,6 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
|
||||
</style>
|
||||
`;
|
||||
}
|
||||
|
||||
private _dragEvent(e): void {
|
||||
this.shadowRoot!.querySelector("#set-temperature")!.innerHTML = formatTemp(
|
||||
String(e.value).split(",")
|
||||
);
|
||||
}
|
||||
|
||||
private _setTemperature(e): void {
|
||||
const stateObj = this.hass!.states[this._config!.entity] as ClimateEntity;
|
||||
if (
|
||||
stateObj.attributes.target_temp_low &&
|
||||
stateObj.attributes.target_temp_high
|
||||
) {
|
||||
if (e.handle.index === 1) {
|
||||
this.hass!.callService("climate", "set_temperature", {
|
||||
entity_id: this._config!.entity,
|
||||
target_temp_low: e.handle.value,
|
||||
target_temp_high: stateObj.attributes.target_temp_high,
|
||||
});
|
||||
} else {
|
||||
this.hass!.callService("climate", "set_temperature", {
|
||||
entity_id: this._config!.entity,
|
||||
target_temp_low: stateObj.attributes.target_temp_low,
|
||||
target_temp_high: e.handle.value,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.hass!.callService("climate", "set_temperature", {
|
||||
entity_id: this._config!.entity,
|
||||
temperature: e.value,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _renderIcon(mode: string, currentMode: string): TemplateResult {
|
||||
if (!modeIcons[mode]) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<ha-icon
|
||||
class="${classMap({ "selected-icon": currentMode === mode })}"
|
||||
.mode="${mode}"
|
||||
.icon="${modeIcons[mode]}"
|
||||
@click="${this._handleModeClick}"
|
||||
></ha-icon>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleModeClick(e: MouseEvent): void {
|
||||
this.hass!.callService("climate", "set_operation_mode", {
|
||||
entity_id: this._config!.entity,
|
||||
operation_mode: (e.currentTarget as any).mode,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -2,7 +2,22 @@ import "../../../cards/ha-weather-card";
|
||||
|
||||
import LegacyWrapperCard from "./hui-legacy-wrapper-card";
|
||||
|
||||
// should be interface when converted to TS
|
||||
export const Config = {
|
||||
entity: "",
|
||||
name: "",
|
||||
};
|
||||
|
||||
class HuiWeatherForecastCard extends LegacyWrapperCard {
|
||||
static async getConfigElement() {
|
||||
await import(/* webpackChunkName: "hui-weather-forecast-card-editor" */ "../editor/config-elements/hui-weather-forecast-card-editor");
|
||||
return document.createElement("hui-weather-forecast-card-editor");
|
||||
}
|
||||
|
||||
static getStubConfig() {
|
||||
return {};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super("ha-weather-card", "weather");
|
||||
}
|
||||
|
@@ -1,38 +0,0 @@
|
||||
const EXCLUDED_DOMAINS = ["zone"];
|
||||
|
||||
function computeUsedEntities(config) {
|
||||
const entities = new Set();
|
||||
|
||||
function addEntityId(entity) {
|
||||
if (typeof entity === "string") {
|
||||
entities.add(entity);
|
||||
} else if (entity.entity) {
|
||||
entities.add(entity.entity);
|
||||
}
|
||||
}
|
||||
|
||||
function addEntities(obj) {
|
||||
if (obj.entity) addEntityId(obj.entity);
|
||||
if (obj.entities) obj.entities.forEach((entity) => addEntityId(entity));
|
||||
if (obj.card) addEntities(obj.card);
|
||||
if (obj.cards) obj.cards.forEach((card) => addEntities(card));
|
||||
if (obj.badges) obj.badges.forEach((badge) => addEntityId(badge));
|
||||
}
|
||||
|
||||
config.views.forEach((view) => addEntities(view));
|
||||
return entities;
|
||||
}
|
||||
|
||||
export default function computeUnusedEntities(hass, config) {
|
||||
const usedEntities = computeUsedEntities(config);
|
||||
return Object.keys(hass.states)
|
||||
.filter(
|
||||
(entity) =>
|
||||
!usedEntities.has(entity) &&
|
||||
!(
|
||||
config.excluded_entities && config.excluded_entities.includes(entity)
|
||||
) &&
|
||||
!EXCLUDED_DOMAINS.includes(entity.split(".", 1)[0])
|
||||
)
|
||||
.sort();
|
||||
}
|
54
src/panels/lovelace/common/compute-unused-entities.ts
Normal file
54
src/panels/lovelace/common/compute-unused-entities.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { LovelaceConfig } from "../../../data/lovelace";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
|
||||
const EXCLUDED_DOMAINS = ["zone"];
|
||||
|
||||
const computeUsedEntities = (config) => {
|
||||
const entities = new Set();
|
||||
|
||||
const addEntityId = (entity) => {
|
||||
if (typeof entity === "string") {
|
||||
entities.add(entity);
|
||||
} else if (entity.entity) {
|
||||
entities.add(entity.entity);
|
||||
}
|
||||
};
|
||||
|
||||
const addEntities = (obj) => {
|
||||
if (obj.entity) {
|
||||
addEntityId(obj.entity);
|
||||
}
|
||||
if (obj.entities) {
|
||||
obj.entities.forEach((entity) => addEntityId(entity));
|
||||
}
|
||||
if (obj.card) {
|
||||
addEntities(obj.card);
|
||||
}
|
||||
if (obj.cards) {
|
||||
obj.cards.forEach((card) => addEntities(card));
|
||||
}
|
||||
if (obj.badges) {
|
||||
obj.badges.forEach((badge) => addEntityId(badge));
|
||||
}
|
||||
};
|
||||
|
||||
config.views.forEach((view) => addEntities(view));
|
||||
return entities;
|
||||
};
|
||||
|
||||
export const computeUnusedEntities = (
|
||||
hass: HomeAssistant,
|
||||
config: LovelaceConfig
|
||||
): string[] => {
|
||||
const usedEntities = computeUsedEntities(config);
|
||||
return Object.keys(hass.states)
|
||||
.filter(
|
||||
(entity) =>
|
||||
!usedEntities.has(entity) &&
|
||||
!(
|
||||
config.excluded_entities && config.excluded_entities.includes(entity)
|
||||
) &&
|
||||
!EXCLUDED_DOMAINS.includes(entity.split(".", 1)[0])
|
||||
)
|
||||
.sort();
|
||||
};
|
@@ -5,7 +5,11 @@ import "../cards/hui-conditional-card";
|
||||
import "../cards/hui-entities-card";
|
||||
import "../cards/hui-entity-button-card";
|
||||
import "../cards/hui-entity-filter-card";
|
||||
import "../cards/hui-error-card";
|
||||
import {
|
||||
createErrorCardElement,
|
||||
createErrorCardConfig,
|
||||
HuiErrorCard,
|
||||
} from "../cards/hui-error-card";
|
||||
import "../cards/hui-glance-card";
|
||||
import "../cards/hui-history-graph-card";
|
||||
import "../cards/hui-horizontal-stack-card";
|
||||
@@ -25,8 +29,8 @@ import "../cards/hui-shopping-list-card";
|
||||
import "../cards/hui-thermostat-card";
|
||||
import "../cards/hui-weather-forecast-card";
|
||||
import "../cards/hui-gauge-card";
|
||||
|
||||
import createErrorCardConfig from "./create-error-card-config";
|
||||
import { LovelaceCard } from "../types";
|
||||
import { LovelaceCardConfig } from "../../../data/lovelace";
|
||||
|
||||
const CARD_TYPES = new Set([
|
||||
"alarm-panel",
|
||||
@@ -58,24 +62,29 @@ const CARD_TYPES = new Set([
|
||||
const CUSTOM_TYPE_PREFIX = "custom:";
|
||||
const TIMEOUT = 2000;
|
||||
|
||||
function _createElement(tag, config) {
|
||||
const element = document.createElement(tag);
|
||||
const _createElement = (
|
||||
tag: string,
|
||||
config: LovelaceCardConfig
|
||||
): LovelaceCard | HuiErrorCard => {
|
||||
const element = document.createElement(tag) as LovelaceCard;
|
||||
try {
|
||||
element.setConfig(config);
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
// tslint:disable-next-line
|
||||
console.error(tag, err);
|
||||
// eslint-disable-next-line
|
||||
return _createErrorElement(err.message, config);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
};
|
||||
|
||||
function _createErrorElement(error, config) {
|
||||
return _createElement("hui-error-card", createErrorCardConfig(error, config));
|
||||
}
|
||||
const _createErrorElement = (
|
||||
error: string,
|
||||
config: LovelaceCardConfig
|
||||
): HuiErrorCard => createErrorCardElement(createErrorCardConfig(error, config));
|
||||
|
||||
export default function createCardElement(config) {
|
||||
export const createCardElement = (
|
||||
config: LovelaceCardConfig
|
||||
): LovelaceCard | HuiErrorCard => {
|
||||
if (!config || typeof config !== "object" || !config.type) {
|
||||
return _createErrorElement("No card type configured.", config);
|
||||
}
|
||||
@@ -111,4 +120,4 @@ export default function createCardElement(config) {
|
||||
}
|
||||
|
||||
return _createElement(`hui-${config.type}-card`, config);
|
||||
}
|
||||
};
|
@@ -1,7 +0,0 @@
|
||||
export default function createErrorConfig(error, origConfig) {
|
||||
return {
|
||||
type: "error",
|
||||
error,
|
||||
origConfig,
|
||||
};
|
||||
}
|
@@ -6,7 +6,12 @@ import "../elements/hui-state-icon-element";
|
||||
import "../elements/hui-state-label-element";
|
||||
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import createErrorCardConfig from "./create-error-card-config";
|
||||
import {
|
||||
createErrorCardElement,
|
||||
createErrorCardConfig,
|
||||
HuiErrorCard,
|
||||
} from "../cards/hui-error-card";
|
||||
import { LovelaceElementConfig, LovelaceElement } from "../elements/types";
|
||||
|
||||
const CUSTOM_TYPE_PREFIX = "custom:";
|
||||
const ELEMENT_TYPES = new Set([
|
||||
@@ -19,22 +24,25 @@ const ELEMENT_TYPES = new Set([
|
||||
]);
|
||||
const TIMEOUT = 2000;
|
||||
|
||||
function _createElement(tag, config) {
|
||||
const element = document.createElement(tag);
|
||||
const _createElement = (
|
||||
tag: string,
|
||||
config: LovelaceElementConfig
|
||||
): LovelaceElement | HuiErrorCard => {
|
||||
const element = document.createElement(tag) as LovelaceElement;
|
||||
try {
|
||||
element.setConfig(config);
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
// tslint:disable-next-line
|
||||
console.error(tag, err);
|
||||
// eslint-disable-next-line
|
||||
return _createErrorElement(err.message, config);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
};
|
||||
|
||||
function _createErrorElement(error, config) {
|
||||
return _createElement("hui-error-card", createErrorCardConfig(error, config));
|
||||
}
|
||||
const _createErrorElement = (
|
||||
error: string,
|
||||
config: LovelaceElementConfig
|
||||
): HuiErrorCard => createErrorCardElement(createErrorCardConfig(error, config));
|
||||
|
||||
function _hideErrorElement(element) {
|
||||
element.style.display = "None";
|
||||
@@ -43,7 +51,9 @@ function _hideErrorElement(element) {
|
||||
}, TIMEOUT);
|
||||
}
|
||||
|
||||
export default function createHuiElement(config) {
|
||||
export const createHuiElement = (
|
||||
config: LovelaceElementConfig
|
||||
): LovelaceElement | HuiErrorCard => {
|
||||
if (!config || typeof config !== "object" || !config.type) {
|
||||
return _createErrorElement("No element type configured.", config);
|
||||
}
|
||||
@@ -76,4 +86,4 @@ export default function createHuiElement(config) {
|
||||
}
|
||||
|
||||
return _createElement(`hui-${config.type}-element`, config);
|
||||
}
|
||||
};
|
@@ -1,5 +1,10 @@
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
|
||||
import {
|
||||
createErrorCardElement,
|
||||
createErrorCardConfig,
|
||||
HuiErrorCard,
|
||||
} from "../cards/hui-error-card";
|
||||
import "../entity-rows/hui-climate-entity-row";
|
||||
import "../entity-rows/hui-cover-entity-row";
|
||||
import "../entity-rows/hui-group-entity-row";
|
||||
@@ -18,8 +23,7 @@ import "../special-rows/hui-call-service-row";
|
||||
import "../special-rows/hui-divider-row";
|
||||
import "../special-rows/hui-section-row";
|
||||
import "../special-rows/hui-weblink-row";
|
||||
|
||||
import createErrorCardConfig from "./create-error-card-config";
|
||||
import { EntityConfig, EntityRow } from "../entity-rows/types";
|
||||
|
||||
const CUSTOM_TYPE_PREFIX = "custom:";
|
||||
const SPECIAL_TYPES = new Set([
|
||||
@@ -51,32 +55,37 @@ const DOMAIN_TO_ELEMENT_TYPE = {
|
||||
};
|
||||
const TIMEOUT = 2000;
|
||||
|
||||
function _createElement(tag, config) {
|
||||
const element = document.createElement(tag);
|
||||
const _createElement = (
|
||||
tag: string,
|
||||
config: EntityConfig
|
||||
): EntityRow | HuiErrorCard => {
|
||||
const element = document.createElement(tag) as EntityRow;
|
||||
try {
|
||||
if ("setConfig" in element) element.setConfig(config);
|
||||
element.setConfig(config);
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
// tslint:disable-next-line
|
||||
console.error(tag, err);
|
||||
// eslint-disable-next-line
|
||||
return _createErrorElement(err.message, config);
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
};
|
||||
|
||||
function _createErrorElement(error, config) {
|
||||
return _createElement("hui-error-card", createErrorCardConfig(error, config));
|
||||
}
|
||||
const _createErrorElement = (
|
||||
error: string,
|
||||
config: EntityConfig
|
||||
): HuiErrorCard => createErrorCardElement(createErrorCardConfig(error, config));
|
||||
|
||||
function _hideErrorElement(element) {
|
||||
const _hideErrorElement = (element) => {
|
||||
element.style.display = "None";
|
||||
return window.setTimeout(() => {
|
||||
element.style.display = "";
|
||||
}, TIMEOUT);
|
||||
}
|
||||
};
|
||||
|
||||
export default function createRowElement(config) {
|
||||
export const createRowElement = (
|
||||
config: EntityConfig
|
||||
): EntityRow | HuiErrorCard => {
|
||||
let tag;
|
||||
|
||||
if (
|
||||
@@ -116,4 +125,4 @@ export default function createRowElement(config) {
|
||||
tag = `hui-${DOMAIN_TO_ELEMENT_TYPE[domain] || "text"}-entity-row`;
|
||||
|
||||
return _createElement(tag, config);
|
||||
}
|
||||
};
|
@@ -110,7 +110,8 @@ class LongPress extends HTMLElement implements LongPress {
|
||||
const clickEnd = (ev: Event) => {
|
||||
if (
|
||||
this.cooldownEnd ||
|
||||
(ev instanceof TouchEvent && this.timer === undefined)
|
||||
(["touchend", "touchcancel"].includes(ev.type) &&
|
||||
this.timer === undefined)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
@@ -42,6 +42,11 @@ const computeCards = (
|
||||
type: "alarm-panel",
|
||||
entity: entityId,
|
||||
});
|
||||
} else if (domain === "camera") {
|
||||
cards.push({
|
||||
type: "picture-entity",
|
||||
entity: entityId,
|
||||
});
|
||||
} else if (domain === "climate") {
|
||||
cards.push({
|
||||
type: "thermostat",
|
||||
@@ -94,7 +99,7 @@ const computeDefaultViewStates = (hass: HomeAssistant): HassEntities => {
|
||||
|
||||
const generateViewConfig = (
|
||||
localize: LocalizeFunc,
|
||||
id: string,
|
||||
path: string,
|
||||
title: string | undefined,
|
||||
icon: string | undefined,
|
||||
entities: HassEntities,
|
||||
@@ -158,7 +163,7 @@ const generateViewConfig = (
|
||||
});
|
||||
|
||||
return {
|
||||
id,
|
||||
path,
|
||||
title,
|
||||
icon,
|
||||
badges,
|
||||
@@ -228,7 +233,6 @@ export const generateLovelaceConfig = (
|
||||
}
|
||||
|
||||
return {
|
||||
_frontendAuto: true,
|
||||
title,
|
||||
views,
|
||||
};
|
||||
|
132
src/panels/lovelace/components/hui-action-editor.ts
Normal file
132
src/panels/lovelace/components/hui-action-editor.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
|
||||
import { TemplateResult } from "lit-html";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
|
||||
import "../../../components/ha-service-picker";
|
||||
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { EditorTarget } from "../editor/types";
|
||||
import {
|
||||
ActionConfig,
|
||||
NavigateActionConfig,
|
||||
CallServiceActionConfig,
|
||||
} from "../../../data/lovelace";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"action-changed": undefined;
|
||||
}
|
||||
// for add event listener
|
||||
interface HTMLElementEventMap {
|
||||
"action-changed": HASSDomEvent<undefined>;
|
||||
}
|
||||
}
|
||||
|
||||
export class HuiActionEditor extends LitElement {
|
||||
public config?: ActionConfig;
|
||||
public label?: string;
|
||||
public actions?: string[];
|
||||
protected hass?: HomeAssistant;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return { hass: {}, config: {}, label: {}, actions: {} };
|
||||
}
|
||||
|
||||
get _action(): string {
|
||||
return this.config!.action || "";
|
||||
}
|
||||
|
||||
get _navigation_path(): string {
|
||||
const config = this.config! as NavigateActionConfig;
|
||||
return config.navigation_path || "";
|
||||
}
|
||||
|
||||
get _service(): string {
|
||||
const config = this.config! as CallServiceActionConfig;
|
||||
return config.service || "";
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass || !this.actions) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<paper-dropdown-menu
|
||||
.label="${this.label}"
|
||||
.configValue="${"action"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
.selected="${this.actions.indexOf(this._action)}"
|
||||
>
|
||||
${
|
||||
this.actions.map((action) => {
|
||||
return html`
|
||||
<paper-item>${action}</paper-item>
|
||||
`;
|
||||
})
|
||||
}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
${
|
||||
this._action === "navigate"
|
||||
? html`
|
||||
<paper-input
|
||||
label="Navigation Path"
|
||||
.value="${this._navigation_path}"
|
||||
.configValue="${"navigation_path"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></paper-input>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
this.config && this.config.action === "call-service"
|
||||
? html`
|
||||
<ha-service-picker
|
||||
.hass="${this.hass}"
|
||||
.value="${this._service}"
|
||||
.configValue="${"service"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></ha-service-picker>
|
||||
<h3>Toggle Editor to input Service Data</h3>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: Event): void {
|
||||
if (!this.hass) {
|
||||
return;
|
||||
}
|
||||
const target = ev.target! as EditorTarget;
|
||||
if (
|
||||
this.config &&
|
||||
this.config[this[`${target.configValue}`]] === target.value
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (target.configValue === "action") {
|
||||
this.config = { action: "none" };
|
||||
}
|
||||
if (target.configValue) {
|
||||
this.config = { ...this.config!, [target.configValue!]: target.value };
|
||||
fireEvent(this, "action-changed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-action-editor": HuiActionEditor;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-action-editor", HuiActionEditor);
|
@@ -1,90 +1,155 @@
|
||||
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
|
||||
import "@polymer/paper-button/paper-button";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { showEditCardDialog } from "../editor/show-edit-card-dialog";
|
||||
import "@polymer/paper-menu-button/paper-menu-button";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
|
||||
import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog";
|
||||
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
|
||||
import { confDeleteCard } from "../editor/delete-card";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { LovelaceCardConfig } from "../../../data/lovelace";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"show-edit-card": {
|
||||
cardConfig?: LovelaceCardConfig;
|
||||
viewId?: string | number;
|
||||
add: boolean;
|
||||
reloadLovelace: () => void;
|
||||
};
|
||||
}
|
||||
}
|
||||
import { Lovelace } from "../types";
|
||||
import { swapCard } from "../editor/config-util";
|
||||
import { showMoveCardViewDialog } from "../editor/card-editor/show-move-card-view-dialog";
|
||||
|
||||
export class HuiCardOptions extends hassLocalizeLitMixin(LitElement) {
|
||||
public cardConfig?: LovelaceCardConfig;
|
||||
protected hass?: HomeAssistant;
|
||||
public hass?: HomeAssistant;
|
||||
public lovelace?: Lovelace;
|
||||
public path?: [number, number];
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return { hass: {} };
|
||||
return { hass: {}, lovelace: {}, path: {} };
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<style>
|
||||
div {
|
||||
div.options {
|
||||
border-top: 1px solid #e8e8e8;
|
||||
padding: 5px 16px;
|
||||
padding: 5px 8px;
|
||||
background: var(--paper-card-background-color, white);
|
||||
box-shadow: rgba(0, 0, 0, 0.14) 0px 2px 2px 0px,
|
||||
rgba(0, 0, 0, 0.12) 0px 1px 5px 0px,
|
||||
rgba(0, 0, 0, 0.12) 0px 1px 5px -4px,
|
||||
rgba(0, 0, 0, 0.2) 0px 3px 1px -2px;
|
||||
display: flex;
|
||||
}
|
||||
div.options .primary-actions {
|
||||
flex: 1;
|
||||
margin: auto;
|
||||
}
|
||||
div.options .secondary-actions {
|
||||
flex: 4;
|
||||
text-align: right;
|
||||
}
|
||||
paper-button {
|
||||
color: var(--primary-color);
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.05em;
|
||||
font-size: 16px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
paper-button.warning:not([disabled]) {
|
||||
color: var(--google-red-500);
|
||||
paper-icon-button {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
paper-icon-button.move-arrow[disabled] {
|
||||
color: var(--disabled-text-color);
|
||||
}
|
||||
paper-menu-button {
|
||||
color: var(--secondary-text-color);
|
||||
padding: 0;
|
||||
}
|
||||
paper-item.header {
|
||||
color: var(--primary-text-color);
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
<slot></slot>
|
||||
<div>
|
||||
<paper-button @click="${this._editCard}"
|
||||
>${
|
||||
this.localize("ui.panel.lovelace.editor.edit_card.edit")
|
||||
}</paper-button
|
||||
>
|
||||
<paper-button class="warning" @click="${this._deleteCard}"
|
||||
>${
|
||||
this.localize("ui.panel.lovelace.editor.edit_card.delete")
|
||||
}</paper-button
|
||||
>
|
||||
<div class="options">
|
||||
<div class="primary-actions">
|
||||
<paper-button @click="${this._editCard}"
|
||||
>${
|
||||
this.localize("ui.panel.lovelace.editor.edit_card.edit")
|
||||
}</paper-button
|
||||
>
|
||||
</div>
|
||||
<div class="secondary-actions">
|
||||
<paper-icon-button
|
||||
title="Move card down"
|
||||
class="move-arrow"
|
||||
icon="hass:arrow-down"
|
||||
@click="${this._cardDown}"
|
||||
?disabled="${
|
||||
this.lovelace!.config.views[this.path![0]].cards!.length ===
|
||||
this.path![1] + 1
|
||||
}"
|
||||
></paper-icon-button>
|
||||
<paper-icon-button
|
||||
title="Move card up"
|
||||
class="move-arrow"
|
||||
icon="hass:arrow-up"
|
||||
@click="${this._cardUp}"
|
||||
?disabled="${this.path![1] === 0}"
|
||||
></paper-icon-button>
|
||||
<paper-menu-button>
|
||||
<paper-icon-button
|
||||
icon="hass:dots-vertical"
|
||||
slot="dropdown-trigger"
|
||||
></paper-icon-button>
|
||||
<paper-listbox slot="dropdown-content">
|
||||
<paper-item @click="${this._moveCard}">Move Card</paper-item>
|
||||
<paper-item @click="${this._deleteCard}"
|
||||
>${
|
||||
this.localize("ui.panel.lovelace.editor.edit_card.delete")
|
||||
}</paper-item
|
||||
>
|
||||
</paper-listbox>
|
||||
</paper-menu-button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _editCard(): void {
|
||||
if (!this.cardConfig) {
|
||||
return;
|
||||
}
|
||||
showEditCardDialog(this, {
|
||||
cardConfig: this.cardConfig,
|
||||
add: false,
|
||||
reloadLovelace: () => fireEvent(this, "config-refresh"),
|
||||
lovelace: this.lovelace!,
|
||||
path: this.path!,
|
||||
});
|
||||
}
|
||||
private _deleteCard(): void {
|
||||
if (!this.cardConfig) {
|
||||
return;
|
||||
}
|
||||
if (!this.cardConfig.id) {
|
||||
this._editCard();
|
||||
return;
|
||||
}
|
||||
confDeleteCard(this.hass!, this.cardConfig.id, () =>
|
||||
fireEvent(this, "config-refresh")
|
||||
|
||||
private _cardUp(): void {
|
||||
const lovelace = this.lovelace!;
|
||||
const path = this.path!;
|
||||
lovelace.saveConfig(
|
||||
swapCard(lovelace.config, path, [path[0], path[1] - 1])
|
||||
);
|
||||
}
|
||||
|
||||
private _cardDown(): void {
|
||||
const lovelace = this.lovelace!;
|
||||
const path = this.path!;
|
||||
lovelace.saveConfig(
|
||||
swapCard(lovelace.config, path, [path[0], path[1] + 1])
|
||||
);
|
||||
}
|
||||
|
||||
private _moveCard(): void {
|
||||
showMoveCardViewDialog(this, {
|
||||
path: this.path!,
|
||||
lovelace: this.lovelace!,
|
||||
});
|
||||
}
|
||||
|
||||
private _deleteCard(): void {
|
||||
confDeleteCard(this.lovelace!, this.path!);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -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) =>
|
||||
|
@@ -19,7 +19,7 @@ declare global {
|
||||
|
||||
export class HuiThemeSelectionEditor extends hassLocalizeLitMixin(LitElement) {
|
||||
public value?: string;
|
||||
protected hass?: HomeAssistant;
|
||||
public hass?: HomeAssistant;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
|
@@ -78,6 +78,7 @@ class HuiTimestampDisplay extends hassLocalizeLitMixin(LitElement) {
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
super.updated(changedProperties);
|
||||
if (!changedProperties.has("format") || !this._connected) {
|
||||
return;
|
||||
}
|
||||
|
@@ -46,7 +46,7 @@ export class HuiNotificationsButton extends EventsMixin(PolymerElement) {
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
notificationsOpen: {
|
||||
open: {
|
||||
type: Boolean,
|
||||
notify: true,
|
||||
},
|
||||
@@ -58,7 +58,7 @@ export class HuiNotificationsButton extends EventsMixin(PolymerElement) {
|
||||
}
|
||||
|
||||
_clicked() {
|
||||
this.notificationsOpen = true;
|
||||
this.open = true;
|
||||
}
|
||||
|
||||
_hasNotifications(notifications) {
|
||||
|
@@ -2,22 +2,11 @@ import { html, LitElement } from "@polymer/lit-element";
|
||||
import { TemplateResult } from "lit-html";
|
||||
import "@polymer/paper-button/paper-button";
|
||||
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { LovelaceCardConfig } from "../../../data/lovelace";
|
||||
import { getCardElementTag } from "../common/get-card-element-tag";
|
||||
import { CardPickTarget } from "./types";
|
||||
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
|
||||
|
||||
import { uid } from "../../../common/util/uid";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"card-picked": {
|
||||
config: LovelaceCardConfig;
|
||||
};
|
||||
}
|
||||
}
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { LovelaceCardConfig } from "../../../../data/lovelace";
|
||||
import { getCardElementTag } from "../../common/get-card-element-tag";
|
||||
import { CardPickTarget } from "../types";
|
||||
import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin";
|
||||
|
||||
const cards = [
|
||||
{ name: "Alarm panel", type: "alarm-panel" },
|
||||
@@ -28,7 +17,7 @@ const cards = [
|
||||
{ name: "Gauge", type: "gauge" },
|
||||
{ name: "Glance", type: "glance" },
|
||||
{ name: "History Graph", type: "history-graph" },
|
||||
{ name: "Horizontal Stack", type: "horizontal-graph" },
|
||||
{ name: "Horizontal Stack", type: "horizontal-stack" },
|
||||
{ name: "iFrame", type: "iframe" },
|
||||
{ name: "Light", type: "light" },
|
||||
{ name: "Map", type: "map" },
|
||||
@@ -47,7 +36,8 @@ const cards = [
|
||||
];
|
||||
|
||||
export class HuiCardPicker extends hassLocalizeLitMixin(LitElement) {
|
||||
protected hass?: HomeAssistant;
|
||||
public hass?: HomeAssistant;
|
||||
public cardPicked?: (cardConf: LovelaceCardConfig) => void;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
@@ -90,16 +80,14 @@ export class HuiCardPicker extends hassLocalizeLitMixin(LitElement) {
|
||||
const tag = getCardElementTag(type);
|
||||
|
||||
const elClass = customElements.get(tag);
|
||||
let config: LovelaceCardConfig = { type, id: uid() };
|
||||
let config: LovelaceCardConfig = { type };
|
||||
|
||||
if (elClass && elClass.getStubConfig) {
|
||||
const cardConfig = elClass.getStubConfig(this.hass);
|
||||
config = { ...config, ...cardConfig };
|
||||
}
|
||||
|
||||
fireEvent(this, "card-picked", {
|
||||
config,
|
||||
});
|
||||
this.cardPicked!(config);
|
||||
}
|
||||
}
|
||||
|
@@ -1,12 +1,12 @@
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
|
||||
import createCardElement from "../common/create-card-element";
|
||||
import createErrorCardConfig from "../common/create-error-card-config";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { LovelaceCardConfig } from "../../../data/lovelace";
|
||||
import { LovelaceCard } from "../types";
|
||||
import { ConfigError } from "./types";
|
||||
import { getCardElementTag } from "../common/get-card-element-tag";
|
||||
import { createCardElement } from "../../common/create-card-element";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { LovelaceCardConfig } from "../../../../data/lovelace";
|
||||
import { LovelaceCard } from "../../types";
|
||||
import { ConfigError } from "../types";
|
||||
import { getCardElementTag } from "../../common/get-card-element-tag";
|
||||
import { createErrorCardConfig } from "../../cards/hui-error-card";
|
||||
|
||||
export class HuiCardPreview extends HTMLElement {
|
||||
private _hass?: HomeAssistant;
|
@@ -0,0 +1,93 @@
|
||||
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
|
||||
import { TemplateResult } from "lit-html";
|
||||
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { HASSDomEvent } from "../../../../common/dom/fire_event";
|
||||
import { LovelaceCardConfig } from "../../../../data/lovelace";
|
||||
import "./hui-edit-card";
|
||||
import "./hui-dialog-pick-card";
|
||||
import { EditCardDialogParams } from "./show-edit-card-dialog";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"reload-lovelace": undefined;
|
||||
}
|
||||
// for add event listener
|
||||
interface HTMLElementEventMap {
|
||||
"reload-lovelace": HASSDomEvent<undefined>;
|
||||
}
|
||||
}
|
||||
|
||||
export class HuiDialogEditCard extends LitElement {
|
||||
protected hass?: HomeAssistant;
|
||||
private _params?: EditCardDialogParams;
|
||||
private _cardConfig?: LovelaceCardConfig;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
hass: {},
|
||||
_params: {},
|
||||
_cardConfig: {},
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._cardPicked = this._cardPicked.bind(this);
|
||||
this._cancel = this._cancel.bind(this);
|
||||
}
|
||||
|
||||
public async showDialog(params: EditCardDialogParams): Promise<void> {
|
||||
this._params = params;
|
||||
this._cardConfig =
|
||||
params.path.length === 2
|
||||
? (this._cardConfig = params.lovelace.config.views[
|
||||
params.path[0]
|
||||
].cards![params.path[1]])
|
||||
: undefined;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._params) {
|
||||
return html``;
|
||||
}
|
||||
if (!this._cardConfig) {
|
||||
// Card picker
|
||||
return html`
|
||||
<hui-dialog-pick-card
|
||||
.hass="${this.hass}"
|
||||
.cardPicked="${this._cardPicked}"
|
||||
.closeDialog="${this._cancel}"
|
||||
></hui-dialog-pick-card>
|
||||
`;
|
||||
}
|
||||
return html`
|
||||
<hui-edit-card
|
||||
.hass="${this.hass}"
|
||||
.lovelace="${this._params.lovelace}"
|
||||
.path="${this._params.path}"
|
||||
.cardConfig="${this._cardConfig}"
|
||||
.closeDialog="${this._cancel}"
|
||||
>
|
||||
</hui-edit-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private _cardPicked(cardConf: LovelaceCardConfig) {
|
||||
this._cardConfig = cardConf;
|
||||
}
|
||||
|
||||
private _cancel() {
|
||||
this._params = undefined;
|
||||
this._cardConfig = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-dialog-edit-card": HuiDialogEditCard;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-dialog-edit-card", HuiDialogEditCard);
|
@@ -0,0 +1,106 @@
|
||||
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
|
||||
import { TemplateResult } from "lit-html";
|
||||
import "@polymer/paper-dialog/paper-dialog";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
// tslint:disable-next-line:no-duplicate-imports
|
||||
import { PaperDialogElement } from "@polymer/paper-dialog/paper-dialog";
|
||||
|
||||
import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin";
|
||||
import { moveCard } from "../config-util";
|
||||
import { MoveCardViewDialogParams } from "./show-move-card-view-dialog";
|
||||
|
||||
export class HuiDialogMoveCardView extends hassLocalizeLitMixin(LitElement) {
|
||||
private _params?: MoveCardViewDialogParams;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
_params: {},
|
||||
};
|
||||
}
|
||||
|
||||
public async showDialog(params: MoveCardViewDialogParams): Promise<void> {
|
||||
this._params = params;
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._params) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<style>
|
||||
paper-item {
|
||||
margin: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
paper-item[active] {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
paper-item[active]:before {
|
||||
border-radius: 4px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
content: "";
|
||||
background-color: var(--primary-color);
|
||||
opacity: 0.12;
|
||||
transition: opacity 15ms linear;
|
||||
will-change: opacity;
|
||||
}
|
||||
</style>
|
||||
<paper-dialog
|
||||
with-backdrop
|
||||
opened
|
||||
@opened-changed="${this._openedChanged}"
|
||||
>
|
||||
<h2>Choose view to move card</h2>
|
||||
${
|
||||
this._params!.lovelace!.config.views.map((view, index) => {
|
||||
return html`
|
||||
<paper-item
|
||||
?active="${this._params!.path![0] === index}"
|
||||
@click="${this._moveCard}"
|
||||
.index="${index}"
|
||||
>${view.title}</paper-item
|
||||
>
|
||||
`;
|
||||
})
|
||||
}
|
||||
</paper-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private get _dialog(): PaperDialogElement {
|
||||
return this.shadowRoot!.querySelector("paper-dialog")!;
|
||||
}
|
||||
|
||||
private _moveCard(e: Event): void {
|
||||
const newView = (e.currentTarget! as any).index;
|
||||
const path = this._params!.path!;
|
||||
if (newView === path[0]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lovelace = this._params!.lovelace!;
|
||||
|
||||
lovelace.saveConfig(moveCard(lovelace.config, path, [newView!]));
|
||||
this._dialog.close();
|
||||
}
|
||||
|
||||
private _openedChanged(ev: MouseEvent) {
|
||||
if (!(ev.detail as any).value) {
|
||||
this._params = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-dialog-move-card-view": HuiDialogMoveCardView;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-dialog-move-card-view", HuiDialogMoveCardView);
|
@@ -0,0 +1,58 @@
|
||||
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
|
||||
import { TemplateResult } from "lit-html";
|
||||
import "@polymer/paper-dialog/paper-dialog";
|
||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
|
||||
import "./hui-card-picker";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin";
|
||||
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 {};
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<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
|
||||
.hass="${this.hass}"
|
||||
.cardPicked="${this.cardPicked}"
|
||||
></hui-card-picker>
|
||||
</paper-dialog-scrollable>
|
||||
<div class="paper-dialog-buttons">
|
||||
<paper-button @click="${this._skipPick}">SKIP</paper-button>
|
||||
</div>
|
||||
</paper-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _openedChanged(ev) {
|
||||
if (!ev.detail.value) {
|
||||
this.closeDialog!();
|
||||
}
|
||||
}
|
||||
|
||||
private _skipPick() {
|
||||
this.cardPicked!({ type: "" });
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-dialog-pick-card": HuiDialogPickCard;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-dialog-pick-card", HuiDialogPickCard);
|
@@ -15,31 +15,22 @@ import "@polymer/paper-dialog/paper-dialog";
|
||||
import { PaperDialogElement } from "@polymer/paper-dialog/paper-dialog";
|
||||
import "@polymer/paper-button/paper-button";
|
||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import {
|
||||
addCard,
|
||||
updateCardConfig,
|
||||
LovelaceCardConfig,
|
||||
} from "../../../data/lovelace";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { LovelaceCardConfig } from "../../../../data/lovelace";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin";
|
||||
|
||||
import "./hui-yaml-editor";
|
||||
import "./hui-card-picker";
|
||||
import "./hui-card-preview";
|
||||
// This is not a duplicate import, one is for types, one is for element.
|
||||
// tslint:disable-next-line
|
||||
import { HuiCardPreview } from "./hui-card-preview";
|
||||
import { LovelaceCardEditor } from "../types";
|
||||
import {
|
||||
YamlChangedEvent,
|
||||
CardPickedEvent,
|
||||
ConfigValue,
|
||||
ConfigError,
|
||||
} from "./types";
|
||||
import { extYamlSchema } from "./yaml-ext-schema";
|
||||
import { EntityConfig } from "../entity-rows/types";
|
||||
import { getCardElementTag } from "../common/get-card-element-tag";
|
||||
import { LovelaceCardEditor, Lovelace } from "../../types";
|
||||
import { YamlChangedEvent, ConfigValue, ConfigError } from "../types";
|
||||
import { extYamlSchema } from "../yaml-ext-schema";
|
||||
import { EntityConfig } from "../../entity-rows/types";
|
||||
import { getCardElementTag } from "../../common/get-card-element-tag";
|
||||
import { addCard, replaceCard } from "../config-util";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
@@ -52,17 +43,30 @@ declare global {
|
||||
"config-changed": {
|
||||
config: LovelaceCardConfig;
|
||||
};
|
||||
"cancel-edit-card": {};
|
||||
}
|
||||
}
|
||||
|
||||
export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
|
||||
public hass?: HomeAssistant;
|
||||
public lovelace?: Lovelace;
|
||||
public path?: [number] | [number, number];
|
||||
public cardConfig?: LovelaceCardConfig;
|
||||
public closeDialog?: () => void;
|
||||
private _configElement?: LovelaceCardEditor | null;
|
||||
private _uiEditor?: boolean;
|
||||
private _configValue?: ConfigValue;
|
||||
private _configState?: string;
|
||||
private _loading?: boolean;
|
||||
private _saving: boolean;
|
||||
private _errorMsg?: TemplateResult;
|
||||
private _cardType?: string;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
hass: {},
|
||||
cardConfig: {},
|
||||
viewId: {},
|
||||
_cardId: {},
|
||||
viewIndex: {},
|
||||
_cardIndex: {},
|
||||
_configElement: {},
|
||||
_configValue: {},
|
||||
_configState: {},
|
||||
@@ -81,38 +85,15 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
|
||||
return this.shadowRoot!.querySelector("hui-card-preview")!;
|
||||
}
|
||||
|
||||
public cardConfig?: LovelaceCardConfig;
|
||||
public viewId?: string | number;
|
||||
protected hass?: HomeAssistant;
|
||||
private _cardId?: string;
|
||||
private _configElement?: LovelaceCardEditor | null;
|
||||
private _uiEditor?: boolean;
|
||||
private _configValue?: ConfigValue;
|
||||
private _configState?: string;
|
||||
private _loading?: boolean;
|
||||
private _saving: boolean;
|
||||
private _errorMsg?: TemplateResult;
|
||||
private _cardType?: string;
|
||||
|
||||
protected constructor() {
|
||||
super();
|
||||
this._saving = false;
|
||||
}
|
||||
|
||||
public async showDialog(): Promise<void> {
|
||||
// Wait till dialog is rendered.
|
||||
if (this._dialog == null) {
|
||||
await this.updateComplete;
|
||||
}
|
||||
this._dialog.open();
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
super.updated(changedProperties);
|
||||
if (
|
||||
!changedProperties.has("cardConfig") &&
|
||||
!changedProperties.has("viewId")
|
||||
) {
|
||||
|
||||
if (!changedProperties.has("cardConfig")) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -122,17 +103,8 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
|
||||
this._errorMsg = undefined;
|
||||
this._configElement = undefined;
|
||||
|
||||
if (this.cardConfig && String(this.cardConfig.id) !== this._cardId) {
|
||||
this._loading = true;
|
||||
this._cardId = String(this.cardConfig.id);
|
||||
this._loadConfigElement(this.cardConfig);
|
||||
} else {
|
||||
this._cardId = undefined;
|
||||
}
|
||||
|
||||
if (this.viewId && !this.cardConfig) {
|
||||
this._resizeDialog();
|
||||
}
|
||||
this._loading = true;
|
||||
this._loadConfigElement(this.cardConfig!);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
@@ -147,7 +119,6 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
|
||||
content = html`
|
||||
<hui-yaml-editor
|
||||
.hass="${this.hass}"
|
||||
.cardId="${this._cardId}"
|
||||
.yaml="${this._configValue!.value}"
|
||||
@yaml-changed="${this._handleYamlChanged}"
|
||||
></hui-yaml-editor>
|
||||
@@ -157,18 +128,15 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
|
||||
<hr />
|
||||
<hui-card-preview .hass="${this.hass}"> </hui-card-preview>
|
||||
`;
|
||||
} else if (this.viewId && !this.cardConfig) {
|
||||
content = html`
|
||||
<hui-card-picker
|
||||
.hass="${this.hass}"
|
||||
@card-picked="${this._handleCardPicked}"
|
||||
></hui-card-picker>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
<paper-dialog with-backdrop>
|
||||
<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}"
|
||||
@@ -192,6 +160,7 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
|
||||
? html`
|
||||
<div class="paper-dialog-buttons">
|
||||
<paper-button
|
||||
class="toggle-button"
|
||||
?hidden="${!this._configValue || !this._configValue.value}"
|
||||
?disabled="${
|
||||
this._configElement === null || this._configState !== "OK"
|
||||
@@ -203,7 +172,7 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
|
||||
)
|
||||
}</paper-button
|
||||
>
|
||||
<paper-button @click="${this._closeDialog}"
|
||||
<paper-button @click="${this.closeDialog}"
|
||||
>${this.localize("ui.common.cancel")}</paper-button
|
||||
>
|
||||
<paper-button
|
||||
@@ -268,19 +237,13 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
|
||||
margin-bottom: 4px;
|
||||
display: block;
|
||||
}
|
||||
.toggle-button {
|
||||
margin-right: auto;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
}
|
||||
|
||||
private _save(): void {
|
||||
this._saving = true;
|
||||
this._updateConfigInBackend();
|
||||
}
|
||||
|
||||
private _saveDone(): void {
|
||||
this._saving = false;
|
||||
}
|
||||
|
||||
private async _loadedDialog(): Promise<void> {
|
||||
await this.updateComplete;
|
||||
this._loading = false;
|
||||
@@ -292,58 +255,42 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
|
||||
fireEvent(this._dialog, "iron-resize");
|
||||
}
|
||||
|
||||
private _closeDialog(): void {
|
||||
this.cardConfig = undefined;
|
||||
this.viewId = undefined;
|
||||
fireEvent(this, "cancel-edit-card");
|
||||
this._dialog.close();
|
||||
}
|
||||
|
||||
private async _updateConfigInBackend(): Promise<void> {
|
||||
private async _save(): Promise<void> {
|
||||
if (!this._isConfigValid()) {
|
||||
alert("Your config is not valid, please fix your config before saving.");
|
||||
this._saveDone();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._isConfigChanged()) {
|
||||
this._closeDialog();
|
||||
this._saveDone();
|
||||
this.closeDialog!();
|
||||
return;
|
||||
}
|
||||
|
||||
this._saving = true;
|
||||
|
||||
const cardConf: LovelaceCardConfig =
|
||||
this._configValue!.format === "yaml"
|
||||
? yaml.safeLoad(this._configValue!.value!, {
|
||||
schema: extYamlSchema,
|
||||
})
|
||||
: this._configValue!.value!;
|
||||
|
||||
try {
|
||||
if (this.viewId) {
|
||||
await addCard(
|
||||
this.hass!,
|
||||
String(this.viewId),
|
||||
this._configValue!.value!,
|
||||
this._configValue!.format
|
||||
);
|
||||
} else {
|
||||
await updateCardConfig(
|
||||
this.hass!,
|
||||
this._cardId!,
|
||||
this._configValue!.value!,
|
||||
this._configValue!.format
|
||||
);
|
||||
}
|
||||
fireEvent(this, "reload-lovelace");
|
||||
this._closeDialog();
|
||||
this._saveDone();
|
||||
const lovelace = this.lovelace!;
|
||||
await lovelace.saveConfig(
|
||||
this._creatingCard
|
||||
? addCard(lovelace.config, this.path as [number], cardConf)
|
||||
: replaceCard(
|
||||
lovelace.config,
|
||||
this.path as [number, number],
|
||||
cardConf
|
||||
)
|
||||
);
|
||||
this.closeDialog!();
|
||||
} catch (err) {
|
||||
alert(`Saving failed: ${err.message}`);
|
||||
this._saveDone();
|
||||
}
|
||||
}
|
||||
|
||||
private async _handleCardPicked(ev: CardPickedEvent): Promise<void> {
|
||||
const succes = await this._loadConfigElement(ev.detail.config);
|
||||
if (!succes) {
|
||||
this._configValue = {
|
||||
format: "yaml",
|
||||
value: yaml.safeDump(ev.detail.config),
|
||||
};
|
||||
} finally {
|
||||
this._saving = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,14 +341,10 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
|
||||
|
||||
private async _toggleEditor(): Promise<void> {
|
||||
if (this._uiEditor && this._configValue!.format === "json") {
|
||||
if (this._isConfigChanged()) {
|
||||
this._configValue = {
|
||||
format: "yaml",
|
||||
value: yaml.safeDump(this._configValue!.value),
|
||||
};
|
||||
} else {
|
||||
this._configValue = { format: "yaml", value: undefined };
|
||||
}
|
||||
this._configValue = {
|
||||
format: "yaml",
|
||||
value: yaml.safeDump(this._configValue!.value),
|
||||
};
|
||||
this._uiEditor = !this._uiEditor;
|
||||
} else if (this._configElement && this._configValue!.format === "yaml") {
|
||||
const yamlConfig = this._configValue!.value;
|
||||
@@ -438,12 +381,12 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
|
||||
}
|
||||
|
||||
private _isConfigChanged(): boolean {
|
||||
if (this.viewId) {
|
||||
if (this._creatingCard) {
|
||||
return true;
|
||||
}
|
||||
const configValue =
|
||||
this._configValue!.format === "yaml"
|
||||
? yaml.safeDump(this._configValue!.value)
|
||||
? yaml.safeLoad(this._configValue!.value)
|
||||
: this._configValue!.value;
|
||||
return JSON.stringify(configValue) !== JSON.stringify(this.cardConfig);
|
||||
}
|
||||
@@ -465,6 +408,7 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
|
||||
if (elClass && elClass.getConfigElement) {
|
||||
configElement = await elClass.getConfigElement();
|
||||
} else {
|
||||
this._configValue = { format: "yaml", value: yaml.safeDump(conf) };
|
||||
this._uiEditor = false;
|
||||
this._configElement = null;
|
||||
return false;
|
||||
@@ -477,6 +421,10 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
|
||||
Your config is not supported by the UI editor:<br /><b>${err.message}</b
|
||||
><br />Falling back to YAML editor.
|
||||
`;
|
||||
this._configValue = {
|
||||
format: "yaml",
|
||||
value: yaml.safeDump(conf),
|
||||
};
|
||||
this._uiEditor = false;
|
||||
this._configElement = null;
|
||||
return false;
|
||||
@@ -492,6 +440,16 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
|
||||
this._updatePreview(conf);
|
||||
return true;
|
||||
}
|
||||
|
||||
private get _creatingCard(): boolean {
|
||||
return this.path!.length === 1;
|
||||
}
|
||||
|
||||
private _openedChanged(ev) {
|
||||
if (!ev.detail.value) {
|
||||
this.closeDialog!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
@@ -1,42 +1,29 @@
|
||||
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
|
||||
import { TemplateResult } from "lit-html";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import "@polymer/paper-spinner/paper-spinner";
|
||||
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { getCardConfig } from "../../../data/lovelace";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
|
||||
export class HuiYAMLEditor extends LitElement {
|
||||
public cardId?: string;
|
||||
protected hass?: HomeAssistant;
|
||||
private _yaml?: string;
|
||||
private _loading?: boolean;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return { _yaml: {}, cardId: {} };
|
||||
return { _yaml: {} };
|
||||
}
|
||||
|
||||
set yaml(yaml: string) {
|
||||
if (yaml === undefined) {
|
||||
this._loading = true;
|
||||
this._loadConfig();
|
||||
return;
|
||||
} else {
|
||||
this._yaml = yaml;
|
||||
if (this._loading) {
|
||||
this._loading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
<paper-spinner
|
||||
?active="${this._loading}"
|
||||
alt="Loading"
|
||||
class="center"
|
||||
></paper-spinner>
|
||||
<paper-textarea
|
||||
max-rows="10"
|
||||
.value="${this._yaml}"
|
||||
@@ -51,31 +38,10 @@ export class HuiYAMLEditor extends LitElement {
|
||||
paper-textarea {
|
||||
--paper-input-container-shared-input-style_-_font-family: monospace;
|
||||
}
|
||||
.center {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
paper-spinner {
|
||||
display: none;
|
||||
}
|
||||
paper-spinner[active] {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _loadConfig(): Promise<void> {
|
||||
if (!this.hass || !this.cardId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._yaml = await getCardConfig(this.hass, this.cardId);
|
||||
if (this._loading) {
|
||||
this._loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private _valueChanged(ev: Event): void {
|
||||
const target = ev.target! as any;
|
||||
this._yaml = target.value;
|
@@ -1,5 +1,5 @@
|
||||
import { LovelaceCardConfig } from "../../../data/lovelace";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { Lovelace } from "../../types";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
@@ -13,17 +13,16 @@ const dialogShowEvent = "show-edit-card";
|
||||
const dialogTag = "hui-dialog-edit-card";
|
||||
|
||||
export interface EditCardDialogParams {
|
||||
cardConfig?: LovelaceCardConfig;
|
||||
viewId?: string | number;
|
||||
add: boolean;
|
||||
reloadLovelace: () => void;
|
||||
lovelace: Lovelace;
|
||||
path: [number] | [number, number];
|
||||
}
|
||||
|
||||
const registerEditCardDialog = (element: HTMLElement) =>
|
||||
fireEvent(element, "register-dialog", {
|
||||
dialogShowEvent,
|
||||
dialogTag,
|
||||
dialogImport: () => import("./hui-dialog-edit-card"),
|
||||
dialogImport: () =>
|
||||
import(/* webpackChunkName: "hui-dialog-edit-card" */ "./hui-dialog-edit-card"),
|
||||
});
|
||||
|
||||
export const showEditCardDialog = (
|
@@ -0,0 +1,35 @@
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { Lovelace } from "../../types";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"show-move-card-view": MoveCardViewDialogParams;
|
||||
}
|
||||
}
|
||||
|
||||
let registeredDialog = false;
|
||||
|
||||
export interface MoveCardViewDialogParams {
|
||||
path: [number, number];
|
||||
lovelace: Lovelace;
|
||||
}
|
||||
|
||||
const registerEditCardDialog = (element: HTMLElement) =>
|
||||
fireEvent(element, "register-dialog", {
|
||||
dialogShowEvent: "show-move-card-view",
|
||||
dialogTag: "hui-dialog-move-card-view",
|
||||
dialogImport: () =>
|
||||
import(/* webpackChunkName: "hui-dialog-move-card-view" */ "./hui-dialog-move-card-view"),
|
||||
});
|
||||
|
||||
export const showMoveCardViewDialog = (
|
||||
element: HTMLElement,
|
||||
moveCardViewDialogParams: MoveCardViewDialogParams
|
||||
) => {
|
||||
if (!registeredDialog) {
|
||||
registeredDialog = true;
|
||||
registerEditCardDialog(element);
|
||||
}
|
||||
fireEvent(element, "show-move-card-view", moveCardViewDialogParams);
|
||||
};
|
@@ -0,0 +1,195 @@
|
||||
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
|
||||
import { TemplateResult } from "lit-html";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
|
||||
import { struct } from "../../common/structs/struct";
|
||||
import { EntitiesEditorEvent, EditorTarget } from "../types";
|
||||
import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { LovelaceCardEditor } from "../../types";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { Config } from "../../cards/hui-alarm-panel-card";
|
||||
import { configElementStyle } from "./config-elements-style";
|
||||
|
||||
import "../../../../components/entity/ha-entity-picker";
|
||||
import "../../../../components/ha-icon";
|
||||
|
||||
const cardConfigStruct = struct({
|
||||
type: "string",
|
||||
entity: "string?",
|
||||
name: "string?",
|
||||
states: "array?",
|
||||
});
|
||||
|
||||
export class HuiAlarmPanelCardEditor extends hassLocalizeLitMixin(LitElement)
|
||||
implements LovelaceCardEditor {
|
||||
public hass?: HomeAssistant;
|
||||
private _config?: Config;
|
||||
|
||||
public setConfig(config: Config): void {
|
||||
config = cardConfigStruct(config);
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return { hass: {}, _config: {} };
|
||||
}
|
||||
|
||||
get _entity(): string {
|
||||
return this._config!.entity || "";
|
||||
}
|
||||
|
||||
get _name(): string {
|
||||
return this._config!.name || "";
|
||||
}
|
||||
|
||||
get _states(): string[] {
|
||||
return this._config!.states || [];
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const states = ["arm_home", "arm_away", "arm_night", "arm_custom_bypass"];
|
||||
|
||||
return html`
|
||||
${configElementStyle} ${this.renderStyle()}
|
||||
<div class="card-config">
|
||||
<div class="side-by-side">
|
||||
<paper-input
|
||||
label="Name"
|
||||
.value="${this._name}"
|
||||
.configValue="${"name"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></paper-input>
|
||||
<ha-entity-picker
|
||||
.hass="${this.hass}"
|
||||
.value="${this._entity}"
|
||||
.configValue=${"entity"}
|
||||
domain-filter="alarm_control_panel"
|
||||
@change="${this._valueChanged}"
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
</div>
|
||||
<span>Used States</span> ${
|
||||
this._states.map((state, index) => {
|
||||
return html`
|
||||
<div class="states">
|
||||
<paper-item>${state}</paper-item>
|
||||
<ha-icon
|
||||
class="deleteState"
|
||||
.value="${index}"
|
||||
icon="hass:close"
|
||||
@click=${this._stateRemoved}
|
||||
></ha-icon>
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
}
|
||||
<paper-dropdown-menu
|
||||
label="Available States"
|
||||
@value-changed="${this._stateAdded}"
|
||||
>
|
||||
<paper-listbox slot="dropdown-content">
|
||||
${
|
||||
states.map((state) => {
|
||||
return html`
|
||||
<paper-item>${state}</paper-item>
|
||||
`;
|
||||
})
|
||||
}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderStyle(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
.states {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.deleteState {
|
||||
visibility: hidden;
|
||||
}
|
||||
.states:hover > .deleteState {
|
||||
visibility: visible;
|
||||
}
|
||||
ha-icon {
|
||||
padding-top: 12px;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
}
|
||||
|
||||
private _stateRemoved(ev: EntitiesEditorEvent): void {
|
||||
if (!this._config || !this._states || !this.hass) {
|
||||
return;
|
||||
}
|
||||
|
||||
const target = ev.target! as EditorTarget;
|
||||
const index = Number(target.value);
|
||||
if (index > -1) {
|
||||
const newStates = this._states;
|
||||
newStates.splice(index, 1);
|
||||
this._config = {
|
||||
...this._config,
|
||||
states: newStates,
|
||||
};
|
||||
fireEvent(this, "config-changed", { config: this._config });
|
||||
}
|
||||
}
|
||||
|
||||
private _stateAdded(ev: EntitiesEditorEvent): void {
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
const target = ev.target! as EditorTarget;
|
||||
if (!target.value || this._states.indexOf(target.value) >= 0) {
|
||||
return;
|
||||
}
|
||||
const newStates = this._states;
|
||||
newStates.push(target.value);
|
||||
this._config = {
|
||||
...this._config,
|
||||
states: newStates,
|
||||
};
|
||||
target.value = "";
|
||||
fireEvent(this, "config-changed", { config: this._config });
|
||||
}
|
||||
|
||||
private _valueChanged(ev: EntitiesEditorEvent): void {
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
const target = ev.target! as EditorTarget;
|
||||
if (this[`_${target.configValue}`] === target.value) {
|
||||
return;
|
||||
}
|
||||
if (target.configValue) {
|
||||
if (target.value === "") {
|
||||
delete this._config[target.configValue!];
|
||||
} else {
|
||||
this._config = {
|
||||
...this._config,
|
||||
[target.configValue!]: target.value,
|
||||
};
|
||||
}
|
||||
}
|
||||
fireEvent(this, "config-changed", { config: this._config });
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-alarm-panel-card-editor": HuiAlarmPanelCardEditor;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-alarm-panel-card-editor", HuiAlarmPanelCardEditor);
|
@@ -1,13 +1,12 @@
|
||||
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
|
||||
import { TemplateResult } from "lit-html";
|
||||
import { struct } from "../../common/structs/struct";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import "@polymer/paper-toggle-button/paper-toggle-button";
|
||||
|
||||
import { processEditorEntities } from "../process-editor-entities";
|
||||
|
||||
import { struct } from "../../common/structs/struct";
|
||||
import { EntitiesEditorEvent, EditorTarget } from "../types";
|
||||
import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
@@ -33,7 +32,6 @@ const entitiesConfigStruct = struct.union([
|
||||
|
||||
const cardConfigStruct = struct({
|
||||
type: "string",
|
||||
id: "string|number",
|
||||
title: "string|number?",
|
||||
theme: "string?",
|
||||
show_header_toggle: "boolean?",
|
||||
@@ -60,8 +58,7 @@ export class HuiEntitiesCardEditor extends hassLocalizeLitMixin(LitElement)
|
||||
|
||||
public setConfig(config: Config): void {
|
||||
config = cardConfigStruct(config);
|
||||
|
||||
this._config = { type: "entities", ...config };
|
||||
this._config = config;
|
||||
this._configEntities = processEditorEntities(config.entities);
|
||||
}
|
||||
|
||||
@@ -75,7 +72,7 @@ export class HuiEntitiesCardEditor extends hassLocalizeLitMixin(LitElement)
|
||||
<div class="card-config">
|
||||
<paper-input
|
||||
label="Title"
|
||||
value="${this._title}"
|
||||
.value="${this._title}"
|
||||
.configValue="${"title"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></paper-input>
|
||||
@@ -118,11 +115,15 @@ export class HuiEntitiesCardEditor extends hassLocalizeLitMixin(LitElement)
|
||||
this._config.entities = ev.detail.entities;
|
||||
this._configEntities = processEditorEntities(this._config.entities);
|
||||
} else if (target.configValue) {
|
||||
this._config = {
|
||||
...this._config,
|
||||
[target.configValue]:
|
||||
target.checked !== undefined ? target.checked : target.value,
|
||||
};
|
||||
if (target.value === "") {
|
||||
delete this._config[target.configValue!];
|
||||
} else {
|
||||
this._config = {
|
||||
...this._config,
|
||||
[target.configValue]:
|
||||
target.checked !== undefined ? target.checked : target.value,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fireEvent(this, "config-changed", { config: this._config });
|
||||
|
@@ -0,0 +1,165 @@
|
||||
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
|
||||
import { TemplateResult } from "lit-html";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
|
||||
import { struct } from "../../common/structs/struct";
|
||||
import {
|
||||
EntitiesEditorEvent,
|
||||
EditorTarget,
|
||||
actionConfigStruct,
|
||||
} from "../types";
|
||||
import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { LovelaceCardEditor } from "../../types";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { Config } from "../../cards/hui-entity-button-card";
|
||||
import { configElementStyle } from "./config-elements-style";
|
||||
import { ActionConfig } from "../../../../data/lovelace";
|
||||
|
||||
import "../../components/hui-action-editor";
|
||||
import "../../components/hui-theme-select-editor";
|
||||
import "../../components/hui-entity-editor";
|
||||
|
||||
const cardConfigStruct = struct({
|
||||
type: "string",
|
||||
entity: "string?",
|
||||
name: "string?",
|
||||
icon: "string?",
|
||||
tap_action: actionConfigStruct,
|
||||
hold_action: actionConfigStruct,
|
||||
theme: "string?",
|
||||
});
|
||||
|
||||
export class HuiEntityButtonCardEditor extends hassLocalizeLitMixin(LitElement)
|
||||
implements LovelaceCardEditor {
|
||||
public hass?: HomeAssistant;
|
||||
private _config?: Config;
|
||||
|
||||
public setConfig(config: Config): void {
|
||||
config = cardConfigStruct(config);
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return { hass: {}, _config: {} };
|
||||
}
|
||||
|
||||
get _entity(): string {
|
||||
return this._config!.entity || "";
|
||||
}
|
||||
|
||||
get _name(): string {
|
||||
return this._config!.name || "";
|
||||
}
|
||||
|
||||
get _icon(): string {
|
||||
return this._config!.icon || "";
|
||||
}
|
||||
|
||||
get _tap_action(): ActionConfig {
|
||||
return this._config!.tap_action || { action: "more-info" };
|
||||
}
|
||||
|
||||
get _hold_action(): ActionConfig {
|
||||
return this._config!.hold_action || { action: "none" };
|
||||
}
|
||||
|
||||
get _theme(): string {
|
||||
return this._config!.theme || "default";
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const actions = ["more-info", "toggle", "navigate", "call-service", "none"];
|
||||
|
||||
return html`
|
||||
${configElementStyle}
|
||||
<div class="card-config">
|
||||
<ha-entity-picker
|
||||
.hass="${this.hass}"
|
||||
.value="${this._entity}"
|
||||
.configValue=${"entity"}
|
||||
@change="${this._valueChanged}"
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
<div class="side-by-side">
|
||||
<paper-input
|
||||
label="Name (Optional)"
|
||||
.value="${this._name}"
|
||||
.configValue="${"name"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></paper-input>
|
||||
<paper-input
|
||||
label="Icon (Optional)"
|
||||
.value="${this._icon}"
|
||||
.configValue="${"icon"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></paper-input>
|
||||
</div>
|
||||
<hui-theme-select-editor
|
||||
.hass="${this.hass}"
|
||||
.value="${this._theme}"
|
||||
.configValue="${"theme"}"
|
||||
@theme-changed="${this._valueChanged}"
|
||||
></hui-theme-select-editor>
|
||||
<div class="side-by-side">
|
||||
<hui-action-editor
|
||||
label="Tap Action"
|
||||
.hass="${this.hass}"
|
||||
.config="${this._tap_action}"
|
||||
.actions="${actions}"
|
||||
.configValue="${"tap_action"}"
|
||||
@action-changed="${this._valueChanged}"
|
||||
></hui-action-editor>
|
||||
<hui-action-editor
|
||||
label="Hold Action"
|
||||
.hass="${this.hass}"
|
||||
.config="${this._hold_action}"
|
||||
.actions="${actions}"
|
||||
.configValue="${"hold_action"}"
|
||||
@action-changed="${this._valueChanged}"
|
||||
></hui-action-editor>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: EntitiesEditorEvent): void {
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
const target = ev.target! as EditorTarget;
|
||||
|
||||
if (
|
||||
this[`_${target.configValue}`] === target.value ||
|
||||
this[`_${target.configValue}`] === target.config
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (target.configValue) {
|
||||
if (target.value === "") {
|
||||
delete this._config[target.configValue!];
|
||||
} else {
|
||||
this._config = {
|
||||
...this._config,
|
||||
[target.configValue!]: target.value ? target.value : target.config,
|
||||
};
|
||||
}
|
||||
}
|
||||
fireEvent(this, "config-changed", { config: this._config });
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-entity-button-card-editor": HuiEntityButtonCardEditor;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(
|
||||
"hui-entity-button-card-editor",
|
||||
HuiEntityButtonCardEditor
|
||||
);
|
@@ -0,0 +1,244 @@
|
||||
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
|
||||
import { TemplateResult } from "lit-html";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-toggle-button/paper-toggle-button";
|
||||
|
||||
import { struct } from "../../common/structs/struct";
|
||||
import { EntitiesEditorEvent, EditorTarget } from "../types";
|
||||
import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { LovelaceCardEditor } from "../../types";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { Config, SeverityConfig } from "../../cards/hui-gauge-card";
|
||||
import { configElementStyle } from "./config-elements-style";
|
||||
|
||||
import "../../components/hui-theme-select-editor";
|
||||
import "../../components/hui-entity-editor";
|
||||
|
||||
const cardConfigStruct = struct({
|
||||
type: "string",
|
||||
name: "string?",
|
||||
entity: "string?",
|
||||
unit: "string?",
|
||||
min: "number?",
|
||||
max: "number?",
|
||||
severity: "object?",
|
||||
theme: "string?",
|
||||
});
|
||||
|
||||
export class HuiGaugeCardEditor extends hassLocalizeLitMixin(LitElement)
|
||||
implements LovelaceCardEditor {
|
||||
public hass?: HomeAssistant;
|
||||
private _config?: Config;
|
||||
private _useSeverity?: boolean;
|
||||
|
||||
public setConfig(config: Config): void {
|
||||
config = cardConfigStruct(config);
|
||||
this._useSeverity = config.severity ? true : false;
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return { hass: {}, _config: {} };
|
||||
}
|
||||
|
||||
get _name(): string {
|
||||
return this._config!.name || "";
|
||||
}
|
||||
|
||||
get _entity(): string {
|
||||
return this._config!.entity || "";
|
||||
}
|
||||
|
||||
get _unit(): string {
|
||||
return this._config!.unit || "";
|
||||
}
|
||||
|
||||
get _theme(): string {
|
||||
return this._config!.theme || "default";
|
||||
}
|
||||
|
||||
get _min(): number {
|
||||
return this._config!.number || 0;
|
||||
}
|
||||
|
||||
get _max(): number {
|
||||
return this._config!.max || 100;
|
||||
}
|
||||
|
||||
get _severity(): SeverityConfig | undefined {
|
||||
return this._config!.severity || undefined;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
${configElementStyle} ${this.renderStyle()}
|
||||
<div class="card-config">
|
||||
<div class="side-by-side">
|
||||
<paper-input
|
||||
label="Name"
|
||||
.value="${this._name}"
|
||||
.configValue=${"name"}
|
||||
@value-changed="${this._valueChanged}"
|
||||
></paper-input>
|
||||
<ha-entity-picker
|
||||
.hass="${this.hass}"
|
||||
.value="${this._entity}"
|
||||
.configValue=${"entity"}
|
||||
domain-filter="sensor"
|
||||
@change="${this._valueChanged}"
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
</div>
|
||||
<div class="side-by-side">
|
||||
<paper-input
|
||||
label="Unit"
|
||||
.value="${this._unit}"
|
||||
.configValue=${"unit"}
|
||||
@value-changed="${this._valueChanged}"
|
||||
></paper-input>
|
||||
<hui-theme-select-editor
|
||||
.hass="${this.hass}"
|
||||
.value="${this._theme}"
|
||||
.configValue="${"theme"}"
|
||||
@theme-changed="${this._valueChanged}"
|
||||
></hui-theme-select-editor>
|
||||
</div>
|
||||
<div class="side-by-side">
|
||||
<paper-input
|
||||
type="number"
|
||||
label="Minimum"
|
||||
.value="${this._min}"
|
||||
.configValue=${"min"}
|
||||
@value-changed="${this._valueChanged}"
|
||||
></paper-input>
|
||||
<paper-input
|
||||
type="number"
|
||||
label="Maximum"
|
||||
.value="${this._max}"
|
||||
.configValue=${"max"}
|
||||
@value-changed="${this._valueChanged}"
|
||||
></paper-input>
|
||||
</div>
|
||||
<div class="side-by-side">
|
||||
<paper-toggle-button
|
||||
?checked="${this._useSeverity !== false}"
|
||||
@change="${this._toggleSeverity}"
|
||||
>Define Severity?</paper-toggle-button
|
||||
>
|
||||
<div class="severity">
|
||||
<paper-input
|
||||
type="number"
|
||||
label="Green"
|
||||
.value="${this._severity ? this._severity.green : 0}"
|
||||
.configValue=${"green"}
|
||||
@value-changed="${this._severityChanged}"
|
||||
></paper-input>
|
||||
<paper-input
|
||||
type="number"
|
||||
label="Yellow"
|
||||
.value="${this._severity ? this._severity.yellow : 0}"
|
||||
.configValue=${"yellow"}
|
||||
@value-changed="${this._severityChanged}"
|
||||
></paper-input>
|
||||
<paper-input
|
||||
type="number"
|
||||
label="Red"
|
||||
.value="${this._severity ? this._severity.red : 0}"
|
||||
.configValue=${"red"}
|
||||
@value-changed="${this._severityChanged}"
|
||||
></paper-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderStyle(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
.severity {
|
||||
display: none;
|
||||
width: 100%;
|
||||
padding-left: 16px;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.severity > * {
|
||||
flex: 1 0 30%;
|
||||
padding-right: 4px;
|
||||
}
|
||||
paper-toggle-button[checked] ~ .severity {
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
}
|
||||
|
||||
private _toggleSeverity(ev: EntitiesEditorEvent): void {
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
const target = ev.target! as EditorTarget;
|
||||
|
||||
this._config.severity = target.checked
|
||||
? {
|
||||
green: 0,
|
||||
yellow: 0,
|
||||
red: 0,
|
||||
}
|
||||
: undefined;
|
||||
fireEvent(this, "config-changed", { config: this._config });
|
||||
}
|
||||
|
||||
private _severityChanged(ev: EntitiesEditorEvent): void {
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
const target = ev.target! as EditorTarget;
|
||||
const severity = {
|
||||
...this._config.severity,
|
||||
[target.configValue!]: Number(target.value),
|
||||
};
|
||||
this._config = {
|
||||
...this._config,
|
||||
severity,
|
||||
};
|
||||
fireEvent(this, "config-changed", { config: this._config });
|
||||
}
|
||||
|
||||
private _valueChanged(ev: EntitiesEditorEvent): void {
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
const target = ev.target! as EditorTarget;
|
||||
|
||||
if (target.configValue) {
|
||||
if (
|
||||
target.value === "" ||
|
||||
(target.type === "number" && isNaN(Number(target.value)))
|
||||
) {
|
||||
delete this._config[target.configValue!];
|
||||
} else {
|
||||
let value: any = target.value;
|
||||
if (target.type === "number") {
|
||||
value = Number(value);
|
||||
}
|
||||
this._config = { ...this._config, [target.configValue!]: value };
|
||||
}
|
||||
}
|
||||
fireEvent(this, "config-changed", { config: this._config });
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-gauge-card-editor": HuiGaugeCardEditor;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-gauge-card-editor", HuiGaugeCardEditor);
|
@@ -1,11 +1,11 @@
|
||||
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
|
||||
import { TemplateResult } from "lit-html";
|
||||
import { struct } from "../../common/structs/struct";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import "@polymer/paper-toggle-button/paper-toggle-button";
|
||||
|
||||
import { struct } from "../../common/structs/struct";
|
||||
import { processEditorEntities } from "../process-editor-entities";
|
||||
import { EntitiesEditorEvent, EditorTarget } from "../types";
|
||||
import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin";
|
||||
@@ -32,7 +32,6 @@ const entitiesConfigStruct = struct.union([
|
||||
|
||||
const cardConfigStruct = struct({
|
||||
type: "string",
|
||||
id: "string|number",
|
||||
title: "string|number?",
|
||||
theme: "string?",
|
||||
columns: "number?",
|
||||
@@ -49,8 +48,7 @@ export class HuiGlanceCardEditor extends hassLocalizeLitMixin(LitElement)
|
||||
|
||||
public setConfig(config: Config): void {
|
||||
config = cardConfigStruct(config);
|
||||
|
||||
this._config = { type: "glance", ...config };
|
||||
this._config = config;
|
||||
this._configEntities = processEditorEntities(config.entities);
|
||||
}
|
||||
|
||||
@@ -66,8 +64,8 @@ export class HuiGlanceCardEditor extends hassLocalizeLitMixin(LitElement)
|
||||
return this._config!.theme || "Backend-selected";
|
||||
}
|
||||
|
||||
get _columns(): string {
|
||||
return this._config!.columns ? String(this._config!.columns) : "";
|
||||
get _columns(): number {
|
||||
return this._config!.columns || NaN;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
@@ -80,7 +78,7 @@ export class HuiGlanceCardEditor extends hassLocalizeLitMixin(LitElement)
|
||||
<div class="card-config">
|
||||
<paper-input
|
||||
label="Title"
|
||||
value="${this._title}"
|
||||
.value="${this._title}"
|
||||
.configValue="${"title"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></paper-input>
|
||||
@@ -93,7 +91,8 @@ export class HuiGlanceCardEditor extends hassLocalizeLitMixin(LitElement)
|
||||
></hui-theme-select-editor>
|
||||
<paper-input
|
||||
label="Columns"
|
||||
value="${this._columns}"
|
||||
type="number"
|
||||
.value="${this._columns}"
|
||||
.configValue="${"columns"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></paper-input>
|
||||
@@ -127,23 +126,29 @@ export class HuiGlanceCardEditor extends hassLocalizeLitMixin(LitElement)
|
||||
}
|
||||
const target = ev.target! as EditorTarget;
|
||||
|
||||
if (
|
||||
(target.configValue! === "title" && target.value === this._title) ||
|
||||
(target.configValue! === "theme" && target.value === this._theme) ||
|
||||
(target.configValue! === "columns" && target.value === this._columns)
|
||||
) {
|
||||
if (target.configValue && this[`_${target.configValue}`] === target.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ev.detail && ev.detail.entities) {
|
||||
this._config.entities = ev.detail.entities;
|
||||
this._configEntities = processEditorEntities(this._config.entities);
|
||||
} else if (target.configValue) {
|
||||
this._config = {
|
||||
...this._config,
|
||||
[target.configValue!]:
|
||||
target.checked !== undefined ? target.checked : target.value,
|
||||
};
|
||||
if (
|
||||
target.value === "" ||
|
||||
(target.type === "number" && isNaN(Number(target.value)))
|
||||
) {
|
||||
delete this._config[target.configValue!];
|
||||
} else {
|
||||
let value: any = target.value;
|
||||
if (target.type === "number") {
|
||||
value = Number(value);
|
||||
}
|
||||
this._config = {
|
||||
...this._config,
|
||||
[target.configValue!]:
|
||||
target.checked !== undefined ? target.checked : value,
|
||||
};
|
||||
}
|
||||
}
|
||||
fireEvent(this, "config-changed", { config: this._config });
|
||||
}
|
||||
|
@@ -0,0 +1,111 @@
|
||||
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
|
||||
import { TemplateResult } from "lit-html";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
|
||||
import { struct } from "../../common/structs/struct";
|
||||
import { EntitiesEditorEvent, EditorTarget } from "../types";
|
||||
import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { LovelaceCardEditor } from "../../types";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { Config } from "../../cards/hui-iframe-card";
|
||||
import { configElementStyle } from "./config-elements-style";
|
||||
|
||||
const cardConfigStruct = struct({
|
||||
type: "string",
|
||||
title: "string?",
|
||||
url: "string?",
|
||||
aspect_ratio: "string?",
|
||||
});
|
||||
|
||||
export class HuiIframeCardEditor extends hassLocalizeLitMixin(LitElement)
|
||||
implements LovelaceCardEditor {
|
||||
public hass?: HomeAssistant;
|
||||
private _config?: Config;
|
||||
|
||||
public setConfig(config: Config): void {
|
||||
config = cardConfigStruct(config);
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return { hass: {}, _config: {} };
|
||||
}
|
||||
|
||||
get _title(): string {
|
||||
return this._config!.title || "";
|
||||
}
|
||||
|
||||
get _url(): string {
|
||||
return this._config!.url || "";
|
||||
}
|
||||
|
||||
get _aspect_ratio(): string {
|
||||
return this._config!.aspect_ratio || "";
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
${configElementStyle}
|
||||
<div class="card-config">
|
||||
<div class="side-by-side">
|
||||
<paper-input
|
||||
label="Title"
|
||||
.value="${this._title}"
|
||||
.configValue="${"title"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></paper-input>
|
||||
<paper-input
|
||||
label="Aspect Ratio"
|
||||
type="number"
|
||||
.value="${Number(this._aspect_ratio.replace("%", ""))}"
|
||||
.configValue="${"aspect_ratio"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></paper-input>
|
||||
</div>
|
||||
<paper-input
|
||||
label="Url"
|
||||
.value="${this._url}"
|
||||
.configValue="${"url"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></paper-input>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: EntitiesEditorEvent): void {
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
const target = ev.target! as EditorTarget;
|
||||
let value = target.value;
|
||||
|
||||
if (target.configValue! === "aspect_ratio" && target.value) {
|
||||
value += "%";
|
||||
}
|
||||
|
||||
if (this[`_${target.configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
if (target.configValue) {
|
||||
if (target.value === "") {
|
||||
delete this._config[target.configValue!];
|
||||
} else {
|
||||
this._config = { ...this._config, [target.configValue!]: value };
|
||||
}
|
||||
}
|
||||
fireEvent(this, "config-changed", { config: this._config });
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-iframe-card-editor": HuiIframeCardEditor;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-iframe-card-editor", HuiIframeCardEditor);
|
@@ -0,0 +1,113 @@
|
||||
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
|
||||
import { TemplateResult } from "lit-html";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
|
||||
import { struct } from "../../common/structs/struct";
|
||||
import { EntitiesEditorEvent, EditorTarget } from "../types";
|
||||
import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { LovelaceCardEditor } from "../../types";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { Config } from "../../cards/hui-light-card";
|
||||
import { configElementStyle } from "./config-elements-style";
|
||||
|
||||
import "../../components/hui-theme-select-editor";
|
||||
import "../../components/hui-entity-editor";
|
||||
|
||||
const cardConfigStruct = struct({
|
||||
type: "string",
|
||||
name: "string?",
|
||||
entity: "string?",
|
||||
theme: "string?",
|
||||
});
|
||||
|
||||
export class HuiLightCardEditor extends hassLocalizeLitMixin(LitElement)
|
||||
implements LovelaceCardEditor {
|
||||
public hass?: HomeAssistant;
|
||||
private _config?: Config;
|
||||
|
||||
public setConfig(config: Config): void {
|
||||
config = cardConfigStruct(config);
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return { hass: {}, _config: {}, _configEntities: {} };
|
||||
}
|
||||
|
||||
get _name(): string {
|
||||
return this._config!.name || "";
|
||||
}
|
||||
|
||||
get _theme(): string {
|
||||
return this._config!.theme || "default";
|
||||
}
|
||||
|
||||
get _entity(): string {
|
||||
return this._config!.entity || "";
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
${configElementStyle}
|
||||
<div class="card-config">
|
||||
<paper-input
|
||||
label="Name"
|
||||
.value="${this._name}"
|
||||
.configValue="${"name"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></paper-input>
|
||||
<div class="side-by-side">
|
||||
<ha-entity-picker
|
||||
.hass="${this.hass}"
|
||||
.value="${this._entity}"
|
||||
.configValue=${"entity"}
|
||||
domain-filter="light"
|
||||
@change="${this._valueChanged}"
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
<hui-theme-select-editor
|
||||
.hass="${this.hass}"
|
||||
.value="${this._theme}"
|
||||
.configValue="${"theme"}"
|
||||
@theme-changed="${this._valueChanged}"
|
||||
></hui-theme-select-editor>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: EntitiesEditorEvent): void {
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
const target = ev.target! as EditorTarget;
|
||||
|
||||
if (this[`_${target.configValue}`] === target.value) {
|
||||
return;
|
||||
}
|
||||
if (target.configValue) {
|
||||
if (target.value === "") {
|
||||
delete this._config[target.configValue!];
|
||||
} else {
|
||||
this._config = {
|
||||
...this._config,
|
||||
[target.configValue!]: target.value,
|
||||
};
|
||||
}
|
||||
}
|
||||
fireEvent(this, "config-changed", { config: this._config });
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-light-card-editor": HuiLightCardEditor;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-light-card-editor", HuiLightCardEditor);
|
@@ -0,0 +1,143 @@
|
||||
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
|
||||
import { TemplateResult } from "lit-html";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
|
||||
import { struct } from "../../common/structs/struct";
|
||||
import { EntitiesEditorEvent, EditorTarget } from "../types";
|
||||
import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { LovelaceCardEditor } from "../../types";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { Config } from "../../cards/hui-alarm-panel-card";
|
||||
import { configElementStyle } from "./config-elements-style";
|
||||
import { processEditorEntities } from "../process-editor-entities";
|
||||
import { EntityConfig } from "../../entity-rows/types";
|
||||
|
||||
import "../../components/hui-entity-editor";
|
||||
|
||||
const entitiesConfigStruct = struct.union([
|
||||
{
|
||||
entity: "entity-id",
|
||||
name: "string?",
|
||||
icon: "icon?",
|
||||
},
|
||||
"entity-id",
|
||||
]);
|
||||
|
||||
const cardConfigStruct = struct({
|
||||
type: "string",
|
||||
title: "string?",
|
||||
aspect_ratio: "string?",
|
||||
default_zoom: "number?",
|
||||
entities: [entitiesConfigStruct],
|
||||
});
|
||||
|
||||
export class HuiMapCardEditor extends hassLocalizeLitMixin(LitElement)
|
||||
implements LovelaceCardEditor {
|
||||
public hass?: HomeAssistant;
|
||||
private _config?: Config;
|
||||
private _configEntities?: EntityConfig[];
|
||||
|
||||
public setConfig(config: Config): void {
|
||||
config = cardConfigStruct(config);
|
||||
this._config = config;
|
||||
this._configEntities = processEditorEntities(config.entities);
|
||||
}
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return { hass: {}, _config: {}, _configEntities: {} };
|
||||
}
|
||||
|
||||
get _title(): string {
|
||||
return this._config!.title || "";
|
||||
}
|
||||
|
||||
get _aspect_ratio(): string {
|
||||
return this._config!.aspect_ratio || "";
|
||||
}
|
||||
|
||||
get _default_zoom(): number {
|
||||
return this._config!.default_zoom || NaN;
|
||||
}
|
||||
|
||||
get _entities(): string[] {
|
||||
return this._config!.entities || [];
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
${configElementStyle}
|
||||
<div class="card-config">
|
||||
<paper-input
|
||||
label="Title"
|
||||
.value="${this._title}"
|
||||
.configValue="${"title"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></paper-input>
|
||||
<div class="side-by-side">
|
||||
<paper-input
|
||||
label="Aspect Ratio"
|
||||
.value="${this._aspect_ratio}"
|
||||
.configValue="${"aspect_ratio"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></paper-input>
|
||||
<paper-input
|
||||
label="Default Zoom"
|
||||
type="number"
|
||||
.value="${this._default_zoom}"
|
||||
.configValue="${"default_zoom"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
></paper-input>
|
||||
</div>
|
||||
<hui-entity-editor
|
||||
.hass="${this.hass}"
|
||||
.entities="${this._configEntities}"
|
||||
@entities-changed="${this._valueChanged}"
|
||||
></hui-entity-editor>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: EntitiesEditorEvent): void {
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
const target = ev.target! as EditorTarget;
|
||||
if (target.configValue && this[`_${target.configValue}`] === target.value) {
|
||||
return;
|
||||
}
|
||||
if (ev.detail && ev.detail.entities) {
|
||||
this._config.entities = ev.detail.entities;
|
||||
this._configEntities = processEditorEntities(this._config.entities);
|
||||
} else if (target.configValue) {
|
||||
if (
|
||||
target.value === "" ||
|
||||
(target.type === "number" && isNaN(Number(target.value)))
|
||||
) {
|
||||
delete this._config[target.configValue!];
|
||||
} else {
|
||||
let value: any = target.value;
|
||||
if (target.type === "number") {
|
||||
value = Number(value);
|
||||
}
|
||||
this._config = {
|
||||
...this._config,
|
||||
[target.configValue!]: value,
|
||||
};
|
||||
}
|
||||
}
|
||||
fireEvent(this, "config-changed", { config: this._config });
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-map-card-editor": HuiMapCardEditor;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-map-card-editor", HuiMapCardEditor);
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user