Compare commits

..

3 Commits

Author SHA1 Message Date
Zack Arnett c22d8ee669 fix rebasing 2021-10-23 15:13:56 -05:00
Zack Arnett 183dc3cfbe Stages 2021-10-23 14:57:53 -05:00
Zack Arnett e3bbe0e93b Add Trend to Entity and Sensor Card 2021-10-23 14:54:01 -05:00
89 changed files with 1942 additions and 2913 deletions
@@ -1,12 +0,0 @@
diff --git a/mwc-icon-button-base.js b/mwc-icon-button-base.js
index 45cdaab93ccc0a6daaaaabc01266dcdc32e46bfd..b3ea5b541597308d85f86ce6c23fd00785fda835 100644
--- a/mwc-icon-button-base.js
+++ b/mwc-icon-button-base.js
@@ -63,7 +63,6 @@ export class IconButtonBase extends LitElement {
@touchend="${this.handleRippleDeactivate}"
@touchcancel="${this.handleRippleDeactivate}"
>${this.renderRipple()}
- <i class="material-icons">${this.icon}</i>
<span
><slot></slot
></span>
+3 -2
View File
@@ -99,7 +99,6 @@ const findDifferentiator = (curString, prevString) => {
gulp.task("gen-icons-json", (done) => {
const meta = getMeta();
const metaAndRemoved = addRemovedMeta(meta);
const split = splitBySize(metaAndRemoved);
@@ -139,9 +138,11 @@ gulp.task("gen-icons-json", (done) => {
JSON.stringify({ version: package.version, parts })
);
const orderedMeta = orderMeta(meta);
fs.writeFileSync(
path.resolve(OUTPUT_DIR, "iconList.json"),
JSON.stringify(orderMeta(meta).map((icon) => icon.name))
JSON.stringify(orderedMeta.map((icon) => icon.name))
);
done();
@@ -1,91 +0,0 @@
/* eslint-disable lit/no-template-arrow */
import { LitElement, TemplateResult, html } from "lit";
import { customElement, state } from "lit/decorators";
import { provideHass } from "../../../src/fake_data/provide_hass";
import type { HomeAssistant } from "../../../src/types";
import "../components/demo-black-white-row";
import { mockEntityRegistry } from "../../../demo/src/stubs/entity_registry";
import { mockDeviceRegistry } from "../../../demo/src/stubs/device_registry";
import { mockAreaRegistry } from "../../../demo/src/stubs/area_registry";
import { mockHassioSupervisor } from "../../../demo/src/stubs/hassio_supervisor";
import "../../../src/panels/config/automation/action/ha-automation-action";
import { HaChooseAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-choose";
import { HaDelayAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-delay";
import { HaDeviceAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-device_id";
import { HaEventAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-event";
import { HaRepeatAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-repeat";
import { HaSceneAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-scene";
import { HaServiceAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-service";
import { HaWaitForTriggerAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger";
import { HaWaitAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-wait_template";
import { Action } from "../../../src/data/script";
import { HaConditionAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-condition";
const SCHEMAS: { name: string; actions: Action[] }[] = [
{ name: "Event", actions: [HaEventAction.defaultConfig] },
{ name: "Device", actions: [HaDeviceAction.defaultConfig] },
{ name: "Service", actions: [HaServiceAction.defaultConfig] },
{ name: "Condition", actions: [HaConditionAction.defaultConfig] },
{ name: "Delay", actions: [HaDelayAction.defaultConfig] },
{ name: "Scene", actions: [HaSceneAction.defaultConfig] },
{ name: "Wait", actions: [HaWaitAction.defaultConfig] },
{ name: "WaitForTrigger", actions: [HaWaitForTriggerAction.defaultConfig] },
{ name: "Repeat", actions: [HaRepeatAction.defaultConfig] },
{ name: "Choose", actions: [HaChooseAction.defaultConfig] },
{ name: "Variables", actions: [{ variables: { hello: "1" } }] },
];
@customElement("demo-automation-editor-action")
class DemoHaAutomationEditorAction extends LitElement {
@state() private hass!: HomeAssistant;
private data: any = SCHEMAS.map((info) => info.actions);
constructor() {
super();
const hass = provideHass(this);
hass.updateTranslations(null, "en");
hass.updateTranslations("config", "en");
mockEntityRegistry(hass);
mockDeviceRegistry(hass);
mockAreaRegistry(hass);
mockHassioSupervisor(hass);
}
protected render(): TemplateResult {
const valueChanged = (ev) => {
const sampleIdx = ev.target.sampleIdx;
this.data[sampleIdx] = ev.detail.value;
this.requestUpdate();
};
return html`
${SCHEMAS.map(
(info, sampleIdx) => html`
<demo-black-white-row
.title=${info.name}
.value=${this.data[sampleIdx]}
>
${["light", "dark"].map(
(slot) =>
html`
<ha-automation-action
slot=${slot}
.hass=${this.hass}
.actions=${this.data[sampleIdx]}
.sampleIdx=${sampleIdx}
@value-changed=${valueChanged}
></ha-automation-action>
`
)}
</demo-black-white-row>
`
)}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-ha-automation-editor-action": DemoHaAutomationEditorAction;
}
}
@@ -1,127 +0,0 @@
/* eslint-disable lit/no-template-arrow */
import { LitElement, TemplateResult, html } from "lit";
import { customElement, state } from "lit/decorators";
import { provideHass } from "../../../src/fake_data/provide_hass";
import type { HomeAssistant } from "../../../src/types";
import "../components/demo-black-white-row";
import { mockEntityRegistry } from "../../../demo/src/stubs/entity_registry";
import { mockDeviceRegistry } from "../../../demo/src/stubs/device_registry";
import { mockAreaRegistry } from "../../../demo/src/stubs/area_registry";
import { mockHassioSupervisor } from "../../../demo/src/stubs/hassio_supervisor";
import type { Condition } from "../../../src/data/automation";
import "../../../src/panels/config/automation/condition/ha-automation-condition";
import { HaDeviceCondition } from "../../../src/panels/config/automation/condition/types/ha-automation-condition-device";
import { HaLogicalCondition } from "../../../src/panels/config/automation/condition/types/ha-automation-condition-logical";
import HaNumericStateCondition from "../../../src/panels/config/automation/condition/types/ha-automation-condition-numeric_state";
import { HaStateCondition } from "../../../src/panels/config/automation/condition/types/ha-automation-condition-state";
import { HaSunCondition } from "../../../src/panels/config/automation/condition/types/ha-automation-condition-sun";
import { HaTemplateCondition } from "../../../src/panels/config/automation/condition/types/ha-automation-condition-template";
import { HaTimeCondition } from "../../../src/panels/config/automation/condition/types/ha-automation-condition-time";
import { HaTriggerCondition } from "../../../src/panels/config/automation/condition/types/ha-automation-condition-trigger";
import { HaZoneCondition } from "../../../src/panels/config/automation/condition/types/ha-automation-condition-zone";
const SCHEMAS: { name: string; conditions: Condition[] }[] = [
{
name: "State",
conditions: [{ condition: "state", ...HaStateCondition.defaultConfig }],
},
{
name: "Numeric State",
conditions: [
{ condition: "numeric_state", ...HaNumericStateCondition.defaultConfig },
],
},
{
name: "Sun",
conditions: [{ condition: "sun", ...HaSunCondition.defaultConfig }],
},
{
name: "Zone",
conditions: [{ condition: "zone", ...HaZoneCondition.defaultConfig }],
},
{
name: "Time",
conditions: [{ condition: "time", ...HaTimeCondition.defaultConfig }],
},
{
name: "Template",
conditions: [
{ condition: "template", ...HaTemplateCondition.defaultConfig },
],
},
{
name: "Device",
conditions: [{ condition: "device", ...HaDeviceCondition.defaultConfig }],
},
{
name: "And",
conditions: [{ condition: "and", ...HaLogicalCondition.defaultConfig }],
},
{
name: "Or",
conditions: [{ condition: "or", ...HaLogicalCondition.defaultConfig }],
},
{
name: "Not",
conditions: [{ condition: "not", ...HaLogicalCondition.defaultConfig }],
},
{
name: "Trigger",
conditions: [{ condition: "trigger", ...HaTriggerCondition.defaultConfig }],
},
];
@customElement("demo-automation-editor-condition")
class DemoHaAutomationEditorCondition extends LitElement {
@state() private hass!: HomeAssistant;
private data: any = SCHEMAS.map((info) => info.conditions);
constructor() {
super();
const hass = provideHass(this);
hass.updateTranslations(null, "en");
hass.updateTranslations("config", "en");
mockEntityRegistry(hass);
mockDeviceRegistry(hass);
mockAreaRegistry(hass);
mockHassioSupervisor(hass);
}
protected render(): TemplateResult {
const valueChanged = (ev) => {
const sampleIdx = ev.target.sampleIdx;
this.data[sampleIdx] = ev.detail.value;
this.requestUpdate();
};
return html`
${SCHEMAS.map(
(info, sampleIdx) => html`
<demo-black-white-row
.title=${info.name}
.value=${this.data[sampleIdx]}
>
${["light", "dark"].map(
(slot) =>
html`
<ha-automation-condition
slot=${slot}
.hass=${this.hass}
.conditions=${this.data[sampleIdx]}
.sampleIdx=${sampleIdx}
@value-changed=${valueChanged}
></ha-automation-condition>
`
)}
</demo-black-white-row>
`
)}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-ha-automation-editor-condition": DemoHaAutomationEditorCondition;
}
}
@@ -1,159 +0,0 @@
/* eslint-disable lit/no-template-arrow */
import { LitElement, TemplateResult, html } from "lit";
import { customElement, state } from "lit/decorators";
import { provideHass } from "../../../src/fake_data/provide_hass";
import type { HomeAssistant } from "../../../src/types";
import "../components/demo-black-white-row";
import { mockEntityRegistry } from "../../../demo/src/stubs/entity_registry";
import { mockDeviceRegistry } from "../../../demo/src/stubs/device_registry";
import { mockAreaRegistry } from "../../../demo/src/stubs/area_registry";
import { mockHassioSupervisor } from "../../../demo/src/stubs/hassio_supervisor";
import type { Trigger } from "../../../src/data/automation";
import { HaGeolocationTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-geo_location";
import { HaEventTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-event";
import { HaHassTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-homeassistant";
import { HaNumericStateTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state";
import { HaSunTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-sun";
import { HaTagTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-tag";
import { HaTemplateTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-template";
import { HaTimeTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-time";
import { HaTimePatternTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-time_pattern";
import { HaWebhookTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-webhook";
import { HaZoneTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-zone";
import { HaDeviceTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-device";
import { HaStateTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-state";
import { HaMQTTTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-mqtt";
import "../../../src/panels/config/automation/trigger/ha-automation-trigger";
const SCHEMAS: { name: string; triggers: Trigger[] }[] = [
{
name: "State",
triggers: [{ platform: "state", ...HaStateTrigger.defaultConfig }],
},
{
name: "MQTT",
triggers: [{ platform: "mqtt", ...HaMQTTTrigger.defaultConfig }],
},
{
name: "GeoLocation",
triggers: [
{ platform: "geo_location", ...HaGeolocationTrigger.defaultConfig },
],
},
{
name: "Home Assistant",
triggers: [{ platform: "homeassistant", ...HaHassTrigger.defaultConfig }],
},
{
name: "Numeric State",
triggers: [
{ platform: "numeric_state", ...HaNumericStateTrigger.defaultConfig },
],
},
{
name: "Sun",
triggers: [{ platform: "sun", ...HaSunTrigger.defaultConfig }],
},
{
name: "Time Pattern",
triggers: [
{ platform: "time_pattern", ...HaTimePatternTrigger.defaultConfig },
],
},
{
name: "Webhook",
triggers: [{ platform: "webhook", ...HaWebhookTrigger.defaultConfig }],
},
{
name: "Zone",
triggers: [{ platform: "zone", ...HaZoneTrigger.defaultConfig }],
},
{
name: "Tag",
triggers: [{ platform: "tag", ...HaTagTrigger.defaultConfig }],
},
{
name: "Time",
triggers: [{ platform: "time", ...HaTimeTrigger.defaultConfig }],
},
{
name: "Template",
triggers: [{ platform: "template", ...HaTemplateTrigger.defaultConfig }],
},
{
name: "Event",
triggers: [{ platform: "event", ...HaEventTrigger.defaultConfig }],
},
{
name: "Device Trigger",
triggers: [{ platform: "device", ...HaDeviceTrigger.defaultConfig }],
},
];
@customElement("demo-automation-editor-trigger")
class DemoHaAutomationEditorTrigger extends LitElement {
@state() private hass!: HomeAssistant;
private data: any = SCHEMAS.map((info) => info.triggers);
constructor() {
super();
const hass = provideHass(this);
hass.updateTranslations(null, "en");
hass.updateTranslations("config", "en");
mockEntityRegistry(hass);
mockDeviceRegistry(hass);
mockAreaRegistry(hass);
mockHassioSupervisor(hass);
}
protected render(): TemplateResult {
const valueChanged = (ev) => {
const sampleIdx = ev.target.sampleIdx;
this.data[sampleIdx] = ev.detail.value;
this.requestUpdate();
};
return html`
${SCHEMAS.map(
(info, sampleIdx) => html`
<demo-black-white-row
.title=${info.name}
.value=${this.data[sampleIdx]}
>
${["light", "dark"].map(
(slot) =>
html`
<ha-automation-trigger
slot=${slot}
.hass=${this.hass}
.triggers=${this.data[sampleIdx]}
.sampleIdx=${sampleIdx}
@value-changed=${valueChanged}
></ha-automation-trigger>
`
)}
</demo-black-white-row>
`
)}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-ha-automation-editor-trigger": DemoHaAutomationEditorTrigger;
}
}
+181 -187
View File
@@ -11,12 +11,6 @@ import {
mdiHomeAssistant,
mdiKey,
mdiNetwork,
mdiNumeric1,
mdiNumeric2,
mdiNumeric3,
mdiNumeric4,
mdiNumeric5,
mdiNumeric6,
mdiPound,
mdiShield,
} from "@mdi/js";
@@ -31,7 +25,7 @@ import "../../../../src/components/buttons/ha-call-api-button";
import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-chip";
import "../../../../src/components/ha-label-badge";
import "../../../../src/components/ha-markdown";
import "../../../../src/components/ha-settings-row";
import "../../../../src/components/ha-svg-icon";
@@ -79,15 +73,6 @@ const STAGE_ICON = {
deprecated: mdiExclamationThick,
};
const RATING_ICON = {
1: mdiNumeric1,
2: mdiNumeric2,
3: mdiNumeric3,
4: mdiNumeric4,
5: mdiNumeric5,
6: mdiNumeric6,
};
@customElement("hassio-addon-info")
class HassioAddonInfo extends LitElement {
@property({ type: Boolean }) public narrow!: boolean;
@@ -261,163 +246,6 @@ class HassioAddonInfo extends LitElement {
>`}
</div>
<div class="capabilities">
${this.addon.stage !== "stable"
? html` <ha-chip
hasIcon
class=${classMap({
yellow: this.addon.stage === "experimental",
red: this.addon.stage === "deprecated",
})}
@click=${this._showMoreInfo}
id="stage"
>
<ha-svg-icon
slot="icon"
.path=${STAGE_ICON[this.addon.stage]}
>
</ha-svg-icon>
${this.supervisor.localize(
`addon.dashboard.capability.stages.${this.addon.stage}`
)}
</ha-chip>`
: ""}
<ha-chip
hasIcon
class=${classMap({
green: [5, 6].includes(Number(this.addon.rating)),
yellow: [3, 4].includes(Number(this.addon.rating)),
red: [1, 2].includes(Number(this.addon.rating)),
})}
@click=${this._showMoreInfo}
id="rating"
>
<ha-svg-icon slot="icon" .path=${RATING_ICON[this.addon.rating]}>
</ha-svg-icon>
${this.supervisor.localize(
"addon.dashboard.capability.label.rating"
)}
</ha-chip>
${this.addon.host_network
? html`
<ha-chip
hasIcon
@click=${this._showMoreInfo}
id="host_network"
>
<ha-svg-icon slot="icon" .path=${mdiNetwork}> </ha-svg-icon>
${this.supervisor.localize(
"addon.dashboard.capability.label.host"
)}
</ha-chip>
`
: ""}
${this.addon.full_access
? html`
<ha-chip
hasIcon
@click=${this._showMoreInfo}
id="full_access"
>
<ha-svg-icon slot="icon" .path=${mdiChip}></ha-svg-icon>
${this.supervisor.localize(
"addon.dashboard.capability.label.hardware"
)}
</ha-chip>
`
: ""}
${this.addon.homeassistant_api
? html`
<ha-chip
hasIcon
@click=${this._showMoreInfo}
id="homeassistant_api"
>
<ha-svg-icon
slot="icon"
.path=${mdiHomeAssistant}
></ha-svg-icon>
${this.supervisor.localize(
"addon.dashboard.capability.label.core"
)}
</ha-chip>
`
: ""}
${this._computeHassioApi
? html`
<ha-chip hasIcon @click=${this._showMoreInfo} id="hassio_api">
<ha-svg-icon
slot="icon"
.path=${mdiHomeAssistant}
></ha-svg-icon>
${this.supervisor.localize(
`addon.dashboard.capability.role.${this.addon.hassio_role}`
) || this.addon.hassio_role}
</ha-chip>
`
: ""}
${this.addon.docker_api
? html`
<ha-chip hasIcon @click=${this._showMoreInfo} id="docker_api">
<ha-svg-icon slot="icon" .path=${mdiDocker}></ha-svg-icon>
${this.supervisor.localize(
"addon.dashboard.capability.label.docker"
)}
</ha-chip>
`
: ""}
${this.addon.host_pid
? html`
<ha-chip hasIcon @click=${this._showMoreInfo} id="host_pid">
<ha-svg-icon slot="icon" .path=${mdiPound}></ha-svg-icon>
${this.supervisor.localize(
"addon.dashboard.capability.label.host_pid"
)}
</ha-chip>
`
: ""}
${this.addon.apparmor !== "default"
? html`
<ha-chip
hasIcon
@click=${this._showMoreInfo}
class=${this._computeApparmorClassName}
id="apparmor"
>
<ha-svg-icon slot="icon" .path=${mdiShield}></ha-svg-icon>
${this.supervisor.localize(
"addon.dashboard.capability.label.apparmor"
)}
</ha-chip>
`
: ""}
${this.addon.auth_api
? html`
<ha-chip hasIcon @click=${this._showMoreInfo} id="auth_api">
<ha-svg-icon slot="icon" .path=${mdiKey}></ha-svg-icon>
${this.supervisor.localize(
"addon.dashboard.capability.label.auth"
)}
</ha-chip>
`
: ""}
${this.addon.ingress
? html`
<ha-chip hasIcon @click=${this._showMoreInfo} id="ingress">
<ha-svg-icon
slot="icon"
.path=${mdiCursorDefaultClickOutline}
></ha-svg-icon>
${this.supervisor.localize(
"addon.dashboard.capability.label.ingress"
)}
</ha-chip>
`
: ""}
</div>
<div class="description light-color">
${this.addon.description}.<br />
${this.supervisor.localize(
@@ -438,6 +266,172 @@ class HassioAddonInfo extends LitElement {
/>
`
: ""}
<div class="security">
${this.addon.stage !== "stable"
? html` <ha-label-badge
class=${classMap({
yellow: this.addon.stage === "experimental",
red: this.addon.stage === "deprecated",
})}
@click=${this._showMoreInfo}
id="stage"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.stage"
)}
description=""
>
<ha-svg-icon
.path=${STAGE_ICON[this.addon.stage]}
></ha-svg-icon>
</ha-label-badge>`
: ""}
<ha-label-badge
class=${classMap({
green: [5, 6].includes(Number(this.addon.rating)),
yellow: [3, 4].includes(Number(this.addon.rating)),
red: [1, 2].includes(Number(this.addon.rating)),
})}
@click=${this._showMoreInfo}
id="rating"
label="rating"
description=""
>
${this.addon.rating}
</ha-label-badge>
${this.addon.host_network
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="host_network"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.host"
)}
description=""
>
<ha-svg-icon .path=${mdiNetwork}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.full_access
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="full_access"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.hardware"
)}
description=""
>
<ha-svg-icon .path=${mdiChip}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.homeassistant_api
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="homeassistant_api"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.hass"
)}
description=""
>
<ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this._computeHassioApi
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="hassio_api"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.hassio"
)}
.description=${this.supervisor.localize(
`addon.dashboard.capability.role.${this.addon.hassio_role}`
) || this.addon.hassio_role}
>
<ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.docker_api
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="docker_api"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.docker"
)}
description=""
>
<ha-svg-icon .path=${mdiDocker}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.host_pid
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="host_pid"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.host_pid"
)}
description=""
>
<ha-svg-icon .path=${mdiPound}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.apparmor
? html`
<ha-label-badge
@click=${this._showMoreInfo}
class=${this._computeApparmorClassName}
id="apparmor"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.apparmor"
)}
description=""
>
<ha-svg-icon .path=${mdiShield}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.auth_api
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="auth_api"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.auth"
)}
description=""
>
<ha-svg-icon .path=${mdiKey}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.ingress
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="ingress"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.ingress"
)}
description=""
>
<ha-svg-icon
.path=${mdiCursorDefaultClickOutline}
></ha-svg-icon>
</ha-label-badge>
`
: ""}
</div>
${this.addon.version
? html`
<div
@@ -1181,31 +1175,34 @@ class HassioAddonInfo extends LitElement {
.description a {
color: var(--primary-color);
}
ha-chip {
text-transform: capitalize;
--ha-chip-text-color: var(--text-primary-color);
--ha-chip-background-color: var(--primary-color);
}
.red {
--ha-chip-background-color: var(--label-badge-red, #df4c1e);
--ha-label-badge-color: var(--label-badge-red, #df4c1e);
}
.blue {
--ha-chip-background-color: var(--label-badge-blue, #039be5);
--ha-label-badge-color: var(--label-badge-blue, #039be5);
}
.green {
--ha-chip-background-color: var(--label-badge-green, #0da035);
--ha-label-badge-color: var(--label-badge-green, #0da035);
}
.yellow {
--ha-chip-background-color: var(--label-badge-yellow, #f4b400);
--ha-label-badge-color: var(--label-badge-yellow, #f4b400);
}
.capabilities {
.security {
margin-bottom: 16px;
}
.card-actions {
justify-content: space-between;
display: flex;
}
.security h3 {
margin-bottom: 8px;
font-weight: normal;
}
.security ha-label-badge {
cursor: pointer;
margin-right: 4px;
--ha-label-badge-padding: 8px 0 0 0;
}
.changelog {
display: contents;
}
@@ -1245,9 +1242,6 @@ class HassioAddonInfo extends LitElement {
}
@media (max-width: 720px) {
ha-chip {
line-height: 36px;
}
.addon-options {
max-width: 100%;
}
+44 -44
View File
@@ -22,23 +22,23 @@
"license": "Apache-2.0",
"dependencies": {
"@braintree/sanitize-url": "^5.0.2",
"@codemirror/commands": "^0.19.5",
"@codemirror/gutter": "^0.19.3",
"@codemirror/highlight": "^0.19.6",
"@codemirror/commands": "^0.19.2",
"@codemirror/gutter": "^0.19.1",
"@codemirror/highlight": "^0.19.2",
"@codemirror/history": "^0.19.0",
"@codemirror/legacy-modes": "^0.19.0",
"@codemirror/rectangular-selection": "^0.19.1",
"@codemirror/search": "^0.19.2",
"@codemirror/state": "^0.19.2",
"@codemirror/stream-parser": "^0.19.2",
"@codemirror/text": "^0.19.4",
"@codemirror/view": "^0.19.9",
"@formatjs/intl-datetimeformat": "^4.2.5",
"@formatjs/intl-getcanonicallocales": "^1.8.0",
"@formatjs/intl-locale": "^2.4.40",
"@formatjs/intl-numberformat": "^7.2.5",
"@formatjs/intl-pluralrules": "^4.1.5",
"@formatjs/intl-relativetimeformat": "^9.3.2",
"@codemirror/rectangular-selection": "^0.19.0",
"@codemirror/search": "^0.19.0",
"@codemirror/state": "^0.19.1",
"@codemirror/stream-parser": "^0.19.1",
"@codemirror/text": "^0.19.2",
"@codemirror/view": "^0.19.4",
"@formatjs/intl-datetimeformat": "^4.2.4",
"@formatjs/intl-getcanonicallocales": "^1.7.3",
"@formatjs/intl-locale": "^2.4.38",
"@formatjs/intl-numberformat": "^7.2.4",
"@formatjs/intl-pluralrules": "^4.1.4",
"@formatjs/intl-relativetimeformat": "^9.3.1",
"@formatjs/intl-utils": "^3.8.4",
"@fullcalendar/common": "5.9.0",
"@fullcalendar/core": "5.9.0",
@@ -46,29 +46,29 @@
"@fullcalendar/interaction": "5.9.0",
"@fullcalendar/list": "5.9.0",
"@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.6.0#./.yarn/patches/@lit-labs/virtualizer/0.7.0.patch",
"@material/chips": "14.0.0-canary.261f2db59.0",
"@material/data-table": "14.0.0-canary.261f2db59.0",
"@material/mwc-button": "0.25.3",
"@material/mwc-checkbox": "0.25.3",
"@material/mwc-circular-progress": "0.25.3",
"@material/mwc-dialog": "0.25.3",
"@material/mwc-fab": "0.25.3",
"@material/mwc-formfield": "0.25.3",
"@material/mwc-icon-button": "patch:@material/mwc-icon-button@0.25.3#./.yarn/patches/@material/mwc-icon-button/remove-icon.patch",
"@material/mwc-linear-progress": "0.25.3",
"@material/mwc-list": "0.25.3",
"@material/mwc-menu": "0.25.3",
"@material/mwc-radio": "0.25.3",
"@material/mwc-ripple": "0.25.3",
"@material/mwc-select": "0.25.3",
"@material/mwc-slider": "0.25.3",
"@material/mwc-switch": "0.25.3",
"@material/mwc-tab": "0.25.3",
"@material/mwc-tab-bar": "0.25.3",
"@material/mwc-textfield": "0.25.3",
"@material/top-app-bar": "14.0.0-canary.261f2db59.0",
"@mdi/js": "6.4.95",
"@mdi/svg": "6.4.95",
"@material/chips": "14.0.0-canary.353ca7e9f.0",
"@material/data-table": "14.0.0-canary.353ca7e9f.0",
"@material/mwc-button": "0.25.2",
"@material/mwc-checkbox": "0.25.2",
"@material/mwc-circular-progress": "0.25.2",
"@material/mwc-dialog": "0.25.2",
"@material/mwc-fab": "0.25.2",
"@material/mwc-formfield": "0.25.2",
"@material/mwc-icon-button": "0.25.2",
"@material/mwc-linear-progress": "0.25.2",
"@material/mwc-list": "0.25.2",
"@material/mwc-menu": "0.25.2",
"@material/mwc-radio": "0.25.2",
"@material/mwc-ripple": "0.25.2",
"@material/mwc-select": "0.25.2",
"@material/mwc-slider": "0.25.2",
"@material/mwc-switch": "0.25.2",
"@material/mwc-tab": "0.25.2",
"@material/mwc-tab-bar": "0.25.2",
"@material/mwc-textfield": "0.25.2",
"@material/top-app-bar": "14.0.0-canary.353ca7e9f.0",
"@mdi/js": "6.3.95",
"@mdi/svg": "6.3.95",
"@polymer/app-layout": "^3.1.0",
"@polymer/iron-flex-layout": "^3.0.1",
"@polymer/iron-icon": "^3.0.1",
@@ -108,7 +108,7 @@
"js-yaml": "^4.1.0",
"leaflet": "^1.7.1",
"leaflet-draw": "^1.0.4",
"lit": "^2.0.2",
"lit": "^2.0.0",
"lit-vaadin-helpers": "^0.2.1",
"marked": "^3.0.2",
"memoize-one": "^5.2.1",
@@ -180,7 +180,7 @@
"eslint-import-resolver-webpack": "^0.13.1",
"eslint-plugin-disable": "^2.0.1",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-lit": "^1.6.1",
"eslint-plugin-lit": "^1.5.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-unused-imports": "^1.1.5",
"eslint-plugin-wc": "^1.3.2",
@@ -230,10 +230,10 @@
"resolutions": {
"@polymer/polymer": "patch:@polymer/polymer@3.4.1#./.yarn/patches/@polymer/polymer/pr-5569.patch",
"@webcomponents/webcomponentsjs": "^2.2.10",
"lit": "^2.0.2",
"lit-html": "2.0.1",
"lit-element": "3.0.1",
"@lit/reactive-element": "1.0.1"
"lit": "^2.0.0",
"lit-html": "2.0.0",
"lit-element": "3.0.0",
"@lit/reactive-element": "1.0.0"
},
"main": "src/home-assistant.js",
"husky": {
+1 -1
View File
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name="home-assistant-frontend",
version="20211026.0",
version="20211020.0",
description="The Home Assistant frontend",
url="https://github.com/home-assistant/frontend",
author="The Home Assistant Authors",
+1 -33
View File
@@ -7,10 +7,9 @@ import {
PropertyValues,
TemplateResult,
} from "lit";
import "./ha-password-manager-polyfill";
import { property, state } from "lit/decorators";
import "../components/ha-checkbox";
import "../components/ha-form/ha-form";
import "../components/ha-formfield";
import "../components/ha-markdown";
import "../components/ha-alert";
import { AuthProvider } from "../data/auth";
@@ -20,7 +19,6 @@ import {
} from "../data/data_entry_flow";
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
import { computeInitialHaFormData } from "../components/ha-form/compute-initial-ha-form-data";
import "./ha-password-manager-polyfill";
type State = "loading" | "error" | "step";
@@ -43,8 +41,6 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
@state() private _submitting = false;
@state() private _storeToken = false;
willUpdate(changedProps: PropertyValues) {
super.willUpdate(changedProps);
@@ -174,9 +170,6 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
}
private _renderStep(step: DataEntryFlowStep): TemplateResult {
const clientIdOrigin = this.clientId
? new URL(this.clientId).origin
: undefined;
switch (step.type) {
case "abort":
return html`
@@ -208,29 +201,12 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
.computeError=${this._computeErrorCallback(step)}
@value-changed=${this._stepDataChanged}
></ha-form>
${clientIdOrigin === window.location.origin && step.step_id !== "mfa"
? html`
<ha-formfield
class="store-token"
.label=${this.localize("ui.panel.page-authorize.store_token")}
>
<ha-checkbox
.checked=${this._storeToken}
@change=${this._storeTokenChanged}
></ha-checkbox>
</ha-formfield>
`
: ""}
`;
default:
return html``;
}
}
private _storeTokenChanged(e: CustomEvent<HTMLInputElement>) {
this._storeToken = (e.currentTarget as HTMLInputElement).checked;
}
private async _providerChanged(newProvider?: AuthProvider) {
if (this._step && this._step.type === "form") {
fetch(`/auth/login_flow/${this._step.flow_id}`, {
@@ -298,9 +274,6 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
if (this.oauth2State) {
url += `&state=${encodeURIComponent(this.oauth2State)}`;
}
if (this._storeToken) {
url += `&storeToken=true`;
}
document.location.assign(url);
}
@@ -384,11 +357,6 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
margin: 24px 0 8px;
text-align: center;
}
/* Align with the rest of the form. */
.store-token {
margin-top: 10px;
margin-left: -16px;
}
`;
}
}
+1 -8
View File
@@ -30,14 +30,6 @@ export function askWrite() {
export function saveTokens(tokens: AuthData | null) {
tokenCache.tokens = tokens;
if (
!tokenCache.writeEnabled &&
new URLSearchParams(window.location.search).get("storeToken") === "true"
) {
tokenCache.writeEnabled = true;
}
if (tokenCache.writeEnabled) {
try {
storage.hassTokens = JSON.stringify(tokens);
@@ -53,6 +45,7 @@ export function enableWrite() {
saveTokens(tokenCache.tokens);
}
}
export function loadTokens() {
if (tokenCache.tokens === undefined) {
try {
+25 -29
View File
@@ -1,45 +1,43 @@
import {
mdiAlertCircle,
mdiBattery,
mdiBatteryCharging,
mdiBatteryOutline,
mdiBrightness5,
mdiBrightness7,
mdiCheckboxMarkedCircle,
mdiCheckCircle,
mdiCropPortrait,
mdiBatteryCharging,
mdiThermometer,
mdiSnowflake,
mdiServerNetworkOff,
mdiServerNetwork,
mdiDoorClosed,
mdiDoorOpen,
mdiFire,
mdiGarage,
mdiGarageOpen,
mdiHome,
mdiHomeOutline,
mdiPowerPlugOff,
mdiPowerPlug,
mdiCheckCircle,
mdiAlertCircle,
mdiSmoke,
mdiFire,
mdiBrightness5,
mdiBrightness7,
mdiLock,
mdiLockOpen,
mdiMusicNote,
mdiMusicNoteOff,
mdiPackage,
mdiPackageUp,
mdiPlay,
mdiPowerPlug,
mdiPowerPlugOff,
mdiRadioboxBlank,
mdiWaterOff,
mdiWater,
mdiWalk,
mdiRun,
mdiServerNetwork,
mdiServerNetworkOff,
mdiSmoke,
mdiSnowflake,
mdiHomeOutline,
mdiHome,
mdiSquare,
mdiSquareOutline,
mdiStop,
mdiThermometer,
mdiMusicNoteOff,
mdiMusicNote,
mdiPackage,
mdiPackageUp,
mdiCropPortrait,
mdiVibrate,
mdiWalk,
mdiWater,
mdiWaterOff,
mdiWindowClosed,
mdiWindowOpen,
mdiRadioboxBlank,
mdiCheckboxMarkedCircle,
} from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket";
@@ -87,8 +85,6 @@ export const binarySensorIcon = (state?: string, stateObj?: HassEntity) => {
return is_off ? mdiPowerPlugOff : mdiPowerPlug;
case "presence":
return is_off ? mdiHomeOutline : mdiHome;
case "running":
return is_off ? mdiStop : mdiPlay;
case "sound":
return is_off ? mdiMusicNoteOff : mdiMusicNote;
case "update":
@@ -1,24 +0,0 @@
/**
* Strips a device name from an entity name.
* @param entityName the entity name
* @param lowerCasedPrefixWithSpaceSuffix the prefix to strip, lower cased with a space suffix
* @returns
*/
export const stripPrefixFromEntityName = (
entityName: string,
lowerCasedPrefixWithSpaceSuffix: string
) => {
if (!entityName.toLowerCase().startsWith(lowerCasedPrefixWithSpaceSuffix)) {
return undefined;
}
const newName = entityName.substring(lowerCasedPrefixWithSpaceSuffix.length);
// If first word already has an upper case letter (e.g. from brand name)
// leave as-is, otherwise capitalize the first word.
return hasUpperCase(newName.substr(0, newName.indexOf(" ")))
? newName
: newName[0].toUpperCase() + newName.slice(1);
};
const hasUpperCase = (str: string): boolean => str.toLowerCase() !== str;
+4 -4
View File
@@ -12,8 +12,8 @@ export const slugify = (value: string, delimiter = "_") => {
.replace(p, (c) => b.charAt(a.indexOf(c))) // Replace special characters
.replace(/&/g, `${delimiter}and${delimiter}`) // Replace & with 'and'
.replace(/[^\w-]+/g, "") // Remove all non-word characters
.replace(/-/g, delimiter) // Replace - with delimiter
.replace(new RegExp(`(${delimiter})\\1+`, "g"), "$1") // Replace multiple delimiters with single delimiter
.replace(new RegExp(`^${delimiter}+`), "") // Trim delimiter from start of text
.replace(new RegExp(`${delimiter}+$`), ""); // Trim delimiter from end of text
.replace(/-/, delimiter) // Replace - with delimiter
.replace(new RegExp(`/${delimiter}${delimiter}+/`, "g"), delimiter) // Replace multiple delimiters with single delimiter
.replace(new RegExp(`/^${delimiter}+/`), "") // Trim delimiter from start of text
.replace(new RegExp(`/-+$/`), ""); // Trim delimiter from end of text
};
+31 -54
View File
@@ -70,7 +70,7 @@ export interface DataTableSortColumnData {
export interface DataTableColumnData extends DataTableSortColumnData {
title: TemplateResult | string;
type?: "numeric" | "icon" | "icon-button" | "overflow-menu";
type?: "numeric" | "icon" | "icon-button";
template?: <T>(data: any, row: T) => TemplateResult | string;
width?: string;
maxWidth?: string;
@@ -281,13 +281,15 @@ export class HaDataTable extends LitElement {
}
const sorted = key === this._sortColumn;
const classes = {
"mdc-data-table__header-cell--numeric":
column.type === "numeric",
"mdc-data-table__header-cell--icon": column.type === "icon",
"mdc-data-table__header-cell--icon-button":
column.type === "icon-button",
"mdc-data-table__header-cell--overflow-menu":
column.type === "overflow-menu",
"mdc-data-table__header-cell--numeric": Boolean(
column.type === "numeric"
),
"mdc-data-table__header-cell--icon": Boolean(
column.type === "icon"
),
"mdc-data-table__header-cell--icon-button": Boolean(
column.type === "icon-button"
),
sortable: Boolean(column.sortable),
"not-sorted": Boolean(column.sortable && !sorted),
grows: Boolean(column.grows),
@@ -403,14 +405,14 @@ export class HaDataTable extends LitElement {
<div
role="cell"
class="mdc-data-table__cell ${classMap({
"mdc-data-table__cell--numeric":
column.type === "numeric",
"mdc-data-table__cell--icon":
column.type === "icon",
"mdc-data-table__cell--numeric": Boolean(
column.type === "numeric"
),
"mdc-data-table__cell--icon": Boolean(
column.type === "icon"
),
"mdc-data-table__cell--icon-button":
column.type === "icon-button",
"mdc-data-table__cell--overflow-menu":
column.type === "overflow-menu",
Boolean(column.type === "icon-button"),
grows: Boolean(column.grows),
forceLTR: Boolean(column.forceLTR),
})}"
@@ -767,65 +769,40 @@ export class HaDataTable extends LitElement {
margin-left: -8px;
}
.mdc-data-table__cell--overflow-menu,
.mdc-data-table__header-cell--overflow-menu,
.mdc-data-table__header-cell--icon-button,
.mdc-data-table__cell--icon-button {
padding: 8px;
}
.mdc-data-table__header-cell--icon-button,
.mdc-data-table__cell--icon-button {
width: 56px;
padding: 8px;
}
.mdc-data-table__cell--overflow-menu,
.mdc-data-table__cell--icon-button {
color: var(--secondary-text-color);
text-overflow: clip;
}
.mdc-data-table__header-cell--icon-button:first-child,
.mdc-data-table__cell--icon-button:first-child,
.mdc-data-table__cell--icon-button:first-child {
width: 64px;
padding-left: 16px;
}
:host([dir="rtl"])
.mdc-data-table__header-cell--icon-button:first-child,
:host([dir="rtl"]) .mdc-data-table__cell--icon-button:first-child {
padding-left: auto;
padding-right: 16px;
}
.mdc-data-table__header-cell--icon-button:last-child,
.mdc-data-table__cell--icon-button:last-child {
width: 64px;
}
.mdc-data-table__cell--overflow-menu:first-child,
.mdc-data-table__header-cell--overflow-menu:first-child,
.mdc-data-table__header-cell--icon-button:first-child,
.mdc-data-table__cell--icon-button:first-child {
padding-left: 16px;
}
:host([dir="rtl"])
.mdc-data-table__header-cell--overflow-menu:first-child,
:host([dir="rtl"]) .mdc-data-table__cell--overflow-menu:first-child,
:host([dir="rtl"])
.mdc-data-table__header-cell--overflow-menu:first-child,
:host([dir="rtl"]) .mdc-data-table__cell--overflow-menu:first-child {
padding-left: 8px;
padding-right: 16px;
}
.mdc-data-table__cell--overflow-menu:last-child,
.mdc-data-table__header-cell--overflow-menu:last-child,
.mdc-data-table__header-cell--icon-button:last-child,
.mdc-data-table__cell--icon-button:last-child {
padding-right: 16px;
}
:host([dir="rtl"])
.mdc-data-table__header-cell--overflow-menu:last-child,
:host([dir="rtl"]) .mdc-data-table__cell--overflow-menu:last-child,
:host([dir="rtl"]) .mdc-data-table__header-cell--icon-button:last-child,
:host([dir="rtl"]) .mdc-data-table__cell--icon-button:last-child {
padding-right: 8px;
padding-right: auto;
padding-left: 16px;
}
.mdc-data-table__cell--overflow-menu,
.mdc-data-table__header-cell--overflow-menu {
overflow: initial;
}
.mdc-data-table__cell--icon-button a {
color: var(--secondary-text-color);
}
+2 -10
View File
@@ -102,12 +102,7 @@ export class HaStatisticPicker extends LitElement {
</style>
<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
<paper-icon-item>
${item.state
? html`<state-badge
slot="item-icon"
.stateObj=${item.state}
></state-badge>`
: ""}
<state-badge slot="item-icon" .stateObj=${item.state}></state-badge>
<paper-item-body two-line="">
${item.name}
<span secondary
@@ -158,10 +153,7 @@ export class HaStatisticPicker extends LitElement {
const entityState = this.hass.states[meta.statistic_id];
if (!entityState) {
if (!entitiesOnly) {
output.push({
id: meta.statistic_id,
name: meta.name || meta.statistic_id,
});
output.push({ id: meta.statistic_id, name: meta.statistic_id });
}
return;
}
-1
View File
@@ -52,7 +52,6 @@ export class HaFormSelect extends LitElement implements HaFormElement {
return html`
<mwc-select
fixedMenuPosition
naturalMenuWidth
.label=${this.label}
.value=${this.data}
.disabled=${this.disabled}
-119
View File
@@ -1,119 +0,0 @@
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import "./ha-button-menu";
import "@material/mwc-list/mwc-list-item";
import "@material/mwc-icon-button";
import "./ha-svg-icon";
import { mdiDotsVertical } from "@mdi/js";
import { HomeAssistant } from "../types";
import "@polymer/paper-tooltip/paper-tooltip";
export interface IconOverflowMenuItem {
[key: string]: any;
path: string;
label: string;
narrowOnly?: boolean;
disabled?: boolean;
tooltip?: string;
onClick: CallableFunction;
}
@customElement("ha-icon-overflow-menu")
export class HaIconOverflowMenu extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Array }) public items: IconOverflowMenuItem[] = [];
@property({ type: Boolean }) public narrow = false;
protected render(): TemplateResult {
return html`
${this.narrow
? html` <!-- Collapsed Representation for Small Screens -->
<ha-button-menu
@click=${this._handleIconOverflowMenuOpened}
@closed=${this._handleIconOverflowMenuClosed}
class="ha-icon-overflow-menu-overflow"
corner="BOTTOM_START"
absolute
>
<mwc-icon-button
.title=${this.hass.localize("ui.common.menu")}
.label=${this.hass.localize("ui.common.overflow_menu")}
slot="trigger"
>
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button>
${this.items.map(
(item) => html`
<mwc-list-item
graphic="icon"
.disabled=${item.disabled}
@click=${item.action}
>
<div slot="graphic">
<ha-svg-icon .path=${item.path}></ha-svg-icon>
</div>
${item.label}
</mwc-list-item>
`
)}
</ha-button-menu>`
: html`
<!-- Icon Representation for Big Screens -->
${this.items.map((item) =>
item.narrowOnly
? ""
: html`<div>
${item.tooltip
? html`<paper-tooltip animation-delay="0" position="left">
${item.tooltip}
</paper-tooltip>`
: ""}
<mwc-icon-button
@click=${item.action}
.label=${item.label}
.disabled=${item.disabled}
>
<ha-svg-icon .path=${item.path}></ha-svg-icon>
</mwc-icon-button>
</div> `
)}
`}
`;
}
protected _handleIconOverflowMenuOpened() {
// If this component is used inside a data table, the z-index of the row
// needs to be increased. Otherwise the ha-button-menu would be displayed
// underneath the next row in the table.
const row = this.closest(".mdc-data-table__row") as HTMLDivElement | null;
if (row) {
row.style.zIndex = "1";
}
}
protected _handleIconOverflowMenuClosed() {
const row = this.closest(".mdc-data-table__row") as HTMLDivElement | null;
if (row) {
row.style.zIndex = "";
}
}
static get styles() {
return css`
:host {
display: flex;
justify-content: flex-end;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-icon-overflow-menu": HaIconOverflowMenu;
}
}
+20
View File
@@ -52,6 +52,18 @@ const mdiDeprecatedIcons: DeprecatedIcon = {
newName: "cast-variant",
removeIn: "2021.12",
},
application: {
newName: "application-outline",
removeIn: "2021.12",
},
"application-cog": {
newName: "application-cog-outline",
removeIn: "2021.12",
},
"application-settings": {
newName: "application-settings-outline",
removeIn: "2021.12",
},
bandcamp: {
removeIn: "2021.12",
},
@@ -65,6 +77,14 @@ const mdiDeprecatedIcons: DeprecatedIcon = {
newName: "cross-bolnisi",
removeIn: "2021.12",
},
"boom-gate-up": {
newName: "boom-gate-arrow-up",
removeIn: "2021.12",
},
"boom-gate-up-outline": {
newName: "boom-gate-arrow-up-outline",
removeIn: "2021.12",
},
"boom-gate-down": {
newName: "boom-gate-arrow-down",
removeIn: "2021.12",
+4 -4
View File
@@ -194,10 +194,10 @@ export interface NumericStateCondition extends BaseCondition {
export interface SunCondition extends BaseCondition {
condition: "sun";
after_offset?: number;
before_offset?: number;
after?: "sunrise" | "sunset";
before?: "sunrise" | "sunset";
after_offset: number;
before_offset: number;
after: "sunrise" | "sunset";
before: "sunrise" | "sunset";
}
export interface ZoneCondition extends BaseCondition {
-2
View File
@@ -74,8 +74,6 @@ export interface StatisticValue {
export interface StatisticsMetaData {
unit_of_measurement: string;
statistic_id: string;
source: string;
name?: string | null;
}
export type StatisticsValidationResult =
+49 -55
View File
@@ -2,7 +2,6 @@ import { HassEntity } from "home-assistant-js-websocket";
import { BINARY_STATE_OFF, BINARY_STATE_ON } from "../common/const";
import { computeDomain } from "../common/entity/compute_domain";
import { computeStateDisplay } from "../common/entity/compute_state_display";
import { LocalizeFunc } from "../common/translations/localize";
import { HomeAssistant } from "../types";
import { UNAVAILABLE_STATES } from "./entity";
@@ -36,11 +35,9 @@ export const getLogbookDataForContext = async (
hass: HomeAssistant,
startDate: string,
contextId?: string
): Promise<LogbookEntry[]> => {
const localize = await hass.loadBackendTranslation("device_class");
return addLogbookMessage(
): Promise<LogbookEntry[]> =>
addLogbookMessage(
hass,
localize,
await getLogbookDataFromServer(
hass,
startDate,
@@ -50,7 +47,6 @@ export const getLogbookDataForContext = async (
contextId
)
);
};
export const getLogbookData = async (
hass: HomeAssistant,
@@ -58,11 +54,9 @@ export const getLogbookData = async (
endDate: string,
entityId?: string,
entity_matches_only?: boolean
): Promise<LogbookEntry[]> => {
const localize = await hass.loadBackendTranslation("device_class");
return addLogbookMessage(
): Promise<LogbookEntry[]> =>
addLogbookMessage(
hass,
localize,
await getLogbookDataCache(
hass,
startDate,
@@ -71,11 +65,9 @@ export const getLogbookData = async (
entity_matches_only
)
);
};
export const addLogbookMessage = (
hass: HomeAssistant,
localize: LocalizeFunc,
logbookData: LogbookEntry[]
): LogbookEntry[] => {
for (const entry of logbookData) {
@@ -83,7 +75,6 @@ export const addLogbookMessage = (
if (entry.state && stateObj) {
entry.message = getLogbookMessage(
hass,
localize,
entry.state,
stateObj,
computeDomain(entry.entity_id!)
@@ -166,7 +157,6 @@ export const clearLogbookCache = (startDate: string, endDate: string) => {
export const getLogbookMessage = (
hass: HomeAssistant,
localize: LocalizeFunc,
state: string,
stateObj: HassEntity,
domain: string
@@ -175,17 +165,21 @@ export const getLogbookMessage = (
case "device_tracker":
case "person":
if (state === "not_home") {
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_away`);
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_away`);
}
if (state === "home") {
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_at_home`);
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_at_home`);
}
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_at_state`, "state", state);
return hass.localize(
`${LOGBOOK_LOCALIZE_PATH}.was_at_state`,
"state",
state
);
case "sun":
return state === "above_horizon"
? localize(`${LOGBOOK_LOCALIZE_PATH}.rose`)
: localize(`${LOGBOOK_LOCALIZE_PATH}.set`);
? hass.localize(`${LOGBOOK_LOCALIZE_PATH}.rose`)
: hass.localize(`${LOGBOOK_LOCALIZE_PATH}.set`);
case "binary_sensor": {
const isOn = state === BINARY_STATE_ON;
@@ -195,19 +189,19 @@ export const getLogbookMessage = (
switch (device_class) {
case "battery":
if (isOn) {
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_low`);
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_low`);
}
if (isOff) {
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_normal`);
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_normal`);
}
break;
case "connectivity":
if (isOn) {
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_connected`);
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_connected`);
}
if (isOff) {
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_disconnected`);
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_disconnected`);
}
break;
@@ -216,46 +210,46 @@ export const getLogbookMessage = (
case "opening":
case "window":
if (isOn) {
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_opened`);
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_opened`);
}
if (isOff) {
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_closed`);
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_closed`);
}
break;
case "lock":
if (isOn) {
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_unlocked`);
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_unlocked`);
}
if (isOff) {
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_locked`);
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_locked`);
}
break;
case "plug":
if (isOn) {
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_plugged_in`);
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_plugged_in`);
}
if (isOff) {
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_unplugged`);
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_unplugged`);
}
break;
case "presence":
if (isOn) {
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_at_home`);
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_at_home`);
}
if (isOff) {
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_away`);
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_away`);
}
break;
case "safety":
if (isOn) {
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_unsafe`);
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_unsafe`);
}
if (isOff) {
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_safe`);
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_safe`);
}
break;
@@ -271,27 +265,27 @@ export const getLogbookMessage = (
case "sound":
case "vibration":
if (isOn) {
return localize(`${LOGBOOK_LOCALIZE_PATH}.detected_device_class`, {
device_class: localize(
`component.binary_sensor.device_class.${device_class}`
),
});
return hass.localize(
`${LOGBOOK_LOCALIZE_PATH}.detected_device_class`,
"device_class",
device_class
);
}
if (isOff) {
return localize(`${LOGBOOK_LOCALIZE_PATH}.cleared_device_class`, {
device_class: localize(
`component.binary_sensor.device_class.${device_class}`
),
});
return hass.localize(
`${LOGBOOK_LOCALIZE_PATH}.cleared_device_class`,
"device_class",
device_class
);
}
break;
case "tamper":
if (isOn) {
return localize(`${LOGBOOK_LOCALIZE_PATH}.detected_tampering`);
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.detected_tampering`);
}
if (isOff) {
return localize(`${LOGBOOK_LOCALIZE_PATH}.cleared_tampering`);
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.cleared_tampering`);
}
break;
}
@@ -302,43 +296,43 @@ export const getLogbookMessage = (
case "cover":
switch (state) {
case "open":
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_opened`);
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_opened`);
case "opening":
return localize(`${LOGBOOK_LOCALIZE_PATH}.is_opening`);
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.is_opening`);
case "closing":
return localize(`${LOGBOOK_LOCALIZE_PATH}.is_closing`);
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.is_closing`);
case "closed":
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_closed`);
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_closed`);
}
break;
case "lock":
if (state === "unlocked") {
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_unlocked`);
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_unlocked`);
}
if (state === "locked") {
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_locked`);
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_locked`);
}
break;
}
if (state === BINARY_STATE_ON) {
return localize(`${LOGBOOK_LOCALIZE_PATH}.turned_on`);
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.turned_on`);
}
if (state === BINARY_STATE_OFF) {
return localize(`${LOGBOOK_LOCALIZE_PATH}.turned_off`);
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.turned_off`);
}
if (UNAVAILABLE_STATES.includes(state)) {
return localize(`${LOGBOOK_LOCALIZE_PATH}.became_unavailable`);
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.became_unavailable`);
}
return hass.localize(
`${LOGBOOK_LOCALIZE_PATH}.changed_to_state`,
"state",
stateObj
? computeStateDisplay(localize, stateObj, hass.locale, state)
? computeStateDisplay(hass.localize, stateObj, hass.locale, state)
: state
);
};
+2 -4
View File
@@ -21,9 +21,7 @@ export interface ScriptEntity extends HassEntityBase {
};
}
export type ScriptConfig = ManualScriptConfig | BlueprintScriptConfig;
export interface ManualScriptConfig {
export interface ScriptConfig {
alias: string;
sequence: Action | Action[];
icon?: string;
@@ -31,7 +29,7 @@ export interface ManualScriptConfig {
max?: number;
}
export interface BlueprintScriptConfig extends ManualScriptConfig {
export interface BlueprintScriptConfig extends ScriptConfig {
use_blueprint: { path: string; input?: BlueprintInput };
}
+1 -2
View File
@@ -37,8 +37,7 @@ export type TranslationCategory =
| "options"
| "device_automation"
| "mfa_setup"
| "system_health"
| "device_class";
| "system_health";
export const fetchTranslationPreferences = (hass: HomeAssistant) =>
fetchFrontendUserData(hass.connection, "language");
-3
View File
@@ -84,9 +84,6 @@ export interface ZWaveJSNodeStatus {
ready: boolean;
status: number;
is_secure: boolean | string;
is_routing: boolean | null;
zwave_plus_version: number | null;
highest_security_class: SecurityClass | null;
}
export interface ZwaveJSNodeMetadata {
+78
View File
@@ -0,0 +1,78 @@
import { LitElement, TemplateResult, html, css } from "lit";
import { property } from "lit/decorators";
import { enableWrite } from "../common/auth/token_storage";
import { HomeAssistant } from "../types";
import "../components/ha-card";
import type { HaCard } from "../components/ha-card";
import "@material/mwc-button/mwc-button";
class HaStoreAuth extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
protected render(): TemplateResult {
return html`
<ha-card>
<div class="card-content">
${this.hass.localize("ui.auth_store.ask")}
</div>
<div class="card-actions">
<mwc-button @click=${this._dismiss}>
${this.hass.localize("ui.auth_store.decline")}
</mwc-button>
<mwc-button raised @click=${this._save}>
${this.hass.localize("ui.auth_store.confirm")}
</mwc-button>
</div>
</ha-card>
`;
}
firstUpdated() {
this.classList.toggle("small", window.innerWidth < 600);
}
private _save(): void {
enableWrite();
this._dismiss();
}
private _dismiss(): void {
const card = this.shadowRoot!.querySelector("ha-card") as HaCard;
card.style.bottom = `-${card.offsetHeight + 8}px`;
setTimeout(() => this.parentNode!.removeChild(this), 300);
}
static get styles() {
return css`
ha-card {
position: fixed;
padding: 8px 0;
bottom: 16px;
right: 16px;
transition: bottom 0.25s;
--ha-card-box-shadow: 0px 3px 5px -1px rgba(0, 0, 0, 0.2),
0px 6px 10px 0px rgba(0, 0, 0, 0.14),
0px 1px 18px 0px rgba(0, 0, 0, 0.12);
}
.card-actions {
text-align: right;
border-top: 0;
}
:host(.small) ha-card {
bottom: 0;
left: 0;
right: 0;
}
`;
}
}
customElements.define("ha-store-auth-card", HaStoreAuth);
declare global {
interface HTMLElementTagNameMap {
"ha-store-auth-card": HaStoreAuth;
}
}
+8 -59
View File
@@ -1,19 +1,12 @@
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { isComponentLoaded } from "../../common/config/is_component_loaded";
import { fireEvent } from "../../common/dom/fire_event";
import { throttle } from "../../common/util/throttle";
import "../../components/chart/state-history-charts";
import { getRecentWithCache } from "../../data/cached-history";
import { HistoryResult } from "../../data/history";
import { HomeAssistant } from "../../types";
declare global {
interface HASSDomEvents {
closed: undefined;
}
}
@customElement("ha-more-info-history")
export class MoreInfoHistory extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -31,26 +24,14 @@ export class MoreInfoHistory extends LitElement {
return html``;
}
const href = "/history?entity_id=" + this.entityId;
return html`${isComponentLoaded(this.hass, "history")
? html` <div class="header">
<div class="title">
${this.hass.localize("ui.dialogs.more_info_control.history")}
</div>
<a href=${href} @click=${this._close}
>${this.hass.localize(
"ui.dialogs.more_info_control.show_more"
)}</a
>
</div>
<state-history-charts
up-to-now
.hass=${this.hass}
.historyData=${this._stateHistory}
.isLoadingData=${!this._stateHistory}
></state-history-charts>`
: ""}`;
? html`<state-history-charts
up-to-now
.hass=${this.hass}
.historyData=${this._stateHistory}
.isLoadingData=${!this._stateHistory}
></state-history-charts>`
: ""} `;
}
protected updated(changedProps: PropertyValues): void {
@@ -97,38 +78,6 @@ export class MoreInfoHistory extends LitElement {
this.hass!.language
);
}
private _close(): void {
setTimeout(() => fireEvent(this, "closed"), 500);
}
static get styles() {
return [
css`
.header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.header > a,
a:visited {
color: var(--primary-color);
}
.title {
font-family: var(--paper-font-title_-_font-family);
-webkit-font-smoothing: var(
--paper-font-title_-_-webkit-font-smoothing
);
font-size: var(--paper-font-subhead_-_font-size);
font-weight: var(--paper-font-title_-_font-weight);
letter-spacing: var(--paper-font-title_-_letter-spacing);
line-height: var(--paper-font-title_-_line-height);
}
`,
];
}
}
declare global {
+7 -39
View File
@@ -1,16 +1,16 @@
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { isComponentLoaded } from "../../common/config/is_component_loaded";
import { fireEvent } from "../../common/dom/fire_event";
import { computeStateDomain } from "../../common/entity/compute_state_domain";
import { throttle } from "../../common/util/throttle";
import "../../components/ha-circular-progress";
import { fetchUsers } from "../../data/user";
import { getLogbookData, LogbookEntry } from "../../data/logbook";
import { loadTraceContexts, TraceContexts } from "../../data/trace";
import { fetchUsers } from "../../data/user";
import "../../panels/logbook/ha-logbook";
import { haStyle } from "../../resources/styles";
import { HomeAssistant } from "../../types";
import { closeDialog } from "../make-dialog-manager";
@customElement("ha-more-info-logbook")
export class MoreInfoLogbook extends LitElement {
@@ -44,8 +44,6 @@ export class MoreInfoLogbook extends LitElement {
return html``;
}
const href = "/logbook?entity_id=" + this.entityId;
return html`
${isComponentLoaded(this.hass, "logbook")
? this._error
@@ -63,16 +61,6 @@ export class MoreInfoLogbook extends LitElement {
`
: this._logbookEntries.length
? html`
<div class="header">
<div class="title">
${this.hass.localize("ui.dialogs.more_info_control.logbook")}
</div>
<a href=${href} @click=${this._close}
>${this.hass.localize(
"ui.dialogs.more_info_control.show_more"
)}</a
>
</div>
<ha-logbook
narrow
no-icon
@@ -93,6 +81,11 @@ export class MoreInfoLogbook extends LitElement {
protected firstUpdated(): void {
this._fetchUserPromise = this._fetchUserNames();
this.addEventListener("click", (ev) => {
if ((ev.composedPath()[0] as HTMLElement).tagName === "A") {
setTimeout(() => closeDialog("ha-more-info-dialog"), 500);
}
});
}
protected updated(changedProps: PropertyValues): void {
@@ -189,10 +182,6 @@ export class MoreInfoLogbook extends LitElement {
this._userIdToName = userIdToName;
}
private _close(): void {
setTimeout(() => fireEvent(this, "closed"), 500);
}
static get styles() {
return [
haStyle,
@@ -214,27 +203,6 @@ export class MoreInfoLogbook extends LitElement {
display: flex;
justify-content: center;
}
.header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.header > a,
a:visited {
color: var(--primary-color);
}
.title {
font-family: var(--paper-font-title_-_font-family);
-webkit-font-smoothing: var(
--paper-font-title_-_-webkit-font-smoothing
);
font-size: var(--paper-font-subhead_-_font-size);
font-weight: var(--paper-font-title_-_font-weight);
letter-spacing: var(--paper-font-title_-_letter-spacing);
line-height: var(--paper-font-title_-_line-height);
}
`,
];
}
-4
View File
@@ -371,10 +371,6 @@ export class HAFullCalendar extends LitElement {
);
--fc-theme-standard-border-color: var(--divider-color);
--fc-border-color: var(--divider-color);
--fc-page-bg-color: var(
--ha-card-background,
var(--card-background-color, white)
);
}
a {
@@ -230,7 +230,6 @@ export default class HaAutomationActionRow extends LitElement {
"ui.panel.config.automation.editor.actions.type_select"
)}
.value=${getType(this.action)}
naturalMenuWidth
@selected=${this._typeChanged}
>
${this._processedTypes(this.hass.localize).map(
@@ -90,7 +90,6 @@ export default class HaAutomationConditionEditor extends LitElement {
"ui.panel.config.automation.editor.conditions.type_select"
)}
.value=${this.condition.condition}
naturalMenuWidth
@selected=${this._typeChanged}
>
${this._processedTypes(this.hass.localize).map(
@@ -1,11 +1,10 @@
import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../../../../common/dom/fire_event";
import { Condition, LogicalCondition } from "../../../../../data/automation";
import { LogicalCondition } from "../../../../../data/automation";
import { HomeAssistant } from "../../../../../types";
import "../ha-automation-condition";
import { ConditionElement } from "../ha-automation-condition-row";
import { HaStateCondition } from "./ha-automation-condition-state";
@customElement("ha-automation-condition-logical")
export class HaLogicalCondition extends LitElement implements ConditionElement {
@@ -14,14 +13,7 @@ export class HaLogicalCondition extends LitElement implements ConditionElement {
@property() public condition!: LogicalCondition;
public static get defaultConfig() {
return {
conditions: [
{
condition: "state",
...HaStateCondition.defaultConfig,
},
] as Condition[],
};
return { conditions: [{ condition: "state" }] };
}
protected render() {
@@ -464,7 +464,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
) {
return;
}
// Wait for dialog to complete closing
// Wait for dialog to complate closing
await new Promise((resolve) => setTimeout(resolve, 0));
}
showAutomationEditor({
@@ -4,12 +4,12 @@ import {
mdiInformationOutline,
mdiPencil,
mdiPencilOff,
mdiPlayCircleOutline,
mdiPlus,
} from "@mdi/js";
import "@polymer/paper-tooltip/paper-tooltip";
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { formatDateTime } from "../../../common/datetime/format_date_time";
@@ -22,7 +22,6 @@ import "../../../components/ha-button-related-filter-menu";
import "../../../components/ha-fab";
import "../../../components/ha-icon-button";
import "../../../components/ha-svg-icon";
import "../../../components/ha-icon-overflow-menu";
import {
AutomationEntity,
triggerAutomationActions,
@@ -136,7 +135,7 @@ class HaAutomationPicker extends LitElement {
template: (_info, automation: any) => html`
<mwc-button
.automation=${automation}
@click=${this._triggerRunActions}
@click=${this._runActions}
.disabled=${UNAVAILABLE_STATES.includes(automation.state)}
>
${this.hass.localize("ui.card.automation.trigger")}
@@ -144,73 +143,78 @@ class HaAutomationPicker extends LitElement {
`,
};
}
columns.actions = {
columns.info = {
title: "",
type: "overflow-menu",
type: "icon-button",
template: (_info, automation) => html`
<ha-icon-button
.automation=${automation}
@click=${this._showInfo}
.label=${this.hass.localize(
"ui.panel.config.automation.picker.show_info_automation"
)}
.path=${mdiInformationOutline}
></ha-icon-button>
`,
};
columns.trace = {
title: "",
type: "icon-button",
template: (_info, automation: any) => html`
<ha-icon-overflow-menu
.hass=${this.hass}
.narrow=${this.narrow}
.items=${[
// Info Button
{
path: mdiInformationOutline,
label: this.hass.localize(
"ui.panel.config.automation.picker.show_info_automation"
),
action: () => this._showInfo(automation),
},
// Trigger Button
{
path: mdiPlayCircleOutline,
label: this.hass.localize("ui.card.automation.trigger"),
narrowOnly: true,
action: () => this._runActions(automation),
},
// Trace Button
{
path: mdiHistory,
disabled: !automation.attributes.id,
label: this.hass.localize(
"ui.panel.config.automation.picker.dev_automation"
),
tooltip: !automation.attributes.id
? this.hass.localize(
"ui.panel.config.automation.picker.dev_only_editable"
)
: "",
action: () => {
if (automation.attributes.id) {
navigate(
`/config/automation/trace/${automation.attributes.id}`
);
}
},
},
// Edit Button
{
path: automation.attributes.id ? mdiPencil : mdiPencilOff,
disabled: !automation.attributes.id,
label: this.hass.localize(
"ui.panel.config.automation.picker.edit_automation"
),
tooltip: !automation.attributes.id
? this.hass.localize(
"ui.panel.config.automation.picker.dev_only_editable"
)
: "",
action: () => {
if (automation.attributes.id) {
navigate(
`/config/automation/edit/${automation.attributes.id}`
);
}
},
},
]}
style="color: var(--secondary-text-color)"
<a
href=${ifDefined(
automation.attributes.id
? `/config/automation/trace/${automation.attributes.id}`
: undefined
)}
>
</ha-icon-overflow-menu>
<ha-icon-button
.label=${this.hass.localize(
"ui.panel.config.automation.picker.dev_automation"
)}
.path=${mdiHistory}
.disabled=${!automation.attributes.id}
></ha-icon-button>
</a>
${!automation.attributes.id
? html`
<paper-tooltip animation-delay="0" position="left">
${this.hass.localize(
"ui.panel.config.automation.picker.dev_only_editable"
)}
</paper-tooltip>
`
: ""}
`,
};
columns.edit = {
title: "",
type: "icon-button",
template: (_info, automation: any) => html`
<a
href=${ifDefined(
automation.attributes.id
? `/config/automation/edit/${automation.attributes.id}`
: undefined
)}
>
<ha-icon-button
.disabled=${!automation.attributes.id}
.label=${this.hass.localize(
"ui.panel.config.automation.picker.edit_automation"
)}
.path=${automation.attributes.id ? mdiPencil : mdiPencilOff}
></ha-icon-button>
</a>
${!automation.attributes.id
? html`
<paper-tooltip animation-delay="0" position="left">
${this.hass.localize(
"ui.panel.config.automation.picker.only_editable"
)}
</paper-tooltip>
`
: ""}
`,
};
return columns;
@@ -281,8 +285,9 @@ class HaAutomationPicker extends LitElement {
this._filterValue = undefined;
}
private _showInfo(automation: AutomationEntity) {
const entityId = automation.entity_id;
private _showInfo(ev) {
ev.stopPropagation();
const entityId = ev.currentTarget.automation.entity_id;
fireEvent(this, "hass-more-info", { entityId });
}
@@ -306,12 +311,9 @@ class HaAutomationPicker extends LitElement {
});
}
private _triggerRunActions = (ev) => {
this._runActions(ev.currentTarget.automation);
};
private _runActions = (automation: AutomationEntity) => {
triggerAutomationActions(this.hass, automation.entity_id);
private _runActions = (ev) => {
const entityId = ev.currentTarget.automation.entity_id;
triggerAutomationActions(this.hass, entityId);
};
private _createNew() {
@@ -179,7 +179,6 @@ export default class HaAutomationTriggerRow extends LitElement {
"ui.panel.config.automation.editor.triggers.type_select"
)}
.value=${this.trigger.platform}
naturalMenuWidth
@selected=${this._typeChanged}
>
${this._processedTypes(this.hass.localize).map(
@@ -10,7 +10,7 @@ import { handleChangeEvent } from "../ha-automation-trigger-row";
const includeDomains = ["zone"];
@customElement("ha-automation-trigger-geo_location")
export class HaGeolocationTrigger extends LitElement {
export default class HaGeolocationTrigger extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public trigger!: GeoLocationTrigger;
@@ -19,7 +19,7 @@ export class HaGeolocationTrigger extends LitElement {
return {
source: "",
zone: "",
event: "enter" as GeoLocationTrigger["event"],
event: "enter",
};
}
@@ -8,14 +8,14 @@ import "../../../../../components/ha-formfield";
import "../../../../../components/ha-radio";
@customElement("ha-automation-trigger-homeassistant")
export class HaHassTrigger extends LitElement {
export default class HaHassTrigger extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public trigger!: HassTrigger;
public static get defaultConfig() {
return {
event: "start" as HassTrigger["event"],
event: "start",
};
}
@@ -12,7 +12,7 @@ import { handleChangeEvent } from "../ha-automation-trigger-row";
import "../../../../../components/ha-duration-input";
@customElement("ha-automation-trigger-numeric_state")
export class HaNumericStateTrigger extends LitElement {
export default class HaNumericStateTrigger extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public trigger!: NumericStateTrigger;
@@ -20,8 +20,7 @@ export class HaSunTrigger extends LitElement implements TriggerElement {
public static get defaultConfig() {
return {
event: "sunrise" as SunTrigger["event"],
offset: 0,
event: "sunrise",
};
}
@@ -27,7 +27,7 @@ export class HaZoneTrigger extends LitElement {
return {
entity_id: "",
zone: "",
event: "enter" as ZoneTrigger["event"],
event: "enter",
};
}
@@ -29,7 +29,6 @@ import {
Blueprints,
deleteBlueprint,
} from "../../../data/blueprint";
import { showScriptEditor } from "../../../data/script";
import {
showAlertDialog,
showConfirmationDialog,
@@ -53,12 +52,6 @@ const createNewFunctions = {
use_blueprint: { path: blueprintMeta.path },
});
},
script: (blueprintMeta: BlueprintMetaDataPath) => {
showScriptEditor({
alias: blueprintMeta.name,
use_blueprint: { path: blueprintMeta.path },
});
},
};
@customElement("ha-blueprint-overview")
@@ -69,38 +62,27 @@ class HaBlueprintOverview extends LitElement {
@property({ type: Boolean }) public narrow!: boolean;
@property({ attribute: false }) public route!: Route;
@property() public route!: Route;
@property({ attribute: false }) public blueprints!: Record<
string,
Blueprints
>;
@property() public blueprints!: Blueprints;
private _processedBlueprints = memoizeOne(
(blueprints: Record<string, Blueprints>) => {
const result: any[] = [];
Object.entries(blueprints).forEach(([type, typeBlueprints]) =>
Object.entries(typeBlueprints).forEach(([path, blueprint]) => {
if ("error" in blueprint) {
result.push({
name: blueprint.error,
type,
error: true,
path,
});
} else {
result.push({
...blueprint.metadata,
type,
error: false,
path,
});
}
})
);
return result;
}
);
private _processedBlueprints = memoizeOne((blueprints: Blueprints) => {
const result = Object.entries(blueprints).map(([path, blueprint]) => {
if ("error" in blueprint) {
return {
name: blueprint.error,
error: true,
path,
};
}
return {
...blueprint.metadata,
error: false,
path,
};
});
return result;
});
private _columns = memoizeOne(
(narrow, _language): DataTableColumnContainer => ({
@@ -120,20 +102,6 @@ class HaBlueprintOverview extends LitElement {
`
: undefined,
},
type: {
title: this.hass.localize(
"ui.panel.config.blueprint.overview.headers.type"
),
template: (type: string) =>
html`${this.hass.localize(
`ui.panel.config.blueprint.overview.types.${type}`
)}`,
sortable: true,
filterable: true,
hidden: narrow,
direction: "asc",
width: "10%",
},
path: {
title: this.hass.localize(
"ui.panel.config.blueprint.overview.headers.file_name"
@@ -146,27 +114,25 @@ class HaBlueprintOverview extends LitElement {
},
create: {
title: "",
width: narrow ? undefined : "20%",
type: narrow ? "icon-button" : undefined,
template: (_, blueprint: any) =>
blueprint.error
? ""
: narrow
? html`<ha-icon-button
? html` <ha-icon-button
.blueprint=${blueprint}
.label=${this.hass.localize(
`ui.panel.config.blueprint.overview.create_${blueprint.domain}`
"ui.panel.config.blueprint.overview.use_blueprint"
)}
@click=${this._createNew}
.path=${mdiRobot}
>
</ha-icon-button>`
@click=${this._createNew}
></ha-icon-button>`
: html`<mwc-button
.blueprint=${blueprint}
@click=${this._createNew}
>
${this.hass.localize(
`ui.panel.config.blueprint.overview.create_${blueprint.domain}`
"ui.panel.config.blueprint.overview.use_blueprint"
)}
</mwc-button>`,
},
@@ -25,7 +25,7 @@ class HaConfigBlueprint extends HassRouterPage {
@property() public showAdvanced!: boolean;
@property() public blueprints: Record<string, Blueprints> = {};
@property() public blueprints: Blueprints = {};
protected routerOptions: RouterOptions = {
defaultPage: "dashboard",
@@ -41,11 +41,7 @@ class HaConfigBlueprint extends HassRouterPage {
};
private async _getBlueprints() {
const [automation, script] = await Promise.all([
fetchBlueprints(this.hass, "automation"),
fetchBlueprints(this.hass, "script"),
]);
this.blueprints = { automation, script };
this.blueprints = await fetchBlueprints(this.hass, "automation");
}
protected firstUpdated(changedProps) {
@@ -25,7 +25,7 @@ class HaConfigCustomize extends LitElement {
protected render(): TemplateResult {
return html`
<hass-tabs-subpage
.hass=${this.hass}
.hass=${this.hass}
.narrow=${this.narrow}
.route=${this.route}
back-path="/config"
@@ -180,16 +180,10 @@ export class HaFormCustomize extends LocalizeMixin(PolymerElement) {
this.newAttributes
);
attrs.forEach((attr) => {
if (
attr.closed ||
attr.secondary ||
!attr.attribute ||
attr.value === null ||
attr.value === undefined
)
if (attr.closed || attr.secondary || !attr.attribute || !attr.value)
return;
const value = attr.type === "json" ? JSON.parse(attr.value) : attr.value;
if (value === null || value === undefined) return;
if (!value) return;
data[attr.attribute] = value;
});
@@ -15,23 +15,18 @@ import { domainIcon } from "../../../../common/entity/domain_icon";
import "../../../../components/entity/state-badge";
import "../../../../components/ha-card";
import "../../../../components/ha-icon";
import type { LovelaceRowConfig } from "../../../lovelace/entity-rows/types";
import type { HomeAssistant } from "../../../../types";
import type { HuiErrorCard } from "../../../lovelace/cards/hui-error-card";
import { HomeAssistant } from "../../../../types";
import { HuiErrorCard } from "../../../lovelace/cards/hui-error-card";
import { createRowElement } from "../../../lovelace/create-element/create-row-element";
import { addEntitiesToLovelaceView } from "../../../lovelace/editor/add-entities-to-view";
import { LovelaceRow } from "../../../lovelace/entity-rows/types";
import { showEntityEditorDialog } from "../../entities/show-dialog-entity-editor";
import { EntityRegistryStateEntry } from "../ha-config-device-page";
import { computeStateName } from "../../../../common/entity/compute_state_name";
import { stripPrefixFromEntityName } from "../../../../common/entity/strip_prefix_from_entity_name";
@customElement("ha-device-entities-card")
export class HaDeviceEntitiesCard extends LitElement {
@property() public header!: string;
@property() public deviceName!: string;
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public entities!: EntityRegistryStateEntry[];
@@ -105,8 +100,14 @@ export class HaDeviceEntitiesCard extends LitElement {
</div>
`
: html`
<div class="empty card-content">
${this.hass.localize("ui.panel.config.devices.entities.none")}
<div class="config-entry-row">
<paper-item-body two-line>
<div>
${this.hass.localize(
"ui.panel.config.devices.entities.none"
)}
</div>
</paper-item-body>
</div>
`}
</ha-card>
@@ -118,21 +119,9 @@ export class HaDeviceEntitiesCard extends LitElement {
}
private _renderEntity(entry: EntityRegistryStateEntry): TemplateResult {
const config: LovelaceRowConfig = {
entity: entry.entity_id,
};
const element = createRowElement(config);
const element = createRowElement({ entity: entry.entity_id });
if (this.hass) {
element.hass = this.hass;
const state = this.hass.states[entry.entity_id];
const name = stripPrefixFromEntityName(
computeStateName(state),
`${this.deviceName} `.toLowerCase()
);
if (name) {
config.name = name;
}
}
// @ts-ignore
element.entry = entry;
@@ -142,11 +131,7 @@ export class HaDeviceEntitiesCard extends LitElement {
private _renderEntry(entry: EntityRegistryStateEntry): TemplateResult {
return html`
<paper-icon-item
class="disabled-entry"
.entry=${entry}
@click=${this._openEditEntry}
>
<paper-icon-item .entry=${entry} @click=${this._openEditEntry}>
<ha-svg-icon
slot="item-icon"
.path=${domainIcon(computeDomain(entry.entity_id))}
@@ -181,8 +166,7 @@ export class HaDeviceEntitiesCard extends LitElement {
this.hass,
this.entities
.filter((entity) => !entity.disabled_by)
.map((entity) => entity.entity_id),
this.deviceName
.map((entity) => entity.entity_id)
);
}
@@ -204,9 +188,6 @@ export class HaDeviceEntitiesCard extends LitElement {
.disabled-entry {
color: var(--secondary-text-color);
}
#entities {
margin-top: -24px; /* match the spacing between card title and content of the device info card above it */
}
#entities > * {
margin: 8px 16px 8px 8px;
}
@@ -215,16 +196,12 @@ export class HaDeviceEntitiesCard extends LitElement {
}
paper-icon-item {
min-height: 40px;
padding: 0 16px;
padding: 0 8px;
cursor: pointer;
--paper-item-icon-width: 48px;
}
.name {
font-size: 14px;
}
.empty {
text-align: center;
}
button.show-more {
color: var(--primary-color);
text-align: left;
@@ -18,7 +18,6 @@ import {
nodeStatus,
ZWaveJSNodeStatus,
ZWaveJSNodeIdentifiers,
SecurityClass,
} from "../../../../../../data/zwave_js";
import { haStyle } from "../../../../../../resources/styles";
import { HomeAssistant } from "../../../../../../types";
@@ -118,33 +117,13 @@ export class HaDeviceInfoZWaveJS extends LitElement {
: this.hass.localize("ui.common.no")}
</div>
<div>
${this.hass.localize(
"ui.panel.config.zwave_js.device_info.highest_security"
)}:
${this._node.highest_security_class !== null
? this.hass.localize(
`ui.panel.config.zwave_js.security_classes.${
SecurityClass[this._node.highest_security_class]
}.title`
)
${this.hass.localize("ui.panel.config.zwave_js.device_info.is_secure")}:
${this._node.is_secure === true
? this.hass.localize("ui.common.yes")
: this._node.is_secure === false
? this.hass.localize(
"ui.panel.config.zwave_js.security_classes.none.title"
)
? this.hass.localize("ui.common.no")
: this.hass.localize("ui.panel.config.zwave_js.device_info.unknown")}
</div>
<div>
${this.hass.localize(
"ui.panel.config.zwave_js.device_info.zwave_plus"
)}:
${this._node.zwave_plus_version
? this.hass.localize(
"ui.panel.config.zwave_js.device_info.zwave_plus_version",
"version",
this._node.zwave_plus_version
)
: this.hass.localize("ui.common.no")}
</div>
`;
}
+106 -105
View File
@@ -52,7 +52,6 @@ import {
loadDeviceRegistryDetailDialog,
showDeviceRegistryDetailDialog,
} from "./device-registry-detail/show-dialog-device-registry-detail";
import { computeDomain } from "../../../common/entity/compute_domain";
export interface EntityRegistryStateEntry extends EntityRegistryEntry {
stateName?: string | null;
@@ -118,19 +117,14 @@ export class HaConfigDevicePage extends LitElement {
private _entitiesByCategory = memoizeOne(
(entities: EntityRegistryEntry[]) => {
const result = groupBy(entities, (entry) =>
entry.entity_category
? entry.entity_category
: ["sensor", "binary_sensor"].includes(computeDomain(entry.entity_id))
? "sensor"
: "control"
const result = groupBy(
entities,
(entry) => entry.entity_category || "state"
) as Record<
| "control"
| "sensor"
| NonNullable<EntityRegistryEntry["entity_category"]>,
"state" | NonNullable<EntityRegistryEntry["entity_category"]>,
EntityRegistryStateEntry[]
>;
for (const key of ["control", "sensor", "diagnostic", "config"]) {
for (const key of ["state", "diagnostic", "config"]) {
if (!(key in result)) {
result[key] = [];
}
@@ -185,7 +179,6 @@ export class HaConfigDevicePage extends LitElement {
`;
}
const deviceName = computeDeviceName(device, this.hass);
const integrations = this._integrations(device, this.entries);
const entities = this._entities(this.deviceId, this.entities);
const entitiesByCategory = this._entitiesByCategory(entities);
@@ -201,13 +194,6 @@ export class HaConfigDevicePage extends LitElement {
: undefined;
const area = this._computeArea(this.areas, device);
const configurationUrlIsHomeAssistant =
device.configuration_url?.startsWith("homeassistant://") || false;
const configurationUrl = configurationUrlIsHomeAssistant
? device.configuration_url!.replace("homeassistant://", "/")
: device.configuration_url;
return html`
<hass-tabs-subpage
.hass=${this.hass}
@@ -218,7 +204,9 @@ export class HaConfigDevicePage extends LitElement {
${
this.narrow
? html`
<span slot="header">${deviceName}</span>
<span slot="header">
${computeDeviceName(device, this.hass)}
</span>
<ha-icon-button
slot="toolbar-icon"
.path=${mdiPencil}
@@ -242,7 +230,7 @@ export class HaConfigDevicePage extends LitElement {
: html`
<div class="header-name">
<div>
<h1>${deviceName}</h1>
<h1>${computeDeviceName(device, this.hass)}</h1>
${area
? html`
<a href="/config/areas/area/${area.area_id}"
@@ -329,15 +317,13 @@ export class HaConfigDevicePage extends LitElement {
: html``
}
${
configurationUrl
device.configuration_url
? html`
<div class="card-actions" slot="actions">
<a
href=${configurationUrl}
href=${device.configuration_url}
target="_blank"
rel="noopener noreferrer"
.target=${configurationUrlIsHomeAssistant
? "_self"
: "_blank"}
>
<mwc-button>
${this.hass.localize(
@@ -357,28 +343,25 @@ export class HaConfigDevicePage extends LitElement {
}
${this._renderIntegrationInfo(device, integrations)}
</ha-device-info-card>
</div>
<div class="column">
${["control", "sensor", "config", "diagnostic"].map((category) =>
// Make sure we render controls if no other cards will be rendered
entitiesByCategory[category].length > 0 ||
(entities.length === 0 && category === "control")
? html`
${["state", "config", "diagnostic"].map((category) =>
!entitiesByCategory[category].length
? ""
: html`
<ha-device-entities-card
.hass=${this.hass}
.header=${this.hass.localize(
`ui.panel.config.devices.entities.${category}`
)}
.deviceName=${deviceName}
.entities=${entitiesByCategory[category]}
.showDisabled=${device.disabled_by !== null}
>
</ha-device-entities-card>
`
: ""
)}
</div>
<div class="column">
<div class="column">
${
isComponentLoaded(this.hass, "automation")
? html`
@@ -437,7 +420,7 @@ export class HaConfigDevicePage extends LitElement {
: "";
})
: html`
<div class="card-content">
<paper-item class="no-link">
${this.hass.localize(
"ui.panel.config.devices.add_prompt",
"name",
@@ -445,82 +428,92 @@ export class HaConfigDevicePage extends LitElement {
"ui.panel.config.devices.automation.automations"
)
)}
</div>
</paper-item>
`}
</ha-card>
`
: ""
}
</div>
<div class="column">
${
isComponentLoaded(this.hass, "scene") && entities.length
? html`
<ha-card>
<h1 class="card-header">
${this.hass.localize(
"ui.panel.config.devices.scene.scenes"
)}
<h1 class="card-header">
${this.hass.localize(
"ui.panel.config.devices.scene.scenes"
)}
<ha-icon-button
@click=${this._createScene}
.disabled=${device.disabled_by}
.label=${device.disabled_by
? this.hass.localize(
"ui.panel.config.devices.scene.create_disabled"
)
: this.hass.localize(
"ui.panel.config.devices.scene.create"
)}
.path=${mdiPlusCircle}
></ha-icon-button>
</h1>
<ha-icon-button @click=${
this._createScene
} .disabled=${device.disabled_by}
.label=${
device.disabled_by
? this.hass.localize(
"ui.panel.config.devices.scene.create_disabled"
)
: this.hass.localize(
"ui.panel.config.devices.scene.create"
)
}
.path=${mdiPlusCircle}
></ha-icon-button>
</h1>
${this._related?.scene?.length
? this._related.scene.map((scene) => {
const entityState = this.hass.states[scene];
return entityState
? html`
<div>
<a
href=${ifDefined(
entityState.attributes.id
? `/config/scene/edit/${entityState.attributes.id}`
: undefined
)}
>
<paper-item
.scene=${entityState}
.disabled=${!entityState.attributes.id}
>
<paper-item-body>
${computeStateName(entityState)}
</paper-item-body>
<ha-icon-next></ha-icon-next>
</paper-item>
</a>
${!entityState.attributes.id
? html`
<paper-tooltip animation-delay="0">
${this.hass.localize(
"ui.panel.config.devices.cant_edit"
)}
</paper-tooltip>
`
: ""}
</div>
`
: "";
})
: html`
<div class="card-content">
${this.hass.localize(
"ui.panel.config.devices.add_prompt",
"name",
this.hass.localize(
"ui.panel.config.devices.scene.scenes"
)
)}
</div>
`}
${
this._related?.scene?.length
? this._related.scene.map((scene) => {
const entityState = this.hass.states[scene];
return entityState
? html`
<div>
<a
href=${ifDefined(
entityState.attributes.id
? `/config/scene/edit/${entityState.attributes.id}`
: undefined
)}
>
<paper-item
.scene=${entityState}
.disabled=${!entityState.attributes
.id}
>
<paper-item-body>
${computeStateName(entityState)}
</paper-item-body>
<ha-icon-next></ha-icon-next>
</paper-item>
</a>
${!entityState.attributes.id
? html`
<paper-tooltip
animation-delay="0"
>
${this.hass.localize(
"ui.panel.config.devices.cant_edit"
)}
</paper-tooltip>
`
: ""}
</div>
`
: "";
})
: html`
<paper-item class="no-link">
${this.hass.localize(
"ui.panel.config.devices.add_prompt",
"name",
this.hass.localize(
"ui.panel.config.devices.scene.scenes"
)
)}
</paper-item>
`
}
</ha-card>
</ha-card>
`
: ""
@@ -565,7 +558,7 @@ export class HaConfigDevicePage extends LitElement {
: "";
})
: html`
<div class="card-content">
<paper-item class="no-link">
${this.hass.localize(
"ui.panel.config.devices.add_prompt",
"name",
@@ -573,14 +566,14 @@ export class HaConfigDevicePage extends LitElement {
"ui.panel.config.devices.script.scripts"
)
)}
</div>
</paper-item>
`}
</ha-card>
`
: ""
}
</div>
</div>
</div>
</ha-config-section>
</hass-tabs-subpage> `;
}
@@ -960,11 +953,19 @@ export class HaConfigDevicePage extends LitElement {
font-size: var(--paper-font-body1_-_font-size);
}
paper-item.no-link {
cursor: default;
}
a {
text-decoration: none;
color: var(--primary-color);
}
ha-card {
padding-bottom: 8px;
}
ha-card a {
color: var(--primary-text-color);
}
@@ -73,6 +73,7 @@ export class DialogEnergyBatterySettings
.label=${this.hass.localize(
"ui.panel.config.energy.battery.dialog.energy_into_battery"
)}
entities-only
@value-changed=${this._statisticToChanged}
></ha-statistic-picker>
@@ -84,6 +85,7 @@ export class DialogEnergyBatterySettings
.label=${this.hass.localize(
"ui.panel.config.energy.battery.dialog.energy_out_of_battery"
)}
entities-only
@value-changed=${this._statisticFromChanged}
></ha-statistic-picker>
@@ -74,6 +74,7 @@ export class DialogEnergyDeviceSettings
.label=${this.hass.localize(
"ui.panel.config.energy.device_consumption.dialog.device_consumption_energy"
)}
entities-only
@value-changed=${this._statisticChanged}
></ha-statistic-picker>
@@ -106,6 +106,7 @@ export class DialogEnergyGasSettings
? "kWh"
: "m³"
})`}
entities-only
@value-changed=${this._statisticChanged}
></ha-statistic-picker>
@@ -103,6 +103,7 @@ export class DialogEnergyGridFlowSettings
.label=${this.hass.localize(
`ui.panel.config.energy.grid.flow_dialog.${this._params.direction}.energy_stat`
)}
entities-only
@value-changed=${this._statisticChanged}
></ha-statistic-picker>
@@ -85,6 +85,7 @@ export class DialogEnergySolarSettings
.label=${this.hass.localize(
"ui.panel.config.energy.solar.dialog.solar_production_energy"
)}
entities-only
@value-changed=${this._statisticChanged}
></ha-statistic-picker>
@@ -202,12 +202,12 @@ class DialogZWaveJSAddNode extends LitElement {
(securityClass) => html`<ha-formfield
.label=${html`<b
>${this.hass.localize(
`ui.panel.config.zwave_js.security_classes.${SecurityClass[securityClass]}.title`
`ui.panel.config.zwave_js.add_node.security_classes.${SecurityClass[securityClass]}.title`
)}</b
>
<div class="secondary">
${this.hass.localize(
`ui.panel.config.zwave_js.security_classes.${SecurityClass[securityClass]}.description`
`ui.panel.config.zwave_js.add_node.security_classes.${SecurityClass[securityClass]}.description`
)}
</div>`}
>
@@ -1,10 +1,9 @@
import { mdiClose, mdiContentCopy } from "@mdi/js";
import { mdiClose, mdiContentCopy, mdiPackageVariant } from "@mdi/js";
import "@polymer/paper-tooltip/paper-tooltip";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { property, state } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event";
import { copyToClipboard } from "../../../common/util/copy-clipboard";
import "../../../components/ha-alert";
import "../../../components/ha-dialog";
import "../../../components/ha-header-bar";
import "../../../components/ha-icon-button";
@@ -97,11 +96,12 @@ class DialogSystemLogDetail extends LitElement {
></ha-icon-button>
</ha-header-bar>
${this.isCustomIntegration
? html`<ha-alert alert-type="warning">
? html`<div class="custom">
<ha-svg-icon .path=${mdiPackageVariant}></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.logs.error_from_custom_integration"
)}
</ha-alert>`
</div>`
: ""}
<div class="contents">
<p>
@@ -215,9 +215,9 @@ class DialogSystemLogDetail extends LitElement {
margin-bottom: 0;
font-family: var(--code-font-family, monospace);
}
ha-alert {
display: block;
margin: -4px 0;
.custom {
padding: 8px 16px;
background-color: var(--warning-color);
}
.contents {
padding: 16px;
@@ -1,205 +0,0 @@
import "@polymer/paper-input/paper-input";
import { css, CSSResultGroup, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/ha-blueprint-picker";
import "../../../components/ha-card";
import "../../../components/ha-circular-progress";
import "../../../components/ha-markdown";
import "../../../components/ha-selector/ha-selector";
import "../../../components/ha-settings-row";
import {
BlueprintOrError,
Blueprints,
fetchBlueprints,
} from "../../../data/blueprint";
import { BlueprintScriptConfig } from "../../../data/script";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import "../ha-config-section";
@customElement("blueprint-script-editor")
export class HaBlueprintScriptEditor extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) public isWide!: boolean;
@property({ reflect: true, type: Boolean }) public narrow!: boolean;
@property({ attribute: false }) public config!: BlueprintScriptConfig;
@state() private _blueprints?: Blueprints;
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
this._getBlueprints();
}
private get _blueprint(): BlueprintOrError | undefined {
if (!this._blueprints) {
return undefined;
}
return this._blueprints[this.config.use_blueprint.path];
}
protected render() {
const blueprint = this._blueprint;
return html` <ha-config-section vertical .isWide=${this.isWide}>
<span slot="header"
>${this.hass.localize(
"ui.panel.config.automation.editor.blueprint.header"
)}</span
>
<ha-card>
<div class="blueprint-picker-container">
${this._blueprints
? Object.keys(this._blueprints).length
? html`
<ha-blueprint-picker
.hass=${this.hass}
.label=${this.hass.localize(
"ui.panel.config.automation.editor.blueprint.blueprint_to_use"
)}
.blueprints=${this._blueprints}
.value=${this.config.use_blueprint.path}
@value-changed=${this._blueprintChanged}
></ha-blueprint-picker>
`
: this.hass.localize(
"ui.panel.config.automation.editor.blueprint.no_blueprints"
)
: html`<ha-circular-progress active></ha-circular-progress>`}
</div>
${this.config.use_blueprint.path
? blueprint && "error" in blueprint
? html`<p class="warning padding">
There is an error in this Blueprint: ${blueprint.error}
</p>`
: html`${blueprint?.metadata.description
? html`<ha-markdown
class="card-content"
breaks
.content=${blueprint.metadata.description}
></ha-markdown>`
: ""}
${blueprint?.metadata?.input &&
Object.keys(blueprint.metadata.input).length
? Object.entries(blueprint.metadata.input).map(
([key, value]) =>
html`<ha-settings-row .narrow=${this.narrow}>
<span slot="heading">${value?.name || key}</span>
<span slot="description">${value?.description}</span>
${value?.selector
? html`<ha-selector
.hass=${this.hass}
.selector=${value.selector}
.key=${key}
.value=${(this.config.use_blueprint.input &&
this.config.use_blueprint.input[key]) ??
value?.default}
@value-changed=${this._inputChanged}
></ha-selector>`
: html`<paper-input
.key=${key}
required
.value=${(this.config.use_blueprint.input &&
this.config.use_blueprint.input[key]) ??
value?.default}
@value-changed=${this._inputChanged}
no-label-float
></paper-input>`}
</ha-settings-row>`
)
: html`<p class="padding">
${this.hass.localize(
"ui.panel.config.automation.editor.blueprint.no_inputs"
)}
</p>`}`
: ""}
</ha-card>
</ha-config-section>`;
}
private async _getBlueprints() {
this._blueprints = await fetchBlueprints(this.hass, "script");
}
private _blueprintChanged(ev) {
ev.stopPropagation();
if (this.config.use_blueprint.path === ev.detail.value) {
return;
}
fireEvent(this, "value-changed", {
value: {
...this.config,
use_blueprint: {
path: ev.detail.value,
},
},
});
}
private _inputChanged(ev) {
ev.stopPropagation();
const target = ev.target as any;
const key = target.key;
const value = ev.detail.value;
if (
(this.config.use_blueprint.input &&
this.config.use_blueprint.input[key] === value) ||
(!this.config.use_blueprint.input && value === "")
) {
return;
}
const input = { ...this.config.use_blueprint.input, [key]: value };
if (value === "" || value === undefined) {
delete input[key];
}
fireEvent(this, "value-changed", {
value: {
...this.config,
use_blueprint: {
...this.config.use_blueprint,
input,
},
},
});
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
.padding {
padding: 16px;
}
.blueprint-picker-container {
padding: 16px;
}
p {
margin-bottom: 0;
}
ha-settings-row {
--paper-time-input-justify-content: flex-end;
border-top: 1px solid var(--divider-color);
}
:host(:not([narrow])) ha-settings-row paper-input {
width: 60%;
}
:host(:not([narrow])) ha-settings-row ha-selector {
width: 60%;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"blueprint-script-editor": HaBlueprintScriptEditor;
}
}
+89 -120
View File
@@ -38,7 +38,6 @@ import {
Action,
deleteScript,
getScriptEditorInitData,
ManualScriptConfig,
MODES,
MODES_MAX,
ScriptConfig,
@@ -56,7 +55,6 @@ import "../automation/action/ha-automation-action";
import { HaDeviceAction } from "../automation/action/types/ha-automation-action-device_id";
import "../ha-config-section";
import { configSections } from "../ha-panel-config";
import "./blueprint-script-editor";
export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -238,62 +236,60 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
>
</paper-input>`
: ""}
${"use_blueprint" in this._config
? ""
: html`<p>
${this.hass.localize(
"ui.panel.config.script.editor.modes.description",
"documentation_link",
html`<a
href=${documentationUrl(
this.hass,
"/integrations/script/#script-modes"
)}
target="_blank"
rel="noreferrer"
>${this.hass.localize(
"ui.panel.config.script.editor.modes.documentation"
)}</a
>`
)}
</p>
<paper-dropdown-menu-light
.label=${this.hass.localize(
"ui.panel.config.script.editor.modes.label"
)}
no-animations
>
<paper-listbox
slot="dropdown-content"
.selected=${this._config.mode
? MODES.indexOf(this._config.mode)
: 0}
@iron-select=${this._modeChanged}
>
${MODES.map(
(mode) => html`
<paper-item .mode=${mode}>
${this.hass.localize(
`ui.panel.config.script.editor.modes.${mode}`
) || mode}
</paper-item>
`
)}
</paper-listbox>
</paper-dropdown-menu-light>
${this._config.mode &&
MODES_MAX.includes(this._config.mode)
? html`<paper-input
.label=${this.hass.localize(
`ui.panel.config.script.editor.max.${this._config.mode}`
)}
type="number"
name="max"
.value=${this._config.max || "10"}
@value-changed=${this._valueChanged}
>
</paper-input>`
: html``} `}
<p>
${this.hass.localize(
"ui.panel.config.script.editor.modes.description",
"documentation_link",
html`<a
href=${documentationUrl(
this.hass,
"/integrations/script/#script-modes"
)}
target="_blank"
rel="noreferrer"
>${this.hass.localize(
"ui.panel.config.script.editor.modes.documentation"
)}</a
>`
)}
</p>
<paper-dropdown-menu-light
.label=${this.hass.localize(
"ui.panel.config.script.editor.modes.label"
)}
no-animations
>
<paper-listbox
slot="dropdown-content"
.selected=${this._config.mode
? MODES.indexOf(this._config.mode)
: 0}
@iron-select=${this._modeChanged}
>
${MODES.map(
(mode) => html`
<paper-item .mode=${mode}>
${this.hass.localize(
`ui.panel.config.script.editor.modes.${mode}`
) || mode}
</paper-item>
`
)}
</paper-listbox>
</paper-dropdown-menu-light>
${this._config.mode &&
MODES_MAX.includes(this._config.mode)
? html`<paper-input
.label=${this.hass.localize(
`ui.panel.config.script.editor.max.${this._config.mode}`
)}
type="number"
name="max"
.value=${this._config.max || "10"}
@value-changed=${this._valueChanged}
>
</paper-input>`
: html``}
</div>
${this.scriptEntityId
? html`
@@ -327,48 +323,37 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
</ha-card>
</ha-config-section>
${"use_blueprint" in this._config
? html`<blueprint-script-editor
.hass=${this.hass}
.narrow=${this.narrow}
.isWide=${this.isWide}
.config=${this._config}
@value-changed=${this._configChanged}
></blueprint-script-editor>`
: html`<ha-config-section
vertical
.isWide=${this.isWide}
<ha-config-section vertical .isWide=${this.isWide}>
<span slot="header">
${this.hass.localize(
"ui.panel.config.script.editor.sequence"
)}
</span>
<span slot="introduction">
<p>
${this.hass.localize(
"ui.panel.config.script.editor.sequence_sentence"
)}
</p>
<a
href=${documentationUrl(
this.hass,
"/docs/scripts/"
)}
target="_blank"
rel="noreferrer"
>
<span slot="header">
${this.hass.localize(
"ui.panel.config.script.editor.sequence"
)}
</span>
<span slot="introduction">
<p>
${this.hass.localize(
"ui.panel.config.script.editor.sequence_sentence"
)}
</p>
<a
href=${documentationUrl(
this.hass,
"/docs/scripts/"
)}
target="_blank"
rel="noreferrer"
>
${this.hass.localize(
"ui.panel.config.script.editor.link_available_actions"
)}
</a>
</span>
<ha-automation-action
.actions=${this._config.sequence}
@value-changed=${this._sequenceChanged}
.hass=${this.hass}
></ha-automation-action>
</ha-config-section>`}
${this.hass.localize(
"ui.panel.config.script.editor.link_available_actions"
)}
</a>
</span>
<ha-automation-action
.actions=${this._config.sequence}
@value-changed=${this._sequenceChanged}
.hass=${this.hass}
></ha-automation-action>
</ha-config-section>
`
: ""}
</div>
@@ -442,7 +427,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
(!oldScript || oldScript !== this.scriptEntityId)
) {
this.hass
.callApi<ManualScriptConfig>(
.callApi<ScriptConfig>(
"GET",
`config/script/config/${computeObjectId(this.scriptEntityId)}`
)
@@ -481,16 +466,11 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
) {
const initData = getScriptEditorInitData();
this._dirty = !!initData;
const baseConfig: Partial<ScriptConfig> = {
alias: this.hass.localize("ui.panel.config.script.editor.default_name"),
};
if (!initData || !("use_blueprint" in initData)) {
baseConfig.sequence = [{ ...HaDeviceAction.defaultConfig }];
}
this._config = {
...baseConfig,
alias: this.hass.localize("ui.panel.config.script.editor.default_name"),
sequence: [{ ...HaDeviceAction.defaultConfig }],
...initData,
} as ScriptConfig;
};
}
}
@@ -568,11 +548,6 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
this._dirty = true;
}
private _configChanged(ev) {
this._config = ev.detail.value;
this._dirty = true;
}
private _sequenceChanged(ev: CustomEvent): void {
this._config = { ...this._config!, sequence: ev.detail.value as Action[] };
this._errors = undefined;
@@ -632,7 +607,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
) {
return;
}
// Wait for dialog to complete closing
// Wait for dialog to complate closing
await new Promise((resolve) => setTimeout(resolve, 0));
}
showScriptEditor({
@@ -774,9 +749,3 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
}
customElements.define("ha-script-editor", HaScriptEditor);
declare global {
interface HTMLElementTagNameMap {
"ha-script-editor": HaScriptEditor;
}
}
@@ -50,21 +50,21 @@ class HaPanelDevStatistics extends LitElement {
private _columns = memoizeOne(
(localize): DataTableColumnContainer => ({
state: {
title: "Name",
title: "Entity",
sortable: true,
filterable: true,
grows: true,
template: (entityState, data: any) =>
html`${entityState
? computeStateName(entityState)
: data.name || data.statistic_id}`,
: data.statistic_id}`,
},
statistic_id: {
title: "Statistic id",
sortable: true,
filterable: true,
hidden: this.narrow,
width: "20%",
width: "30%",
},
unit_of_measurement: {
title: "Unit",
@@ -72,12 +72,6 @@ class HaPanelDevStatistics extends LitElement {
filterable: true,
width: "10%",
},
source: {
title: "Source",
sortable: true,
filterable: true,
width: "10%",
},
issues: {
title: "Issue",
sortable: true,
@@ -152,7 +146,6 @@ class HaPanelDevStatistics extends LitElement {
this._data.push({
statistic_id: statisticId,
unit_of_measurement: "",
source: "",
state: this.hass.states[statisticId],
issues: issues[statisticId],
});
-3
View File
@@ -12,7 +12,6 @@ import {
} from "date-fns";
import { css, html, LitElement, PropertyValues } from "lit";
import { property, state } from "lit/decorators";
import { extractSearchParam } from "../../common/url/search-params";
import { computeRTL } from "../../common/util/compute_rtl";
import "../../components/chart/state-history-charts";
import "../../components/entity/ha-entity-picker";
@@ -137,8 +136,6 @@ class HaPanelHistory extends LitElement {
[this.hass.localize("ui.components.date-range-picker.ranges.last_week")]:
[addDays(weekStart, -7), addDays(weekEnd, -7)],
};
this._entityId = extractSearchParam("entity_id") ?? "";
}
protected updated(changedProps: PropertyValues) {
-5
View File
@@ -222,7 +222,6 @@ class HaLogbook extends LitElement {
}?run_id=${
this.traceContexts[item.context_id!].run_id
}`}
@click=${this._close}
>${this.hass.localize(
"ui.components.logbook.show_trace"
)}</a
@@ -255,10 +254,6 @@ class HaLogbook extends LitElement {
});
}
private _close(): void {
setTimeout(() => fireEvent(this, "closed"), 500);
}
static get styles(): CSSResultGroup {
return [
haStyle,
-3
View File
@@ -33,7 +33,6 @@ import "../../layouts/ha-app-layout";
import { haStyle } from "../../resources/styles";
import { HomeAssistant } from "../../types";
import "./ha-logbook";
import { extractSearchParam } from "../../common/url/search-params";
@customElement("ha-panel-logbook")
export class HaPanelLogbook extends LitElement {
@@ -159,8 +158,6 @@ export class HaPanelLogbook extends LitElement {
[this.hass.localize("ui.components.date-range-picker.ranges.last_week")]:
[addDays(weekStart, -7), addDays(weekEnd, -7)],
};
this._entityId = extractSearchParam("entity_id") ?? "";
}
protected updated(changedProps: PropertyValues<this>) {
@@ -8,18 +8,17 @@ import {
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { customElement, property, state, query } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { fireEvent } from "../../../common/dom/fire_event";
import { alarmPanelIcon } from "../../../common/entity/alarm_panel_icon";
import "../../../components/ha-card";
import "../../../components/ha-chip";
import "../../../components/ha-label-badge";
import {
callAlarmAction,
FORMAT_NUMBER,
} from "../../../data/alarm_control_panel";
import { UNAVAILABLE } from "../../../data/entity";
import type { HomeAssistant } from "../../../types";
import { findEntities } from "../common/find-entities";
import { createEntityNotFoundWarning } from "../components/hui-warning";
@@ -145,24 +144,19 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
`;
}
const stateLabel = this._stateDisplay(stateObj.state);
return html`
<ha-card>
<h1 class="card-header">
${this._config.name ||
stateObj.attributes.friendly_name ||
stateLabel}
<ha-chip
hasIcon
class=${classMap({ [stateObj.state]: true })}
@click=${this._handleMoreInfo}
>
<ha-svg-icon slot="icon" .path=${alarmPanelIcon(stateObj.state)}>
</ha-svg-icon>
${stateLabel}
</ha-chip>
</h1>
<ha-card
.header=${this._config.name ||
stateObj.attributes.friendly_name ||
this._stateDisplay(stateObj.state)}
>
<ha-label-badge
class=${classMap({ [stateObj.state]: true })}
.label=${this._stateIconLabel(stateObj.state)}
@click=${this._handleMoreInfo}
>
<ha-svg-icon .path=${alarmPanelIcon(stateObj.state)}></ha-svg-icon>
</ha-label-badge>
<div id="armActions" class="actions">
${(stateObj.state === "disarmed"
? this._config.states!
@@ -221,16 +215,23 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
`;
}
private _stateIconLabel(entityState: string): string {
const stateLabel = entityState.split("_").pop();
return stateLabel === "disarmed" ||
stateLabel === "triggered" ||
!stateLabel
? ""
: this._stateDisplay(entityState);
}
private _actionDisplay(entityState: string): string {
return this.hass!.localize(`ui.card.alarm_control_panel.${entityState}`);
}
private _stateDisplay(entityState: string): string {
return entityState === UNAVAILABLE
? this.hass!.localize("state.default.unavailable")
: this.hass!.localize(
`component.alarm_control_panel.state._.${entityState}`
) || entityState;
return this.hass!.localize(
`component.alarm_control_panel.state._.${entityState}`
);
}
private _handlePadClick(e: MouseEvent): void {
@@ -272,20 +273,15 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
--alarm-state-color: var(--alarm-color-armed);
}
ha-chip {
--ha-chip-background-color: var(--alarm-state-color);
--ha-chip-text-color: var(--text-primary-color);
line-height: initial;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.unavailable {
--alarm-state-color: var(--state-unavailable-color);
ha-label-badge {
--ha-label-badge-color: var(--alarm-state-color);
--label-badge-text-color: var(--alarm-state-color);
--label-badge-background-color: var(--card-background-color);
color: var(--alarm-state-color);
position: absolute;
right: 12px;
top: 8px;
cursor: pointer;
}
.disarmed {
+90 -13
View File
@@ -1,3 +1,4 @@
import { mdiArrowDown, mdiArrowUp } from "@mdi/js";
import {
css,
CSSResultGroup,
@@ -7,6 +8,7 @@ import {
TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { ifDefined } from "lit/directives/if-defined";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { fireEvent } from "../../../common/dom/fire_event";
@@ -16,10 +18,12 @@ import { computeStateDomain } from "../../../common/entity/compute_state_domain"
import { computeStateName } from "../../../common/entity/compute_state_name";
import { isValidEntityId } from "../../../common/entity/valid_entity_id";
import { formatNumber } from "../../../common/number/format_number";
import { round } from "../../../common/number/round";
import { iconColorCSS } from "../../../common/style/icon_color_css";
import "../../../components/ha-card";
import "../../../components/ha-icon";
import { UNAVAILABLE_STATES } from "../../../data/entity";
import { fetchRecent } from "../../../data/history";
import { HomeAssistant } from "../../../types";
import { formatAttributeValue } from "../../../util/hass-attributes-util";
import { computeCardSize } from "../common/compute-card-size";
@@ -66,8 +70,14 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
@state() private _config?: EntityCardConfig;
@state() private _lastState?: number;
private _footerElement?: HuiErrorCard | LovelaceHeaderFooter;
private _date?: Date;
private _fetching = false;
public setConfig(config: EntityCardConfig): void {
if (!config.entity) {
throw new Error("Entity must be specified");
@@ -76,7 +86,10 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
throw new Error("Invalid entity");
}
this._config = config;
this._config = {
hours_to_show: 24,
...config,
};
if (this._config.footer) {
this._footerElement = createHeaderFooterElement(this._config.footer);
@@ -115,23 +128,38 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
: !UNAVAILABLE_STATES.includes(stateObj.state);
const name = this._config.name || computeStateName(stateObj);
const trend = this._lastState
? round((Number(stateObj.state) / this._lastState) * 100, 0)
: undefined;
return html`
<ha-card @click=${this._handleClick} tabindex="0">
<div class="header">
<div class="name" .title=${name}>${name}</div>
<div class="icon">
<ha-state-icon
.icon=${this._config.icon}
.state=${stateObj}
data-domain=${ifDefined(
this._config.state_color ||
(domain === "light" && this._config.state_color !== false)
? domain
: undefined
)}
data-state=${stateObj ? computeActiveState(stateObj) : ""}
></ha-state-icon>
${this._config.show_trend && trend
? html`
<div class="trend ${classMap({ error: trend < 100 })}">
<ha-svg-icon
.path=${trend < 100 ? mdiArrowDown : mdiArrowUp}
></ha-svg-icon>
${trend} %
</div>
`
: html`
<ha-state-icon
.icon=${this._config.icon}
.state=${stateObj}
data-domain=${ifDefined(
this._config.state_color ||
(domain === "light" &&
this._config.state_color !== false)
? domain
: undefined
)}
data-state=${stateObj ? computeActiveState(stateObj) : ""}
></ha-state-icon>
`}
</div>
</div>
<div class="info">
@@ -177,7 +205,11 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (!this._config || !this.hass) {
if (
!this._config ||
!this.hass ||
(this._fetching && !changedProps.has("_config"))
) {
return;
}
@@ -194,12 +226,46 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
) {
applyThemesOnElement(this, this.hass.themes, this._config!.theme);
}
if (changedProps.has("_config")) {
if (!oldConfig || oldConfig.entity !== this._config.entity) {
this._lastState = undefined;
}
this._getStateHistory();
} else if (Date.now() - this._date!.getTime() >= 60000) {
this._getStateHistory();
}
}
private _handleClick(): void {
fireEvent(this, "hass-more-info", { entityId: this._config!.entity });
}
private async _getStateHistory(): Promise<void> {
if (this._fetching) {
return;
}
this._fetching = true;
const now = new Date();
const startTime = new Date(
new Date().setHours(now.getHours() - this._config!.hours_to_show!)
);
const stateHistory = await fetchRecent(
this.hass!,
this._config!.entity,
startTime,
startTime
);
this._lastState = Number(stateHistory[0][0].state);
this._date = now;
this._fetching = false;
}
static get styles(): CSSResultGroup {
return [
iconColorCSS,
@@ -234,6 +300,17 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
line-height: 40px;
}
.trend {
font-size: 16px;
color: var(--success-color);
display: flex;
align-items: center;
}
.trend.error {
color: var(--error-color);
}
.info {
padding: 0px 16px 16px;
margin-top: -4px;
@@ -171,7 +171,6 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard {
}
ha-markdown {
padding: 0 16px 16px;
word-wrap: break-word;
}
ha-markdown.no-header {
padding-top: 16px;
@@ -51,6 +51,7 @@ class HuiSensorCard extends HuiEntityCard {
const entityCardConfig: EntityCardConfig = {
...cardConfig,
hours_to_show,
type: "entity",
};
@@ -78,7 +78,7 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard {
}
const configEntities = config.entities
? processConfigEntities(config.entities, false)
? processConfigEntities(config.entities)
: [];
this._entities = [];
@@ -87,14 +87,7 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
}
public getCardSize(): number {
let cardSize = 0;
if (this._config?.show_current !== false) {
cardSize += 2;
}
if (this._config?.show_forecast !== false) {
cardSize += 3;
}
return cardSize;
return this._config?.show_forecast !== false ? 5 : 2;
}
public setConfig(config: WeatherForecastCardConfig): void {
@@ -175,7 +168,6 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
stateObj.attributes.forecast?.length
? stateObj.attributes.forecast.slice(0, this._veryVeryNarrow ? 3 : 5)
: undefined;
const weather = !forecast || this._config?.show_current !== false;
let hourly: boolean | undefined;
let dayNight: boolean | undefined;
@@ -210,81 +202,74 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
hasAction(this._config.tap_action) ? "0" : undefined
)}
>
${weather
? html`
<div class="content">
<div class="icon-image">
${weatherStateIcon ||
html`
<ha-state-icon
class="weather-icon"
.state=${stateObj}
></ha-state-icon>
`}
</div>
<div class="info">
<div class="name-state">
<div class="state">
${computeStateDisplay(
this.hass.localize,
stateObj,
this.hass.locale
)}
</div>
<div class="name" .title=${name}>${name}</div>
</div>
<div class="temp-attribute">
<div class="temp">
${formatNumber(
stateObj.attributes.temperature,
this.hass.locale
)}&nbsp;<span
>${getWeatherUnit(this.hass, "temperature")}</span
>
</div>
<div class="attribute">
${this._config.secondary_info_attribute !== undefined
? html`
${this._config.secondary_info_attribute in
weatherAttrIcons
? html`
<ha-svg-icon
class="attr-icon"
.path=${weatherAttrIcons[
this._config.secondary_info_attribute
]}
></ha-svg-icon>
`
: this.hass!.localize(
`ui.card.weather.attributes.${this._config.secondary_info_attribute}`
)}
${this._config.secondary_info_attribute ===
"wind_speed"
? getWind(
this.hass,
stateObj.attributes.wind_speed,
stateObj.attributes.wind_bearing
)
: html`
${formatNumber(
stateObj.attributes[
this._config.secondary_info_attribute
],
this.hass.locale
)}
${getWeatherUnit(
this.hass,
this._config.secondary_info_attribute
)}
`}
`
: getSecondaryWeatherAttribute(this.hass, stateObj)}
</div>
</div>
</div>
<div class="content">
<div class="icon-image">
${weatherStateIcon ||
html`
<ha-state-icon
class="weather-icon"
.state=${stateObj}
></ha-state-icon>
`}
</div>
<div class="info">
<div class="name-state">
<div class="state">
${computeStateDisplay(
this.hass.localize,
stateObj,
this.hass.locale
)}
</div>
`
: ""}
<div class="name" .title=${name}>${name}</div>
</div>
<div class="temp-attribute">
<div class="temp">
${formatNumber(
stateObj.attributes.temperature,
this.hass.locale
)}&nbsp;<span>${getWeatherUnit(this.hass, "temperature")}</span>
</div>
<div class="attribute">
${this._config.secondary_info_attribute !== undefined
? html`
${this._config.secondary_info_attribute in
weatherAttrIcons
? html`
<ha-svg-icon
class="attr-icon"
.path=${weatherAttrIcons[
this._config.secondary_info_attribute
]}
></ha-svg-icon>
`
: this.hass!.localize(
`ui.card.weather.attributes.${this._config.secondary_info_attribute}`
)}
${this._config.secondary_info_attribute === "wind_speed"
? getWind(
this.hass,
stateObj.attributes.wind_speed,
stateObj.attributes.wind_bearing
)
: html`
${formatNumber(
stateObj.attributes[
this._config.secondary_info_attribute
],
this.hass.locale
)}
${getWeatherUnit(
this.hass,
this._config.secondary_info_attribute
)}
`}
`
: getSecondaryWeatherAttribute(this.hass, stateObj)}
</div>
</div>
</div>
</div>
${forecast
? html`
<div class="forecast">
+3 -1
View File
@@ -40,6 +40,8 @@ export interface EntityCardConfig extends LovelaceCardConfig {
unit?: string;
theme?: string;
state_color?: boolean;
hours_to_show?: number;
show_trend?: boolean;
}
export interface EntitiesCardEntityConfig extends EntityConfig {
@@ -357,6 +359,7 @@ export interface SensorCardConfig extends LovelaceCardConfig {
detail?: number;
theme?: string;
hours_to_show?: number;
show_trend?: boolean;
limits?: {
min?: number;
max?: number;
@@ -387,7 +390,6 @@ export interface ThermostatCardConfig extends LovelaceCardConfig {
export interface WeatherForecastCardConfig extends LovelaceCardConfig {
entity: string;
name?: string;
show_current?: boolean;
show_forecast?: boolean;
secondary_info_attribute?: string;
theme?: string;
@@ -3,7 +3,6 @@ import { computeDomain } from "../../../common/entity/compute_domain";
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { splitByGroups } from "../../../common/entity/split_by_groups";
import { stripPrefixFromEntityName } from "../../../common/entity/strip_prefix_from_entity_name";
import { stringCompare } from "../../../common/string/compare";
import { LocalizeFunc } from "../../../common/translations/localize";
import type { AreaRegistryEntry } from "../../../data/area_registry";
@@ -20,6 +19,7 @@ import {
AlarmPanelCardConfig,
EntitiesCardConfig,
HumidifierCardConfig,
LightCardConfig,
PictureEntityCardConfig,
ThermostatCardConfig,
} from "../cards/types";
@@ -31,8 +31,6 @@ const HIDE_DOMAIN = new Set([
"device_tracker",
"geo_location",
"persistent_notification",
"script",
"sun",
"zone",
]);
@@ -85,7 +83,8 @@ const splitByAreas = (
export const computeCards = (
states: Array<[string, HassEntity?]>,
entityCardOptions: Partial<EntitiesCardConfig>
entityCardOptions: Partial<EntitiesCardConfig>,
single = false
): LovelaceCardConfig[] => {
const cards: LovelaceCardConfig[] = [];
@@ -93,7 +92,7 @@ export const computeCards = (
const entities: Array<string | LovelaceRowConfig> = [];
const titlePrefix = entityCardOptions.title
? `${entityCardOptions.title} `.toLowerCase()
? `${entityCardOptions.title} `
: undefined;
for (const [entityId, stateObj] of states) {
@@ -123,6 +122,12 @@ export const computeCards = (
entity: entityId,
};
cards.push(cardConfig);
} else if (domain === "light" && single) {
const cardConfig: LightCardConfig = {
type: "light",
entity: entityId,
};
cards.push(cardConfig);
} else if (domain === "media_player") {
const cardConfig = {
type: "media-control",
@@ -148,18 +153,16 @@ export const computeCards = (
) {
// Do nothing.
} else {
let name: string | undefined;
let name: string;
const entityConf =
titlePrefix &&
stateObj &&
// eslint-disable-next-line no-cond-assign
(name = stripPrefixFromEntityName(
computeStateName(stateObj),
titlePrefix
))
(name = computeStateName(stateObj)) !== titlePrefix &&
name.startsWith(titlePrefix)
? {
entity: entityId,
name,
name: adjustName(name.substr(titlePrefix.length)),
}
: entityId;
@@ -178,6 +181,15 @@ export const computeCards = (
return cards;
};
const hasUpperCase = (str: string): boolean => str.toLowerCase() !== str;
const adjustName = (name: string): string =>
// If first word already has an upper case letter (e.g. from brand name)
// leave as-is, otherwise capitalize the first word.
hasUpperCase(name.substr(0, name.indexOf(" ")))
? name
: name[0].toUpperCase() + name.slice(1);
const computeDefaultViewStates = (
entities: HassEntities,
entityEntries: EntityRegistryEntry[]
+1 -1
View File
@@ -52,7 +52,7 @@ export function hasConfigOrEntitiesChanged(
const oldHass = changedProps.get("hass") as HomeAssistant;
const entities = processConfigEntities(element._config!.entities, false);
const entities = processConfigEntities(element._config!.entities);
return entities.some(
(entity) =>
@@ -5,8 +5,7 @@ import { EntityConfig, LovelaceRowConfig } from "../entity-rows/types";
export const processConfigEntities = <
T extends EntityConfig | LovelaceRowConfig
>(
entities: Array<T | string>,
checkEntityId = true
entities: Array<T | string>
): T[] => {
if (!entities || !Array.isArray(entities)) {
throw new Error("Entities need to be an array");
@@ -36,7 +35,7 @@ export const processConfigEntities = <
throw new Error(`Invalid entity specified at position ${index}.`);
}
if (checkEntityId && !isValidEntityId((config as EntityConfig).entity!)) {
if (!isValidEntityId((config as EntityConfig).entity!)) {
throw new Error(
`Invalid entity ID at position ${index}: ${
(config as EntityConfig).entity
@@ -10,6 +10,10 @@ import {
import { customElement, property, state } from "lit/decorators";
import { guard } from "lit/directives/guard";
import type { SortableEvent } from "sortablejs";
import Sortable, {
AutoScroll,
OnSpill,
} from "sortablejs/modular/sortable.core.esm";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/entity/ha-entity-picker";
import type { HaEntityPicker } from "../../../components/entity/ha-entity-picker";
@@ -18,8 +22,6 @@ import { sortableStyles } from "../../../resources/ha-sortable-style";
import { HomeAssistant } from "../../../types";
import { EntityConfig } from "../entity-rows/types";
let Sortable;
@customElement("hui-entity-editor")
export class HuiEntityEditor extends LitElement {
@property({ attribute: false }) protected hass?: HomeAssistant;
@@ -32,7 +34,7 @@ export class HuiEntityEditor extends LitElement {
@state() private _renderEmptySortable = false;
private _sortable?;
private _sortable?: Sortable;
public connectedCallback() {
super.connectedCallback();
@@ -84,6 +86,11 @@ export class HuiEntityEditor extends LitElement {
`;
}
protected firstUpdated(): void {
Sortable.mount(OnSpill);
Sortable.mount(new AutoScroll());
}
protected updated(changedProps: PropertyValues): void {
super.updated(changedProps);
@@ -121,17 +128,7 @@ export class HuiEntityEditor extends LitElement {
this._renderEmptySortable = false;
}
private async _createSortable() {
if (!Sortable) {
const sortableImport = await import(
"sortablejs/modular/sortable.core.esm"
);
Sortable = sortableImport.Sortable;
Sortable.mount(sortableImport.OnSpill);
Sortable.mount(sortableImport.AutoScroll());
}
private _createSortable() {
this._sortable = new Sortable(this.shadowRoot!.querySelector(".entities"), {
animation: 150,
fallbackClass: "sortable-fallback",
@@ -13,10 +13,8 @@ import { showSelectViewDialog } from "./select-view/show-select-view-dialog";
export const addEntitiesToLovelaceView = async (
element: HTMLElement,
hass: HomeAssistant,
entities: string[],
cardTitle?: string
entities: string[]
) => {
hass.loadFragmentTranslation("lovelace");
const dashboards = await fetchDashboards(hass);
const storageDashs = dashboards.filter(
@@ -32,7 +30,6 @@ export const addEntitiesToLovelaceView = async (
showSuggestCardDialog(element, {
entities,
yaml: true,
cardTitle,
});
return;
}
@@ -71,7 +68,6 @@ export const addEntitiesToLovelaceView = async (
showSuggestCardDialog(element, {
entities,
yaml: true,
cardTitle,
});
} else {
// all storage dashboards are generated
@@ -91,7 +87,6 @@ export const addEntitiesToLovelaceView = async (
if (!storageDashs.length && lovelaceConfig.views.length === 1) {
showSuggestCardDialog(element, {
cardTitle,
lovelaceConfig: lovelaceConfig!,
saveConfig: async (newConfig: LovelaceConfig): Promise<void> => {
try {
@@ -112,11 +107,9 @@ export const addEntitiesToLovelaceView = async (
lovelaceConfig,
urlPath,
allowDashboardChange: true,
actionLabel: hass.localize("ui.common.next"),
dashboards,
viewSelectedCallback: (newUrlPath, selectedDashConfig, viewIndex) => {
showSuggestCardDialog(element, {
cardTitle,
lovelaceConfig: selectedDashConfig,
saveConfig: async (newConfig: LovelaceConfig): Promise<void> => {
try {
@@ -75,8 +75,6 @@ export class HuiDialogEditCard
@state() private _dirty = false;
@state() private _isEscapeEnabled = true;
public async showDialog(params: EditCardDialogParams): Promise<void> {
this._params = params;
this._GUImode = true;
@@ -95,9 +93,6 @@ export class HuiDialogEditCard
}
public closeDialog(): boolean {
this._isEscapeEnabled = true;
window.removeEventListener("dialog-closed", this._enableEscapeKeyClose);
window.removeEventListener("hass-more-info", this._disableEscapeKeyClose);
if (this._dirty) {
this._confirmCancel();
return false;
@@ -130,16 +125,6 @@ export class HuiDialogEditCard
}
}
private _enableEscapeKeyClose = (ev: any) => {
if (ev.detail.dialog === "ha-more-info-dialog") {
this._isEscapeEnabled = true;
}
};
private _disableEscapeKeyClose = () => {
this._isEscapeEnabled = false;
};
protected render(): TemplateResult {
if (!this._params) {
return html``;
@@ -172,7 +157,6 @@ export class HuiDialogEditCard
<ha-dialog
open
scrimClickAction
.escapeKeyAction=${this._isEscapeEnabled ? undefined : ""}
@keydown=${this._ignoreKeydown}
@closed=${this._cancel}
@opened=${this._opened}
@@ -296,8 +280,6 @@ export class HuiDialogEditCard
}
private _opened() {
window.addEventListener("dialog-closed", this._enableEscapeKeyClose);
window.addEventListener("hass-more-info", this._disableEscapeKeyClose);
this._cardEditorEl?.focusYamlEditor();
}
@@ -35,9 +35,8 @@ export class HuiDialogSuggestCard extends LitElement {
entityId,
this.hass.states[entityId],
]),
{
title: params.cardTitle,
}
{},
true
);
if (!Object.isFrozen(this._cardConfig)) {
this._cardConfig = deepFreeze(this._cardConfig);
@@ -2,7 +2,6 @@ import { fireEvent } from "../../../../common/dom/fire_event";
import { LovelaceCardConfig, LovelaceConfig } from "../../../../data/lovelace";
export interface SuggestCardDialogParams {
cardTitle?: string;
lovelaceConfig?: LovelaceConfig;
yaml?: boolean;
saveConfig?: (config: LovelaceConfig) => void;
@@ -1,7 +1,15 @@
import "@polymer/paper-input/paper-input";
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { assert, assign, boolean, object, optional, string } from "superstruct";
import {
assert,
assign,
boolean,
number,
object,
optional,
string,
} from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event";
import { computeDomain } from "../../../../common/entity/compute_domain";
import { domainIcon } from "../../../../common/entity/domain_icon";
@@ -29,6 +37,8 @@ const cardConfigStruct = assign(
theme: optional(string()),
state_color: optional(boolean()),
footer: optional(headerFooterConfigStructs),
hours_to_show: optional(number()),
show_trend: optional(boolean()),
})
);
@@ -74,6 +84,14 @@ export class HuiEntityCardEditor
return this._config!.theme || "";
}
get _hours_to_show(): number | string {
return this._config!.hours_to_show || "24";
}
get _show_trend(): boolean {
return this._config!.show_trend || false;
}
protected render(): TemplateResult {
if (!this.hass || !this._config) {
return html``;
@@ -167,6 +185,36 @@ export class HuiEntityCardEditor
</ha-switch>
</ha-formfield>
</div>
<div class="side-by-side">
<ha-formfield
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.entity.show_trend"
)}
>
<ha-switch
.checked=${this._show_trend}
.configValue=${"show_trend"}
@change=${this._valueChanged}
></ha-switch>
</ha-formfield>
${this._show_trend
? html`
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.hours_to_show"
)} (${this.hass.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})"
type="number"
.value=${this._hours_to_show}
min="1"
.configValue=${"hours_to_show"}
@value-changed=${this._valueChanged}
></paper-input>
`
: ""}
</div>
</div>
`;
}
@@ -4,7 +4,15 @@ import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { assert, assign, number, object, optional, string } from "superstruct";
import {
assert,
assign,
boolean,
number,
object,
optional,
string,
} from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event";
import { computeDomain } from "../../../../common/entity/compute_domain";
import { domainIcon } from "../../../../common/entity/domain_icon";
@@ -31,6 +39,7 @@ const cardConfigStruct = assign(
detail: optional(number()),
theme: optional(string()),
hours_to_show: optional(number()),
show_trend: optional(boolean()),
})
);
@@ -82,6 +91,10 @@ export class HuiSensorCardEditor
return this._config!.hours_to_show || "24";
}
get _show_trend(): boolean {
return this._config!.show_trend || false;
}
protected render(): TemplateResult {
if (!this.hass || !this._config) {
return html``;
@@ -193,6 +206,17 @@ export class HuiSensorCardEditor
@value-changed=${this._valueChanged}
></paper-input>
</div>
<ha-formfield
label=${this.hass.localize(
"ui.panel.lovelace.editor.card.sensor.show_trend"
)}
>
<ha-switch
.checked=${this._show_trend}
.configValue=${"show_trend"}
@change=${this._change}
></ha-switch>
</ha-formfield>
</div>
`;
}
@@ -202,15 +226,21 @@ export class HuiSensorCardEditor
return;
}
const value = (ev.target! as EditorTarget).checked ? 2 : 1;
const target = ev.target! as EditorTarget;
const value =
target.configValue === "detail"
? (ev.target! as EditorTarget).checked
? 2
: 1
: target.checked;
if (this._detail === value) {
if (this[`_${target.configValue}`] === value) {
return;
}
this._config = {
...this._config,
detail: value,
[target.configValue!]: value,
};
fireEvent(this, "config-changed", { config: this._config });
@@ -61,7 +61,7 @@ export class HuiStatisticsGraphCardEditor
assert(config, cardConfigStruct);
this._config = config;
this._configEntities = config.entities
? processConfigEntities(config.entities, false).map((cfg) => cfg.entity)
? processConfigEntities(config.entities).map((cfg) => cfg.entity)
: [];
}
@@ -6,7 +6,7 @@ import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import "../../../../components/entity/ha-entity-attribute-picker";
import "../../../../components/entity/ha-entity-picker";
import "../../../../components/ha-formfield";
import "../../../../components/ha-radio";
import "../../../../components/ha-switch";
import { HomeAssistant } from "../../../../types";
import { WeatherForecastCardConfig } from "../../cards/types";
import "../../components/hui-theme-select-editor";
@@ -15,8 +15,6 @@ import { actionConfigStruct } from "../structs/action-struct";
import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style";
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
import { UNAVAILABLE } from "../../../../data/entity";
import { WeatherEntity } from "../../../../data/weather";
const cardConfigStruct = assign(
baseLovelaceCardConfig,
@@ -24,7 +22,6 @@ const cardConfigStruct = assign(
entity: optional(string()),
name: optional(string()),
theme: optional(string()),
show_current: optional(boolean()),
show_forecast: optional(boolean()),
secondary_info_attribute: optional(string()),
tap_action: optional(actionConfigStruct),
@@ -47,18 +44,6 @@ export class HuiWeatherForecastCardEditor
public setConfig(config: WeatherForecastCardConfig): void {
assert(config, cardConfigStruct);
this._config = config;
if (
/* cannot show forecast in case it is unavailable on the entity */
(config.show_forecast === true && this._has_forecast === false) ||
/* cannot hide both weather and forecast, need one of them */
(config.show_current === false && config.show_forecast === false)
) {
/* reset to sane default, show weather, but hide forecast */
fireEvent(this, "config-changed", {
config: { ...config, show_current: true, show_forecast: false },
});
}
}
get _entity(): string {
@@ -73,28 +58,14 @@ export class HuiWeatherForecastCardEditor
return this._config!.theme || "";
}
get _show_current(): boolean {
return this._config!.show_current ?? true;
}
get _show_forecast(): boolean {
return this._config!.show_forecast ?? this._has_forecast === true;
return this._config!.show_forecast || true;
}
get _secondary_info_attribute(): string {
return this._config!.secondary_info_attribute || "";
}
get _has_forecast(): boolean | undefined {
if (this.hass && this._config) {
const stateObj = this.hass.states[this._config.entity] as WeatherEntity;
if (stateObj && stateObj.state !== UNAVAILABLE) {
return !!stateObj.attributes.forecast?.length;
}
}
return undefined;
}
protected render(): TemplateResult {
if (!this.hass || !this._config) {
return html``;
@@ -132,6 +103,8 @@ export class HuiWeatherForecastCardEditor
.configValue=${"theme"}
@value-changed=${this._valueChanged}
></hui-theme-select-editor>
</div>
<div class="side-by-side">
<ha-entity-attribute-picker
.hass=${this.hass}
.entityId=${this._entity}
@@ -144,51 +117,17 @@ export class HuiWeatherForecastCardEditor
.configValue=${"secondary_info_attribute"}
@value-changed=${this._valueChanged}
></ha-entity-attribute-picker>
</div>
<div class="side-by-side">
<ha-formfield
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.weather-forecast.show_both"
"ui.panel.lovelace.editor.card.weather-forecast.show_forecast"
)}
.dir=${computeRTLDirection(this.hass)}
>
<ha-radio
.disabled=${this._has_forecast === false}
.checked=${this._has_forecast === true &&
this._show_current &&
this._show_forecast}
.configValue=${"show_both"}
@change=${this._valueChanged}
></ha-radio
></ha-formfield>
<ha-formfield
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.weather-forecast.show_only_current"
)}
.dir=${computeRTLDirection(this.hass)}
>
<ha-radio
.disabled=${this._has_forecast === false}
.checked=${this._has_forecast === false ||
(this._show_current && !this._show_forecast)}
.configValue=${"show_current"}
@change=${this._valueChanged}
></ha-radio
></ha-formfield>
<ha-formfield
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.weather-forecast.show_only_forecast"
)}
.dir=${computeRTLDirection(this.hass)}
>
<ha-radio
.disabled=${this._has_forecast === false}
.checked=${this._has_forecast === true &&
!this._show_current &&
this._show_forecast}
<ha-switch
.checked=${this._config!.show_forecast !== false}
.configValue=${"show_forecast"}
@change=${this._valueChanged}
></ha-radio
></ha-switch
></ha-formfield>
</div>
</div>
@@ -204,20 +143,7 @@ export class HuiWeatherForecastCardEditor
return;
}
if (target.configValue) {
if (target.configValue.startsWith("show_")) {
this._config = { ...this._config };
if (target.configValue === "show_both") {
/* delete since true is default */
delete this._config.show_current;
delete this._config.show_forecast;
} else if (target.configValue === "show_current") {
delete this._config.show_current;
this._config.show_forecast = false;
} else if (target.configValue === "show_forecast") {
delete this._config.show_forecast;
this._config.show_current = false;
}
} else if (target.value === "") {
if (target.value === "") {
this._config = { ...this._config };
delete this._config[target.configValue!];
} else {
@@ -10,6 +10,10 @@ import {
import { customElement, property, state } from "lit/decorators";
import { guard } from "lit/directives/guard";
import type { SortableEvent } from "sortablejs";
import Sortable, {
AutoScroll,
OnSpill,
} from "sortablejs/modular/sortable.core.esm";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/entity/ha-entity-picker";
import type { HaEntityPicker } from "../../../components/entity/ha-entity-picker";
@@ -19,8 +23,6 @@ import { sortableStyles } from "../../../resources/ha-sortable-style";
import { HomeAssistant } from "../../../types";
import { EntityConfig, LovelaceRowConfig } from "../entity-rows/types";
let Sortable;
declare global {
interface HASSDomEvents {
"entities-changed": {
@@ -41,7 +43,7 @@ export class HuiEntitiesCardRowEditor extends LitElement {
@state() private _renderEmptySortable = false;
private _sortable?;
private _sortable?: Sortable;
public connectedCallback() {
super.connectedCallback();
@@ -132,6 +134,11 @@ export class HuiEntitiesCardRowEditor extends LitElement {
`;
}
protected firstUpdated(): void {
Sortable.mount(OnSpill);
Sortable.mount(new AutoScroll());
}
protected updated(changedProps: PropertyValues): void {
super.updated(changedProps);
@@ -169,17 +176,7 @@ export class HuiEntitiesCardRowEditor extends LitElement {
this._renderEmptySortable = false;
}
private async _createSortable() {
if (!Sortable) {
const sortableImport = await import(
"sortablejs/modular/sortable.core.esm"
);
Sortable = sortableImport.Sortable;
Sortable.mount(sortableImport.OnSpill);
Sortable.mount(sortableImport.AutoScroll());
}
private _createSortable() {
this._sortable = new Sortable(this.shadowRoot!.querySelector(".entities"), {
animation: 150,
fallbackClass: "sortable-fallback",
@@ -131,7 +131,7 @@ export class HuiDialogSelectView extends LitElement {
${this.hass!.localize("ui.common.cancel")}
</mwc-button>
<mwc-button slot="primaryAction" @click=${this._selectView}>
${this._params.actionLabel || this.hass!.localize("ui.common.move")}
${this.hass!.localize("ui.common.move")}
</mwc-button>
</ha-dialog>
`;
@@ -7,7 +7,6 @@ export interface SelectViewDialogParams {
dashboards?: LovelaceDashboard[];
urlPath?: string | null;
header?: string;
actionLabel?: string;
viewSelectedCallback: (
urlPath: string | null,
config: LovelaceConfig,
+2 -4
View File
@@ -713,8 +713,7 @@ class HUIRoot extends LitElement {
});
}
private _moveViewLeft(ev) {
ev.stopPropagation();
private _moveViewLeft() {
if (this._curView === 0) {
return;
}
@@ -725,8 +724,7 @@ class HUIRoot extends LitElement {
lovelace.saveConfig(swapView(lovelace.config, oldIndex, newIndex));
}
private _moveViewRight(ev) {
ev.stopPropagation();
private _moveViewRight() {
if ((this._curView! as number) + 1 === this.lovelace!.config.views.length) {
return;
}
-1
View File
@@ -13,7 +13,6 @@ import "@formatjs/intl-relativetimeformat/polyfill";
import "@formatjs/intl-relativetimeformat/locale-data/en";
import "@formatjs/intl-datetimeformat/polyfill";
import "@formatjs/intl-datetimeformat/locale-data/en";
import "@formatjs/intl-datetimeformat/add-all-tz";
// To use comlink under ES5
import "proxy-polyfill";
+11
View File
@@ -1,3 +1,4 @@
import { askWrite } from "../common/auth/token_storage";
import { subscribeUser, userCollection } from "../data/ws-user";
import { Constructor } from "../types";
import { clearState } from "../util/ha-pref-storage";
@@ -25,6 +26,16 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
subscribeUser(this.hass!.connection, (user) =>
this._updateHass({ user })
);
if (askWrite()) {
this.updateComplete
.then(() => import("../dialogs/ha-store-auth-card"))
.then(() => {
const el = document.createElement("ha-store-auth-card");
this.provideHass(el);
document.body.appendChild(el);
});
}
}
private async _handleLogout() {
+1 -10
View File
@@ -179,16 +179,7 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
});
subscribeEntities(conn, (states) => this._updateHass({ states }));
subscribeConfig(conn, (config) => {
if (
this.hass?.config?.time_zone !== config.time_zone &&
"__setDefaultTimeZone" in Intl.DateTimeFormat
) {
// @ts-ignore
Intl.DateTimeFormat.__setDefaultTimeZone(config.time_zone);
}
this._updateHass({ config });
});
subscribeConfig(conn, (config) => this._updateHass({ config }));
subscribeServices(conn, (services) => this._updateHass({ services }));
subscribePanels(conn, (panels) => this._updateHass({ panels }));
subscribeFrontendUserData(conn, "core", (userData) =>
+40 -52
View File
@@ -105,6 +105,11 @@
}
},
"ui": {
"auth_store": {
"ask": "Do you want to stay logged in?",
"decline": "No",
"confirm": "Yes"
},
"card": {
"alarm_control_panel": {
"code": "Code",
@@ -628,10 +633,8 @@
"edit": "Edit entity",
"details": "Details",
"history": "History",
"logbook": "Logbook",
"last_changed": "Last changed",
"last_updated": "Last updated",
"show_more": "Show more",
"script": {
"last_action": "Last action",
"last_triggered": "Last triggered"
@@ -1693,17 +1696,17 @@
"label": "Call service"
},
"delay": {
"label": "Wait for time to pass (delay)",
"delay": "Duration"
"label": "Delay",
"delay": "[%key:ui::panel::config::automation::editor::actions::type::delay::label%]"
},
"wait_template": {
"label": "Wait for a template",
"label": "Wait",
"wait_template": "Wait Template",
"timeout": "Timeout (optional)",
"continue_timeout": "Continue on timeout"
},
"wait_for_trigger": {
"label": "Wait for a trigger",
"label": "Wait for trigger",
"timeout": "[%key:ui::panel::config::automation::editor::actions::type::wait_template::timeout%]",
"continue_timeout": "[%key:ui::panel::config::automation::editor::actions::type::wait_template::continue_timeout%]"
},
@@ -1796,18 +1799,13 @@
"learn_more": "Learn more about using blueprints",
"headers": {
"name": "Name",
"type": "Type",
"domain": "Domain",
"file_name": "File name"
},
"types": {
"automation": "Automation",
"script": "Script"
},
"confirm_delete_header": "Delete this blueprint?",
"confirm_delete_text": "Are you sure you want to delete this blueprint?",
"add_blueprint": "Import blueprint",
"create_automation": "Create automation",
"create_script": "Create script",
"use_blueprint": "Create automation",
"delete_blueprint": "Delete blueprint",
"share_blueprint": "Share blueprint",
"share_blueprint_no_url": "Unable to share blueprint: no source url",
@@ -2175,10 +2173,9 @@
"device_not_found": "Device not found.",
"entities": {
"entities": "Entities",
"control": "Controls",
"sensor": "Sensors",
"state": "State",
"diagnostic": "Diagnostic",
"config": "Configuration",
"config": "Config",
"add_entities_lovelace": "Add to Lovelace",
"none": "This device has no entities",
"hide_disabled": "Hide disabled",
@@ -2834,10 +2831,8 @@
"reinterview_device": "Re-interview Device",
"heal_node": "Heal Device",
"remove_failed": "Remove Failed Device",
"highest_security": "Highest Security",
"unknown": "Unknown",
"zwave_plus": "Z-Wave Plus",
"zwave_plus_version": "Version {version}"
"is_secure": "Secure",
"unknown": "Unknown"
},
"node_config": {
"header": "Z-Wave Device Configuration",
@@ -2873,27 +2868,24 @@
"inclusion_finished": "The device has been added.",
"view_device": "View Device",
"interview_started": "The device is being interviewed. This may take some time.",
"interview_failed": "The device interview failed. Additional information may be available in the logs."
},
"security_classes": {
"None": {
"title": "None"
},
"S2_Unauthenticated": {
"title": "S2 Unauthenticated",
"description": "Like S2 Authenticated, but without verification that the correct device is included"
},
"S2_Authenticated": {
"title": "S2 Authenticated",
"description": "Example: Lighting, Sensors and Security Systems"
},
"S2_AccessControl": {
"title": "S2 Access Control",
"description": "Example: Door Locks and Garage Doors"
},
"S0_Legacy": {
"title": "S0 Legacy",
"description": "Example: Legacy Door Locks without S2 support"
"interview_failed": "The device interview failed. Additional information may be available in the logs.",
"security_classes": {
"S2_Unauthenticated": {
"title": "S2 Unauthenticated",
"description": "Like S2 Authenticated, but without verification that the correct device is included"
},
"S2_Authenticated": {
"title": "S2 Authenticated",
"description": "Example: Lighting, Sensors and Security Systems"
},
"S2_AccessControl": {
"title": "S2 Access Control",
"description": "Example: Door Locks and Garage Doors"
},
"S0_Legacy": {
"title": "S0 Legacy",
"description": "Example: Legacy Door Locks without S2 support"
}
}
},
"remove_node": {
@@ -3285,7 +3277,8 @@
},
"entity": {
"name": "Entity",
"description": "The Entity card gives you a quick overview of your entitys state."
"description": "The Entity card gives you a quick overview of your entitys state.",
"show_trend": "Show Trend?"
},
"button": {
"name": "Button",
@@ -3423,7 +3416,8 @@
"name": "Sensor",
"show_more_detail": "Show more detail",
"graph_type": "Graph Type",
"description": "The Sensor card gives you a quick overview of your sensors state with an optional graph to visualize change over time."
"description": "The Sensor card gives you a quick overview of your sensors state with an optional graph to visualize change over time.",
"show_trend": "Show Trend?"
},
"shopping-list": {
"name": "Shopping List",
@@ -3441,9 +3435,7 @@
"weather-forecast": {
"name": "Weather Forecast",
"description": "The Weather Forecast card displays the weather. Very useful to include on interfaces that people display on the wall.",
"show_both": "Show current Weather and Forecast",
"show_only_current": "Show only current Weather",
"show_only_forecast": "Show only Forecast"
"show_forecast": "Show Forecast"
}
},
"view": {
@@ -3671,7 +3663,6 @@
"logging_in_with": "Logging in with **{authProviderName}**.",
"pick_auth_provider": "Or log in with",
"abort_intro": "Login aborted",
"store_token": "Keep me logged in",
"form": {
"working": "Please wait",
"unknown_error": "Something went wrong",
@@ -4090,10 +4081,11 @@
"description": "This add-on is using Ingress to embed its interface securely into Home Assistant."
},
"label": {
"core": "Core",
"stage": "stage",
"rating": "rating",
"hardware": "hardware",
"host": "host",
"hass": "hass",
"hassio": "hassio",
"docker": "docker",
"host_pid": "host pid",
@@ -4101,10 +4093,6 @@
"auth": "auth",
"ingress": "ingress"
},
"stages": {
"experimental": "Experimental",
"deprecated": "Deprecated"
},
"role": {
"manager": "manager",
"default": "default",
-1
View File
@@ -31,7 +31,6 @@ const hassAttributeUtil = {
"power",
"presence",
"problem",
"running",
"safety",
"smoke",
"sound",
-27
View File
@@ -1,27 +0,0 @@
import { assert } from "chai";
import { slugify } from "../../../src/common/string/slugify";
describe("slugify", () => {
// With default delimiter
assert.strictEqual(slugify("abc"), "abc");
assert.strictEqual(slugify("ABC"), "abc");
assert.strictEqual(slugify("abc DEF"), "abc_def");
assert.strictEqual(slugify("abc-DEF"), "abc_def");
assert.strictEqual(slugify("abc_DEF"), "abc_def");
assert.strictEqual(slugify("abc å DEF"), "abc_a_def");
assert.strictEqual(slugify("abc:DEF"), "abc_def");
assert.strictEqual(slugify("abc&DEF"), "abc_and_def");
assert.strictEqual(slugify("abc^^DEF"), "abcdef");
assert.strictEqual(slugify("abc DEF"), "abc_def");
assert.strictEqual(slugify("_abc DEF"), "abc_def");
assert.strictEqual(slugify("abc DEF_"), "abc_def");
assert.strictEqual(slugify("abc-DEF ghi"), "abc_def_ghi");
assert.strictEqual(slugify("abc-DEF-ghi"), "abc_def_ghi");
assert.strictEqual(slugify("abc - DEF - ghi"), "abc_def_ghi");
assert.strictEqual(slugify("abc---DEF---ghi"), "abc_def_ghi");
assert.strictEqual(slugify("___abc___DEF___ghi___"), "abc_def_ghi");
// With custom delimiter
assert.strictEqual(slugify("abc def", "-"), "abc-def");
assert.strictEqual(slugify("abc-def", "-"), "abc-def");
});
+736 -747
View File
File diff suppressed because it is too large Load Diff