mirror of
https://github.com/home-assistant/frontend.git
synced 2025-10-12 13:19:38 +00:00
Compare commits
80 Commits
20211020.0
...
Use-autofi
Author | SHA1 | Date | |
---|---|---|---|
![]() |
faf8e49c12 | ||
![]() |
4624c3d75b | ||
![]() |
7d196b4b95 | ||
![]() |
6347e44d94 | ||
![]() |
719d9386c5 | ||
![]() |
bb734be4bc | ||
![]() |
7cadaf1dc3 | ||
![]() |
c30453a86f | ||
![]() |
c2e3d0188e | ||
![]() |
aabb8ea16f | ||
![]() |
df572d59c5 | ||
![]() |
5ef7a37c20 | ||
![]() |
4b44e197ae | ||
![]() |
8b5b21ae69 | ||
![]() |
f5417fad6f | ||
![]() |
7fa6317f5c | ||
![]() |
74533cebc6 | ||
![]() |
10986db7c6 | ||
![]() |
67648baca7 | ||
![]() |
dc9182e9ab | ||
![]() |
4a7a81ffdb | ||
![]() |
09ef72647e | ||
![]() |
da38e6f986 | ||
![]() |
bd1a9f2cb0 | ||
![]() |
171eddd779 | ||
![]() |
7acc2f9e08 | ||
![]() |
27a6341137 | ||
![]() |
6c5e15e707 | ||
![]() |
06b1718ade | ||
![]() |
e50d2e16a7 | ||
![]() |
0b2404a0f2 | ||
![]() |
371804591d | ||
![]() |
54c64c15f3 | ||
![]() |
0e1124cd4f | ||
![]() |
70fd759e18 | ||
![]() |
8e383b2bec | ||
![]() |
63cd576d56 | ||
![]() |
32ac04ea78 | ||
![]() |
5d6bacb0bd | ||
![]() |
398d777681 | ||
![]() |
549a360d98 | ||
![]() |
1140e6026c | ||
![]() |
29a1167782 | ||
![]() |
d61a77f2d9 | ||
![]() |
b9bde1960b | ||
![]() |
a12c2eea5d | ||
![]() |
b5c717a559 | ||
![]() |
3adbc4cfaf | ||
![]() |
dd11fb1b99 | ||
![]() |
bf0d102c86 | ||
![]() |
dad2b92d2e | ||
![]() |
d027ec0018 | ||
![]() |
0c038398aa | ||
![]() |
5c3e0cc016 | ||
![]() |
9bcd26ce57 | ||
![]() |
3e8a6c418c | ||
![]() |
279f3e1183 | ||
![]() |
f77339ad85 | ||
![]() |
da73b316ff | ||
![]() |
82a49d2cbf | ||
![]() |
05711b4636 | ||
![]() |
2c2809573f | ||
![]() |
bbbeafcc92 | ||
![]() |
95c6adc739 | ||
![]() |
7c2e0aea92 | ||
![]() |
d05c76356f | ||
![]() |
cddf6ce1f4 | ||
![]() |
8abb212ae7 | ||
![]() |
0056d75127 | ||
![]() |
5be475ea17 | ||
![]() |
b157cf5294 | ||
![]() |
48c9c89e3d | ||
![]() |
80bbc9990a | ||
![]() |
736e117eca | ||
![]() |
5e52bd905d | ||
![]() |
31b69147f4 | ||
![]() |
bc5010a953 | ||
![]() |
49947f3337 | ||
![]() |
d3ce4af541 | ||
![]() |
d45f47d908 |
12
.yarn/patches/@material/mwc-icon-button/remove-icon.patch
Normal file
12
.yarn/patches/@material/mwc-icon-button/remove-icon.patch
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
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>
|
@@ -26,6 +26,7 @@ const getMeta = () => {
|
|||||||
path: svg.match(/ d="([^"]+)"/)[1],
|
path: svg.match(/ d="([^"]+)"/)[1],
|
||||||
name: icon.name,
|
name: icon.name,
|
||||||
tags: icon.tags,
|
tags: icon.tags,
|
||||||
|
aliases: icon.aliases,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -37,6 +38,7 @@ const addRemovedMeta = (meta) => {
|
|||||||
path: removeIcon.path,
|
path: removeIcon.path,
|
||||||
name: removeIcon.name,
|
name: removeIcon.name,
|
||||||
tags: [],
|
tags: [],
|
||||||
|
aliases: [],
|
||||||
}));
|
}));
|
||||||
const combinedMeta = [...meta, ...removedMeta];
|
const combinedMeta = [...meta, ...removedMeta];
|
||||||
return combinedMeta.sort((a, b) => a.name.localeCompare(b.name));
|
return combinedMeta.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
@@ -99,6 +101,7 @@ const findDifferentiator = (curString, prevString) => {
|
|||||||
|
|
||||||
gulp.task("gen-icons-json", (done) => {
|
gulp.task("gen-icons-json", (done) => {
|
||||||
const meta = getMeta();
|
const meta = getMeta();
|
||||||
|
|
||||||
const metaAndRemoved = addRemovedMeta(meta);
|
const metaAndRemoved = addRemovedMeta(meta);
|
||||||
const split = splitBySize(metaAndRemoved);
|
const split = splitBySize(metaAndRemoved);
|
||||||
|
|
||||||
@@ -138,11 +141,17 @@ gulp.task("gen-icons-json", (done) => {
|
|||||||
JSON.stringify({ version: package.version, parts })
|
JSON.stringify({ version: package.version, parts })
|
||||||
);
|
);
|
||||||
|
|
||||||
const orderedMeta = orderMeta(meta);
|
|
||||||
|
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
path.resolve(OUTPUT_DIR, "iconList.json"),
|
path.resolve(OUTPUT_DIR, "iconList.json"),
|
||||||
JSON.stringify(orderedMeta.map((icon) => icon.name))
|
JSON.stringify(
|
||||||
|
orderMeta(meta).map((icon) => ({
|
||||||
|
name: icon.name,
|
||||||
|
keywords: [
|
||||||
|
...icon.tags.map((t) => t.toLowerCase().replace(/\s\/\s/g, " ")),
|
||||||
|
...icon.aliases,
|
||||||
|
],
|
||||||
|
}))
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
|
91
gallery/src/demos/demo-automation-editor-action.ts
Normal file
91
gallery/src/demos/demo-automation-editor-action.ts
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
}
|
127
gallery/src/demos/demo-automation-editor-condition.ts
Normal file
127
gallery/src/demos/demo-automation-editor-condition.ts
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
}
|
159
gallery/src/demos/demo-automation-editor-trigger.ts
Normal file
159
gallery/src/demos/demo-automation-editor-trigger.ts
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
}
|
@@ -222,6 +222,30 @@ const SCHEMAS: {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "OctoPrint",
|
||||||
|
translations: {
|
||||||
|
username: "Username",
|
||||||
|
host: "Host",
|
||||||
|
port: "Port Number",
|
||||||
|
path: "Application Path",
|
||||||
|
ssl: "Use SSL",
|
||||||
|
},
|
||||||
|
schema: [
|
||||||
|
{ type: "string", name: "username", required: true, default: "" },
|
||||||
|
{ type: "string", name: "host", required: true, default: "" },
|
||||||
|
{
|
||||||
|
type: "integer",
|
||||||
|
valueMin: 1,
|
||||||
|
valueMax: 65535,
|
||||||
|
name: "port",
|
||||||
|
optional: true,
|
||||||
|
default: 80,
|
||||||
|
},
|
||||||
|
{ type: "string", name: "path", optional: true, default: "/" },
|
||||||
|
{ type: "boolean", name: "ssl", optional: true, default: false },
|
||||||
|
],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@customElement("demo-ha-form")
|
@customElement("demo-ha-form")
|
||||||
|
@@ -11,6 +11,12 @@ import {
|
|||||||
mdiHomeAssistant,
|
mdiHomeAssistant,
|
||||||
mdiKey,
|
mdiKey,
|
||||||
mdiNetwork,
|
mdiNetwork,
|
||||||
|
mdiNumeric1,
|
||||||
|
mdiNumeric2,
|
||||||
|
mdiNumeric3,
|
||||||
|
mdiNumeric4,
|
||||||
|
mdiNumeric5,
|
||||||
|
mdiNumeric6,
|
||||||
mdiPound,
|
mdiPound,
|
||||||
mdiShield,
|
mdiShield,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
@@ -25,7 +31,7 @@ import "../../../../src/components/buttons/ha-call-api-button";
|
|||||||
import "../../../../src/components/buttons/ha-progress-button";
|
import "../../../../src/components/buttons/ha-progress-button";
|
||||||
import "../../../../src/components/ha-alert";
|
import "../../../../src/components/ha-alert";
|
||||||
import "../../../../src/components/ha-card";
|
import "../../../../src/components/ha-card";
|
||||||
import "../../../../src/components/ha-label-badge";
|
import "../../../../src/components/ha-chip";
|
||||||
import "../../../../src/components/ha-markdown";
|
import "../../../../src/components/ha-markdown";
|
||||||
import "../../../../src/components/ha-settings-row";
|
import "../../../../src/components/ha-settings-row";
|
||||||
import "../../../../src/components/ha-svg-icon";
|
import "../../../../src/components/ha-svg-icon";
|
||||||
@@ -73,6 +79,15 @@ const STAGE_ICON = {
|
|||||||
deprecated: mdiExclamationThick,
|
deprecated: mdiExclamationThick,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const RATING_ICON = {
|
||||||
|
1: mdiNumeric1,
|
||||||
|
2: mdiNumeric2,
|
||||||
|
3: mdiNumeric3,
|
||||||
|
4: mdiNumeric4,
|
||||||
|
5: mdiNumeric5,
|
||||||
|
6: mdiNumeric6,
|
||||||
|
};
|
||||||
|
|
||||||
@customElement("hassio-addon-info")
|
@customElement("hassio-addon-info")
|
||||||
class HassioAddonInfo extends LitElement {
|
class HassioAddonInfo extends LitElement {
|
||||||
@property({ type: Boolean }) public narrow!: boolean;
|
@property({ type: Boolean }) public narrow!: boolean;
|
||||||
@@ -246,6 +261,163 @@ class HassioAddonInfo extends LitElement {
|
|||||||
>`}
|
>`}
|
||||||
</div>
|
</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">
|
<div class="description light-color">
|
||||||
${this.addon.description}.<br />
|
${this.addon.description}.<br />
|
||||||
${this.supervisor.localize(
|
${this.supervisor.localize(
|
||||||
@@ -266,172 +438,6 @@ 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
|
${this.addon.version
|
||||||
? html`
|
? html`
|
||||||
<div
|
<div
|
||||||
@@ -1175,34 +1181,31 @@ class HassioAddonInfo extends LitElement {
|
|||||||
.description a {
|
.description a {
|
||||||
color: var(--primary-color);
|
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 {
|
.red {
|
||||||
--ha-label-badge-color: var(--label-badge-red, #df4c1e);
|
--ha-chip-background-color: var(--label-badge-red, #df4c1e);
|
||||||
}
|
}
|
||||||
.blue {
|
.blue {
|
||||||
--ha-label-badge-color: var(--label-badge-blue, #039be5);
|
--ha-chip-background-color: var(--label-badge-blue, #039be5);
|
||||||
}
|
}
|
||||||
.green {
|
.green {
|
||||||
--ha-label-badge-color: var(--label-badge-green, #0da035);
|
--ha-chip-background-color: var(--label-badge-green, #0da035);
|
||||||
}
|
}
|
||||||
.yellow {
|
.yellow {
|
||||||
--ha-label-badge-color: var(--label-badge-yellow, #f4b400);
|
--ha-chip-background-color: var(--label-badge-yellow, #f4b400);
|
||||||
}
|
}
|
||||||
.security {
|
.capabilities {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
.card-actions {
|
.card-actions {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
display: flex;
|
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 {
|
.changelog {
|
||||||
display: contents;
|
display: contents;
|
||||||
}
|
}
|
||||||
@@ -1242,6 +1245,9 @@ class HassioAddonInfo extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
|
ha-chip {
|
||||||
|
line-height: 36px;
|
||||||
|
}
|
||||||
.addon-options {
|
.addon-options {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
89
package.json
89
package.json
@@ -22,23 +22,23 @@
|
|||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@braintree/sanitize-url": "^5.0.2",
|
"@braintree/sanitize-url": "^5.0.2",
|
||||||
"@codemirror/commands": "^0.19.2",
|
"@codemirror/commands": "^0.19.5",
|
||||||
"@codemirror/gutter": "^0.19.1",
|
"@codemirror/gutter": "^0.19.3",
|
||||||
"@codemirror/highlight": "^0.19.2",
|
"@codemirror/highlight": "^0.19.6",
|
||||||
"@codemirror/history": "^0.19.0",
|
"@codemirror/history": "^0.19.0",
|
||||||
"@codemirror/legacy-modes": "^0.19.0",
|
"@codemirror/legacy-modes": "^0.19.0",
|
||||||
"@codemirror/rectangular-selection": "^0.19.0",
|
"@codemirror/rectangular-selection": "^0.19.1",
|
||||||
"@codemirror/search": "^0.19.0",
|
"@codemirror/search": "^0.19.2",
|
||||||
"@codemirror/state": "^0.19.1",
|
"@codemirror/state": "^0.19.2",
|
||||||
"@codemirror/stream-parser": "^0.19.1",
|
"@codemirror/stream-parser": "^0.19.2",
|
||||||
"@codemirror/text": "^0.19.2",
|
"@codemirror/text": "^0.19.4",
|
||||||
"@codemirror/view": "^0.19.4",
|
"@codemirror/view": "^0.19.9",
|
||||||
"@formatjs/intl-datetimeformat": "^4.2.4",
|
"@formatjs/intl-datetimeformat": "^4.2.5",
|
||||||
"@formatjs/intl-getcanonicallocales": "^1.7.3",
|
"@formatjs/intl-getcanonicallocales": "^1.8.0",
|
||||||
"@formatjs/intl-locale": "^2.4.38",
|
"@formatjs/intl-locale": "^2.4.40",
|
||||||
"@formatjs/intl-numberformat": "^7.2.4",
|
"@formatjs/intl-numberformat": "^7.2.5",
|
||||||
"@formatjs/intl-pluralrules": "^4.1.4",
|
"@formatjs/intl-pluralrules": "^4.1.5",
|
||||||
"@formatjs/intl-relativetimeformat": "^9.3.1",
|
"@formatjs/intl-relativetimeformat": "^9.3.2",
|
||||||
"@formatjs/intl-utils": "^3.8.4",
|
"@formatjs/intl-utils": "^3.8.4",
|
||||||
"@fullcalendar/common": "5.9.0",
|
"@fullcalendar/common": "5.9.0",
|
||||||
"@fullcalendar/core": "5.9.0",
|
"@fullcalendar/core": "5.9.0",
|
||||||
@@ -46,29 +46,29 @@
|
|||||||
"@fullcalendar/interaction": "5.9.0",
|
"@fullcalendar/interaction": "5.9.0",
|
||||||
"@fullcalendar/list": "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",
|
"@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.353ca7e9f.0",
|
"@material/chips": "14.0.0-canary.261f2db59.0",
|
||||||
"@material/data-table": "14.0.0-canary.353ca7e9f.0",
|
"@material/data-table": "14.0.0-canary.261f2db59.0",
|
||||||
"@material/mwc-button": "0.25.2",
|
"@material/mwc-button": "0.25.3",
|
||||||
"@material/mwc-checkbox": "0.25.2",
|
"@material/mwc-checkbox": "0.25.3",
|
||||||
"@material/mwc-circular-progress": "0.25.2",
|
"@material/mwc-circular-progress": "0.25.3",
|
||||||
"@material/mwc-dialog": "0.25.2",
|
"@material/mwc-dialog": "0.25.3",
|
||||||
"@material/mwc-fab": "0.25.2",
|
"@material/mwc-fab": "0.25.3",
|
||||||
"@material/mwc-formfield": "0.25.2",
|
"@material/mwc-formfield": "0.25.3",
|
||||||
"@material/mwc-icon-button": "0.25.2",
|
"@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.2",
|
"@material/mwc-linear-progress": "0.25.3",
|
||||||
"@material/mwc-list": "0.25.2",
|
"@material/mwc-list": "0.25.3",
|
||||||
"@material/mwc-menu": "0.25.2",
|
"@material/mwc-menu": "0.25.3",
|
||||||
"@material/mwc-radio": "0.25.2",
|
"@material/mwc-radio": "0.25.3",
|
||||||
"@material/mwc-ripple": "0.25.2",
|
"@material/mwc-ripple": "0.25.3",
|
||||||
"@material/mwc-select": "0.25.2",
|
"@material/mwc-select": "0.25.3",
|
||||||
"@material/mwc-slider": "0.25.2",
|
"@material/mwc-slider": "0.25.3",
|
||||||
"@material/mwc-switch": "0.25.2",
|
"@material/mwc-switch": "0.25.3",
|
||||||
"@material/mwc-tab": "0.25.2",
|
"@material/mwc-tab": "0.25.3",
|
||||||
"@material/mwc-tab-bar": "0.25.2",
|
"@material/mwc-tab-bar": "0.25.3",
|
||||||
"@material/mwc-textfield": "0.25.2",
|
"@material/mwc-textfield": "0.25.3",
|
||||||
"@material/top-app-bar": "14.0.0-canary.353ca7e9f.0",
|
"@material/top-app-bar": "14.0.0-canary.261f2db59.0",
|
||||||
"@mdi/js": "6.3.95",
|
"@mdi/js": "6.4.95",
|
||||||
"@mdi/svg": "6.3.95",
|
"@mdi/svg": "6.4.95",
|
||||||
"@polymer/app-layout": "^3.1.0",
|
"@polymer/app-layout": "^3.1.0",
|
||||||
"@polymer/iron-flex-layout": "^3.0.1",
|
"@polymer/iron-flex-layout": "^3.0.1",
|
||||||
"@polymer/iron-icon": "^3.0.1",
|
"@polymer/iron-icon": "^3.0.1",
|
||||||
@@ -78,7 +78,6 @@
|
|||||||
"@polymer/paper-input": "^3.2.1",
|
"@polymer/paper-input": "^3.2.1",
|
||||||
"@polymer/paper-item": "^3.0.1",
|
"@polymer/paper-item": "^3.0.1",
|
||||||
"@polymer/paper-listbox": "^3.0.1",
|
"@polymer/paper-listbox": "^3.0.1",
|
||||||
"@polymer/paper-ripple": "^3.0.2",
|
|
||||||
"@polymer/paper-slider": "^3.0.1",
|
"@polymer/paper-slider": "^3.0.1",
|
||||||
"@polymer/paper-styles": "^3.0.1",
|
"@polymer/paper-styles": "^3.0.1",
|
||||||
"@polymer/paper-tabs": "^3.1.0",
|
"@polymer/paper-tabs": "^3.1.0",
|
||||||
@@ -109,7 +108,7 @@
|
|||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"leaflet": "^1.7.1",
|
"leaflet": "^1.7.1",
|
||||||
"leaflet-draw": "^1.0.4",
|
"leaflet-draw": "^1.0.4",
|
||||||
"lit": "^2.0.0",
|
"lit": "^2.0.2",
|
||||||
"lit-vaadin-helpers": "^0.2.1",
|
"lit-vaadin-helpers": "^0.2.1",
|
||||||
"marked": "^3.0.2",
|
"marked": "^3.0.2",
|
||||||
"memoize-one": "^5.2.1",
|
"memoize-one": "^5.2.1",
|
||||||
@@ -181,7 +180,7 @@
|
|||||||
"eslint-import-resolver-webpack": "^0.13.1",
|
"eslint-import-resolver-webpack": "^0.13.1",
|
||||||
"eslint-plugin-disable": "^2.0.1",
|
"eslint-plugin-disable": "^2.0.1",
|
||||||
"eslint-plugin-import": "^2.24.2",
|
"eslint-plugin-import": "^2.24.2",
|
||||||
"eslint-plugin-lit": "^1.5.1",
|
"eslint-plugin-lit": "^1.6.1",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
"eslint-plugin-unused-imports": "^1.1.5",
|
"eslint-plugin-unused-imports": "^1.1.5",
|
||||||
"eslint-plugin-wc": "^1.3.2",
|
"eslint-plugin-wc": "^1.3.2",
|
||||||
@@ -231,10 +230,10 @@
|
|||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@polymer/polymer": "patch:@polymer/polymer@3.4.1#./.yarn/patches/@polymer/polymer/pr-5569.patch",
|
"@polymer/polymer": "patch:@polymer/polymer@3.4.1#./.yarn/patches/@polymer/polymer/pr-5569.patch",
|
||||||
"@webcomponents/webcomponentsjs": "^2.2.10",
|
"@webcomponents/webcomponentsjs": "^2.2.10",
|
||||||
"lit": "^2.0.0",
|
"lit": "^2.0.2",
|
||||||
"lit-html": "2.0.0",
|
"lit-html": "2.0.1",
|
||||||
"lit-element": "3.0.0",
|
"lit-element": "3.0.1",
|
||||||
"@lit/reactive-element": "1.0.0"
|
"@lit/reactive-element": "1.0.1"
|
||||||
},
|
},
|
||||||
"main": "src/home-assistant.js",
|
"main": "src/home-assistant.js",
|
||||||
"husky": {
|
"husky": {
|
||||||
|
2
setup.py
2
setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="home-assistant-frontend",
|
name="home-assistant-frontend",
|
||||||
version="20211020.0",
|
version="20211028.0",
|
||||||
description="The Home Assistant frontend",
|
description="The Home Assistant frontend",
|
||||||
url="https://github.com/home-assistant/frontend",
|
url="https://github.com/home-assistant/frontend",
|
||||||
author="The Home Assistant Authors",
|
author="The Home Assistant Authors",
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
|
import { genClientId } from "home-assistant-js-websocket";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
@@ -7,18 +8,20 @@ import {
|
|||||||
PropertyValues,
|
PropertyValues,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import "./ha-password-manager-polyfill";
|
|
||||||
import { property, state } from "lit/decorators";
|
import { property, state } from "lit/decorators";
|
||||||
import "../components/ha-form/ha-form";
|
|
||||||
import "../components/ha-markdown";
|
|
||||||
import "../components/ha-alert";
|
import "../components/ha-alert";
|
||||||
|
import "../components/ha-checkbox";
|
||||||
|
import { computeInitialHaFormData } from "../components/ha-form/compute-initial-ha-form-data";
|
||||||
|
import "../components/ha-form/ha-form";
|
||||||
|
import "../components/ha-formfield";
|
||||||
|
import "../components/ha-markdown";
|
||||||
import { AuthProvider } from "../data/auth";
|
import { AuthProvider } from "../data/auth";
|
||||||
import {
|
import {
|
||||||
DataEntryFlowStep,
|
DataEntryFlowStep,
|
||||||
DataEntryFlowStepForm,
|
DataEntryFlowStepForm,
|
||||||
} from "../data/data_entry_flow";
|
} from "../data/data_entry_flow";
|
||||||
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
|
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";
|
type State = "loading" | "error" | "step";
|
||||||
|
|
||||||
@@ -41,6 +44,8 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
|||||||
|
|
||||||
@state() private _submitting = false;
|
@state() private _submitting = false;
|
||||||
|
|
||||||
|
@state() private _storeToken = false;
|
||||||
|
|
||||||
willUpdate(changedProps: PropertyValues) {
|
willUpdate(changedProps: PropertyValues) {
|
||||||
super.willUpdate(changedProps);
|
super.willUpdate(changedProps);
|
||||||
|
|
||||||
@@ -201,12 +206,30 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
|||||||
.computeError=${this._computeErrorCallback(step)}
|
.computeError=${this._computeErrorCallback(step)}
|
||||||
@value-changed=${this._stepDataChanged}
|
@value-changed=${this._stepDataChanged}
|
||||||
></ha-form>
|
></ha-form>
|
||||||
|
${this.clientId === genClientId() &&
|
||||||
|
!["select_mfa_module", "mfa"].includes(step.step_id)
|
||||||
|
? 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:
|
default:
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _storeTokenChanged(e: CustomEvent<HTMLInputElement>) {
|
||||||
|
this._storeToken = (e.currentTarget as HTMLInputElement).checked;
|
||||||
|
}
|
||||||
|
|
||||||
private async _providerChanged(newProvider?: AuthProvider) {
|
private async _providerChanged(newProvider?: AuthProvider) {
|
||||||
if (this._step && this._step.type === "form") {
|
if (this._step && this._step.type === "form") {
|
||||||
fetch(`/auth/login_flow/${this._step.flow_id}`, {
|
fetch(`/auth/login_flow/${this._step.flow_id}`, {
|
||||||
@@ -274,6 +297,9 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
|||||||
if (this.oauth2State) {
|
if (this.oauth2State) {
|
||||||
url += `&state=${encodeURIComponent(this.oauth2State)}`;
|
url += `&state=${encodeURIComponent(this.oauth2State)}`;
|
||||||
}
|
}
|
||||||
|
if (this._storeToken) {
|
||||||
|
url += `&storeToken=true`;
|
||||||
|
}
|
||||||
|
|
||||||
document.location.assign(url);
|
document.location.assign(url);
|
||||||
}
|
}
|
||||||
@@ -357,6 +383,11 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
|||||||
margin: 24px 0 8px;
|
margin: 24px 0 8px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
/* Align with the rest of the form. */
|
||||||
|
.store-token {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-left: -16px;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { AuthData } from "home-assistant-js-websocket";
|
import { AuthData } from "home-assistant-js-websocket";
|
||||||
|
import { extractSearchParam } from "../url/search-params";
|
||||||
|
|
||||||
const storage = window.localStorage || {};
|
const storage = window.localStorage || {};
|
||||||
|
|
||||||
@@ -30,6 +31,11 @@ export function askWrite() {
|
|||||||
|
|
||||||
export function saveTokens(tokens: AuthData | null) {
|
export function saveTokens(tokens: AuthData | null) {
|
||||||
tokenCache.tokens = tokens;
|
tokenCache.tokens = tokens;
|
||||||
|
|
||||||
|
if (!tokenCache.writeEnabled && extractSearchParam("storeToken") === "true") {
|
||||||
|
tokenCache.writeEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (tokenCache.writeEnabled) {
|
if (tokenCache.writeEnabled) {
|
||||||
try {
|
try {
|
||||||
storage.hassTokens = JSON.stringify(tokens);
|
storage.hassTokens = JSON.stringify(tokens);
|
||||||
@@ -45,7 +51,6 @@ export function enableWrite() {
|
|||||||
saveTokens(tokenCache.tokens);
|
saveTokens(tokenCache.tokens);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadTokens() {
|
export function loadTokens() {
|
||||||
if (tokenCache.tokens === undefined) {
|
if (tokenCache.tokens === undefined) {
|
||||||
try {
|
try {
|
||||||
|
@@ -1,2 +1,3 @@
|
|||||||
/** An empty image which can be set as src of an img element. */
|
/** An empty image which can be set as src of an img element. */
|
||||||
export default "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
|
export const emptyImageBase64 =
|
||||||
|
"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
|
||||||
|
@@ -1,43 +1,45 @@
|
|||||||
import {
|
import {
|
||||||
mdiBattery,
|
|
||||||
mdiBatteryOutline,
|
|
||||||
mdiBatteryCharging,
|
|
||||||
mdiThermometer,
|
|
||||||
mdiSnowflake,
|
|
||||||
mdiServerNetworkOff,
|
|
||||||
mdiServerNetwork,
|
|
||||||
mdiDoorClosed,
|
|
||||||
mdiDoorOpen,
|
|
||||||
mdiGarage,
|
|
||||||
mdiGarageOpen,
|
|
||||||
mdiPowerPlugOff,
|
|
||||||
mdiPowerPlug,
|
|
||||||
mdiCheckCircle,
|
|
||||||
mdiAlertCircle,
|
mdiAlertCircle,
|
||||||
mdiSmoke,
|
mdiBattery,
|
||||||
mdiFire,
|
mdiBatteryCharging,
|
||||||
|
mdiBatteryOutline,
|
||||||
mdiBrightness5,
|
mdiBrightness5,
|
||||||
mdiBrightness7,
|
mdiBrightness7,
|
||||||
|
mdiCheckboxMarkedCircle,
|
||||||
|
mdiCheckCircle,
|
||||||
|
mdiCropPortrait,
|
||||||
|
mdiDoorClosed,
|
||||||
|
mdiDoorOpen,
|
||||||
|
mdiFire,
|
||||||
|
mdiGarage,
|
||||||
|
mdiGarageOpen,
|
||||||
|
mdiHome,
|
||||||
|
mdiHomeOutline,
|
||||||
mdiLock,
|
mdiLock,
|
||||||
mdiLockOpen,
|
mdiLockOpen,
|
||||||
mdiWaterOff,
|
|
||||||
mdiWater,
|
|
||||||
mdiWalk,
|
|
||||||
mdiRun,
|
|
||||||
mdiHomeOutline,
|
|
||||||
mdiHome,
|
|
||||||
mdiSquare,
|
|
||||||
mdiSquareOutline,
|
|
||||||
mdiMusicNoteOff,
|
|
||||||
mdiMusicNote,
|
mdiMusicNote,
|
||||||
|
mdiMusicNoteOff,
|
||||||
mdiPackage,
|
mdiPackage,
|
||||||
mdiPackageUp,
|
mdiPackageUp,
|
||||||
mdiCropPortrait,
|
mdiPlay,
|
||||||
|
mdiPowerPlug,
|
||||||
|
mdiPowerPlugOff,
|
||||||
|
mdiRadioboxBlank,
|
||||||
|
mdiRun,
|
||||||
|
mdiServerNetwork,
|
||||||
|
mdiServerNetworkOff,
|
||||||
|
mdiSmoke,
|
||||||
|
mdiSnowflake,
|
||||||
|
mdiSquare,
|
||||||
|
mdiSquareOutline,
|
||||||
|
mdiStop,
|
||||||
|
mdiThermometer,
|
||||||
mdiVibrate,
|
mdiVibrate,
|
||||||
|
mdiWalk,
|
||||||
|
mdiWater,
|
||||||
|
mdiWaterOff,
|
||||||
mdiWindowClosed,
|
mdiWindowClosed,
|
||||||
mdiWindowOpen,
|
mdiWindowOpen,
|
||||||
mdiRadioboxBlank,
|
|
||||||
mdiCheckboxMarkedCircle,
|
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
|
|
||||||
@@ -85,6 +87,8 @@ export const binarySensorIcon = (state?: string, stateObj?: HassEntity) => {
|
|||||||
return is_off ? mdiPowerPlugOff : mdiPowerPlug;
|
return is_off ? mdiPowerPlugOff : mdiPowerPlug;
|
||||||
case "presence":
|
case "presence":
|
||||||
return is_off ? mdiHomeOutline : mdiHome;
|
return is_off ? mdiHomeOutline : mdiHome;
|
||||||
|
case "running":
|
||||||
|
return is_off ? mdiStop : mdiPlay;
|
||||||
case "sound":
|
case "sound":
|
||||||
return is_off ? mdiMusicNoteOff : mdiMusicNote;
|
return is_off ? mdiMusicNoteOff : mdiMusicNote;
|
||||||
case "update":
|
case "update":
|
||||||
|
@@ -39,7 +39,7 @@ export const computeStateDisplay = (
|
|||||||
const domain = computeStateDomain(stateObj);
|
const domain = computeStateDomain(stateObj);
|
||||||
|
|
||||||
if (domain === "input_datetime") {
|
if (domain === "input_datetime") {
|
||||||
if (state) {
|
if (state !== undefined) {
|
||||||
// If trying to display an explicit state, need to parse the explict state to `Date` then format.
|
// If trying to display an explicit state, need to parse the explict state to `Date` then format.
|
||||||
// Attributes aren't available, we have to use `state`.
|
// Attributes aren't available, we have to use `state`.
|
||||||
try {
|
try {
|
||||||
@@ -63,7 +63,7 @@ export const computeStateDisplay = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
} catch {
|
} catch (_e) {
|
||||||
// Formatting methods may throw error if date parsing doesn't go well,
|
// Formatting methods may throw error if date parsing doesn't go well,
|
||||||
// just return the state string in that case.
|
// just return the state string in that case.
|
||||||
return state;
|
return state;
|
||||||
@@ -71,7 +71,17 @@ export const computeStateDisplay = (
|
|||||||
} else {
|
} else {
|
||||||
// If not trying to display an explicit state, create `Date` object from `stateObj`'s attributes then format.
|
// If not trying to display an explicit state, create `Date` object from `stateObj`'s attributes then format.
|
||||||
let date: Date;
|
let date: Date;
|
||||||
if (!stateObj.attributes.has_time) {
|
if (stateObj.attributes.has_date && stateObj.attributes.has_time) {
|
||||||
|
date = new Date(
|
||||||
|
stateObj.attributes.year,
|
||||||
|
stateObj.attributes.month - 1,
|
||||||
|
stateObj.attributes.day,
|
||||||
|
stateObj.attributes.hour,
|
||||||
|
stateObj.attributes.minute
|
||||||
|
);
|
||||||
|
return formatDateTime(date, locale);
|
||||||
|
}
|
||||||
|
if (stateObj.attributes.has_date) {
|
||||||
date = new Date(
|
date = new Date(
|
||||||
stateObj.attributes.year,
|
stateObj.attributes.year,
|
||||||
stateObj.attributes.month - 1,
|
stateObj.attributes.month - 1,
|
||||||
@@ -79,20 +89,12 @@ export const computeStateDisplay = (
|
|||||||
);
|
);
|
||||||
return formatDate(date, locale);
|
return formatDate(date, locale);
|
||||||
}
|
}
|
||||||
if (!stateObj.attributes.has_date) {
|
if (stateObj.attributes.has_time) {
|
||||||
date = new Date();
|
date = new Date();
|
||||||
date.setHours(stateObj.attributes.hour, stateObj.attributes.minute);
|
date.setHours(stateObj.attributes.hour, stateObj.attributes.minute);
|
||||||
return formatTime(date, locale);
|
return formatTime(date, locale);
|
||||||
}
|
}
|
||||||
|
return stateObj.state;
|
||||||
date = new Date(
|
|
||||||
stateObj.attributes.year,
|
|
||||||
stateObj.attributes.month - 1,
|
|
||||||
stateObj.attributes.day,
|
|
||||||
stateObj.attributes.hour,
|
|
||||||
stateObj.attributes.minute
|
|
||||||
);
|
|
||||||
return formatDateTime(date, locale);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
24
src/common/entity/strip_prefix_from_entity_name.ts
Normal file
24
src/common/entity/strip_prefix_from_entity_name.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
@@ -12,8 +12,8 @@ export const slugify = (value: string, delimiter = "_") => {
|
|||||||
.replace(p, (c) => b.charAt(a.indexOf(c))) // Replace special characters
|
.replace(p, (c) => b.charAt(a.indexOf(c))) // Replace special characters
|
||||||
.replace(/&/g, `${delimiter}and${delimiter}`) // Replace & with 'and'
|
.replace(/&/g, `${delimiter}and${delimiter}`) // Replace & with 'and'
|
||||||
.replace(/[^\w-]+/g, "") // Remove all non-word characters
|
.replace(/[^\w-]+/g, "") // Remove all non-word characters
|
||||||
.replace(/-/, delimiter) // Replace - with delimiter
|
.replace(/-/g, delimiter) // Replace - with delimiter
|
||||||
.replace(new RegExp(`/${delimiter}${delimiter}+/`, "g"), delimiter) // Replace multiple delimiters with single 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 start of text
|
||||||
.replace(new RegExp(`/-+$/`), ""); // Trim delimiter from end of text
|
.replace(new RegExp(`${delimiter}+$`), ""); // Trim delimiter from end of text
|
||||||
};
|
};
|
||||||
|
@@ -24,7 +24,7 @@ const BINARY_SENSOR_DEVICE_CLASS_COLOR_NOT_INVERTED = new Set([
|
|||||||
"plug",
|
"plug",
|
||||||
"power",
|
"power",
|
||||||
"presence",
|
"presence",
|
||||||
"update",
|
"running",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const STATIC_STATE_COLORS = new Set([
|
const STATIC_STATE_COLORS = new Set([
|
||||||
|
@@ -70,7 +70,7 @@ export interface DataTableSortColumnData {
|
|||||||
|
|
||||||
export interface DataTableColumnData extends DataTableSortColumnData {
|
export interface DataTableColumnData extends DataTableSortColumnData {
|
||||||
title: TemplateResult | string;
|
title: TemplateResult | string;
|
||||||
type?: "numeric" | "icon" | "icon-button";
|
type?: "numeric" | "icon" | "icon-button" | "overflow-menu";
|
||||||
template?: <T>(data: any, row: T) => TemplateResult | string;
|
template?: <T>(data: any, row: T) => TemplateResult | string;
|
||||||
width?: string;
|
width?: string;
|
||||||
maxWidth?: string;
|
maxWidth?: string;
|
||||||
@@ -281,15 +281,13 @@ export class HaDataTable extends LitElement {
|
|||||||
}
|
}
|
||||||
const sorted = key === this._sortColumn;
|
const sorted = key === this._sortColumn;
|
||||||
const classes = {
|
const classes = {
|
||||||
"mdc-data-table__header-cell--numeric": Boolean(
|
"mdc-data-table__header-cell--numeric":
|
||||||
column.type === "numeric"
|
column.type === "numeric",
|
||||||
),
|
"mdc-data-table__header-cell--icon": column.type === "icon",
|
||||||
"mdc-data-table__header-cell--icon": Boolean(
|
"mdc-data-table__header-cell--icon-button":
|
||||||
column.type === "icon"
|
column.type === "icon-button",
|
||||||
),
|
"mdc-data-table__header-cell--overflow-menu":
|
||||||
"mdc-data-table__header-cell--icon-button": Boolean(
|
column.type === "overflow-menu",
|
||||||
column.type === "icon-button"
|
|
||||||
),
|
|
||||||
sortable: Boolean(column.sortable),
|
sortable: Boolean(column.sortable),
|
||||||
"not-sorted": Boolean(column.sortable && !sorted),
|
"not-sorted": Boolean(column.sortable && !sorted),
|
||||||
grows: Boolean(column.grows),
|
grows: Boolean(column.grows),
|
||||||
@@ -405,14 +403,14 @@ export class HaDataTable extends LitElement {
|
|||||||
<div
|
<div
|
||||||
role="cell"
|
role="cell"
|
||||||
class="mdc-data-table__cell ${classMap({
|
class="mdc-data-table__cell ${classMap({
|
||||||
"mdc-data-table__cell--numeric": Boolean(
|
"mdc-data-table__cell--numeric":
|
||||||
column.type === "numeric"
|
column.type === "numeric",
|
||||||
),
|
"mdc-data-table__cell--icon":
|
||||||
"mdc-data-table__cell--icon": Boolean(
|
column.type === "icon",
|
||||||
column.type === "icon"
|
|
||||||
),
|
|
||||||
"mdc-data-table__cell--icon-button":
|
"mdc-data-table__cell--icon-button":
|
||||||
Boolean(column.type === "icon-button"),
|
column.type === "icon-button",
|
||||||
|
"mdc-data-table__cell--overflow-menu":
|
||||||
|
column.type === "overflow-menu",
|
||||||
grows: Boolean(column.grows),
|
grows: Boolean(column.grows),
|
||||||
forceLTR: Boolean(column.forceLTR),
|
forceLTR: Boolean(column.forceLTR),
|
||||||
})}"
|
})}"
|
||||||
@@ -769,40 +767,65 @@ export class HaDataTable extends LitElement {
|
|||||||
margin-left: -8px;
|
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__header-cell--icon-button,
|
||||||
.mdc-data-table__cell--icon-button {
|
.mdc-data-table__cell--icon-button {
|
||||||
width: 56px;
|
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mdc-data-table__header-cell--icon-button,
|
||||||
|
.mdc-data-table__cell--icon-button {
|
||||||
|
width: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mdc-data-table__cell--overflow-menu,
|
||||||
.mdc-data-table__cell--icon-button {
|
.mdc-data-table__cell--icon-button {
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
text-overflow: clip;
|
text-overflow: clip;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mdc-data-table__header-cell--icon-button:first-child,
|
.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__header-cell--icon-button:last-child,
|
||||||
.mdc-data-table__cell--icon-button:last-child {
|
.mdc-data-table__cell--icon-button:last-child {
|
||||||
width: 64px;
|
width: 64px;
|
||||||
padding-right: 16px;
|
|
||||||
}
|
|
||||||
: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: auto;
|
|
||||||
padding-left: 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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-left: 16px;
|
||||||
|
}
|
||||||
|
.mdc-data-table__cell--overflow-menu,
|
||||||
|
.mdc-data-table__header-cell--overflow-menu {
|
||||||
|
overflow: initial;
|
||||||
|
}
|
||||||
.mdc-data-table__cell--icon-button a {
|
.mdc-data-table__cell--icon-button a {
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
|
@@ -77,7 +77,7 @@ export class HaStateLabelBadge extends LitElement {
|
|||||||
const domain = computeStateDomain(entityState);
|
const domain = computeStateDomain(entityState);
|
||||||
|
|
||||||
const showIcon = this.icon || this._computeShowIcon(domain, entityState);
|
const showIcon = this.icon || this._computeShowIcon(domain, entityState);
|
||||||
const image = showIcon
|
const image = this.icon
|
||||||
? ""
|
? ""
|
||||||
: this.image
|
: this.image
|
||||||
? this.image
|
? this.image
|
||||||
|
@@ -102,7 +102,12 @@ export class HaStatisticPicker extends LitElement {
|
|||||||
</style>
|
</style>
|
||||||
<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
|
<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
|
||||||
<paper-icon-item>
|
<paper-icon-item>
|
||||||
<state-badge slot="item-icon" .stateObj=${item.state}></state-badge>
|
${item.state
|
||||||
|
? html`<state-badge
|
||||||
|
slot="item-icon"
|
||||||
|
.stateObj=${item.state}
|
||||||
|
></state-badge>`
|
||||||
|
: ""}
|
||||||
<paper-item-body two-line="">
|
<paper-item-body two-line="">
|
||||||
${item.name}
|
${item.name}
|
||||||
<span secondary
|
<span secondary
|
||||||
@@ -153,7 +158,10 @@ export class HaStatisticPicker extends LitElement {
|
|||||||
const entityState = this.hass.states[meta.statistic_id];
|
const entityState = this.hass.states[meta.statistic_id];
|
||||||
if (!entityState) {
|
if (!entityState) {
|
||||||
if (!entitiesOnly) {
|
if (!entitiesOnly) {
|
||||||
output.push({ id: meta.statistic_id, name: meta.statistic_id });
|
output.push({
|
||||||
|
id: meta.statistic_id,
|
||||||
|
name: meta.name || meta.statistic_id,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@@ -80,16 +80,14 @@ export class StateBadge extends LitElement {
|
|||||||
|
|
||||||
this._showIcon = true;
|
this._showIcon = true;
|
||||||
|
|
||||||
if (stateObj) {
|
if (stateObj && this.overrideImage === undefined) {
|
||||||
// hide icon if we have entity picture
|
// hide icon if we have entity picture
|
||||||
if (
|
if (
|
||||||
((stateObj.attributes.entity_picture_local ||
|
(stateObj.attributes.entity_picture_local ||
|
||||||
stateObj.attributes.entity_picture) &&
|
stateObj.attributes.entity_picture) &&
|
||||||
!this.overrideIcon) ||
|
!this.overrideIcon
|
||||||
this.overrideImage
|
|
||||||
) {
|
) {
|
||||||
let imageUrl =
|
let imageUrl =
|
||||||
this.overrideImage ||
|
|
||||||
stateObj.attributes.entity_picture_local ||
|
stateObj.attributes.entity_picture_local ||
|
||||||
stateObj.attributes.entity_picture;
|
stateObj.attributes.entity_picture;
|
||||||
if (this.hass) {
|
if (this.hass) {
|
||||||
|
@@ -86,7 +86,7 @@ class HaCameraStream extends LitElement {
|
|||||||
}
|
}
|
||||||
if (this.stateObj.attributes.frontend_stream_type === STREAM_TYPE_HLS) {
|
if (this.stateObj.attributes.frontend_stream_type === STREAM_TYPE_HLS) {
|
||||||
return this._url
|
return this._url
|
||||||
? html` <ha-hls-player
|
? html`<ha-hls-player
|
||||||
autoplay
|
autoplay
|
||||||
playsinline
|
playsinline
|
||||||
.allowExoPlayer=${this.allowExoPlayer}
|
.allowExoPlayer=${this.allowExoPlayer}
|
||||||
@@ -98,7 +98,7 @@ class HaCameraStream extends LitElement {
|
|||||||
: html``;
|
: html``;
|
||||||
}
|
}
|
||||||
if (this.stateObj.attributes.frontend_stream_type === STREAM_TYPE_WEB_RTC) {
|
if (this.stateObj.attributes.frontend_stream_type === STREAM_TYPE_WEB_RTC) {
|
||||||
return html` <ha-web-rtc-player
|
return html`<ha-web-rtc-player
|
||||||
autoplay
|
autoplay
|
||||||
playsinline
|
playsinline
|
||||||
.muted=${this.muted}
|
.muted=${this.muted}
|
||||||
@@ -115,23 +115,18 @@ class HaCameraStream extends LitElement {
|
|||||||
// Fallback when unable to fetch stream url
|
// Fallback when unable to fetch stream url
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (
|
if (!supportsFeature(this.stateObj!, CAMERA_SUPPORT_STREAM)) {
|
||||||
!isComponentLoaded(this.hass!, "stream") ||
|
|
||||||
!supportsFeature(this.stateObj!, CAMERA_SUPPORT_STREAM)
|
|
||||||
) {
|
|
||||||
// Steaming is not supported by the camera so fallback to MJPEG stream
|
// Steaming is not supported by the camera so fallback to MJPEG stream
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
this.stateObj!.attributes.frontend_stream_type === STREAM_TYPE_WEB_RTC &&
|
this.stateObj!.attributes.frontend_stream_type === STREAM_TYPE_WEB_RTC
|
||||||
typeof RTCPeerConnection === "undefined"
|
|
||||||
) {
|
) {
|
||||||
// Stream requires WebRTC but browser does not support, so fallback to
|
// Browser support required for WebRTC
|
||||||
// MJPEG stream.
|
return typeof RTCPeerConnection === "undefined";
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
// Render stream
|
// Server side stream component required for HLS
|
||||||
return false;
|
return !isComponentLoaded(this.hass!, "stream");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _getStreamUrl(): Promise<void> {
|
private async _getStreamUrl(): Promise<void> {
|
||||||
|
@@ -36,7 +36,11 @@ export class HaFormInteger extends LitElement implements HaFormElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if ("valueMin" in this.schema && "valueMax" in this.schema) {
|
if (
|
||||||
|
this.schema.valueMin !== undefined &&
|
||||||
|
this.schema.valueMax !== undefined &&
|
||||||
|
this.schema.valueMax - this.schema.valueMin < 256
|
||||||
|
) {
|
||||||
return html`
|
return html`
|
||||||
<div>
|
<div>
|
||||||
${this.label}
|
${this.label}
|
||||||
@@ -96,10 +100,15 @@ export class HaFormInteger extends LitElement implements HaFormElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.schema.optional) {
|
if (this.schema.optional) {
|
||||||
return 0;
|
return this.schema.valueMin || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.schema.description?.suggested_value || this.schema.default || 0;
|
return (
|
||||||
|
this.schema.description?.suggested_value ||
|
||||||
|
this.schema.default ||
|
||||||
|
this.schema.valueMin ||
|
||||||
|
0
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleCheckboxChange(ev: Event) {
|
private _handleCheckboxChange(ev: Event) {
|
||||||
|
@@ -52,6 +52,7 @@ export class HaFormSelect extends LitElement implements HaFormElement {
|
|||||||
return html`
|
return html`
|
||||||
<mwc-select
|
<mwc-select
|
||||||
fixedMenuPosition
|
fixedMenuPosition
|
||||||
|
naturalMenuWidth
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.value=${this.data}
|
.value=${this.data}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
|
@@ -229,6 +229,7 @@ class HaHLSPlayer extends LitElement {
|
|||||||
|
|
||||||
video {
|
video {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
max-height: var(--video-max-height, calc(100vh - 97px));
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
119
src/components/ha-icon-overflow-menu.ts
Normal file
119
src/components/ha-icon-overflow-menu.ts
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@@ -7,14 +7,19 @@ import { css, html, LitElement, TemplateResult } from "lit";
|
|||||||
import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers";
|
import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
import { customIcons } from "../data/custom_icons";
|
||||||
import { PolymerChangedEvent } from "../polymer-types";
|
import { PolymerChangedEvent } from "../polymer-types";
|
||||||
import "./ha-icon";
|
import "./ha-icon";
|
||||||
import "./ha-icon-button";
|
import "./ha-icon-button";
|
||||||
|
|
||||||
let mdiIconList: string[] = [];
|
type IconItem = {
|
||||||
|
icon: string;
|
||||||
|
keywords: string[];
|
||||||
|
};
|
||||||
|
let iconItems: IconItem[] = [];
|
||||||
|
|
||||||
// eslint-disable-next-line lit/prefer-static-styles
|
// eslint-disable-next-line lit/prefer-static-styles
|
||||||
const rowRenderer: ComboBoxLitRenderer<string> = (item) => html`<style>
|
const rowRenderer: ComboBoxLitRenderer<IconItem> = (item) => html`<style>
|
||||||
paper-icon-item {
|
paper-icon-item {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: -8px;
|
margin: -8px;
|
||||||
@@ -37,8 +42,8 @@ const rowRenderer: ComboBoxLitRenderer<string> = (item) => html`<style>
|
|||||||
|
|
||||||
<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
|
<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
|
||||||
<paper-icon-item>
|
<paper-icon-item>
|
||||||
<ha-icon .icon=${item} slot="item-icon"></ha-icon>
|
<ha-icon .icon=${item.icon} slot="item-icon"></ha-icon>
|
||||||
<paper-item-body>${item}</paper-item-body>
|
<paper-item-body>${item.icon}</paper-item-body>
|
||||||
</paper-icon-item>`;
|
</paper-icon-item>`;
|
||||||
|
|
||||||
@customElement("ha-icon-picker")
|
@customElement("ha-icon-picker")
|
||||||
@@ -66,7 +71,7 @@ export class HaIconPicker extends LitElement {
|
|||||||
item-label-path="icon"
|
item-label-path="icon"
|
||||||
.value=${this._value}
|
.value=${this._value}
|
||||||
allow-custom-value
|
allow-custom-value
|
||||||
.filteredItems=${mdiIconList}
|
.filteredItems=${iconItems}
|
||||||
${comboBoxRenderer(rowRenderer)}
|
${comboBoxRenderer(rowRenderer)}
|
||||||
@opened-changed=${this._openedChanged}
|
@opened-changed=${this._openedChanged}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
@@ -105,10 +110,38 @@ export class HaIconPicker extends LitElement {
|
|||||||
|
|
||||||
private async _openedChanged(ev: PolymerChangedEvent<boolean>) {
|
private async _openedChanged(ev: PolymerChangedEvent<boolean>) {
|
||||||
this._opened = ev.detail.value;
|
this._opened = ev.detail.value;
|
||||||
if (this._opened && !mdiIconList.length) {
|
if (this._opened && !iconItems.length) {
|
||||||
const iconList = await import("../../build/mdi/iconList.json");
|
const iconList = await import("../../build/mdi/iconList.json");
|
||||||
mdiIconList = iconList.default.map((icon) => `mdi:${icon}`);
|
|
||||||
(this.comboBox as any).filteredItems = mdiIconList;
|
iconItems = iconList.default.map((icon) => ({
|
||||||
|
icon: `mdi:${icon.name}`,
|
||||||
|
keywords: icon.keywords,
|
||||||
|
}));
|
||||||
|
|
||||||
|
(this.comboBox as any).filteredItems = iconItems;
|
||||||
|
|
||||||
|
Object.keys(customIcons).forEach((iconSet) => {
|
||||||
|
this._loadCustomIconItems(iconSet);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _loadCustomIconItems(iconsetPrefix: string) {
|
||||||
|
try {
|
||||||
|
const getIconList = customIcons[iconsetPrefix].getIconList;
|
||||||
|
if (typeof getIconList !== "function") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const iconList = await getIconList();
|
||||||
|
const customIconItems = iconList.map((icon) => ({
|
||||||
|
icon: `${iconsetPrefix}:${icon.name}`,
|
||||||
|
keywords: icon.keywords ?? [],
|
||||||
|
}));
|
||||||
|
iconItems.push(...customIconItems);
|
||||||
|
(this.comboBox as any).filteredItems = iconItems;
|
||||||
|
} catch (e) {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
console.warn(`Unable to load icon list for ${iconsetPrefix} iconset`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,16 +166,30 @@ export class HaIconPicker extends LitElement {
|
|||||||
const filterString = ev.detail.value.toLowerCase();
|
const filterString = ev.detail.value.toLowerCase();
|
||||||
const characterCount = filterString.length;
|
const characterCount = filterString.length;
|
||||||
if (characterCount >= 2) {
|
if (characterCount >= 2) {
|
||||||
const filteredItems = mdiIconList.filter((icon) =>
|
const filteredItems: IconItem[] = [];
|
||||||
icon.includes(filterString)
|
const filteredItemsByKeywords: IconItem[] = [];
|
||||||
);
|
|
||||||
|
iconItems.forEach((item) => {
|
||||||
|
if (item.icon.includes(filterString)) {
|
||||||
|
filteredItems.push(item);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (item.keywords.some((t) => t.includes(filterString))) {
|
||||||
|
filteredItemsByKeywords.push(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
filteredItems.push(...filteredItemsByKeywords);
|
||||||
|
|
||||||
if (filteredItems.length > 0) {
|
if (filteredItems.length > 0) {
|
||||||
(this.comboBox as any).filteredItems = filteredItems;
|
(this.comboBox as any).filteredItems = filteredItems;
|
||||||
} else {
|
} else {
|
||||||
(this.comboBox as any).filteredItems = [filterString];
|
(this.comboBox as any).filteredItems = [
|
||||||
|
{ icon: filterString, keywords: [] },
|
||||||
|
];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
(this.comboBox as any).filteredItems = mdiIconList;
|
(this.comboBox as any).filteredItems = iconItems;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -10,7 +10,7 @@ import {
|
|||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { debounce } from "../common/util/debounce";
|
import { debounce } from "../common/util/debounce";
|
||||||
import { CustomIcon, customIconsets } from "../data/custom_iconsets";
|
import { CustomIcon, customIcons } from "../data/custom_icons";
|
||||||
import {
|
import {
|
||||||
checkCacheVersion,
|
checkCacheVersion,
|
||||||
Chunks,
|
Chunks,
|
||||||
@@ -52,18 +52,6 @@ const mdiDeprecatedIcons: DeprecatedIcon = {
|
|||||||
newName: "cast-variant",
|
newName: "cast-variant",
|
||||||
removeIn: "2021.12",
|
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: {
|
bandcamp: {
|
||||||
removeIn: "2021.12",
|
removeIn: "2021.12",
|
||||||
},
|
},
|
||||||
@@ -77,14 +65,6 @@ const mdiDeprecatedIcons: DeprecatedIcon = {
|
|||||||
newName: "cross-bolnisi",
|
newName: "cross-bolnisi",
|
||||||
removeIn: "2021.12",
|
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": {
|
"boom-gate-down": {
|
||||||
newName: "boom-gate-arrow-down",
|
newName: "boom-gate-arrow-down",
|
||||||
removeIn: "2021.12",
|
removeIn: "2021.12",
|
||||||
@@ -376,7 +356,7 @@ export class HaIcon extends LitElement {
|
|||||||
|
|
||||||
@state() private _path?: string;
|
@state() private _path?: string;
|
||||||
|
|
||||||
@state() private _viewBox?;
|
@state() private _viewBox?: string;
|
||||||
|
|
||||||
@state() private _legacy = false;
|
@state() private _legacy = false;
|
||||||
|
|
||||||
@@ -406,6 +386,7 @@ export class HaIcon extends LitElement {
|
|||||||
if (!this.icon) {
|
if (!this.icon) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const requestedIcon = this.icon;
|
||||||
const [iconPrefix, origIconName] = this.icon.split(":", 2);
|
const [iconPrefix, origIconName] = this.icon.split(":", 2);
|
||||||
|
|
||||||
let iconName = origIconName;
|
let iconName = origIconName;
|
||||||
@@ -415,10 +396,10 @@ export class HaIcon extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!MDI_PREFIXES.includes(iconPrefix)) {
|
if (!MDI_PREFIXES.includes(iconPrefix)) {
|
||||||
if (iconPrefix in customIconsets) {
|
if (iconPrefix in customIcons) {
|
||||||
const customIconset = customIconsets[iconPrefix];
|
const customIcon = customIcons[iconPrefix];
|
||||||
if (customIconset) {
|
if (customIcon && typeof customIcon.getIcon === "function") {
|
||||||
this._setCustomPath(customIconset(iconName));
|
this._setCustomPath(customIcon.getIcon(iconName), requestedIcon);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -461,14 +442,16 @@ export class HaIcon extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (databaseIcon) {
|
if (databaseIcon) {
|
||||||
this._path = databaseIcon;
|
if (this.icon === requestedIcon) {
|
||||||
|
this._path = databaseIcon;
|
||||||
|
}
|
||||||
cachedIcons[iconName] = databaseIcon;
|
cachedIcons[iconName] = databaseIcon;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const chunk = findIconChunk(iconName);
|
const chunk = findIconChunk(iconName);
|
||||||
|
|
||||||
if (chunk in chunks) {
|
if (chunk in chunks) {
|
||||||
this._setPath(chunks[chunk], iconName);
|
this._setPath(chunks[chunk], iconName, requestedIcon);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -476,19 +459,31 @@ export class HaIcon extends LitElement {
|
|||||||
response.json()
|
response.json()
|
||||||
);
|
);
|
||||||
chunks[chunk] = iconPromise;
|
chunks[chunk] = iconPromise;
|
||||||
this._setPath(iconPromise, iconName);
|
this._setPath(iconPromise, iconName, requestedIcon);
|
||||||
debouncedWriteCache();
|
debouncedWriteCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _setCustomPath(promise: Promise<CustomIcon>) {
|
private async _setCustomPath(
|
||||||
|
promise: Promise<CustomIcon>,
|
||||||
|
requestedIcon: string
|
||||||
|
) {
|
||||||
const icon = await promise;
|
const icon = await promise;
|
||||||
|
if (this.icon !== requestedIcon) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this._path = icon.path;
|
this._path = icon.path;
|
||||||
this._viewBox = icon.viewBox;
|
this._viewBox = icon.viewBox;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _setPath(promise: Promise<Icons>, iconName: string) {
|
private async _setPath(
|
||||||
|
promise: Promise<Icons>,
|
||||||
|
iconName: string,
|
||||||
|
requestedIcon: string
|
||||||
|
) {
|
||||||
const iconPack = await promise;
|
const iconPack = await promise;
|
||||||
this._path = iconPack[iconName];
|
if (this.icon === requestedIcon) {
|
||||||
|
this._path = iconPack[iconName];
|
||||||
|
}
|
||||||
cachedIcons[iconName] = iconPack[iconName];
|
cachedIcons[iconName] = iconPack[iconName];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -145,9 +145,15 @@ class HaWebRtcPlayer extends LitElement {
|
|||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
|
:host,
|
||||||
video {
|
video {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
video {
|
||||||
|
width: 100%;
|
||||||
|
max-height: var(--video-max-height, calc(100vh - 97px));
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@ import { customElement, property } from "lit/decorators";
|
|||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { styleMap } from "lit/directives/style-map";
|
import { styleMap } from "lit/directives/style-map";
|
||||||
import { Person } from "../../data/person";
|
import { Person } from "../../data/person";
|
||||||
import { computeInitials } from "./ha-user-badge";
|
import { computeUserInitials } from "../../data/user";
|
||||||
|
|
||||||
@customElement("ha-person-badge")
|
@customElement("ha-person-badge")
|
||||||
class PersonBadge extends LitElement {
|
class PersonBadge extends LitElement {
|
||||||
@@ -22,7 +22,7 @@ class PersonBadge extends LitElement {
|
|||||||
class="picture"
|
class="picture"
|
||||||
></div>`;
|
></div>`;
|
||||||
}
|
}
|
||||||
const initials = computeInitials(this.person.name);
|
const initials = computeUserInitials(this.person.name);
|
||||||
return html`<div
|
return html`<div
|
||||||
class="initials ${classMap({ long: initials!.length > 2 })}"
|
class="initials ${classMap({ long: initials!.length > 2 })}"
|
||||||
>
|
>
|
||||||
|
@@ -10,25 +10,9 @@ import { customElement, property, state } from "lit/decorators";
|
|||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { styleMap } from "lit/directives/style-map";
|
import { styleMap } from "lit/directives/style-map";
|
||||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||||
import { User } from "../../data/user";
|
import { computeUserInitials, User } from "../../data/user";
|
||||||
import { CurrentUser, HomeAssistant } from "../../types";
|
import { CurrentUser, HomeAssistant } from "../../types";
|
||||||
|
|
||||||
export const computeInitials = (name: string) => {
|
|
||||||
if (!name) {
|
|
||||||
return "?";
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
name
|
|
||||||
.trim()
|
|
||||||
// Split by space and take first 3 words
|
|
||||||
.split(" ")
|
|
||||||
.slice(0, 3)
|
|
||||||
// Of each word, take first letter
|
|
||||||
.map((s) => s.substr(0, 1))
|
|
||||||
.join("")
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
@customElement("ha-user-badge")
|
@customElement("ha-user-badge")
|
||||||
class UserBadge extends LitElement {
|
class UserBadge extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@@ -75,7 +59,7 @@ class UserBadge extends LitElement {
|
|||||||
class="picture"
|
class="picture"
|
||||||
></div>`;
|
></div>`;
|
||||||
}
|
}
|
||||||
const initials = computeInitials(this.user.name);
|
const initials = computeUserInitials(this.user.name);
|
||||||
return html`<div
|
return html`<div
|
||||||
class="initials ${classMap({ long: initials!.length > 2 })}"
|
class="initials ${classMap({ long: initials!.length > 2 })}"
|
||||||
>
|
>
|
||||||
|
@@ -194,10 +194,10 @@ export interface NumericStateCondition extends BaseCondition {
|
|||||||
|
|
||||||
export interface SunCondition extends BaseCondition {
|
export interface SunCondition extends BaseCondition {
|
||||||
condition: "sun";
|
condition: "sun";
|
||||||
after_offset: number;
|
after_offset?: number;
|
||||||
before_offset: number;
|
before_offset?: number;
|
||||||
after: "sunrise" | "sunset";
|
after?: "sunrise" | "sunset";
|
||||||
before: "sunrise" | "sunset";
|
before?: "sunrise" | "sunset";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ZoneCondition extends BaseCondition {
|
export interface ZoneCondition extends BaseCondition {
|
||||||
|
@@ -62,6 +62,8 @@ export type CloudStatus = CloudStatusNotLoggedIn | CloudStatusLoggedIn;
|
|||||||
|
|
||||||
export interface SubscriptionInfo {
|
export interface SubscriptionInfo {
|
||||||
human_description: string;
|
human_description: string;
|
||||||
|
provider: string;
|
||||||
|
plan_renewal_date?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CloudWebhook {
|
export interface CloudWebhook {
|
||||||
@@ -76,6 +78,39 @@ export interface ThingTalkConversion {
|
|||||||
placeholders: PlaceholderContainer;
|
placeholders: PlaceholderContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const cloudLogin = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
email: string,
|
||||||
|
password: string
|
||||||
|
) =>
|
||||||
|
hass.callApi("POST", "cloud/login", {
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const cloudLogout = (hass: HomeAssistant) =>
|
||||||
|
hass.callApi("POST", "cloud/logout");
|
||||||
|
|
||||||
|
export const cloudForgotPassword = (hass: HomeAssistant, email: string) =>
|
||||||
|
hass.callApi("POST", "cloud/forgot_password", {
|
||||||
|
email,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const cloudRegister = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
email: string,
|
||||||
|
password: string
|
||||||
|
) =>
|
||||||
|
hass.callApi("POST", "cloud/register", {
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const cloudResendVerification = (hass: HomeAssistant, email: string) =>
|
||||||
|
hass.callApi("POST", "cloud/resend_confirm", {
|
||||||
|
email,
|
||||||
|
});
|
||||||
|
|
||||||
export const fetchCloudStatus = (hass: HomeAssistant) =>
|
export const fetchCloudStatus = (hass: HomeAssistant) =>
|
||||||
hass.callWS<CloudStatus>({ type: "cloud/status" });
|
hass.callWS<CloudStatus>({ type: "cloud/status" });
|
||||||
|
|
||||||
|
40
src/data/custom_icons.ts
Normal file
40
src/data/custom_icons.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { customIconsets } from "./custom_iconsets";
|
||||||
|
|
||||||
|
export interface CustomIcon {
|
||||||
|
path: string;
|
||||||
|
viewBox?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CustomIconListItem {
|
||||||
|
name: string;
|
||||||
|
keywords?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CustomIconHelpers {
|
||||||
|
getIcon: (name: string) => Promise<CustomIcon>;
|
||||||
|
getIconList?: () => Promise<CustomIconListItem[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CustomIconsWindow {
|
||||||
|
customIcons?: {
|
||||||
|
[key: string]: CustomIconHelpers;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const customIconsWindow = window as CustomIconsWindow;
|
||||||
|
|
||||||
|
if (!("customIcons" in customIconsWindow)) {
|
||||||
|
customIconsWindow.customIcons = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proxy for backward compatibility with icon sets
|
||||||
|
export const customIcons = new Proxy(customIconsWindow.customIcons!, {
|
||||||
|
get: (obj, prop: string) =>
|
||||||
|
obj[prop] ??
|
||||||
|
(customIconsets[prop]
|
||||||
|
? {
|
||||||
|
getIcon: customIconsets[prop],
|
||||||
|
}
|
||||||
|
: undefined),
|
||||||
|
has: (obj, prop: string) => prop in obj || prop in customIconsets,
|
||||||
|
});
|
@@ -1,9 +1,6 @@
|
|||||||
export interface CustomIcon {
|
import { CustomIcon } from "./custom_icons";
|
||||||
path: string;
|
|
||||||
viewBox?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CustomIconsetsWindow {
|
interface CustomIconsetsWindow {
|
||||||
customIconsets?: { [key: string]: (name: string) => Promise<CustomIcon> };
|
customIconsets?: { [key: string]: (name: string) => Promise<CustomIcon> };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -74,6 +74,8 @@ export interface StatisticValue {
|
|||||||
export interface StatisticsMetaData {
|
export interface StatisticsMetaData {
|
||||||
unit_of_measurement: string;
|
unit_of_measurement: string;
|
||||||
statistic_id: string;
|
statistic_id: string;
|
||||||
|
source: string;
|
||||||
|
name?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type StatisticsValidationResult =
|
export type StatisticsValidationResult =
|
||||||
|
@@ -2,6 +2,7 @@ import { HassEntity } from "home-assistant-js-websocket";
|
|||||||
import { BINARY_STATE_OFF, BINARY_STATE_ON } from "../common/const";
|
import { BINARY_STATE_OFF, BINARY_STATE_ON } from "../common/const";
|
||||||
import { computeDomain } from "../common/entity/compute_domain";
|
import { computeDomain } from "../common/entity/compute_domain";
|
||||||
import { computeStateDisplay } from "../common/entity/compute_state_display";
|
import { computeStateDisplay } from "../common/entity/compute_state_display";
|
||||||
|
import { LocalizeFunc } from "../common/translations/localize";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import { UNAVAILABLE_STATES } from "./entity";
|
import { UNAVAILABLE_STATES } from "./entity";
|
||||||
|
|
||||||
@@ -35,9 +36,11 @@ export const getLogbookDataForContext = async (
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
startDate: string,
|
startDate: string,
|
||||||
contextId?: string
|
contextId?: string
|
||||||
): Promise<LogbookEntry[]> =>
|
): Promise<LogbookEntry[]> => {
|
||||||
addLogbookMessage(
|
const localize = await hass.loadBackendTranslation("device_class");
|
||||||
|
return addLogbookMessage(
|
||||||
hass,
|
hass,
|
||||||
|
localize,
|
||||||
await getLogbookDataFromServer(
|
await getLogbookDataFromServer(
|
||||||
hass,
|
hass,
|
||||||
startDate,
|
startDate,
|
||||||
@@ -47,6 +50,7 @@ export const getLogbookDataForContext = async (
|
|||||||
contextId
|
contextId
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const getLogbookData = async (
|
export const getLogbookData = async (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@@ -54,9 +58,11 @@ export const getLogbookData = async (
|
|||||||
endDate: string,
|
endDate: string,
|
||||||
entityId?: string,
|
entityId?: string,
|
||||||
entity_matches_only?: boolean
|
entity_matches_only?: boolean
|
||||||
): Promise<LogbookEntry[]> =>
|
): Promise<LogbookEntry[]> => {
|
||||||
addLogbookMessage(
|
const localize = await hass.loadBackendTranslation("device_class");
|
||||||
|
return addLogbookMessage(
|
||||||
hass,
|
hass,
|
||||||
|
localize,
|
||||||
await getLogbookDataCache(
|
await getLogbookDataCache(
|
||||||
hass,
|
hass,
|
||||||
startDate,
|
startDate,
|
||||||
@@ -65,9 +71,11 @@ export const getLogbookData = async (
|
|||||||
entity_matches_only
|
entity_matches_only
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const addLogbookMessage = (
|
export const addLogbookMessage = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
localize: LocalizeFunc,
|
||||||
logbookData: LogbookEntry[]
|
logbookData: LogbookEntry[]
|
||||||
): LogbookEntry[] => {
|
): LogbookEntry[] => {
|
||||||
for (const entry of logbookData) {
|
for (const entry of logbookData) {
|
||||||
@@ -75,6 +83,7 @@ export const addLogbookMessage = (
|
|||||||
if (entry.state && stateObj) {
|
if (entry.state && stateObj) {
|
||||||
entry.message = getLogbookMessage(
|
entry.message = getLogbookMessage(
|
||||||
hass,
|
hass,
|
||||||
|
localize,
|
||||||
entry.state,
|
entry.state,
|
||||||
stateObj,
|
stateObj,
|
||||||
computeDomain(entry.entity_id!)
|
computeDomain(entry.entity_id!)
|
||||||
@@ -157,6 +166,7 @@ export const clearLogbookCache = (startDate: string, endDate: string) => {
|
|||||||
|
|
||||||
export const getLogbookMessage = (
|
export const getLogbookMessage = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
localize: LocalizeFunc,
|
||||||
state: string,
|
state: string,
|
||||||
stateObj: HassEntity,
|
stateObj: HassEntity,
|
||||||
domain: string
|
domain: string
|
||||||
@@ -165,21 +175,17 @@ export const getLogbookMessage = (
|
|||||||
case "device_tracker":
|
case "device_tracker":
|
||||||
case "person":
|
case "person":
|
||||||
if (state === "not_home") {
|
if (state === "not_home") {
|
||||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_away`);
|
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_away`);
|
||||||
}
|
}
|
||||||
if (state === "home") {
|
if (state === "home") {
|
||||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_at_home`);
|
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_at_home`);
|
||||||
}
|
}
|
||||||
return hass.localize(
|
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_at_state`, "state", state);
|
||||||
`${LOGBOOK_LOCALIZE_PATH}.was_at_state`,
|
|
||||||
"state",
|
|
||||||
state
|
|
||||||
);
|
|
||||||
|
|
||||||
case "sun":
|
case "sun":
|
||||||
return state === "above_horizon"
|
return state === "above_horizon"
|
||||||
? hass.localize(`${LOGBOOK_LOCALIZE_PATH}.rose`)
|
? localize(`${LOGBOOK_LOCALIZE_PATH}.rose`)
|
||||||
: hass.localize(`${LOGBOOK_LOCALIZE_PATH}.set`);
|
: localize(`${LOGBOOK_LOCALIZE_PATH}.set`);
|
||||||
|
|
||||||
case "binary_sensor": {
|
case "binary_sensor": {
|
||||||
const isOn = state === BINARY_STATE_ON;
|
const isOn = state === BINARY_STATE_ON;
|
||||||
@@ -189,19 +195,19 @@ export const getLogbookMessage = (
|
|||||||
switch (device_class) {
|
switch (device_class) {
|
||||||
case "battery":
|
case "battery":
|
||||||
if (isOn) {
|
if (isOn) {
|
||||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_low`);
|
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_low`);
|
||||||
}
|
}
|
||||||
if (isOff) {
|
if (isOff) {
|
||||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_normal`);
|
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_normal`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "connectivity":
|
case "connectivity":
|
||||||
if (isOn) {
|
if (isOn) {
|
||||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_connected`);
|
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_connected`);
|
||||||
}
|
}
|
||||||
if (isOff) {
|
if (isOff) {
|
||||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_disconnected`);
|
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_disconnected`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -210,46 +216,46 @@ export const getLogbookMessage = (
|
|||||||
case "opening":
|
case "opening":
|
||||||
case "window":
|
case "window":
|
||||||
if (isOn) {
|
if (isOn) {
|
||||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_opened`);
|
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_opened`);
|
||||||
}
|
}
|
||||||
if (isOff) {
|
if (isOff) {
|
||||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_closed`);
|
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_closed`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "lock":
|
case "lock":
|
||||||
if (isOn) {
|
if (isOn) {
|
||||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_unlocked`);
|
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_unlocked`);
|
||||||
}
|
}
|
||||||
if (isOff) {
|
if (isOff) {
|
||||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_locked`);
|
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_locked`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "plug":
|
case "plug":
|
||||||
if (isOn) {
|
if (isOn) {
|
||||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_plugged_in`);
|
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_plugged_in`);
|
||||||
}
|
}
|
||||||
if (isOff) {
|
if (isOff) {
|
||||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_unplugged`);
|
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_unplugged`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "presence":
|
case "presence":
|
||||||
if (isOn) {
|
if (isOn) {
|
||||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_at_home`);
|
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_at_home`);
|
||||||
}
|
}
|
||||||
if (isOff) {
|
if (isOff) {
|
||||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_away`);
|
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_away`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "safety":
|
case "safety":
|
||||||
if (isOn) {
|
if (isOn) {
|
||||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_unsafe`);
|
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_unsafe`);
|
||||||
}
|
}
|
||||||
if (isOff) {
|
if (isOff) {
|
||||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_safe`);
|
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_safe`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -265,27 +271,27 @@ export const getLogbookMessage = (
|
|||||||
case "sound":
|
case "sound":
|
||||||
case "vibration":
|
case "vibration":
|
||||||
if (isOn) {
|
if (isOn) {
|
||||||
return hass.localize(
|
return localize(`${LOGBOOK_LOCALIZE_PATH}.detected_device_class`, {
|
||||||
`${LOGBOOK_LOCALIZE_PATH}.detected_device_class`,
|
device_class: localize(
|
||||||
"device_class",
|
`component.binary_sensor.device_class.${device_class}`
|
||||||
device_class
|
),
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
if (isOff) {
|
if (isOff) {
|
||||||
return hass.localize(
|
return localize(`${LOGBOOK_LOCALIZE_PATH}.cleared_device_class`, {
|
||||||
`${LOGBOOK_LOCALIZE_PATH}.cleared_device_class`,
|
device_class: localize(
|
||||||
"device_class",
|
`component.binary_sensor.device_class.${device_class}`
|
||||||
device_class
|
),
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "tamper":
|
case "tamper":
|
||||||
if (isOn) {
|
if (isOn) {
|
||||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.detected_tampering`);
|
return localize(`${LOGBOOK_LOCALIZE_PATH}.detected_tampering`);
|
||||||
}
|
}
|
||||||
if (isOff) {
|
if (isOff) {
|
||||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.cleared_tampering`);
|
return localize(`${LOGBOOK_LOCALIZE_PATH}.cleared_tampering`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -296,43 +302,43 @@ export const getLogbookMessage = (
|
|||||||
case "cover":
|
case "cover":
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case "open":
|
case "open":
|
||||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_opened`);
|
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_opened`);
|
||||||
case "opening":
|
case "opening":
|
||||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.is_opening`);
|
return localize(`${LOGBOOK_LOCALIZE_PATH}.is_opening`);
|
||||||
case "closing":
|
case "closing":
|
||||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.is_closing`);
|
return localize(`${LOGBOOK_LOCALIZE_PATH}.is_closing`);
|
||||||
case "closed":
|
case "closed":
|
||||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_closed`);
|
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_closed`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "lock":
|
case "lock":
|
||||||
if (state === "unlocked") {
|
if (state === "unlocked") {
|
||||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_unlocked`);
|
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_unlocked`);
|
||||||
}
|
}
|
||||||
if (state === "locked") {
|
if (state === "locked") {
|
||||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.was_locked`);
|
return localize(`${LOGBOOK_LOCALIZE_PATH}.was_locked`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state === BINARY_STATE_ON) {
|
if (state === BINARY_STATE_ON) {
|
||||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.turned_on`);
|
return localize(`${LOGBOOK_LOCALIZE_PATH}.turned_on`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state === BINARY_STATE_OFF) {
|
if (state === BINARY_STATE_OFF) {
|
||||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.turned_off`);
|
return localize(`${LOGBOOK_LOCALIZE_PATH}.turned_off`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (UNAVAILABLE_STATES.includes(state)) {
|
if (UNAVAILABLE_STATES.includes(state)) {
|
||||||
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.became_unavailable`);
|
return localize(`${LOGBOOK_LOCALIZE_PATH}.became_unavailable`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return hass.localize(
|
return hass.localize(
|
||||||
`${LOGBOOK_LOCALIZE_PATH}.changed_to_state`,
|
`${LOGBOOK_LOCALIZE_PATH}.changed_to_state`,
|
||||||
"state",
|
"state",
|
||||||
stateObj
|
stateObj
|
||||||
? computeStateDisplay(hass.localize, stateObj, hass.locale, state)
|
? computeStateDisplay(localize, stateObj, hass.locale, state)
|
||||||
: state
|
: state
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -21,7 +21,9 @@ export interface ScriptEntity extends HassEntityBase {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ScriptConfig {
|
export type ScriptConfig = ManualScriptConfig | BlueprintScriptConfig;
|
||||||
|
|
||||||
|
export interface ManualScriptConfig {
|
||||||
alias: string;
|
alias: string;
|
||||||
sequence: Action | Action[];
|
sequence: Action | Action[];
|
||||||
icon?: string;
|
icon?: string;
|
||||||
@@ -29,7 +31,7 @@ export interface ScriptConfig {
|
|||||||
max?: number;
|
max?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BlueprintScriptConfig extends ScriptConfig {
|
export interface BlueprintScriptConfig extends ManualScriptConfig {
|
||||||
use_blueprint: { path: string; input?: BlueprintInput };
|
use_blueprint: { path: string; input?: BlueprintInput };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -37,7 +37,8 @@ export type TranslationCategory =
|
|||||||
| "options"
|
| "options"
|
||||||
| "device_automation"
|
| "device_automation"
|
||||||
| "mfa_setup"
|
| "mfa_setup"
|
||||||
| "system_health";
|
| "system_health"
|
||||||
|
| "device_class";
|
||||||
|
|
||||||
export const fetchTranslationPreferences = (hass: HomeAssistant) =>
|
export const fetchTranslationPreferences = (hass: HomeAssistant) =>
|
||||||
fetchFrontendUserData(hass.connection, "language");
|
fetchFrontendUserData(hass.connection, "language");
|
||||||
|
@@ -57,3 +57,19 @@ export const deleteUser = async (hass: HomeAssistant, userId: string) =>
|
|||||||
type: "config/auth/delete",
|
type: "config/auth/delete",
|
||||||
user_id: userId,
|
user_id: userId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const computeUserInitials = (name: string) => {
|
||||||
|
if (!name) {
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
name
|
||||||
|
.trim()
|
||||||
|
// Split by space and take first 3 words
|
||||||
|
.split(" ")
|
||||||
|
.slice(0, 3)
|
||||||
|
// Of each word, take first letter
|
||||||
|
.map((s) => s.substr(0, 1))
|
||||||
|
.join("")
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@@ -84,6 +84,9 @@ export interface ZWaveJSNodeStatus {
|
|||||||
ready: boolean;
|
ready: boolean;
|
||||||
status: number;
|
status: number;
|
||||||
is_secure: boolean | string;
|
is_secure: boolean | string;
|
||||||
|
is_routing: boolean | null;
|
||||||
|
zwave_plus_version: number | null;
|
||||||
|
highest_security_class: SecurityClass | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ZwaveJSNodeMetadata {
|
export interface ZwaveJSNodeMetadata {
|
||||||
|
@@ -2,7 +2,7 @@ import { css } from "lit";
|
|||||||
|
|
||||||
export const configFlowContentStyles = css`
|
export const configFlowContentStyles = css`
|
||||||
h2 {
|
h2 {
|
||||||
margin: 24px 0 0;
|
margin: 24px 38px 0 0;
|
||||||
padding: 0 24px;
|
padding: 0 24px;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
|
@@ -1,78 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -10,7 +10,8 @@ import { property, state } from "lit/decorators";
|
|||||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||||
import "../../../components/ha-camera-stream";
|
import "../../../components/ha-camera-stream";
|
||||||
import { HaCheckbox } from "../../../components/ha-checkbox";
|
import type { HaCheckbox } from "../../../components/ha-checkbox";
|
||||||
|
import "../../../components/ha-checkbox";
|
||||||
import {
|
import {
|
||||||
CameraEntity,
|
CameraEntity,
|
||||||
CameraPreferences,
|
CameraPreferences,
|
||||||
@@ -20,6 +21,7 @@ import {
|
|||||||
updateCameraPrefs,
|
updateCameraPrefs,
|
||||||
} from "../../../data/camera";
|
} from "../../../data/camera";
|
||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
|
import "../../../components/ha-formfield";
|
||||||
|
|
||||||
class MoreInfoCamera extends LitElement {
|
class MoreInfoCamera extends LitElement {
|
||||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||||
@@ -54,12 +56,11 @@ class MoreInfoCamera extends LitElement {
|
|||||||
></ha-camera-stream>
|
></ha-camera-stream>
|
||||||
${this._cameraPrefs
|
${this._cameraPrefs
|
||||||
? html`
|
? html`
|
||||||
<ha-formfield>
|
<ha-formfield label="Preload stream">
|
||||||
<ha-checkbox
|
<ha-checkbox
|
||||||
.checked=${this._cameraPrefs.preload_stream}
|
.checked=${this._cameraPrefs.preload_stream}
|
||||||
@change=${this._handleCheckboxChanged}
|
@change=${this._handleCheckboxChanged}
|
||||||
>
|
>
|
||||||
Preload stream
|
|
||||||
</ha-checkbox>
|
</ha-checkbox>
|
||||||
</ha-formfield>
|
</ha-formfield>
|
||||||
`
|
`
|
||||||
@@ -123,12 +124,12 @@ class MoreInfoCamera extends LitElement {
|
|||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
ha-checkbox {
|
ha-formfield {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
background-color: var(--secondary-background-color);
|
background-color: var(--secondary-background-color);
|
||||||
padding: 5px;
|
padding-right: 16px;
|
||||||
border-bottom-left-radius: 4px;
|
border-bottom-left-radius: 4px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@@ -1,12 +1,19 @@
|
|||||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { throttle } from "../../common/util/throttle";
|
import { throttle } from "../../common/util/throttle";
|
||||||
import "../../components/chart/state-history-charts";
|
import "../../components/chart/state-history-charts";
|
||||||
import { getRecentWithCache } from "../../data/cached-history";
|
import { getRecentWithCache } from "../../data/cached-history";
|
||||||
import { HistoryResult } from "../../data/history";
|
import { HistoryResult } from "../../data/history";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HASSDomEvents {
|
||||||
|
closed: undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@customElement("ha-more-info-history")
|
@customElement("ha-more-info-history")
|
||||||
export class MoreInfoHistory extends LitElement {
|
export class MoreInfoHistory extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@@ -24,14 +31,26 @@ export class MoreInfoHistory extends LitElement {
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const href = "/history?entity_id=" + this.entityId;
|
||||||
|
|
||||||
return html`${isComponentLoaded(this.hass, "history")
|
return html`${isComponentLoaded(this.hass, "history")
|
||||||
? html`<state-history-charts
|
? html` <div class="header">
|
||||||
up-to-now
|
<div class="title">
|
||||||
.hass=${this.hass}
|
${this.hass.localize("ui.dialogs.more_info_control.history")}
|
||||||
.historyData=${this._stateHistory}
|
</div>
|
||||||
.isLoadingData=${!this._stateHistory}
|
<a href=${href} @click=${this._close}
|
||||||
></state-history-charts>`
|
>${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>`
|
||||||
|
: ""}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updated(changedProps: PropertyValues): void {
|
protected updated(changedProps: PropertyValues): void {
|
||||||
@@ -78,6 +97,38 @@ export class MoreInfoHistory extends LitElement {
|
|||||||
this.hass!.language
|
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 {
|
declare global {
|
||||||
|
@@ -1,16 +1,16 @@
|
|||||||
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||||
import { throttle } from "../../common/util/throttle";
|
import { throttle } from "../../common/util/throttle";
|
||||||
import "../../components/ha-circular-progress";
|
import "../../components/ha-circular-progress";
|
||||||
import { fetchUsers } from "../../data/user";
|
|
||||||
import { getLogbookData, LogbookEntry } from "../../data/logbook";
|
import { getLogbookData, LogbookEntry } from "../../data/logbook";
|
||||||
import { loadTraceContexts, TraceContexts } from "../../data/trace";
|
import { loadTraceContexts, TraceContexts } from "../../data/trace";
|
||||||
|
import { fetchUsers } from "../../data/user";
|
||||||
import "../../panels/logbook/ha-logbook";
|
import "../../panels/logbook/ha-logbook";
|
||||||
import { haStyle } from "../../resources/styles";
|
import { haStyle } from "../../resources/styles";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import { closeDialog } from "../make-dialog-manager";
|
|
||||||
|
|
||||||
@customElement("ha-more-info-logbook")
|
@customElement("ha-more-info-logbook")
|
||||||
export class MoreInfoLogbook extends LitElement {
|
export class MoreInfoLogbook extends LitElement {
|
||||||
@@ -44,6 +44,8 @@ export class MoreInfoLogbook extends LitElement {
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const href = "/logbook?entity_id=" + this.entityId;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${isComponentLoaded(this.hass, "logbook")
|
${isComponentLoaded(this.hass, "logbook")
|
||||||
? this._error
|
? this._error
|
||||||
@@ -61,6 +63,16 @@ export class MoreInfoLogbook extends LitElement {
|
|||||||
`
|
`
|
||||||
: this._logbookEntries.length
|
: this._logbookEntries.length
|
||||||
? html`
|
? 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
|
<ha-logbook
|
||||||
narrow
|
narrow
|
||||||
no-icon
|
no-icon
|
||||||
@@ -81,11 +93,6 @@ export class MoreInfoLogbook extends LitElement {
|
|||||||
|
|
||||||
protected firstUpdated(): void {
|
protected firstUpdated(): void {
|
||||||
this._fetchUserPromise = this._fetchUserNames();
|
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 {
|
protected updated(changedProps: PropertyValues): void {
|
||||||
@@ -182,6 +189,10 @@ export class MoreInfoLogbook extends LitElement {
|
|||||||
this._userIdToName = userIdToName;
|
this._userIdToName = userIdToName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _close(): void {
|
||||||
|
setTimeout(() => fireEvent(this, "closed"), 500);
|
||||||
|
}
|
||||||
|
|
||||||
static get styles() {
|
static get styles() {
|
||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
@@ -203,6 +214,27 @@ export class MoreInfoLogbook extends LitElement {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
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);
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -60,10 +60,12 @@ const connProm = async (auth) => {
|
|||||||
searchParams.delete("auth_callback");
|
searchParams.delete("auth_callback");
|
||||||
searchParams.delete("code");
|
searchParams.delete("code");
|
||||||
searchParams.delete("state");
|
searchParams.delete("state");
|
||||||
|
searchParams.delete("storeToken");
|
||||||
|
const search = searchParams.toString();
|
||||||
history.replaceState(
|
history.replaceState(
|
||||||
null,
|
null,
|
||||||
"",
|
"",
|
||||||
`${location.pathname}?${searchParams.toString()}`
|
`${location.pathname}${search ? `?${search}` : ""}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,13 +2,16 @@ import "@material/mwc-button/mwc-button";
|
|||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state, query } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import type { LocalizeFunc } from "../common/translations/localize";
|
import type { LocalizeFunc } from "../common/translations/localize";
|
||||||
import { createCurrencyListEl } from "../components/currency-datalist";
|
import { createCurrencyListEl } from "../components/currency-datalist";
|
||||||
import "../components/map/ha-locations-editor";
|
import "../components/map/ha-locations-editor";
|
||||||
import type { MarkerLocation } from "../components/map/ha-locations-editor";
|
import type {
|
||||||
|
HaLocationsEditor,
|
||||||
|
MarkerLocation,
|
||||||
|
} from "../components/map/ha-locations-editor";
|
||||||
import { createTimezoneListEl } from "../components/timezone-datalist";
|
import { createTimezoneListEl } from "../components/timezone-datalist";
|
||||||
import {
|
import {
|
||||||
ConfigUpdateValues,
|
ConfigUpdateValues,
|
||||||
@@ -46,6 +49,8 @@ class OnboardingCoreConfig extends LitElement {
|
|||||||
|
|
||||||
@state() private _timeZone?: string;
|
@state() private _timeZone?: string;
|
||||||
|
|
||||||
|
@query("ha-locations-editor", true) private map!: HaLocationsEditor;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<p>
|
<p>
|
||||||
@@ -305,6 +310,7 @@ class OnboardingCoreConfig extends LitElement {
|
|||||||
|
|
||||||
if (values.latitude && values.longitude) {
|
if (values.latitude && values.longitude) {
|
||||||
this._location = [Number(values.latitude), Number(values.longitude)];
|
this._location = [Number(values.latitude), Number(values.longitude)];
|
||||||
|
this.map.autoFit = true;
|
||||||
}
|
}
|
||||||
if (values.elevation) {
|
if (values.elevation) {
|
||||||
this._elevation = String(values.elevation);
|
this._elevation = String(values.elevation);
|
||||||
|
@@ -371,6 +371,10 @@ export class HAFullCalendar extends LitElement {
|
|||||||
);
|
);
|
||||||
--fc-theme-standard-border-color: var(--divider-color);
|
--fc-theme-standard-border-color: var(--divider-color);
|
||||||
--fc-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 {
|
a {
|
||||||
|
@@ -230,6 +230,7 @@ export default class HaAutomationActionRow extends LitElement {
|
|||||||
"ui.panel.config.automation.editor.actions.type_select"
|
"ui.panel.config.automation.editor.actions.type_select"
|
||||||
)}
|
)}
|
||||||
.value=${getType(this.action)}
|
.value=${getType(this.action)}
|
||||||
|
naturalMenuWidth
|
||||||
@selected=${this._typeChanged}
|
@selected=${this._typeChanged}
|
||||||
>
|
>
|
||||||
${this._processedTypes(this.hass.localize).map(
|
${this._processedTypes(this.hass.localize).map(
|
||||||
|
@@ -90,6 +90,7 @@ export default class HaAutomationConditionEditor extends LitElement {
|
|||||||
"ui.panel.config.automation.editor.conditions.type_select"
|
"ui.panel.config.automation.editor.conditions.type_select"
|
||||||
)}
|
)}
|
||||||
.value=${this.condition.condition}
|
.value=${this.condition.condition}
|
||||||
|
naturalMenuWidth
|
||||||
@selected=${this._typeChanged}
|
@selected=${this._typeChanged}
|
||||||
>
|
>
|
||||||
${this._processedTypes(this.hass.localize).map(
|
${this._processedTypes(this.hass.localize).map(
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
import { html, LitElement } from "lit";
|
import { html, LitElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
import { LogicalCondition } from "../../../../../data/automation";
|
import { Condition, LogicalCondition } from "../../../../../data/automation";
|
||||||
import { HomeAssistant } from "../../../../../types";
|
import { HomeAssistant } from "../../../../../types";
|
||||||
import "../ha-automation-condition";
|
import "../ha-automation-condition";
|
||||||
import { ConditionElement } from "../ha-automation-condition-row";
|
import { ConditionElement } from "../ha-automation-condition-row";
|
||||||
|
import { HaStateCondition } from "./ha-automation-condition-state";
|
||||||
|
|
||||||
@customElement("ha-automation-condition-logical")
|
@customElement("ha-automation-condition-logical")
|
||||||
export class HaLogicalCondition extends LitElement implements ConditionElement {
|
export class HaLogicalCondition extends LitElement implements ConditionElement {
|
||||||
@@ -13,7 +14,14 @@ export class HaLogicalCondition extends LitElement implements ConditionElement {
|
|||||||
@property() public condition!: LogicalCondition;
|
@property() public condition!: LogicalCondition;
|
||||||
|
|
||||||
public static get defaultConfig() {
|
public static get defaultConfig() {
|
||||||
return { conditions: [{ condition: "state" }] };
|
return {
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
condition: "state",
|
||||||
|
...HaStateCondition.defaultConfig,
|
||||||
|
},
|
||||||
|
] as Condition[],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
|
@@ -464,7 +464,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Wait for dialog to complate closing
|
// Wait for dialog to complete closing
|
||||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
}
|
}
|
||||||
showAutomationEditor({
|
showAutomationEditor({
|
||||||
|
@@ -4,12 +4,12 @@ import {
|
|||||||
mdiInformationOutline,
|
mdiInformationOutline,
|
||||||
mdiPencil,
|
mdiPencil,
|
||||||
mdiPencilOff,
|
mdiPencilOff,
|
||||||
|
mdiPlayCircleOutline,
|
||||||
mdiPlus,
|
mdiPlus,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import "@polymer/paper-tooltip/paper-tooltip";
|
import "@polymer/paper-tooltip/paper-tooltip";
|
||||||
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { ifDefined } from "lit/directives/if-defined";
|
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||||
import { formatDateTime } from "../../../common/datetime/format_date_time";
|
import { formatDateTime } from "../../../common/datetime/format_date_time";
|
||||||
@@ -22,6 +22,7 @@ import "../../../components/ha-button-related-filter-menu";
|
|||||||
import "../../../components/ha-fab";
|
import "../../../components/ha-fab";
|
||||||
import "../../../components/ha-icon-button";
|
import "../../../components/ha-icon-button";
|
||||||
import "../../../components/ha-svg-icon";
|
import "../../../components/ha-svg-icon";
|
||||||
|
import "../../../components/ha-icon-overflow-menu";
|
||||||
import {
|
import {
|
||||||
AutomationEntity,
|
AutomationEntity,
|
||||||
triggerAutomationActions,
|
triggerAutomationActions,
|
||||||
@@ -135,7 +136,7 @@ class HaAutomationPicker extends LitElement {
|
|||||||
template: (_info, automation: any) => html`
|
template: (_info, automation: any) => html`
|
||||||
<mwc-button
|
<mwc-button
|
||||||
.automation=${automation}
|
.automation=${automation}
|
||||||
@click=${this._runActions}
|
@click=${this._triggerRunActions}
|
||||||
.disabled=${UNAVAILABLE_STATES.includes(automation.state)}
|
.disabled=${UNAVAILABLE_STATES.includes(automation.state)}
|
||||||
>
|
>
|
||||||
${this.hass.localize("ui.card.automation.trigger")}
|
${this.hass.localize("ui.card.automation.trigger")}
|
||||||
@@ -143,78 +144,73 @@ class HaAutomationPicker extends LitElement {
|
|||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
columns.info = {
|
columns.actions = {
|
||||||
title: "",
|
title: "",
|
||||||
type: "icon-button",
|
type: "overflow-menu",
|
||||||
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`
|
template: (_info, automation: any) => html`
|
||||||
<a
|
<ha-icon-overflow-menu
|
||||||
href=${ifDefined(
|
.hass=${this.hass}
|
||||||
automation.attributes.id
|
.narrow=${this.narrow}
|
||||||
? `/config/automation/trace/${automation.attributes.id}`
|
.items=${[
|
||||||
: undefined
|
// 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)"
|
||||||
>
|
>
|
||||||
<ha-icon-button
|
</ha-icon-overflow-menu>
|
||||||
.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;
|
return columns;
|
||||||
@@ -285,9 +281,8 @@ class HaAutomationPicker extends LitElement {
|
|||||||
this._filterValue = undefined;
|
this._filterValue = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _showInfo(ev) {
|
private _showInfo(automation: AutomationEntity) {
|
||||||
ev.stopPropagation();
|
const entityId = automation.entity_id;
|
||||||
const entityId = ev.currentTarget.automation.entity_id;
|
|
||||||
fireEvent(this, "hass-more-info", { entityId });
|
fireEvent(this, "hass-more-info", { entityId });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,9 +306,12 @@ class HaAutomationPicker extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _runActions = (ev) => {
|
private _triggerRunActions = (ev) => {
|
||||||
const entityId = ev.currentTarget.automation.entity_id;
|
this._runActions(ev.currentTarget.automation);
|
||||||
triggerAutomationActions(this.hass, entityId);
|
};
|
||||||
|
|
||||||
|
private _runActions = (automation: AutomationEntity) => {
|
||||||
|
triggerAutomationActions(this.hass, automation.entity_id);
|
||||||
};
|
};
|
||||||
|
|
||||||
private _createNew() {
|
private _createNew() {
|
||||||
|
@@ -179,6 +179,7 @@ export default class HaAutomationTriggerRow extends LitElement {
|
|||||||
"ui.panel.config.automation.editor.triggers.type_select"
|
"ui.panel.config.automation.editor.triggers.type_select"
|
||||||
)}
|
)}
|
||||||
.value=${this.trigger.platform}
|
.value=${this.trigger.platform}
|
||||||
|
naturalMenuWidth
|
||||||
@selected=${this._typeChanged}
|
@selected=${this._typeChanged}
|
||||||
>
|
>
|
||||||
${this._processedTypes(this.hass.localize).map(
|
${this._processedTypes(this.hass.localize).map(
|
||||||
|
@@ -10,7 +10,7 @@ import { handleChangeEvent } from "../ha-automation-trigger-row";
|
|||||||
const includeDomains = ["zone"];
|
const includeDomains = ["zone"];
|
||||||
|
|
||||||
@customElement("ha-automation-trigger-geo_location")
|
@customElement("ha-automation-trigger-geo_location")
|
||||||
export default class HaGeolocationTrigger extends LitElement {
|
export class HaGeolocationTrigger extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ attribute: false }) public trigger!: GeoLocationTrigger;
|
@property({ attribute: false }) public trigger!: GeoLocationTrigger;
|
||||||
@@ -19,7 +19,7 @@ export default class HaGeolocationTrigger extends LitElement {
|
|||||||
return {
|
return {
|
||||||
source: "",
|
source: "",
|
||||||
zone: "",
|
zone: "",
|
||||||
event: "enter",
|
event: "enter" as GeoLocationTrigger["event"],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -8,14 +8,14 @@ import "../../../../../components/ha-formfield";
|
|||||||
import "../../../../../components/ha-radio";
|
import "../../../../../components/ha-radio";
|
||||||
|
|
||||||
@customElement("ha-automation-trigger-homeassistant")
|
@customElement("ha-automation-trigger-homeassistant")
|
||||||
export default class HaHassTrigger extends LitElement {
|
export class HaHassTrigger extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ attribute: false }) public trigger!: HassTrigger;
|
@property({ attribute: false }) public trigger!: HassTrigger;
|
||||||
|
|
||||||
public static get defaultConfig() {
|
public static get defaultConfig() {
|
||||||
return {
|
return {
|
||||||
event: "start",
|
event: "start" as HassTrigger["event"],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -12,7 +12,7 @@ import { handleChangeEvent } from "../ha-automation-trigger-row";
|
|||||||
import "../../../../../components/ha-duration-input";
|
import "../../../../../components/ha-duration-input";
|
||||||
|
|
||||||
@customElement("ha-automation-trigger-numeric_state")
|
@customElement("ha-automation-trigger-numeric_state")
|
||||||
export default class HaNumericStateTrigger extends LitElement {
|
export class HaNumericStateTrigger extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public trigger!: NumericStateTrigger;
|
@property() public trigger!: NumericStateTrigger;
|
||||||
|
@@ -20,7 +20,8 @@ export class HaSunTrigger extends LitElement implements TriggerElement {
|
|||||||
|
|
||||||
public static get defaultConfig() {
|
public static get defaultConfig() {
|
||||||
return {
|
return {
|
||||||
event: "sunrise",
|
event: "sunrise" as SunTrigger["event"],
|
||||||
|
offset: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -27,7 +27,7 @@ export class HaZoneTrigger extends LitElement {
|
|||||||
return {
|
return {
|
||||||
entity_id: "",
|
entity_id: "",
|
||||||
zone: "",
|
zone: "",
|
||||||
event: "enter",
|
event: "enter" as ZoneTrigger["event"],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -29,6 +29,7 @@ import {
|
|||||||
Blueprints,
|
Blueprints,
|
||||||
deleteBlueprint,
|
deleteBlueprint,
|
||||||
} from "../../../data/blueprint";
|
} from "../../../data/blueprint";
|
||||||
|
import { showScriptEditor } from "../../../data/script";
|
||||||
import {
|
import {
|
||||||
showAlertDialog,
|
showAlertDialog,
|
||||||
showConfirmationDialog,
|
showConfirmationDialog,
|
||||||
@@ -52,6 +53,12 @@ const createNewFunctions = {
|
|||||||
use_blueprint: { path: blueprintMeta.path },
|
use_blueprint: { path: blueprintMeta.path },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
script: (blueprintMeta: BlueprintMetaDataPath) => {
|
||||||
|
showScriptEditor({
|
||||||
|
alias: blueprintMeta.name,
|
||||||
|
use_blueprint: { path: blueprintMeta.path },
|
||||||
|
});
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@customElement("ha-blueprint-overview")
|
@customElement("ha-blueprint-overview")
|
||||||
@@ -62,27 +69,38 @@ class HaBlueprintOverview extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public narrow!: boolean;
|
@property({ type: Boolean }) public narrow!: boolean;
|
||||||
|
|
||||||
@property() public route!: Route;
|
@property({ attribute: false }) public route!: Route;
|
||||||
|
|
||||||
@property() public blueprints!: Blueprints;
|
@property({ attribute: false }) public blueprints!: Record<
|
||||||
|
string,
|
||||||
|
Blueprints
|
||||||
|
>;
|
||||||
|
|
||||||
private _processedBlueprints = memoizeOne((blueprints: Blueprints) => {
|
private _processedBlueprints = memoizeOne(
|
||||||
const result = Object.entries(blueprints).map(([path, blueprint]) => {
|
(blueprints: Record<string, Blueprints>) => {
|
||||||
if ("error" in blueprint) {
|
const result: any[] = [];
|
||||||
return {
|
Object.entries(blueprints).forEach(([type, typeBlueprints]) =>
|
||||||
name: blueprint.error,
|
Object.entries(typeBlueprints).forEach(([path, blueprint]) => {
|
||||||
error: true,
|
if ("error" in blueprint) {
|
||||||
path,
|
result.push({
|
||||||
};
|
name: blueprint.error,
|
||||||
}
|
type,
|
||||||
return {
|
error: true,
|
||||||
...blueprint.metadata,
|
path,
|
||||||
error: false,
|
});
|
||||||
path,
|
} else {
|
||||||
};
|
result.push({
|
||||||
});
|
...blueprint.metadata,
|
||||||
return result;
|
type,
|
||||||
});
|
error: false,
|
||||||
|
path,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
private _columns = memoizeOne(
|
private _columns = memoizeOne(
|
||||||
(narrow, _language): DataTableColumnContainer => ({
|
(narrow, _language): DataTableColumnContainer => ({
|
||||||
@@ -102,6 +120,20 @@ class HaBlueprintOverview extends LitElement {
|
|||||||
`
|
`
|
||||||
: undefined,
|
: 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: {
|
path: {
|
||||||
title: this.hass.localize(
|
title: this.hass.localize(
|
||||||
"ui.panel.config.blueprint.overview.headers.file_name"
|
"ui.panel.config.blueprint.overview.headers.file_name"
|
||||||
@@ -114,25 +146,27 @@ class HaBlueprintOverview extends LitElement {
|
|||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
title: "",
|
title: "",
|
||||||
|
width: narrow ? undefined : "20%",
|
||||||
type: narrow ? "icon-button" : undefined,
|
type: narrow ? "icon-button" : undefined,
|
||||||
template: (_, blueprint: any) =>
|
template: (_, blueprint: any) =>
|
||||||
blueprint.error
|
blueprint.error
|
||||||
? ""
|
? ""
|
||||||
: narrow
|
: narrow
|
||||||
? html` <ha-icon-button
|
? html`<ha-icon-button
|
||||||
.blueprint=${blueprint}
|
.blueprint=${blueprint}
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.config.blueprint.overview.use_blueprint"
|
`ui.panel.config.blueprint.overview.create_${blueprint.domain}`
|
||||||
)}
|
)}
|
||||||
.path=${mdiRobot}
|
|
||||||
@click=${this._createNew}
|
@click=${this._createNew}
|
||||||
></ha-icon-button>`
|
.path=${mdiRobot}
|
||||||
|
>
|
||||||
|
</ha-icon-button>`
|
||||||
: html`<mwc-button
|
: html`<mwc-button
|
||||||
.blueprint=${blueprint}
|
.blueprint=${blueprint}
|
||||||
@click=${this._createNew}
|
@click=${this._createNew}
|
||||||
>
|
>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.blueprint.overview.use_blueprint"
|
`ui.panel.config.blueprint.overview.create_${blueprint.domain}`
|
||||||
)}
|
)}
|
||||||
</mwc-button>`,
|
</mwc-button>`,
|
||||||
},
|
},
|
||||||
|
@@ -25,7 +25,7 @@ class HaConfigBlueprint extends HassRouterPage {
|
|||||||
|
|
||||||
@property() public showAdvanced!: boolean;
|
@property() public showAdvanced!: boolean;
|
||||||
|
|
||||||
@property() public blueprints: Blueprints = {};
|
@property() public blueprints: Record<string, Blueprints> = {};
|
||||||
|
|
||||||
protected routerOptions: RouterOptions = {
|
protected routerOptions: RouterOptions = {
|
||||||
defaultPage: "dashboard",
|
defaultPage: "dashboard",
|
||||||
@@ -41,7 +41,11 @@ class HaConfigBlueprint extends HassRouterPage {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private async _getBlueprints() {
|
private async _getBlueprints() {
|
||||||
this.blueprints = await fetchBlueprints(this.hass, "automation");
|
const [automation, script] = await Promise.all([
|
||||||
|
fetchBlueprints(this.hass, "automation"),
|
||||||
|
fetchBlueprints(this.hass, "script"),
|
||||||
|
]);
|
||||||
|
this.blueprints = { automation, script };
|
||||||
}
|
}
|
||||||
|
|
||||||
protected firstUpdated(changedProps) {
|
protected firstUpdated(changedProps) {
|
||||||
|
@@ -1,246 +0,0 @@
|
|||||||
import "@material/mwc-button";
|
|
||||||
import "@polymer/paper-item/paper-item-body";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
/* eslint-plugin-disable lit */
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
import { formatDateTime } from "../../../../common/datetime/format_date_time";
|
|
||||||
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
|
|
||||||
import "../../../../components/buttons/ha-call-api-button";
|
|
||||||
import "../../../../components/ha-card";
|
|
||||||
import { fetchCloudSubscriptionInfo } from "../../../../data/cloud";
|
|
||||||
import "../../../../layouts/hass-subpage";
|
|
||||||
import { EventsMixin } from "../../../../mixins/events-mixin";
|
|
||||||
import LocalizeMixin from "../../../../mixins/localize-mixin";
|
|
||||||
import "../../../../styles/polymer-ha-style";
|
|
||||||
import "../../ha-config-section";
|
|
||||||
import "./cloud-alexa-pref";
|
|
||||||
import "./cloud-google-pref";
|
|
||||||
import "./cloud-remote-pref";
|
|
||||||
import "./cloud-tts-pref";
|
|
||||||
import "./cloud-webhooks";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @appliesMixin EventsMixin
|
|
||||||
* @appliesMixin LocalizeMixin
|
|
||||||
*/
|
|
||||||
class CloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style include="iron-flex ha-style">
|
|
||||||
[slot="introduction"] {
|
|
||||||
margin: -1em 0;
|
|
||||||
}
|
|
||||||
[slot="introduction"] a {
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
padding-bottom: 24px;
|
|
||||||
}
|
|
||||||
.account-row {
|
|
||||||
display: flex;
|
|
||||||
padding: 0 16px;
|
|
||||||
}
|
|
||||||
mwc-button {
|
|
||||||
align-self: center;
|
|
||||||
}
|
|
||||||
.soon {
|
|
||||||
font-style: italic;
|
|
||||||
margin-top: 24px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.nowrap {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
.wrap {
|
|
||||||
white-space: normal;
|
|
||||||
}
|
|
||||||
.status {
|
|
||||||
text-transform: capitalize;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<hass-subpage
|
|
||||||
hass="[[hass]]"
|
|
||||||
narrow="[[narrow]]"
|
|
||||||
header="Home Assistant Cloud"
|
|
||||||
>
|
|
||||||
<div class="content">
|
|
||||||
<ha-config-section is-wide="[[isWide]]">
|
|
||||||
<span slot="header">Home Assistant Cloud</span>
|
|
||||||
<div slot="introduction">
|
|
||||||
<p>
|
|
||||||
[[localize('ui.panel.config.cloud.account.thank_you_note')]]
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ha-card
|
|
||||||
header="[[localize('ui.panel.config.cloud.account.nabu_casa_account')]]"
|
|
||||||
>
|
|
||||||
<div class="account-row">
|
|
||||||
<paper-item-body two-line="">
|
|
||||||
[[cloudStatus.email]]
|
|
||||||
<div secondary class="wrap">
|
|
||||||
[[_formatSubscription(_subscription)]]
|
|
||||||
</div>
|
|
||||||
</paper-item-body>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="account-row">
|
|
||||||
<paper-item-body
|
|
||||||
>[[localize('ui.panel.config.cloud.account.connection_status')]]</paper-item-body
|
|
||||||
>
|
|
||||||
<div class="status">
|
|
||||||
[[_computeConnectionStatus(cloudStatus.cloud)]]
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card-actions">
|
|
||||||
<a
|
|
||||||
href="https://account.nabucasa.com"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
<mwc-button
|
|
||||||
>[[localize('ui.panel.config.cloud.account.manage_account')]]</mwc-button
|
|
||||||
>
|
|
||||||
</a>
|
|
||||||
<mwc-button style="float: right" on-click="handleLogout"
|
|
||||||
>[[localize('ui.panel.config.cloud.account.sign_out')]]</mwc-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
</ha-config-section>
|
|
||||||
|
|
||||||
<ha-config-section is-wide="[[isWide]]">
|
|
||||||
<span slot="header"
|
|
||||||
>[[localize('ui.panel.config.cloud.account.integrations')]]</span
|
|
||||||
>
|
|
||||||
<div slot="introduction">
|
|
||||||
<p>
|
|
||||||
[[localize('ui.panel.config.cloud.account.integrations_introduction')]]
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
[[localize('ui.panel.config.cloud.account.integrations_introduction2')]]
|
|
||||||
<a
|
|
||||||
href="https://www.nabucasa.com"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
[[localize('ui.panel.config.cloud.account.integrations_link_all_features')]]</a
|
|
||||||
>.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<cloud-remote-pref
|
|
||||||
hass="[[hass]]"
|
|
||||||
cloud-status="[[cloudStatus]]"
|
|
||||||
dir="[[_rtlDirection]]"
|
|
||||||
></cloud-remote-pref>
|
|
||||||
|
|
||||||
<cloud-tts-pref
|
|
||||||
hass="[[hass]]"
|
|
||||||
cloud-status="[[cloudStatus]]"
|
|
||||||
dir="[[_rtlDirection]]"
|
|
||||||
></cloud-tts-pref>
|
|
||||||
|
|
||||||
<cloud-alexa-pref
|
|
||||||
hass="[[hass]]"
|
|
||||||
cloud-status="[[cloudStatus]]"
|
|
||||||
dir="[[_rtlDirection]]"
|
|
||||||
></cloud-alexa-pref>
|
|
||||||
|
|
||||||
<cloud-google-pref
|
|
||||||
hass="[[hass]]"
|
|
||||||
cloud-status="[[cloudStatus]]"
|
|
||||||
dir="[[_rtlDirection]]"
|
|
||||||
></cloud-google-pref>
|
|
||||||
|
|
||||||
<cloud-webhooks
|
|
||||||
hass="[[hass]]"
|
|
||||||
cloud-status="[[cloudStatus]]"
|
|
||||||
dir="[[_rtlDirection]]"
|
|
||||||
></cloud-webhooks>
|
|
||||||
</ha-config-section>
|
|
||||||
</div>
|
|
||||||
</hass-subpage>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: Object,
|
|
||||||
isWide: Boolean,
|
|
||||||
narrow: Boolean,
|
|
||||||
cloudStatus: Object,
|
|
||||||
_subscription: {
|
|
||||||
type: Object,
|
|
||||||
value: null,
|
|
||||||
},
|
|
||||||
_rtlDirection: {
|
|
||||||
type: Boolean,
|
|
||||||
computed: "_computeRTLDirection(hass)",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
ready() {
|
|
||||||
super.ready();
|
|
||||||
this._fetchSubscriptionInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeConnectionStatus(status) {
|
|
||||||
return status === "connected"
|
|
||||||
? this.hass.localize("ui.panel.config.cloud.account.connected")
|
|
||||||
: status === "disconnected"
|
|
||||||
? this.hass.localize("ui.panel.config.cloud.account.not_connected")
|
|
||||||
: this.hass.localize("ui.panel.config.cloud.account.connecting");
|
|
||||||
}
|
|
||||||
|
|
||||||
async _fetchSubscriptionInfo() {
|
|
||||||
this._subscription = await fetchCloudSubscriptionInfo(this.hass);
|
|
||||||
if (
|
|
||||||
this._subscription.provider &&
|
|
||||||
this.cloudStatus &&
|
|
||||||
this.cloudStatus.cloud !== "connected"
|
|
||||||
) {
|
|
||||||
this.fire("ha-refresh-cloud-status");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleLogout() {
|
|
||||||
this.hass
|
|
||||||
.callApi("post", "cloud/logout")
|
|
||||||
.then(() => this.fire("ha-refresh-cloud-status"));
|
|
||||||
}
|
|
||||||
|
|
||||||
_formatSubscription(subInfo) {
|
|
||||||
if (subInfo === null) {
|
|
||||||
return this.hass.localize(
|
|
||||||
"ui.panel.config.cloud.account.fetching_subscription"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let description = subInfo.human_description;
|
|
||||||
|
|
||||||
if (subInfo.plan_renewal_date) {
|
|
||||||
description = description.replace(
|
|
||||||
"{periodEnd}",
|
|
||||||
formatDateTime(
|
|
||||||
new Date(subInfo.plan_renewal_date * 1000),
|
|
||||||
this.hass.locale
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeRTLDirection(hass) {
|
|
||||||
return computeRTLDirection(hass);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("cloud-account", CloudAccount);
|
|
268
src/panels/config/cloud/account/cloud-account.ts
Normal file
268
src/panels/config/cloud/account/cloud-account.ts
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
import "@material/mwc-button";
|
||||||
|
import "@polymer/paper-item/paper-item-body";
|
||||||
|
import { LitElement, css, html, PropertyValues } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { formatDateTime } from "../../../../common/datetime/format_date_time";
|
||||||
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
|
||||||
|
import "../../../../components/buttons/ha-call-api-button";
|
||||||
|
import "../../../../components/ha-card";
|
||||||
|
import {
|
||||||
|
cloudLogout,
|
||||||
|
CloudStatusLoggedIn,
|
||||||
|
fetchCloudSubscriptionInfo,
|
||||||
|
SubscriptionInfo,
|
||||||
|
} from "../../../../data/cloud";
|
||||||
|
import "../../../../layouts/hass-subpage";
|
||||||
|
import { HomeAssistant } from "../../../../types";
|
||||||
|
import "../../ha-config-section";
|
||||||
|
import "./cloud-alexa-pref";
|
||||||
|
import "./cloud-google-pref";
|
||||||
|
import "./cloud-remote-pref";
|
||||||
|
import "./cloud-tts-pref";
|
||||||
|
import "./cloud-webhooks";
|
||||||
|
|
||||||
|
@customElement("cloud-account")
|
||||||
|
export class CloudAccount extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public isWide = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public narrow = false;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public cloudStatus!: CloudStatusLoggedIn;
|
||||||
|
|
||||||
|
@state() private _subscription?: SubscriptionInfo;
|
||||||
|
|
||||||
|
@state() private _rtlDirection: "rtl" | "ltr" = "rtl";
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
return html`
|
||||||
|
<hass-subpage
|
||||||
|
.hass=${this.hass}
|
||||||
|
.narrow=${this.narrow}
|
||||||
|
header="Home Assistant Cloud"
|
||||||
|
>
|
||||||
|
<div class="content">
|
||||||
|
<ha-config-section .isWide=${this.isWide}>
|
||||||
|
<span slot="header">Home Assistant Cloud</span>
|
||||||
|
<div slot="introduction">
|
||||||
|
<p>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.account.thank_you_note"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ha-card
|
||||||
|
.header=${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.account.nabu_casa_account"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div class="account-row">
|
||||||
|
<paper-item-body two-line>
|
||||||
|
${this.cloudStatus.email}
|
||||||
|
<div secondary class="wrap">
|
||||||
|
${this._subscription
|
||||||
|
? this._subscription.human_description.replace(
|
||||||
|
"{periodEnd}",
|
||||||
|
this._subscription.plan_renewal_date
|
||||||
|
? formatDateTime(
|
||||||
|
new Date(
|
||||||
|
this._subscription.plan_renewal_date * 1000
|
||||||
|
),
|
||||||
|
this.hass.locale
|
||||||
|
)
|
||||||
|
: ""
|
||||||
|
)
|
||||||
|
: this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.account.fetching_subscription"
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</paper-item-body>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="account-row">
|
||||||
|
<paper-item-body>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.account.connection_status"
|
||||||
|
)}
|
||||||
|
</paper-item-body>
|
||||||
|
<div class="status">
|
||||||
|
${this.cloudStatus.cloud === "connected"
|
||||||
|
? this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.account.connected"
|
||||||
|
)
|
||||||
|
: this.cloudStatus.cloud === "disconnected"
|
||||||
|
? this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.account.not_connected"
|
||||||
|
)
|
||||||
|
: this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.account.connecting"
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-actions">
|
||||||
|
<a
|
||||||
|
href="https://account.nabucasa.com"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
<mwc-button>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.account.manage_account"
|
||||||
|
)}
|
||||||
|
</mwc-button>
|
||||||
|
</a>
|
||||||
|
<mwc-button @click=${this._handleLogout}
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.account.sign_out"
|
||||||
|
)}</mwc-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
</ha-config-section>
|
||||||
|
|
||||||
|
<ha-config-section .isWide=${this.isWide}>
|
||||||
|
<span slot="header"
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.account.integrations"
|
||||||
|
)}</span
|
||||||
|
>
|
||||||
|
<div slot="introduction">
|
||||||
|
<p>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.account.integrations_introduction"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.account.integrations_introduction2"
|
||||||
|
)}
|
||||||
|
<a
|
||||||
|
href="https://www.nabucasa.com"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.account.integrations_link_all_features"
|
||||||
|
)}</a
|
||||||
|
>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<cloud-remote-pref
|
||||||
|
.hass=${this.hass}
|
||||||
|
.cloudStatus=${this.cloudStatus}
|
||||||
|
dir=${this._rtlDirection}
|
||||||
|
></cloud-remote-pref>
|
||||||
|
|
||||||
|
<cloud-tts-pref
|
||||||
|
.hass=${this.hass}
|
||||||
|
.cloudStatus=${this.cloudStatus}
|
||||||
|
dir=${this._rtlDirection}
|
||||||
|
></cloud-tts-pref>
|
||||||
|
|
||||||
|
<cloud-alexa-pref
|
||||||
|
.hass=${this.hass}
|
||||||
|
.cloudStatus=${this.cloudStatus}
|
||||||
|
dir=${this._rtlDirection}
|
||||||
|
></cloud-alexa-pref>
|
||||||
|
|
||||||
|
<cloud-google-pref
|
||||||
|
.hass=${this.hass}
|
||||||
|
.cloudStatus=${this.cloudStatus}
|
||||||
|
dir=${this._rtlDirection}
|
||||||
|
></cloud-google-pref>
|
||||||
|
|
||||||
|
<cloud-webhooks
|
||||||
|
.hass=${this.hass}
|
||||||
|
.cloudStatus=${this.cloudStatus}
|
||||||
|
dir=${this._rtlDirection}
|
||||||
|
></cloud-webhooks>
|
||||||
|
</ha-config-section>
|
||||||
|
</div>
|
||||||
|
</hass-subpage>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
firstUpdated() {
|
||||||
|
this._fetchSubscriptionInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updated(changedProps: PropertyValues) {
|
||||||
|
if (changedProps.has("hass")) {
|
||||||
|
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||||
|
if (!oldHass || oldHass.locale !== this.hass.locale) {
|
||||||
|
this._rtlDirection = computeRTLDirection(this.hass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _fetchSubscriptionInfo() {
|
||||||
|
this._subscription = await fetchCloudSubscriptionInfo(this.hass);
|
||||||
|
if (
|
||||||
|
this._subscription.provider &&
|
||||||
|
this.cloudStatus &&
|
||||||
|
this.cloudStatus.cloud !== "connected"
|
||||||
|
) {
|
||||||
|
fireEvent(this, "ha-refresh-cloud-status");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _handleLogout() {
|
||||||
|
await cloudLogout(this.hass);
|
||||||
|
fireEvent(this, "ha-refresh-cloud-status");
|
||||||
|
}
|
||||||
|
|
||||||
|
_computeRTLDirection(hass) {
|
||||||
|
return computeRTLDirection(hass);
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
return css`
|
||||||
|
[slot="introduction"] {
|
||||||
|
margin: -1em 0;
|
||||||
|
}
|
||||||
|
[slot="introduction"] a {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding-bottom: 24px;
|
||||||
|
}
|
||||||
|
.account-row {
|
||||||
|
display: flex;
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
.card-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.card-actions a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
mwc-button {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
.wrap {
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
.status {
|
||||||
|
text-transform: capitalize;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("cloud-account", CloudAccount);
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"cloud-account": CloudAccount;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,152 +0,0 @@
|
|||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
/* eslint-plugin-disable lit */
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
import "../../../../components/buttons/ha-progress-button";
|
|
||||||
import "../../../../components/ha-card";
|
|
||||||
import "../../../../layouts/hass-subpage";
|
|
||||||
import { EventsMixin } from "../../../../mixins/events-mixin";
|
|
||||||
import LocalizeMixin from "../../../../mixins/localize-mixin";
|
|
||||||
import "../../../../styles/polymer-ha-style";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @appliesMixin EventsMixin
|
|
||||||
* @appliesMixin LocalizeMixin
|
|
||||||
*/
|
|
||||||
class CloudForgotPassword extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style include="iron-flex ha-style">
|
|
||||||
.content {
|
|
||||||
padding-bottom: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-card {
|
|
||||||
max-width: 600px;
|
|
||||||
margin: 0 auto;
|
|
||||||
margin-top: 24px;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
@apply --paper-font-headline;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
.error {
|
|
||||||
color: var(--error-color);
|
|
||||||
}
|
|
||||||
.card-actions {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.card-actions a {
|
|
||||||
color: var(--primary-text-color);
|
|
||||||
}
|
|
||||||
[hidden] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<hass-subpage
|
|
||||||
hass="[[hass]]"
|
|
||||||
narrow="[[narrow]]"
|
|
||||||
header="[[localize('ui.panel.config.cloud.forgot_password.title')]]"
|
|
||||||
>
|
|
||||||
<div class="content">
|
|
||||||
<ha-card
|
|
||||||
header="[[localize('ui.panel.config.cloud.forgot_password.subtitle')]]"
|
|
||||||
>
|
|
||||||
<div class="card-content">
|
|
||||||
<p>
|
|
||||||
[[localize('ui.panel.config.cloud.forgot_password.instructions')]]
|
|
||||||
</p>
|
|
||||||
<div class="error" hidden$="[[!_error]]">[[_error]]</div>
|
|
||||||
<paper-input
|
|
||||||
autofocus=""
|
|
||||||
id="email"
|
|
||||||
label="[[localize('ui.panel.config.cloud.forgot_password.email')]]"
|
|
||||||
value="{{email}}"
|
|
||||||
type="email"
|
|
||||||
on-keydown="_keyDown"
|
|
||||||
error-message="[[localize('ui.panel.config.cloud.forgot_password.email_error_msg')]]"
|
|
||||||
></paper-input>
|
|
||||||
</div>
|
|
||||||
<div class="card-actions">
|
|
||||||
<ha-progress-button
|
|
||||||
on-click="_handleEmailPasswordReset"
|
|
||||||
progress="[[_requestInProgress]]"
|
|
||||||
>[[localize('ui.panel.config.cloud.forgot_password.send_reset_email')]]</ha-progress-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
</div>
|
|
||||||
</hass-subpage>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: Object,
|
|
||||||
narrow: Boolean,
|
|
||||||
email: {
|
|
||||||
type: String,
|
|
||||||
notify: true,
|
|
||||||
observer: "_emailChanged",
|
|
||||||
},
|
|
||||||
_requestInProgress: {
|
|
||||||
type: Boolean,
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
_error: {
|
|
||||||
type: String,
|
|
||||||
value: "",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
_emailChanged() {
|
|
||||||
this._error = "";
|
|
||||||
this.$.email.invalid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_keyDown(ev) {
|
|
||||||
// validate on enter
|
|
||||||
if (ev.keyCode === 13) {
|
|
||||||
this._handleEmailPasswordReset();
|
|
||||||
ev.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleEmailPasswordReset() {
|
|
||||||
if (!this.email || !this.email.includes("@")) {
|
|
||||||
this.$.email.invalid = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.$.email.invalid) return;
|
|
||||||
|
|
||||||
this._requestInProgress = true;
|
|
||||||
|
|
||||||
this.hass
|
|
||||||
.callApi("post", "cloud/forgot_password", {
|
|
||||||
email: this.email,
|
|
||||||
})
|
|
||||||
.then(
|
|
||||||
() => {
|
|
||||||
this._requestInProgress = false;
|
|
||||||
this.fire("cloud-done", {
|
|
||||||
flashMessage: this.hass.localize(
|
|
||||||
"ui.panel.config.cloud.forgot_password.check_your_email"
|
|
||||||
),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
(err) =>
|
|
||||||
this.setProperties({
|
|
||||||
_requestInProgress: false,
|
|
||||||
_error:
|
|
||||||
err && err.body && err.body.message
|
|
||||||
? err.body.message
|
|
||||||
: "Unknown error",
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("cloud-forgot-password", CloudForgotPassword);
|
|
156
src/panels/config/cloud/forgot-password/cloud-forgot-password.ts
Normal file
156
src/panels/config/cloud/forgot-password/cloud-forgot-password.ts
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
import "@material/mwc-textfield/mwc-textfield";
|
||||||
|
import type { TextField } from "@material/mwc-textfield/mwc-textfield";
|
||||||
|
import { css, html, LitElement, TemplateResult } from "lit";
|
||||||
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
import "../../../../components/buttons/ha-progress-button";
|
||||||
|
import "../../../../components/ha-alert";
|
||||||
|
import "../../../../components/ha-card";
|
||||||
|
import { cloudForgotPassword } from "../../../../data/cloud";
|
||||||
|
import "../../../../layouts/hass-subpage";
|
||||||
|
import { haStyle } from "../../../../resources/styles";
|
||||||
|
import { HomeAssistant } from "../../../../types";
|
||||||
|
|
||||||
|
@customElement("cloud-forgot-password")
|
||||||
|
export class CloudForgotPassword extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public narrow = false;
|
||||||
|
|
||||||
|
@property() public email?: string;
|
||||||
|
|
||||||
|
@state() public _requestInProgress = false;
|
||||||
|
|
||||||
|
@state() private _error?: string;
|
||||||
|
|
||||||
|
@query("#email", true) private _emailField!: TextField;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<hass-subpage
|
||||||
|
.hass=${this.hass}
|
||||||
|
.narrow=${this.narrow}
|
||||||
|
.header=${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.forgot_password.title"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div class="content">
|
||||||
|
<ha-card
|
||||||
|
.header=${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.forgot_password.subtitle"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div class="card-content">
|
||||||
|
<p>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.forgot_password.instructions"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
${this._error
|
||||||
|
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||||
|
: ""}
|
||||||
|
<mwc-textfield
|
||||||
|
autofocus
|
||||||
|
id="email"
|
||||||
|
label=${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.forgot_password.email"
|
||||||
|
)}
|
||||||
|
.value=${this.email}
|
||||||
|
type="email"
|
||||||
|
required
|
||||||
|
@keydown=${this._keyDown}
|
||||||
|
.validationMessage=${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.forgot_password.email_error_msg"
|
||||||
|
)}
|
||||||
|
></mwc-textfield>
|
||||||
|
</div>
|
||||||
|
<div class="card-actions">
|
||||||
|
<ha-progress-button
|
||||||
|
@click=${this._handleEmailPasswordReset}
|
||||||
|
.progress=${this._requestInProgress}
|
||||||
|
>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.forgot_password.send_reset_email"
|
||||||
|
)}
|
||||||
|
</ha-progress-button>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
</div>
|
||||||
|
</hass-subpage>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _keyDown(ev: KeyboardEvent) {
|
||||||
|
if (ev.key === "Enter") {
|
||||||
|
this._handleEmailPasswordReset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _handleEmailPasswordReset() {
|
||||||
|
const emailField = this._emailField;
|
||||||
|
|
||||||
|
const email = emailField.value;
|
||||||
|
|
||||||
|
if (!emailField.reportValidity()) {
|
||||||
|
emailField.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._requestInProgress = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await cloudForgotPassword(this.hass, email);
|
||||||
|
// @ts-ignore
|
||||||
|
fireEvent(this, "email-changed", { value: email });
|
||||||
|
this._requestInProgress = false;
|
||||||
|
// @ts-ignore
|
||||||
|
fireEvent(this, "cloud-done", {
|
||||||
|
flashMessage: this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.forgot_password.check_your_email"
|
||||||
|
),
|
||||||
|
});
|
||||||
|
} catch (err: any) {
|
||||||
|
this._requestInProgress = false;
|
||||||
|
this._error =
|
||||||
|
err && err.body && err.body.message
|
||||||
|
? err.body.message
|
||||||
|
: "Unknown error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
return [
|
||||||
|
haStyle,
|
||||||
|
css`
|
||||||
|
.content {
|
||||||
|
padding-bottom: 24px;
|
||||||
|
}
|
||||||
|
ha-card {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
mwc-textfield {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.card-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.card-actions a {
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"cloud-forgot-password": CloudForgotPassword;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,336 +0,0 @@
|
|||||||
import "@material/mwc-button";
|
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import "@polymer/paper-item/paper-item";
|
|
||||||
import "@polymer/paper-item/paper-item-body";
|
|
||||||
import "@polymer/paper-ripple/paper-ripple";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
/* eslint-plugin-disable lit */
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
import { computeRTL } from "../../../../common/util/compute_rtl";
|
|
||||||
import "../../../../components/buttons/ha-progress-button";
|
|
||||||
import "../../../../components/ha-card";
|
|
||||||
import "../../../../components/ha-icon";
|
|
||||||
import "../../../../components/ha-icon-button";
|
|
||||||
import "../../../../components/ha-icon-next";
|
|
||||||
import "../../../../layouts/hass-subpage";
|
|
||||||
import { EventsMixin } from "../../../../mixins/events-mixin";
|
|
||||||
import LocalizeMixin from "../../../../mixins/localize-mixin";
|
|
||||||
import NavigateMixin from "../../../../mixins/navigate-mixin";
|
|
||||||
import "../../../../styles/polymer-ha-style";
|
|
||||||
import "../../ha-config-section";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @appliesMixin NavigateMixin
|
|
||||||
* @appliesMixin EventsMixin
|
|
||||||
* @appliesMixin LocalizeMixin
|
|
||||||
*/
|
|
||||||
class CloudLogin extends LocalizeMixin(
|
|
||||||
NavigateMixin(EventsMixin(PolymerElement))
|
|
||||||
) {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style include="iron-flex ha-style">
|
|
||||||
.content {
|
|
||||||
padding-bottom: 24px;
|
|
||||||
}
|
|
||||||
[slot="introduction"] {
|
|
||||||
margin: -1em 0;
|
|
||||||
}
|
|
||||||
[slot="introduction"] a {
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
paper-item {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
ha-card {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
ha-card .card-header {
|
|
||||||
margin-bottom: -8px;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
@apply --paper-font-headline;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
.error {
|
|
||||||
color: var(--error-color);
|
|
||||||
}
|
|
||||||
.card-actions {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
[hidden] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.flash-msg {
|
|
||||||
padding-right: 44px;
|
|
||||||
}
|
|
||||||
.flash-msg ha-icon-button {
|
|
||||||
position: absolute;
|
|
||||||
top: 4px;
|
|
||||||
right: 8px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
:host([rtl]) .flash-msg ha-icon-button {
|
|
||||||
right: auto;
|
|
||||||
left: 8px;
|
|
||||||
}
|
|
||||||
.login-form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.pwd-forgot-link {
|
|
||||||
color: var(--secondary-text-color) !important;
|
|
||||||
text-align: right !important;
|
|
||||||
align-self: flex-end;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<hass-subpage
|
|
||||||
hass="[[hass]]"
|
|
||||||
narrow="[[narrow]]"
|
|
||||||
header="Home Assistant Cloud"
|
|
||||||
>
|
|
||||||
<div class="content">
|
|
||||||
<ha-config-section is-wide="[[isWide]]">
|
|
||||||
<span slot="header">Home Assistant Cloud</span>
|
|
||||||
<div slot="introduction">
|
|
||||||
<p>[[localize('ui.panel.config.cloud.login.introduction')]]</p>
|
|
||||||
<p>
|
|
||||||
[[localize('ui.panel.config.cloud.login.introduction2')]]
|
|
||||||
<a
|
|
||||||
href="https://www.nabucasa.com"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
Nabu Casa, Inc</a
|
|
||||||
>[[localize('ui.panel.config.cloud.login.introduction2a')]]
|
|
||||||
</p>
|
|
||||||
<p>[[localize('ui.panel.config.cloud.login.introduction3')]]</p>
|
|
||||||
<p>
|
|
||||||
<a
|
|
||||||
href="https://www.nabucasa.com"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
[[localize('ui.panel.config.cloud.login.learn_more_link')]]
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ha-card hidden$="[[!flashMessage]]">
|
|
||||||
<div class="card-content flash-msg">
|
|
||||||
[[flashMessage]]
|
|
||||||
<ha-icon-button
|
|
||||||
label="[[localize('ui.panel.config.cloud.login.dismiss')]]"
|
|
||||||
on-click="_dismissFlash"
|
|
||||||
>
|
|
||||||
<ha-icon icon="hass:close"></ha-icon>
|
|
||||||
</ha-icon-button>
|
|
||||||
<paper-ripple id="flashRipple" noink=""></paper-ripple>
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
|
|
||||||
<ha-card
|
|
||||||
header="[[localize('ui.panel.config.cloud.login.sign_in')]]"
|
|
||||||
>
|
|
||||||
<div class="card-content login-form">
|
|
||||||
<div class="error" hidden$="[[!_error]]">[[_error]]</div>
|
|
||||||
<paper-input
|
|
||||||
label="[[localize('ui.panel.config.cloud.login.email')]]"
|
|
||||||
id="email"
|
|
||||||
type="email"
|
|
||||||
value="{{email}}"
|
|
||||||
on-keydown="_keyDown"
|
|
||||||
error-message="[[localize('ui.panel.config.cloud.login.email_error_msg')]]"
|
|
||||||
></paper-input>
|
|
||||||
<paper-input
|
|
||||||
id="password"
|
|
||||||
label="[[localize('ui.panel.config.cloud.login.password')]]"
|
|
||||||
value="{{_password}}"
|
|
||||||
type="password"
|
|
||||||
on-keydown="_keyDown"
|
|
||||||
error-message="[[localize('ui.panel.config.cloud.login.password_error_msg')]]"
|
|
||||||
></paper-input>
|
|
||||||
<button
|
|
||||||
class="link pwd-forgot-link"
|
|
||||||
hidden="[[_requestInProgress]]"
|
|
||||||
on-click="_handleForgotPassword"
|
|
||||||
>
|
|
||||||
[[localize('ui.panel.config.cloud.login.forgot_password')]]
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="card-actions">
|
|
||||||
<ha-progress-button
|
|
||||||
on-click="_handleLogin"
|
|
||||||
progress="[[_requestInProgress]]"
|
|
||||||
>[[localize('ui.panel.config.cloud.login.sign_in')]]</ha-progress-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
|
|
||||||
<ha-card>
|
|
||||||
<paper-item on-click="_handleRegister">
|
|
||||||
<paper-item-body two-line="">
|
|
||||||
[[localize('ui.panel.config.cloud.login.start_trial')]]
|
|
||||||
<div secondary="">
|
|
||||||
[[localize('ui.panel.config.cloud.login.trial_info')]]
|
|
||||||
</div>
|
|
||||||
</paper-item-body>
|
|
||||||
<ha-icon-next></ha-icon-next>
|
|
||||||
</paper-item>
|
|
||||||
</ha-card>
|
|
||||||
</ha-config-section>
|
|
||||||
</div>
|
|
||||||
</hass-subpage>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: Object,
|
|
||||||
isWide: Boolean,
|
|
||||||
narrow: Boolean,
|
|
||||||
email: {
|
|
||||||
type: String,
|
|
||||||
notify: true,
|
|
||||||
},
|
|
||||||
_password: {
|
|
||||||
type: String,
|
|
||||||
value: "",
|
|
||||||
},
|
|
||||||
_requestInProgress: {
|
|
||||||
type: Boolean,
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
flashMessage: {
|
|
||||||
type: String,
|
|
||||||
notify: true,
|
|
||||||
},
|
|
||||||
rtl: {
|
|
||||||
type: Boolean,
|
|
||||||
reflectToAttribute: true,
|
|
||||||
computed: "_computeRTL(hass)",
|
|
||||||
},
|
|
||||||
_error: String,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static get observers() {
|
|
||||||
return ["_inputChanged(email, _password)"];
|
|
||||||
}
|
|
||||||
|
|
||||||
connectedCallback() {
|
|
||||||
super.connectedCallback();
|
|
||||||
if (this.flashMessage) {
|
|
||||||
// Wait for DOM to be drawn
|
|
||||||
requestAnimationFrame(() =>
|
|
||||||
requestAnimationFrame(() => this.$.flashRipple.simulatedRipple())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_inputChanged() {
|
|
||||||
this.$.email.invalid = false;
|
|
||||||
this.$.password.invalid = false;
|
|
||||||
this._error = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_keyDown(ev) {
|
|
||||||
// validate on enter
|
|
||||||
if (ev.keyCode === 13) {
|
|
||||||
this._handleLogin();
|
|
||||||
ev.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleLogin() {
|
|
||||||
let invalid = false;
|
|
||||||
|
|
||||||
if (!this.email || !this.email.includes("@")) {
|
|
||||||
this.$.email.invalid = true;
|
|
||||||
this.$.email.focus();
|
|
||||||
invalid = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._password.length < 8) {
|
|
||||||
this.$.password.invalid = true;
|
|
||||||
|
|
||||||
if (!invalid) {
|
|
||||||
invalid = true;
|
|
||||||
this.$.password.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (invalid) return;
|
|
||||||
|
|
||||||
this._requestInProgress = true;
|
|
||||||
|
|
||||||
this.hass
|
|
||||||
.callApi("post", "cloud/login", {
|
|
||||||
email: this.email,
|
|
||||||
password: this._password,
|
|
||||||
})
|
|
||||||
.then(
|
|
||||||
() => {
|
|
||||||
this.fire("ha-refresh-cloud-status");
|
|
||||||
this.setProperties({
|
|
||||||
email: "",
|
|
||||||
_password: "",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
// Do this before setProperties because changing it clears errors.
|
|
||||||
this._password = "";
|
|
||||||
|
|
||||||
const errCode = err && err.body && err.body.code;
|
|
||||||
if (errCode === "PasswordChangeRequired") {
|
|
||||||
alert(
|
|
||||||
"[[localize('ui.panel.config.cloud.login.alert_password_change_required')]]"
|
|
||||||
);
|
|
||||||
this.navigate("/config/cloud/forgot-password");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = {
|
|
||||||
_requestInProgress: false,
|
|
||||||
_error:
|
|
||||||
err && err.body && err.body.message
|
|
||||||
? err.body.message
|
|
||||||
: "Unknown error",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (errCode === "UserNotConfirmed") {
|
|
||||||
props._error =
|
|
||||||
"[[localize('ui.panel.config.cloud.login.alert_email_confirm_necessary')]]";
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setProperties(props);
|
|
||||||
this.$.email.focus();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleRegister() {
|
|
||||||
this.flashMessage = "";
|
|
||||||
this.navigate("/config/cloud/register");
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleForgotPassword() {
|
|
||||||
this.flashMessage = "";
|
|
||||||
this.navigate("/config/cloud/forgot-password");
|
|
||||||
}
|
|
||||||
|
|
||||||
_dismissFlash() {
|
|
||||||
// give some time to let the ripple finish.
|
|
||||||
setTimeout(() => {
|
|
||||||
this.flashMessage = "";
|
|
||||||
}, 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeRTL(hass) {
|
|
||||||
return computeRTL(hass);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("cloud-login", CloudLogin);
|
|
310
src/panels/config/cloud/login/cloud-login.ts
Normal file
310
src/panels/config/cloud/login/cloud-login.ts
Normal file
@@ -0,0 +1,310 @@
|
|||||||
|
import "@material/mwc-button";
|
||||||
|
import "@material/mwc-textfield/mwc-textfield";
|
||||||
|
import type { TextField } from "@material/mwc-textfield/mwc-textfield";
|
||||||
|
import "@polymer/paper-input/paper-input";
|
||||||
|
import "@polymer/paper-item/paper-item";
|
||||||
|
import "@polymer/paper-item/paper-item-body";
|
||||||
|
import { css, html, LitElement, TemplateResult } from "lit";
|
||||||
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
import { navigate } from "../../../../common/navigate";
|
||||||
|
import "../../../../components/buttons/ha-progress-button";
|
||||||
|
import "../../../../components/ha-alert";
|
||||||
|
import "../../../../components/ha-card";
|
||||||
|
import "../../../../components/ha-icon-next";
|
||||||
|
import { cloudLogin } from "../../../../data/cloud";
|
||||||
|
import { showAlertDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||||
|
import "../../../../layouts/hass-subpage";
|
||||||
|
import { haStyle } from "../../../../resources/styles";
|
||||||
|
import "../../../../styles/polymer-ha-style";
|
||||||
|
import { HomeAssistant } from "../../../../types";
|
||||||
|
import "../../ha-config-section";
|
||||||
|
|
||||||
|
@customElement("cloud-login")
|
||||||
|
export class CloudLogin extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public isWide = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public narrow = false;
|
||||||
|
|
||||||
|
@property() public email?: string;
|
||||||
|
|
||||||
|
@property() public flashMessage?: string;
|
||||||
|
|
||||||
|
@state() private _password?: string;
|
||||||
|
|
||||||
|
@state() private _requestInProgress = false;
|
||||||
|
|
||||||
|
@state() private _error?: string;
|
||||||
|
|
||||||
|
@query("#email", true) private _emailField!: TextField;
|
||||||
|
|
||||||
|
@query("#password", true) private _passwordField!: TextField;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<hass-subpage
|
||||||
|
.hass=${this.hass}
|
||||||
|
.narrow=${this.narrow}
|
||||||
|
header="Home Assistant Cloud"
|
||||||
|
>
|
||||||
|
<div class="content">
|
||||||
|
<ha-config-section isWide=${this.isWide}>
|
||||||
|
<span slot="header">Home Assistant Cloud</span>
|
||||||
|
<div slot="introduction">
|
||||||
|
<p>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.login.introduction"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.login.introduction2"
|
||||||
|
)}
|
||||||
|
<a
|
||||||
|
href="https://www.nabucasa.com"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
Nabu Casa, Inc</a
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.login.introduction2a"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.login.introduction3"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a
|
||||||
|
href="https://www.nabucasa.com"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.login.learn_more_link"
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${this.flashMessage
|
||||||
|
? html`<ha-alert
|
||||||
|
dismissable
|
||||||
|
@alert-dismissed-clicked=${this._dismissFlash}
|
||||||
|
>
|
||||||
|
${this.flashMessage}
|
||||||
|
</ha-alert>`
|
||||||
|
: ""}
|
||||||
|
|
||||||
|
<ha-card
|
||||||
|
.header=${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.login.sign_in"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div class="card-content login-form">
|
||||||
|
${this._error
|
||||||
|
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||||
|
: ""}
|
||||||
|
<mwc-textfield
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.login.email"
|
||||||
|
)}
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
required
|
||||||
|
.value=${this.email}
|
||||||
|
@keydown=${this._keyDown}
|
||||||
|
.disabled=${this._requestInProgress}
|
||||||
|
.validationMessage=${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.login.email_error_msg"
|
||||||
|
)}
|
||||||
|
></mwc-textfield>
|
||||||
|
<mwc-textfield
|
||||||
|
id="password"
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.login.password"
|
||||||
|
)}
|
||||||
|
.value=${this._password || ""}
|
||||||
|
type="password"
|
||||||
|
required
|
||||||
|
minlength="8"
|
||||||
|
@keydown=${this._keyDown}
|
||||||
|
.disabled=${this._requestInProgress}
|
||||||
|
.validationMessage=${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.login.password_error_msg"
|
||||||
|
)}
|
||||||
|
></mwc-textfield>
|
||||||
|
<button
|
||||||
|
class="link pwd-forgot-link"
|
||||||
|
.disabled=${this._requestInProgress}
|
||||||
|
@click=${this._handleForgotPassword}
|
||||||
|
>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.login.forgot_password"
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="card-actions">
|
||||||
|
<ha-progress-button
|
||||||
|
@click=${this._handleLogin}
|
||||||
|
.progress=${this._requestInProgress}
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.login.sign_in"
|
||||||
|
)}</ha-progress-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
|
||||||
|
<ha-card>
|
||||||
|
<paper-item @click=${this._handleRegister}>
|
||||||
|
<paper-item-body two-line>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.login.start_trial"
|
||||||
|
)}
|
||||||
|
<div secondary>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.login.trial_info"
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</paper-item-body>
|
||||||
|
<ha-icon-next></ha-icon-next>
|
||||||
|
</paper-item>
|
||||||
|
</ha-card>
|
||||||
|
</ha-config-section>
|
||||||
|
</div>
|
||||||
|
</hass-subpage>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _keyDown(ev: KeyboardEvent) {
|
||||||
|
if (ev.key === "Enter") {
|
||||||
|
this._handleLogin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _handleLogin() {
|
||||||
|
const emailField = this._emailField;
|
||||||
|
const passwordField = this._passwordField;
|
||||||
|
|
||||||
|
const email = emailField.value;
|
||||||
|
const password = passwordField.value;
|
||||||
|
|
||||||
|
if (!emailField.reportValidity()) {
|
||||||
|
passwordField.reportValidity();
|
||||||
|
emailField.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!passwordField.reportValidity()) {
|
||||||
|
passwordField.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._requestInProgress = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await cloudLogin(this.hass, email, password);
|
||||||
|
fireEvent(this, "ha-refresh-cloud-status");
|
||||||
|
this.email = "";
|
||||||
|
this._password = "";
|
||||||
|
} catch (err: any) {
|
||||||
|
const errCode = err && err.body && err.body.code;
|
||||||
|
if (errCode === "PasswordChangeRequired") {
|
||||||
|
showAlertDialog(this, {
|
||||||
|
title: this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.login.alert_password_change_required"
|
||||||
|
),
|
||||||
|
});
|
||||||
|
navigate("/config/cloud/forgot-password");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._password = "";
|
||||||
|
this._requestInProgress = false;
|
||||||
|
|
||||||
|
if (errCode === "UserNotConfirmed") {
|
||||||
|
this._error = this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.login.alert_email_confirm_necessary"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this._error =
|
||||||
|
err && err.body && err.body.message
|
||||||
|
? err.body.message
|
||||||
|
: "Unknown error";
|
||||||
|
}
|
||||||
|
|
||||||
|
emailField.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleRegister() {
|
||||||
|
this._dismissFlash();
|
||||||
|
// @ts-ignore
|
||||||
|
fireEvent(this, "email-changed", { value: this._emailField.value });
|
||||||
|
navigate("/config/cloud/register");
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleForgotPassword() {
|
||||||
|
this._dismissFlash();
|
||||||
|
// @ts-ignore
|
||||||
|
fireEvent(this, "email-changed", { value: this._emailField.value });
|
||||||
|
navigate("/config/cloud/forgot-password");
|
||||||
|
}
|
||||||
|
|
||||||
|
private _dismissFlash() {
|
||||||
|
// @ts-ignore
|
||||||
|
fireEvent(this, "flash-message-changed", { value: "" });
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
return [
|
||||||
|
haStyle,
|
||||||
|
css`
|
||||||
|
.content {
|
||||||
|
padding-bottom: 24px;
|
||||||
|
}
|
||||||
|
[slot="introduction"] {
|
||||||
|
margin: -1em 0;
|
||||||
|
}
|
||||||
|
[slot="introduction"] a {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
paper-item {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
ha-card {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
ha-card .card-header {
|
||||||
|
margin-bottom: -8px;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.card-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.login-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.pwd-forgot-link {
|
||||||
|
color: var(--secondary-text-color) !important;
|
||||||
|
text-align: right !important;
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"cloud-login": CloudLogin;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,229 +0,0 @@
|
|||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
/* eslint-plugin-disable lit */
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
import "../../../../components/buttons/ha-progress-button";
|
|
||||||
import "../../../../components/ha-card";
|
|
||||||
import "../../../../layouts/hass-subpage";
|
|
||||||
import { EventsMixin } from "../../../../mixins/events-mixin";
|
|
||||||
import LocalizeMixin from "../../../../mixins/localize-mixin";
|
|
||||||
import "../../../../styles/polymer-ha-style";
|
|
||||||
import { documentationUrl } from "../../../../util/documentation-url";
|
|
||||||
import "../../ha-config-section";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @appliesMixin EventsMixin
|
|
||||||
* @appliesMixin LocalizeMixin
|
|
||||||
*/
|
|
||||||
class CloudRegister extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style include="iron-flex ha-style">
|
|
||||||
[slot=introduction] {
|
|
||||||
margin: -1em 0;
|
|
||||||
}
|
|
||||||
[slot=introduction] a {
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
paper-item {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
@apply --paper-font-headline;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
.error {
|
|
||||||
color: var(--error-color);
|
|
||||||
}
|
|
||||||
.card-actions {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
[hidden] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<hass-subpage hass="[[hass]]" narrow="[[narrow]]" header="[[localize('ui.panel.config.cloud.register.title')]]">
|
|
||||||
<div class="content">
|
|
||||||
<ha-config-section is-wide="[[isWide]]">
|
|
||||||
<span slot="header">[[localize('ui.panel.config.cloud.register.headline')]]</span>
|
|
||||||
<div slot="introduction">
|
|
||||||
<p>
|
|
||||||
[[localize('ui.panel.config.cloud.register.information')]]
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
[[localize('ui.panel.config.cloud.register.information2')]]
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
<li>[[localize('ui.panel.config.cloud.register.feature_remote_control')]]</li>
|
|
||||||
<li>[[localize('ui.panel.config.cloud.register.feature_google_home')]]</li>
|
|
||||||
<li>[[localize('ui.panel.config.cloud.register.feature_amazon_alexa')]]</li>
|
|
||||||
<li>[[localize('ui.panel.config.cloud.register.feature_webhook_apps')]]</li>
|
|
||||||
</ul>
|
|
||||||
<p>
|
|
||||||
[[localize('ui.panel.config.cloud.register.information3')]] <a href='https://www.nabucasa.com' target='_blank'>Nabu Casa, Inc</a>[[localize('ui.panel.config.cloud.register.information3a')]]
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
[[localize('ui.panel.config.cloud.register.information4')]]
|
|
||||||
</p><ul>
|
|
||||||
<li><a href="[[_computeDocumentationUrlTos(hass)]]" target="_blank" rel="noreferrer">[[localize('ui.panel.config.cloud.register.link_terms_conditions')]]</a></li>
|
|
||||||
<li><a href="[[_computeDocumentationUrlPrivacy(hass)]]" target="_blank" rel="noreferrer">[[localize('ui.panel.config.cloud.register.link_privacy_policy')]]</a></li>
|
|
||||||
</ul>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ha-card header="[[localize('ui.panel.config.cloud.register.create_account')]]">
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="header">
|
|
||||||
<div class="error" hidden$="[[!_error]]">[[_error]]</div>
|
|
||||||
</div>
|
|
||||||
<paper-input autofocus="" id="email" label="[[localize('ui.panel.config.cloud.register.email_address')]]" type="email" value="{{email}}" on-keydown="_keyDown" error-message="[[localize('ui.panel.config.cloud.register.email_error_msg')]]"></paper-input>
|
|
||||||
<paper-input id="password" label="Password" value="{{_password}}" type="password" on-keydown="_keyDown" error-message="[[localize('ui.panel.config.cloud.register.password_error_msg')]]"></paper-input>
|
|
||||||
</div>
|
|
||||||
<div class="card-actions">
|
|
||||||
<ha-progress-button on-click="_handleRegister" progress="[[_requestInProgress]]">[[localize('ui.panel.config.cloud.register.start_trial')]]</ha-progress-button>
|
|
||||||
<button class="link" hidden="[[_requestInProgress]]" on-click="_handleResendVerifyEmail">[[localize('ui.panel.config.cloud.register.resend_confirmation_email')]]</button>
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
</ha-config-section>
|
|
||||||
</div>
|
|
||||||
</hass-subpage>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: Object,
|
|
||||||
isWide: Boolean,
|
|
||||||
narrow: Boolean,
|
|
||||||
email: {
|
|
||||||
type: String,
|
|
||||||
notify: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
_requestInProgress: {
|
|
||||||
type: Boolean,
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
_password: {
|
|
||||||
type: String,
|
|
||||||
value: "",
|
|
||||||
},
|
|
||||||
_error: {
|
|
||||||
type: String,
|
|
||||||
value: "",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static get observers() {
|
|
||||||
return ["_inputChanged(email, _password)"];
|
|
||||||
}
|
|
||||||
|
|
||||||
_inputChanged() {
|
|
||||||
this._error = "";
|
|
||||||
this.$.email.invalid = false;
|
|
||||||
this.$.password.invalid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_keyDown(ev) {
|
|
||||||
// validate on enter
|
|
||||||
if (ev.keyCode === 13) {
|
|
||||||
this._handleRegister();
|
|
||||||
ev.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeDocumentationUrlTos(hass) {
|
|
||||||
return documentationUrl(hass, "/tos/");
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeDocumentationUrlPrivacy(hass) {
|
|
||||||
return documentationUrl(hass, "/privacy/");
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleRegister() {
|
|
||||||
let invalid = false;
|
|
||||||
|
|
||||||
if (!this.email || !this.email.includes("@")) {
|
|
||||||
this.$.email.invalid = true;
|
|
||||||
this.$.email.focus();
|
|
||||||
invalid = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._password.length < 8) {
|
|
||||||
this.$.password.invalid = true;
|
|
||||||
|
|
||||||
if (!invalid) {
|
|
||||||
invalid = true;
|
|
||||||
this.$.password.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (invalid) return;
|
|
||||||
|
|
||||||
this._requestInProgress = true;
|
|
||||||
|
|
||||||
this.hass
|
|
||||||
.callApi("post", "cloud/register", {
|
|
||||||
email: this.email,
|
|
||||||
password: this._password,
|
|
||||||
})
|
|
||||||
.then(
|
|
||||||
() => this._verificationEmailSent(),
|
|
||||||
(err) => {
|
|
||||||
// Do this before setProperties because changing it clears errors.
|
|
||||||
this._password = "";
|
|
||||||
|
|
||||||
this.setProperties({
|
|
||||||
_requestInProgress: false,
|
|
||||||
_error:
|
|
||||||
err && err.body && err.body.message
|
|
||||||
? err.body.message
|
|
||||||
: "Unknown error",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleResendVerifyEmail() {
|
|
||||||
if (!this.email) {
|
|
||||||
this.$.email.invalid = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hass
|
|
||||||
.callApi("post", "cloud/resend_confirm", {
|
|
||||||
email: this.email,
|
|
||||||
})
|
|
||||||
.then(
|
|
||||||
() => this._verificationEmailSent(),
|
|
||||||
(err) =>
|
|
||||||
this.setProperties({
|
|
||||||
_error:
|
|
||||||
err && err.body && err.body.message
|
|
||||||
? err.body.message
|
|
||||||
: "Unknown error",
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_verificationEmailSent() {
|
|
||||||
this.setProperties({
|
|
||||||
_requestInProgress: false,
|
|
||||||
_password: "",
|
|
||||||
});
|
|
||||||
this.fire("cloud-done", {
|
|
||||||
flashMessage: this.hass.localize(
|
|
||||||
"ui.panel.config.cloud.register.account_created"
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("cloud-register", CloudRegister);
|
|
293
src/panels/config/cloud/register/cloud-register.ts
Normal file
293
src/panels/config/cloud/register/cloud-register.ts
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
import "@material/mwc-textfield/mwc-textfield";
|
||||||
|
import type { TextField } from "@material/mwc-textfield/mwc-textfield";
|
||||||
|
import { css, html, LitElement, TemplateResult } from "lit";
|
||||||
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
import "../../../../components/buttons/ha-progress-button";
|
||||||
|
import "../../../../components/ha-alert";
|
||||||
|
import "../../../../components/ha-card";
|
||||||
|
import { cloudRegister, cloudResendVerification } from "../../../../data/cloud";
|
||||||
|
import "../../../../layouts/hass-subpage";
|
||||||
|
import { haStyle } from "../../../../resources/styles";
|
||||||
|
import { HomeAssistant } from "../../../../types";
|
||||||
|
import "../../ha-config-section";
|
||||||
|
|
||||||
|
@customElement("cloud-register")
|
||||||
|
export class CloudRegister extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public isWide = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public narrow = false;
|
||||||
|
|
||||||
|
@property() public email?: string;
|
||||||
|
|
||||||
|
@state() private _requestInProgress = false;
|
||||||
|
|
||||||
|
@state() private _password = "";
|
||||||
|
|
||||||
|
@state() private _error?: string;
|
||||||
|
|
||||||
|
@query("#email", true) private _emailField!: TextField;
|
||||||
|
|
||||||
|
@query("#password", true) private _passwordField!: TextField;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<hass-subpage
|
||||||
|
.hass=${this.hass}
|
||||||
|
.narrow=${this.narrow}
|
||||||
|
.header=${this.hass.localize("ui.panel.config.cloud.register.title")}
|
||||||
|
>
|
||||||
|
<div class="content">
|
||||||
|
<ha-config-section .isWide=${this.isWide}>
|
||||||
|
<span slot="header"
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.register.headline"
|
||||||
|
)}</span
|
||||||
|
>
|
||||||
|
<div slot="introduction">
|
||||||
|
<p>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.register.information"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.register.information2"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.register.feature_remote_control"
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.register.feature_google_home"
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.register.feature_amazon_alexa"
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.register.feature_webhook_apps"
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.register.information3"
|
||||||
|
)}
|
||||||
|
<a href="https://www.nabucasa.com" target="_blank"
|
||||||
|
>Nabu Casa, Inc</a
|
||||||
|
>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.register.information3a"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.register.information4"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="https://www.nabucasa.com/tos/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.register.link_terms_conditions"
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="https://www.nabucasa.com/privacy_policy/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.register.link_privacy_policy"
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<ha-card
|
||||||
|
.header=${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.register.create_account"
|
||||||
|
)}
|
||||||
|
><div class="card-content register-form">
|
||||||
|
${this._error
|
||||||
|
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||||
|
: ""}
|
||||||
|
<mwc-textfield
|
||||||
|
autofocus
|
||||||
|
id="email"
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.register.email_address"
|
||||||
|
)}
|
||||||
|
type="email"
|
||||||
|
required
|
||||||
|
.value=${this.email}
|
||||||
|
@keydown=${this._keyDown}
|
||||||
|
validationMessage=${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.register.email_error_msg"
|
||||||
|
)}
|
||||||
|
></mwc-textfield>
|
||||||
|
<mwc-textfield
|
||||||
|
id="password"
|
||||||
|
label="Password"
|
||||||
|
.value=${this._password}
|
||||||
|
type="password"
|
||||||
|
minlength="8"
|
||||||
|
required
|
||||||
|
@keydown=${this._keyDown}
|
||||||
|
validationMessage=${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.register.password_error_msg"
|
||||||
|
)}
|
||||||
|
></mwc-textfield>
|
||||||
|
</div>
|
||||||
|
<div class="card-actions">
|
||||||
|
<ha-progress-button
|
||||||
|
@click=${this._handleRegister}
|
||||||
|
.progress=${this._requestInProgress}
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.register.start_trial"
|
||||||
|
)}</ha-progress-button
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="link"
|
||||||
|
.disabled=${this._requestInProgress}
|
||||||
|
@click=${this._handleResendVerifyEmail}
|
||||||
|
>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.register.resend_confirm_email"
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
</ha-config-section>
|
||||||
|
</div>
|
||||||
|
</hass-subpage>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _keyDown(ev: KeyboardEvent) {
|
||||||
|
if (ev.key === "Enter") {
|
||||||
|
this._handleRegister();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _handleRegister() {
|
||||||
|
const emailField = this._emailField;
|
||||||
|
const passwordField = this._passwordField;
|
||||||
|
|
||||||
|
const email = emailField.value;
|
||||||
|
const password = passwordField.value;
|
||||||
|
|
||||||
|
if (!emailField.reportValidity()) {
|
||||||
|
passwordField.reportValidity();
|
||||||
|
emailField.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!passwordField.reportValidity()) {
|
||||||
|
passwordField.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._requestInProgress = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await cloudRegister(this.hass, email, password);
|
||||||
|
this._verificationEmailSent(email);
|
||||||
|
} catch (err: any) {
|
||||||
|
this._password = "";
|
||||||
|
this._requestInProgress = false;
|
||||||
|
this._error =
|
||||||
|
err && err.body && err.body.message
|
||||||
|
? err.body.message
|
||||||
|
: "Unknown error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _handleResendVerifyEmail() {
|
||||||
|
const emailField = this._emailField;
|
||||||
|
|
||||||
|
const email = emailField.value;
|
||||||
|
|
||||||
|
if (!emailField.reportValidity()) {
|
||||||
|
emailField.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await cloudResendVerification(this.hass, email);
|
||||||
|
this._verificationEmailSent(email);
|
||||||
|
} catch (err: any) {
|
||||||
|
this._error =
|
||||||
|
err && err.body && err.body.message
|
||||||
|
? err.body.message
|
||||||
|
: "Unknown error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _verificationEmailSent(email: string) {
|
||||||
|
this._requestInProgress = false;
|
||||||
|
this._password = "";
|
||||||
|
// @ts-ignore
|
||||||
|
fireEvent(this, "email-changed", { value: email });
|
||||||
|
// @ts-ignore
|
||||||
|
fireEvent(this, "cloud-done", {
|
||||||
|
flashMessage: this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.register.account_created"
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
return [
|
||||||
|
haStyle,
|
||||||
|
css`
|
||||||
|
[slot="introduction"] {
|
||||||
|
margin: -1em 0;
|
||||||
|
}
|
||||||
|
[slot="introduction"] a {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
paper-item {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.register-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.card-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"cloud-register": CloudRegister;
|
||||||
|
}
|
||||||
|
}
|
@@ -25,7 +25,7 @@ class HaConfigCustomize extends LitElement {
|
|||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<hass-tabs-subpage
|
<hass-tabs-subpage
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
back-path="/config"
|
back-path="/config"
|
||||||
|
@@ -180,10 +180,16 @@ export class HaFormCustomize extends LocalizeMixin(PolymerElement) {
|
|||||||
this.newAttributes
|
this.newAttributes
|
||||||
);
|
);
|
||||||
attrs.forEach((attr) => {
|
attrs.forEach((attr) => {
|
||||||
if (attr.closed || attr.secondary || !attr.attribute || !attr.value)
|
if (
|
||||||
|
attr.closed ||
|
||||||
|
attr.secondary ||
|
||||||
|
!attr.attribute ||
|
||||||
|
attr.value === null ||
|
||||||
|
attr.value === undefined
|
||||||
|
)
|
||||||
return;
|
return;
|
||||||
const value = attr.type === "json" ? JSON.parse(attr.value) : attr.value;
|
const value = attr.type === "json" ? JSON.parse(attr.value) : attr.value;
|
||||||
if (!value) return;
|
if (value === null || value === undefined) return;
|
||||||
data[attr.attribute] = value;
|
data[attr.attribute] = value;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -15,18 +15,23 @@ import { domainIcon } from "../../../../common/entity/domain_icon";
|
|||||||
import "../../../../components/entity/state-badge";
|
import "../../../../components/entity/state-badge";
|
||||||
import "../../../../components/ha-card";
|
import "../../../../components/ha-card";
|
||||||
import "../../../../components/ha-icon";
|
import "../../../../components/ha-icon";
|
||||||
import { HomeAssistant } from "../../../../types";
|
import type { LovelaceRowConfig } from "../../../lovelace/entity-rows/types";
|
||||||
import { HuiErrorCard } from "../../../lovelace/cards/hui-error-card";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
|
import type { HuiErrorCard } from "../../../lovelace/cards/hui-error-card";
|
||||||
import { createRowElement } from "../../../lovelace/create-element/create-row-element";
|
import { createRowElement } from "../../../lovelace/create-element/create-row-element";
|
||||||
import { addEntitiesToLovelaceView } from "../../../lovelace/editor/add-entities-to-view";
|
import { addEntitiesToLovelaceView } from "../../../lovelace/editor/add-entities-to-view";
|
||||||
import { LovelaceRow } from "../../../lovelace/entity-rows/types";
|
import { LovelaceRow } from "../../../lovelace/entity-rows/types";
|
||||||
import { showEntityEditorDialog } from "../../entities/show-dialog-entity-editor";
|
import { showEntityEditorDialog } from "../../entities/show-dialog-entity-editor";
|
||||||
import { EntityRegistryStateEntry } from "../ha-config-device-page";
|
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")
|
@customElement("ha-device-entities-card")
|
||||||
export class HaDeviceEntitiesCard extends LitElement {
|
export class HaDeviceEntitiesCard extends LitElement {
|
||||||
@property() public header!: string;
|
@property() public header!: string;
|
||||||
|
|
||||||
|
@property() public deviceName!: string;
|
||||||
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public entities!: EntityRegistryStateEntry[];
|
@property() public entities!: EntityRegistryStateEntry[];
|
||||||
@@ -100,14 +105,8 @@ export class HaDeviceEntitiesCard extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: html`
|
: html`
|
||||||
<div class="config-entry-row">
|
<div class="empty card-content">
|
||||||
<paper-item-body two-line>
|
${this.hass.localize("ui.panel.config.devices.entities.none")}
|
||||||
<div>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.devices.entities.none"
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</paper-item-body>
|
|
||||||
</div>
|
</div>
|
||||||
`}
|
`}
|
||||||
</ha-card>
|
</ha-card>
|
||||||
@@ -119,9 +118,21 @@ export class HaDeviceEntitiesCard extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _renderEntity(entry: EntityRegistryStateEntry): TemplateResult {
|
private _renderEntity(entry: EntityRegistryStateEntry): TemplateResult {
|
||||||
const element = createRowElement({ entity: entry.entity_id });
|
const config: LovelaceRowConfig = {
|
||||||
|
entity: entry.entity_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
const element = createRowElement(config);
|
||||||
if (this.hass) {
|
if (this.hass) {
|
||||||
element.hass = 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
|
// @ts-ignore
|
||||||
element.entry = entry;
|
element.entry = entry;
|
||||||
@@ -131,7 +142,11 @@ export class HaDeviceEntitiesCard extends LitElement {
|
|||||||
|
|
||||||
private _renderEntry(entry: EntityRegistryStateEntry): TemplateResult {
|
private _renderEntry(entry: EntityRegistryStateEntry): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<paper-icon-item .entry=${entry} @click=${this._openEditEntry}>
|
<paper-icon-item
|
||||||
|
class="disabled-entry"
|
||||||
|
.entry=${entry}
|
||||||
|
@click=${this._openEditEntry}
|
||||||
|
>
|
||||||
<ha-svg-icon
|
<ha-svg-icon
|
||||||
slot="item-icon"
|
slot="item-icon"
|
||||||
.path=${domainIcon(computeDomain(entry.entity_id))}
|
.path=${domainIcon(computeDomain(entry.entity_id))}
|
||||||
@@ -166,7 +181,8 @@ export class HaDeviceEntitiesCard extends LitElement {
|
|||||||
this.hass,
|
this.hass,
|
||||||
this.entities
|
this.entities
|
||||||
.filter((entity) => !entity.disabled_by)
|
.filter((entity) => !entity.disabled_by)
|
||||||
.map((entity) => entity.entity_id)
|
.map((entity) => entity.entity_id),
|
||||||
|
this.deviceName
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,6 +204,9 @@ export class HaDeviceEntitiesCard extends LitElement {
|
|||||||
.disabled-entry {
|
.disabled-entry {
|
||||||
color: var(--secondary-text-color);
|
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 > * {
|
#entities > * {
|
||||||
margin: 8px 16px 8px 8px;
|
margin: 8px 16px 8px 8px;
|
||||||
}
|
}
|
||||||
@@ -196,12 +215,16 @@ export class HaDeviceEntitiesCard extends LitElement {
|
|||||||
}
|
}
|
||||||
paper-icon-item {
|
paper-icon-item {
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
padding: 0 8px;
|
padding: 0 16px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
--paper-item-icon-width: 48px;
|
||||||
}
|
}
|
||||||
.name {
|
.name {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
.empty {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
button.show-more {
|
button.show-more {
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
@@ -18,6 +18,7 @@ import {
|
|||||||
nodeStatus,
|
nodeStatus,
|
||||||
ZWaveJSNodeStatus,
|
ZWaveJSNodeStatus,
|
||||||
ZWaveJSNodeIdentifiers,
|
ZWaveJSNodeIdentifiers,
|
||||||
|
SecurityClass,
|
||||||
} from "../../../../../../data/zwave_js";
|
} from "../../../../../../data/zwave_js";
|
||||||
import { haStyle } from "../../../../../../resources/styles";
|
import { haStyle } from "../../../../../../resources/styles";
|
||||||
import { HomeAssistant } from "../../../../../../types";
|
import { HomeAssistant } from "../../../../../../types";
|
||||||
@@ -117,13 +118,33 @@ export class HaDeviceInfoZWaveJS extends LitElement {
|
|||||||
: this.hass.localize("ui.common.no")}
|
: this.hass.localize("ui.common.no")}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
${this.hass.localize("ui.panel.config.zwave_js.device_info.is_secure")}:
|
${this.hass.localize(
|
||||||
${this._node.is_secure === true
|
"ui.panel.config.zwave_js.device_info.highest_security"
|
||||||
? this.hass.localize("ui.common.yes")
|
)}:
|
||||||
|
${this._node.highest_security_class !== null
|
||||||
|
? this.hass.localize(
|
||||||
|
`ui.panel.config.zwave_js.security_classes.${
|
||||||
|
SecurityClass[this._node.highest_security_class]
|
||||||
|
}.title`
|
||||||
|
)
|
||||||
: this._node.is_secure === false
|
: this._node.is_secure === false
|
||||||
? this.hass.localize("ui.common.no")
|
? this.hass.localize(
|
||||||
|
"ui.panel.config.zwave_js.security_classes.none.title"
|
||||||
|
)
|
||||||
: this.hass.localize("ui.panel.config.zwave_js.device_info.unknown")}
|
: this.hass.localize("ui.panel.config.zwave_js.device_info.unknown")}
|
||||||
</div>
|
</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>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -52,6 +52,7 @@ import {
|
|||||||
loadDeviceRegistryDetailDialog,
|
loadDeviceRegistryDetailDialog,
|
||||||
showDeviceRegistryDetailDialog,
|
showDeviceRegistryDetailDialog,
|
||||||
} from "./device-registry-detail/show-dialog-device-registry-detail";
|
} from "./device-registry-detail/show-dialog-device-registry-detail";
|
||||||
|
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||||
|
|
||||||
export interface EntityRegistryStateEntry extends EntityRegistryEntry {
|
export interface EntityRegistryStateEntry extends EntityRegistryEntry {
|
||||||
stateName?: string | null;
|
stateName?: string | null;
|
||||||
@@ -117,14 +118,19 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
|
|
||||||
private _entitiesByCategory = memoizeOne(
|
private _entitiesByCategory = memoizeOne(
|
||||||
(entities: EntityRegistryEntry[]) => {
|
(entities: EntityRegistryEntry[]) => {
|
||||||
const result = groupBy(
|
const result = groupBy(entities, (entry) =>
|
||||||
entities,
|
entry.entity_category
|
||||||
(entry) => entry.entity_category || "state"
|
? entry.entity_category
|
||||||
|
: ["sensor", "binary_sensor"].includes(computeDomain(entry.entity_id))
|
||||||
|
? "sensor"
|
||||||
|
: "control"
|
||||||
) as Record<
|
) as Record<
|
||||||
"state" | NonNullable<EntityRegistryEntry["entity_category"]>,
|
| "control"
|
||||||
|
| "sensor"
|
||||||
|
| NonNullable<EntityRegistryEntry["entity_category"]>,
|
||||||
EntityRegistryStateEntry[]
|
EntityRegistryStateEntry[]
|
||||||
>;
|
>;
|
||||||
for (const key of ["state", "diagnostic", "config"]) {
|
for (const key of ["control", "sensor", "diagnostic", "config"]) {
|
||||||
if (!(key in result)) {
|
if (!(key in result)) {
|
||||||
result[key] = [];
|
result[key] = [];
|
||||||
}
|
}
|
||||||
@@ -179,6 +185,7 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deviceName = computeDeviceName(device, this.hass);
|
||||||
const integrations = this._integrations(device, this.entries);
|
const integrations = this._integrations(device, this.entries);
|
||||||
const entities = this._entities(this.deviceId, this.entities);
|
const entities = this._entities(this.deviceId, this.entities);
|
||||||
const entitiesByCategory = this._entitiesByCategory(entities);
|
const entitiesByCategory = this._entitiesByCategory(entities);
|
||||||
@@ -194,6 +201,13 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
: undefined;
|
: undefined;
|
||||||
const area = this._computeArea(this.areas, device);
|
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`
|
return html`
|
||||||
<hass-tabs-subpage
|
<hass-tabs-subpage
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@@ -204,9 +218,7 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
${
|
${
|
||||||
this.narrow
|
this.narrow
|
||||||
? html`
|
? html`
|
||||||
<span slot="header">
|
<span slot="header">${deviceName}</span>
|
||||||
${computeDeviceName(device, this.hass)}
|
|
||||||
</span>
|
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
slot="toolbar-icon"
|
slot="toolbar-icon"
|
||||||
.path=${mdiPencil}
|
.path=${mdiPencil}
|
||||||
@@ -230,7 +242,7 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
: html`
|
: html`
|
||||||
<div class="header-name">
|
<div class="header-name">
|
||||||
<div>
|
<div>
|
||||||
<h1>${computeDeviceName(device, this.hass)}</h1>
|
<h1>${deviceName}</h1>
|
||||||
${area
|
${area
|
||||||
? html`
|
? html`
|
||||||
<a href="/config/areas/area/${area.area_id}"
|
<a href="/config/areas/area/${area.area_id}"
|
||||||
@@ -317,13 +329,15 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
: html``
|
: html``
|
||||||
}
|
}
|
||||||
${
|
${
|
||||||
device.configuration_url
|
configurationUrl
|
||||||
? html`
|
? html`
|
||||||
<div class="card-actions" slot="actions">
|
<div class="card-actions" slot="actions">
|
||||||
<a
|
<a
|
||||||
href=${device.configuration_url}
|
href=${configurationUrl}
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
|
.target=${configurationUrlIsHomeAssistant
|
||||||
|
? "_self"
|
||||||
|
: "_blank"}
|
||||||
>
|
>
|
||||||
<mwc-button>
|
<mwc-button>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
@@ -343,25 +357,28 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
}
|
}
|
||||||
${this._renderIntegrationInfo(device, integrations)}
|
${this._renderIntegrationInfo(device, integrations)}
|
||||||
</ha-device-info-card>
|
</ha-device-info-card>
|
||||||
|
</div>
|
||||||
${["state", "config", "diagnostic"].map((category) =>
|
<div class="column">
|
||||||
!entitiesByCategory[category].length
|
${["control", "sensor", "config", "diagnostic"].map((category) =>
|
||||||
? ""
|
// Make sure we render controls if no other cards will be rendered
|
||||||
: html`
|
entitiesByCategory[category].length > 0 ||
|
||||||
|
(entities.length === 0 && category === "control")
|
||||||
|
? html`
|
||||||
<ha-device-entities-card
|
<ha-device-entities-card
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.header=${this.hass.localize(
|
.header=${this.hass.localize(
|
||||||
`ui.panel.config.devices.entities.${category}`
|
`ui.panel.config.devices.entities.${category}`
|
||||||
)}
|
)}
|
||||||
|
.deviceName=${deviceName}
|
||||||
.entities=${entitiesByCategory[category]}
|
.entities=${entitiesByCategory[category]}
|
||||||
.showDisabled=${device.disabled_by !== null}
|
.showDisabled=${device.disabled_by !== null}
|
||||||
>
|
>
|
||||||
</ha-device-entities-card>
|
</ha-device-entities-card>
|
||||||
`
|
`
|
||||||
|
: ""
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
${
|
${
|
||||||
isComponentLoaded(this.hass, "automation")
|
isComponentLoaded(this.hass, "automation")
|
||||||
? html`
|
? html`
|
||||||
@@ -420,7 +437,7 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
: "";
|
: "";
|
||||||
})
|
})
|
||||||
: html`
|
: html`
|
||||||
<paper-item class="no-link">
|
<div class="card-content">
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.devices.add_prompt",
|
"ui.panel.config.devices.add_prompt",
|
||||||
"name",
|
"name",
|
||||||
@@ -428,92 +445,82 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
"ui.panel.config.devices.automation.automations"
|
"ui.panel.config.devices.automation.automations"
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</paper-item>
|
</div>
|
||||||
`}
|
`}
|
||||||
</ha-card>
|
</ha-card>
|
||||||
`
|
`
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
</div>
|
|
||||||
<div class="column">
|
|
||||||
${
|
${
|
||||||
isComponentLoaded(this.hass, "scene") && entities.length
|
isComponentLoaded(this.hass, "scene") && entities.length
|
||||||
? html`
|
? html`
|
||||||
<ha-card>
|
<ha-card>
|
||||||
<h1 class="card-header">
|
<h1 class="card-header">
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.devices.scene.scenes"
|
"ui.panel.config.devices.scene.scenes"
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ha-icon-button @click=${
|
<ha-icon-button
|
||||||
this._createScene
|
@click=${this._createScene}
|
||||||
} .disabled=${device.disabled_by}
|
.disabled=${device.disabled_by}
|
||||||
.label=${
|
.label=${device.disabled_by
|
||||||
device.disabled_by
|
? this.hass.localize(
|
||||||
? this.hass.localize(
|
"ui.panel.config.devices.scene.create_disabled"
|
||||||
"ui.panel.config.devices.scene.create_disabled"
|
)
|
||||||
)
|
: this.hass.localize(
|
||||||
: this.hass.localize(
|
"ui.panel.config.devices.scene.create"
|
||||||
"ui.panel.config.devices.scene.create"
|
)}
|
||||||
)
|
.path=${mdiPlusCircle}
|
||||||
}
|
></ha-icon-button>
|
||||||
.path=${mdiPlusCircle}
|
</h1>
|
||||||
></ha-icon-button>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
${
|
${this._related?.scene?.length
|
||||||
this._related?.scene?.length
|
? this._related.scene.map((scene) => {
|
||||||
? this._related.scene.map((scene) => {
|
const entityState = this.hass.states[scene];
|
||||||
const entityState = this.hass.states[scene];
|
return entityState
|
||||||
return entityState
|
? html`
|
||||||
? html`
|
<div>
|
||||||
<div>
|
<a
|
||||||
<a
|
href=${ifDefined(
|
||||||
href=${ifDefined(
|
entityState.attributes.id
|
||||||
entityState.attributes.id
|
? `/config/scene/edit/${entityState.attributes.id}`
|
||||||
? `/config/scene/edit/${entityState.attributes.id}`
|
: undefined
|
||||||
: undefined
|
)}
|
||||||
)}
|
>
|
||||||
>
|
<paper-item
|
||||||
<paper-item
|
.scene=${entityState}
|
||||||
.scene=${entityState}
|
.disabled=${!entityState.attributes.id}
|
||||||
.disabled=${!entityState.attributes
|
>
|
||||||
.id}
|
<paper-item-body>
|
||||||
>
|
${computeStateName(entityState)}
|
||||||
<paper-item-body>
|
</paper-item-body>
|
||||||
${computeStateName(entityState)}
|
<ha-icon-next></ha-icon-next>
|
||||||
</paper-item-body>
|
</paper-item>
|
||||||
<ha-icon-next></ha-icon-next>
|
</a>
|
||||||
</paper-item>
|
${!entityState.attributes.id
|
||||||
</a>
|
? html`
|
||||||
${!entityState.attributes.id
|
<paper-tooltip animation-delay="0">
|
||||||
? html`
|
${this.hass.localize(
|
||||||
<paper-tooltip
|
"ui.panel.config.devices.cant_edit"
|
||||||
animation-delay="0"
|
)}
|
||||||
>
|
</paper-tooltip>
|
||||||
${this.hass.localize(
|
`
|
||||||
"ui.panel.config.devices.cant_edit"
|
: ""}
|
||||||
)}
|
</div>
|
||||||
</paper-tooltip>
|
`
|
||||||
`
|
: "";
|
||||||
: ""}
|
})
|
||||||
</div>
|
: html`
|
||||||
`
|
<div class="card-content">
|
||||||
: "";
|
${this.hass.localize(
|
||||||
})
|
"ui.panel.config.devices.add_prompt",
|
||||||
: html`
|
"name",
|
||||||
<paper-item class="no-link">
|
this.hass.localize(
|
||||||
${this.hass.localize(
|
"ui.panel.config.devices.scene.scenes"
|
||||||
"ui.panel.config.devices.add_prompt",
|
)
|
||||||
"name",
|
)}
|
||||||
this.hass.localize(
|
</div>
|
||||||
"ui.panel.config.devices.scene.scenes"
|
`}
|
||||||
)
|
|
||||||
)}
|
|
||||||
</paper-item>
|
|
||||||
`
|
|
||||||
}
|
|
||||||
</ha-card>
|
|
||||||
</ha-card>
|
</ha-card>
|
||||||
`
|
`
|
||||||
: ""
|
: ""
|
||||||
@@ -558,7 +565,7 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
: "";
|
: "";
|
||||||
})
|
})
|
||||||
: html`
|
: html`
|
||||||
<paper-item class="no-link">
|
<div class="card-content">
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.devices.add_prompt",
|
"ui.panel.config.devices.add_prompt",
|
||||||
"name",
|
"name",
|
||||||
@@ -566,14 +573,14 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
"ui.panel.config.devices.script.scripts"
|
"ui.panel.config.devices.script.scripts"
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</paper-item>
|
</div>
|
||||||
`}
|
`}
|
||||||
</ha-card>
|
</ha-card>
|
||||||
`
|
`
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ha-config-section>
|
</ha-config-section>
|
||||||
</hass-tabs-subpage> `;
|
</hass-tabs-subpage> `;
|
||||||
}
|
}
|
||||||
@@ -953,19 +960,11 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
font-size: var(--paper-font-body1_-_font-size);
|
font-size: var(--paper-font-body1_-_font-size);
|
||||||
}
|
}
|
||||||
|
|
||||||
paper-item.no-link {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-card {
|
|
||||||
padding-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-card a {
|
ha-card a {
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
}
|
}
|
||||||
|
@@ -73,7 +73,6 @@ export class DialogEnergyBatterySettings
|
|||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.config.energy.battery.dialog.energy_into_battery"
|
"ui.panel.config.energy.battery.dialog.energy_into_battery"
|
||||||
)}
|
)}
|
||||||
entities-only
|
|
||||||
@value-changed=${this._statisticToChanged}
|
@value-changed=${this._statisticToChanged}
|
||||||
></ha-statistic-picker>
|
></ha-statistic-picker>
|
||||||
|
|
||||||
@@ -85,7 +84,6 @@ export class DialogEnergyBatterySettings
|
|||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.config.energy.battery.dialog.energy_out_of_battery"
|
"ui.panel.config.energy.battery.dialog.energy_out_of_battery"
|
||||||
)}
|
)}
|
||||||
entities-only
|
|
||||||
@value-changed=${this._statisticFromChanged}
|
@value-changed=${this._statisticFromChanged}
|
||||||
></ha-statistic-picker>
|
></ha-statistic-picker>
|
||||||
|
|
||||||
|
@@ -74,7 +74,6 @@ export class DialogEnergyDeviceSettings
|
|||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.config.energy.device_consumption.dialog.device_consumption_energy"
|
"ui.panel.config.energy.device_consumption.dialog.device_consumption_energy"
|
||||||
)}
|
)}
|
||||||
entities-only
|
|
||||||
@value-changed=${this._statisticChanged}
|
@value-changed=${this._statisticChanged}
|
||||||
></ha-statistic-picker>
|
></ha-statistic-picker>
|
||||||
|
|
||||||
|
@@ -106,7 +106,6 @@ export class DialogEnergyGasSettings
|
|||||||
? "kWh"
|
? "kWh"
|
||||||
: "m³"
|
: "m³"
|
||||||
})`}
|
})`}
|
||||||
entities-only
|
|
||||||
@value-changed=${this._statisticChanged}
|
@value-changed=${this._statisticChanged}
|
||||||
></ha-statistic-picker>
|
></ha-statistic-picker>
|
||||||
|
|
||||||
|
@@ -103,7 +103,6 @@ export class DialogEnergyGridFlowSettings
|
|||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
`ui.panel.config.energy.grid.flow_dialog.${this._params.direction}.energy_stat`
|
`ui.panel.config.energy.grid.flow_dialog.${this._params.direction}.energy_stat`
|
||||||
)}
|
)}
|
||||||
entities-only
|
|
||||||
@value-changed=${this._statisticChanged}
|
@value-changed=${this._statisticChanged}
|
||||||
></ha-statistic-picker>
|
></ha-statistic-picker>
|
||||||
|
|
||||||
|
@@ -85,7 +85,6 @@ export class DialogEnergySolarSettings
|
|||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.config.energy.solar.dialog.solar_production_energy"
|
"ui.panel.config.energy.solar.dialog.solar_production_energy"
|
||||||
)}
|
)}
|
||||||
entities-only
|
|
||||||
@value-changed=${this._statisticChanged}
|
@value-changed=${this._statisticChanged}
|
||||||
></ha-statistic-picker>
|
></ha-statistic-picker>
|
||||||
|
|
||||||
|
@@ -202,12 +202,12 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
(securityClass) => html`<ha-formfield
|
(securityClass) => html`<ha-formfield
|
||||||
.label=${html`<b
|
.label=${html`<b
|
||||||
>${this.hass.localize(
|
>${this.hass.localize(
|
||||||
`ui.panel.config.zwave_js.add_node.security_classes.${SecurityClass[securityClass]}.title`
|
`ui.panel.config.zwave_js.security_classes.${SecurityClass[securityClass]}.title`
|
||||||
)}</b
|
)}</b
|
||||||
>
|
>
|
||||||
<div class="secondary">
|
<div class="secondary">
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
`ui.panel.config.zwave_js.add_node.security_classes.${SecurityClass[securityClass]}.description`
|
`ui.panel.config.zwave_js.security_classes.${SecurityClass[securityClass]}.description`
|
||||||
)}
|
)}
|
||||||
</div>`}
|
</div>`}
|
||||||
>
|
>
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
import { mdiClose, mdiContentCopy, mdiPackageVariant } from "@mdi/js";
|
import { mdiClose, mdiContentCopy } from "@mdi/js";
|
||||||
import "@polymer/paper-tooltip/paper-tooltip";
|
import "@polymer/paper-tooltip/paper-tooltip";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { property, state } from "lit/decorators";
|
import { property, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import { copyToClipboard } from "../../../common/util/copy-clipboard";
|
import { copyToClipboard } from "../../../common/util/copy-clipboard";
|
||||||
|
import "../../../components/ha-alert";
|
||||||
import "../../../components/ha-dialog";
|
import "../../../components/ha-dialog";
|
||||||
import "../../../components/ha-header-bar";
|
import "../../../components/ha-header-bar";
|
||||||
import "../../../components/ha-icon-button";
|
import "../../../components/ha-icon-button";
|
||||||
@@ -96,12 +97,11 @@ class DialogSystemLogDetail extends LitElement {
|
|||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
</ha-header-bar>
|
</ha-header-bar>
|
||||||
${this.isCustomIntegration
|
${this.isCustomIntegration
|
||||||
? html`<div class="custom">
|
? html`<ha-alert alert-type="warning">
|
||||||
<ha-svg-icon .path=${mdiPackageVariant}></ha-svg-icon>
|
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.logs.error_from_custom_integration"
|
"ui.panel.config.logs.error_from_custom_integration"
|
||||||
)}
|
)}
|
||||||
</div>`
|
</ha-alert>`
|
||||||
: ""}
|
: ""}
|
||||||
<div class="contents">
|
<div class="contents">
|
||||||
<p>
|
<p>
|
||||||
@@ -215,9 +215,9 @@ class DialogSystemLogDetail extends LitElement {
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
font-family: var(--code-font-family, monospace);
|
font-family: var(--code-font-family, monospace);
|
||||||
}
|
}
|
||||||
.custom {
|
ha-alert {
|
||||||
padding: 8px 16px;
|
display: block;
|
||||||
background-color: var(--warning-color);
|
margin: -4px 0;
|
||||||
}
|
}
|
||||||
.contents {
|
.contents {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
205
src/panels/config/script/blueprint-script-editor.ts
Normal file
205
src/panels/config/script/blueprint-script-editor.ts
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@@ -38,6 +38,7 @@ import {
|
|||||||
Action,
|
Action,
|
||||||
deleteScript,
|
deleteScript,
|
||||||
getScriptEditorInitData,
|
getScriptEditorInitData,
|
||||||
|
ManualScriptConfig,
|
||||||
MODES,
|
MODES,
|
||||||
MODES_MAX,
|
MODES_MAX,
|
||||||
ScriptConfig,
|
ScriptConfig,
|
||||||
@@ -55,6 +56,7 @@ import "../automation/action/ha-automation-action";
|
|||||||
import { HaDeviceAction } from "../automation/action/types/ha-automation-action-device_id";
|
import { HaDeviceAction } from "../automation/action/types/ha-automation-action-device_id";
|
||||||
import "../ha-config-section";
|
import "../ha-config-section";
|
||||||
import { configSections } from "../ha-panel-config";
|
import { configSections } from "../ha-panel-config";
|
||||||
|
import "./blueprint-script-editor";
|
||||||
|
|
||||||
export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@@ -236,60 +238,62 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
>
|
>
|
||||||
</paper-input>`
|
</paper-input>`
|
||||||
: ""}
|
: ""}
|
||||||
<p>
|
${"use_blueprint" in this._config
|
||||||
${this.hass.localize(
|
? ""
|
||||||
"ui.panel.config.script.editor.modes.description",
|
: html`<p>
|
||||||
"documentation_link",
|
${this.hass.localize(
|
||||||
html`<a
|
"ui.panel.config.script.editor.modes.description",
|
||||||
href=${documentationUrl(
|
"documentation_link",
|
||||||
this.hass,
|
html`<a
|
||||||
"/integrations/script/#script-modes"
|
href=${documentationUrl(
|
||||||
)}
|
this.hass,
|
||||||
target="_blank"
|
"/integrations/script/#script-modes"
|
||||||
rel="noreferrer"
|
)}
|
||||||
>${this.hass.localize(
|
target="_blank"
|
||||||
"ui.panel.config.script.editor.modes.documentation"
|
rel="noreferrer"
|
||||||
)}</a
|
>${this.hass.localize(
|
||||||
>`
|
"ui.panel.config.script.editor.modes.documentation"
|
||||||
)}
|
)}</a
|
||||||
</p>
|
>`
|
||||||
<paper-dropdown-menu-light
|
)}
|
||||||
.label=${this.hass.localize(
|
</p>
|
||||||
"ui.panel.config.script.editor.modes.label"
|
<paper-dropdown-menu-light
|
||||||
)}
|
.label=${this.hass.localize(
|
||||||
no-animations
|
"ui.panel.config.script.editor.modes.label"
|
||||||
>
|
)}
|
||||||
<paper-listbox
|
no-animations
|
||||||
slot="dropdown-content"
|
>
|
||||||
.selected=${this._config.mode
|
<paper-listbox
|
||||||
? MODES.indexOf(this._config.mode)
|
slot="dropdown-content"
|
||||||
: 0}
|
.selected=${this._config.mode
|
||||||
@iron-select=${this._modeChanged}
|
? MODES.indexOf(this._config.mode)
|
||||||
>
|
: 0}
|
||||||
${MODES.map(
|
@iron-select=${this._modeChanged}
|
||||||
(mode) => html`
|
>
|
||||||
<paper-item .mode=${mode}>
|
${MODES.map(
|
||||||
${this.hass.localize(
|
(mode) => html`
|
||||||
`ui.panel.config.script.editor.modes.${mode}`
|
<paper-item .mode=${mode}>
|
||||||
) || mode}
|
${this.hass.localize(
|
||||||
</paper-item>
|
`ui.panel.config.script.editor.modes.${mode}`
|
||||||
`
|
) || mode}
|
||||||
)}
|
</paper-item>
|
||||||
</paper-listbox>
|
`
|
||||||
</paper-dropdown-menu-light>
|
)}
|
||||||
${this._config.mode &&
|
</paper-listbox>
|
||||||
MODES_MAX.includes(this._config.mode)
|
</paper-dropdown-menu-light>
|
||||||
? html`<paper-input
|
${this._config.mode &&
|
||||||
.label=${this.hass.localize(
|
MODES_MAX.includes(this._config.mode)
|
||||||
`ui.panel.config.script.editor.max.${this._config.mode}`
|
? html`<paper-input
|
||||||
)}
|
.label=${this.hass.localize(
|
||||||
type="number"
|
`ui.panel.config.script.editor.max.${this._config.mode}`
|
||||||
name="max"
|
)}
|
||||||
.value=${this._config.max || "10"}
|
type="number"
|
||||||
@value-changed=${this._valueChanged}
|
name="max"
|
||||||
>
|
.value=${this._config.max || "10"}
|
||||||
</paper-input>`
|
@value-changed=${this._valueChanged}
|
||||||
: html``}
|
>
|
||||||
|
</paper-input>`
|
||||||
|
: html``} `}
|
||||||
</div>
|
</div>
|
||||||
${this.scriptEntityId
|
${this.scriptEntityId
|
||||||
? html`
|
? html`
|
||||||
@@ -323,37 +327,48 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
</ha-card>
|
</ha-card>
|
||||||
</ha-config-section>
|
</ha-config-section>
|
||||||
|
|
||||||
<ha-config-section vertical .isWide=${this.isWide}>
|
${"use_blueprint" in this._config
|
||||||
<span slot="header">
|
? html`<blueprint-script-editor
|
||||||
${this.hass.localize(
|
.hass=${this.hass}
|
||||||
"ui.panel.config.script.editor.sequence"
|
.narrow=${this.narrow}
|
||||||
)}
|
.isWide=${this.isWide}
|
||||||
</span>
|
.config=${this._config}
|
||||||
<span slot="introduction">
|
@value-changed=${this._configChanged}
|
||||||
<p>
|
></blueprint-script-editor>`
|
||||||
${this.hass.localize(
|
: html`<ha-config-section
|
||||||
"ui.panel.config.script.editor.sequence_sentence"
|
vertical
|
||||||
)}
|
.isWide=${this.isWide}
|
||||||
</p>
|
|
||||||
<a
|
|
||||||
href=${documentationUrl(
|
|
||||||
this.hass,
|
|
||||||
"/docs/scripts/"
|
|
||||||
)}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
>
|
||||||
${this.hass.localize(
|
<span slot="header">
|
||||||
"ui.panel.config.script.editor.link_available_actions"
|
${this.hass.localize(
|
||||||
)}
|
"ui.panel.config.script.editor.sequence"
|
||||||
</a>
|
)}
|
||||||
</span>
|
</span>
|
||||||
<ha-automation-action
|
<span slot="introduction">
|
||||||
.actions=${this._config.sequence}
|
<p>
|
||||||
@value-changed=${this._sequenceChanged}
|
${this.hass.localize(
|
||||||
.hass=${this.hass}
|
"ui.panel.config.script.editor.sequence_sentence"
|
||||||
></ha-automation-action>
|
)}
|
||||||
</ha-config-section>
|
</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>`}
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
@@ -427,7 +442,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
(!oldScript || oldScript !== this.scriptEntityId)
|
(!oldScript || oldScript !== this.scriptEntityId)
|
||||||
) {
|
) {
|
||||||
this.hass
|
this.hass
|
||||||
.callApi<ScriptConfig>(
|
.callApi<ManualScriptConfig>(
|
||||||
"GET",
|
"GET",
|
||||||
`config/script/config/${computeObjectId(this.scriptEntityId)}`
|
`config/script/config/${computeObjectId(this.scriptEntityId)}`
|
||||||
)
|
)
|
||||||
@@ -466,11 +481,16 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
) {
|
) {
|
||||||
const initData = getScriptEditorInitData();
|
const initData = getScriptEditorInitData();
|
||||||
this._dirty = !!initData;
|
this._dirty = !!initData;
|
||||||
this._config = {
|
const baseConfig: Partial<ScriptConfig> = {
|
||||||
alias: this.hass.localize("ui.panel.config.script.editor.default_name"),
|
alias: this.hass.localize("ui.panel.config.script.editor.default_name"),
|
||||||
sequence: [{ ...HaDeviceAction.defaultConfig }],
|
|
||||||
...initData,
|
|
||||||
};
|
};
|
||||||
|
if (!initData || !("use_blueprint" in initData)) {
|
||||||
|
baseConfig.sequence = [{ ...HaDeviceAction.defaultConfig }];
|
||||||
|
}
|
||||||
|
this._config = {
|
||||||
|
...baseConfig,
|
||||||
|
...initData,
|
||||||
|
} as ScriptConfig;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -548,6 +568,11 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
this._dirty = true;
|
this._dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _configChanged(ev) {
|
||||||
|
this._config = ev.detail.value;
|
||||||
|
this._dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
private _sequenceChanged(ev: CustomEvent): void {
|
private _sequenceChanged(ev: CustomEvent): void {
|
||||||
this._config = { ...this._config!, sequence: ev.detail.value as Action[] };
|
this._config = { ...this._config!, sequence: ev.detail.value as Action[] };
|
||||||
this._errors = undefined;
|
this._errors = undefined;
|
||||||
@@ -607,7 +632,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Wait for dialog to complate closing
|
// Wait for dialog to complete closing
|
||||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
}
|
}
|
||||||
showScriptEditor({
|
showScriptEditor({
|
||||||
@@ -749,3 +774,9 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("ha-script-editor", HaScriptEditor);
|
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(
|
private _columns = memoizeOne(
|
||||||
(localize): DataTableColumnContainer => ({
|
(localize): DataTableColumnContainer => ({
|
||||||
state: {
|
state: {
|
||||||
title: "Entity",
|
title: "Name",
|
||||||
sortable: true,
|
sortable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
grows: true,
|
grows: true,
|
||||||
template: (entityState, data: any) =>
|
template: (entityState, data: any) =>
|
||||||
html`${entityState
|
html`${entityState
|
||||||
? computeStateName(entityState)
|
? computeStateName(entityState)
|
||||||
: data.statistic_id}`,
|
: data.name || data.statistic_id}`,
|
||||||
},
|
},
|
||||||
statistic_id: {
|
statistic_id: {
|
||||||
title: "Statistic id",
|
title: "Statistic id",
|
||||||
sortable: true,
|
sortable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
hidden: this.narrow,
|
hidden: this.narrow,
|
||||||
width: "30%",
|
width: "20%",
|
||||||
},
|
},
|
||||||
unit_of_measurement: {
|
unit_of_measurement: {
|
||||||
title: "Unit",
|
title: "Unit",
|
||||||
@@ -72,6 +72,12 @@ class HaPanelDevStatistics extends LitElement {
|
|||||||
filterable: true,
|
filterable: true,
|
||||||
width: "10%",
|
width: "10%",
|
||||||
},
|
},
|
||||||
|
source: {
|
||||||
|
title: "Source",
|
||||||
|
sortable: true,
|
||||||
|
filterable: true,
|
||||||
|
width: "10%",
|
||||||
|
},
|
||||||
issues: {
|
issues: {
|
||||||
title: "Issue",
|
title: "Issue",
|
||||||
sortable: true,
|
sortable: true,
|
||||||
@@ -146,6 +152,7 @@ class HaPanelDevStatistics extends LitElement {
|
|||||||
this._data.push({
|
this._data.push({
|
||||||
statistic_id: statisticId,
|
statistic_id: statisticId,
|
||||||
unit_of_measurement: "",
|
unit_of_measurement: "",
|
||||||
|
source: "",
|
||||||
state: this.hass.states[statisticId],
|
state: this.hass.states[statisticId],
|
||||||
issues: issues[statisticId],
|
issues: issues[statisticId],
|
||||||
});
|
});
|
||||||
|
@@ -12,6 +12,7 @@ import {
|
|||||||
} from "date-fns";
|
} from "date-fns";
|
||||||
import { css, html, LitElement, PropertyValues } from "lit";
|
import { css, html, LitElement, PropertyValues } from "lit";
|
||||||
import { property, state } from "lit/decorators";
|
import { property, state } from "lit/decorators";
|
||||||
|
import { extractSearchParam } from "../../common/url/search-params";
|
||||||
import { computeRTL } from "../../common/util/compute_rtl";
|
import { computeRTL } from "../../common/util/compute_rtl";
|
||||||
import "../../components/chart/state-history-charts";
|
import "../../components/chart/state-history-charts";
|
||||||
import "../../components/entity/ha-entity-picker";
|
import "../../components/entity/ha-entity-picker";
|
||||||
@@ -136,6 +137,8 @@ class HaPanelHistory extends LitElement {
|
|||||||
[this.hass.localize("ui.components.date-range-picker.ranges.last_week")]:
|
[this.hass.localize("ui.components.date-range-picker.ranges.last_week")]:
|
||||||
[addDays(weekStart, -7), addDays(weekEnd, -7)],
|
[addDays(weekStart, -7), addDays(weekEnd, -7)],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this._entityId = extractSearchParam("entity_id") ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updated(changedProps: PropertyValues) {
|
protected updated(changedProps: PropertyValues) {
|
||||||
|
@@ -15,7 +15,6 @@ import { formatTimeWithSeconds } from "../../common/datetime/format_time";
|
|||||||
import { restoreScroll } from "../../common/decorators/restore-scroll";
|
import { restoreScroll } from "../../common/decorators/restore-scroll";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { computeDomain } from "../../common/entity/compute_domain";
|
import { computeDomain } from "../../common/entity/compute_domain";
|
||||||
import { domainIcon } from "../../common/entity/domain_icon";
|
|
||||||
import { computeRTL, emitRTLDirection } from "../../common/util/compute_rtl";
|
import { computeRTL, emitRTLDirection } from "../../common/util/compute_rtl";
|
||||||
import "../../components/entity/state-badge";
|
import "../../components/entity/state-badge";
|
||||||
import "../../components/ha-circular-progress";
|
import "../../components/ha-circular-progress";
|
||||||
@@ -151,12 +150,13 @@ class HaLogbook extends LitElement {
|
|||||||
html`
|
html`
|
||||||
<state-badge
|
<state-badge
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.overrideIcon=${item.icon ??
|
.overrideIcon=${item.icon}
|
||||||
domainIcon(domain, stateObj, item.state)}
|
|
||||||
.overrideImage=${DOMAINS_WITH_DYNAMIC_PICTURE.has(domain)
|
.overrideImage=${DOMAINS_WITH_DYNAMIC_PICTURE.has(domain)
|
||||||
? ""
|
? ""
|
||||||
: stateObj?.attributes.entity_picture_local ||
|
: stateObj?.attributes.entity_picture_local ||
|
||||||
stateObj?.attributes.entity_picture}
|
stateObj?.attributes.entity_picture}
|
||||||
|
.stateObj=${stateObj}
|
||||||
|
.stateColor=${false}
|
||||||
></state-badge>
|
></state-badge>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
@@ -222,6 +222,7 @@ class HaLogbook extends LitElement {
|
|||||||
}?run_id=${
|
}?run_id=${
|
||||||
this.traceContexts[item.context_id!].run_id
|
this.traceContexts[item.context_id!].run_id
|
||||||
}`}
|
}`}
|
||||||
|
@click=${this._close}
|
||||||
>${this.hass.localize(
|
>${this.hass.localize(
|
||||||
"ui.components.logbook.show_trace"
|
"ui.components.logbook.show_trace"
|
||||||
)}</a
|
)}</a
|
||||||
@@ -254,6 +255,10 @@ class HaLogbook extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _close(): void {
|
||||||
|
setTimeout(() => fireEvent(this, "closed"), 500);
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
|
@@ -33,6 +33,7 @@ import "../../layouts/ha-app-layout";
|
|||||||
import { haStyle } from "../../resources/styles";
|
import { haStyle } from "../../resources/styles";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import "./ha-logbook";
|
import "./ha-logbook";
|
||||||
|
import { extractSearchParam } from "../../common/url/search-params";
|
||||||
|
|
||||||
@customElement("ha-panel-logbook")
|
@customElement("ha-panel-logbook")
|
||||||
export class HaPanelLogbook extends LitElement {
|
export class HaPanelLogbook extends LitElement {
|
||||||
@@ -158,6 +159,8 @@ export class HaPanelLogbook extends LitElement {
|
|||||||
[this.hass.localize("ui.components.date-range-picker.ranges.last_week")]:
|
[this.hass.localize("ui.components.date-range-picker.ranges.last_week")]:
|
||||||
[addDays(weekStart, -7), addDays(weekEnd, -7)],
|
[addDays(weekStart, -7), addDays(weekEnd, -7)],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this._entityId = extractSearchParam("entity_id") ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updated(changedProps: PropertyValues<this>) {
|
protected updated(changedProps: PropertyValues<this>) {
|
||||||
|
@@ -8,17 +8,18 @@ import {
|
|||||||
PropertyValues,
|
PropertyValues,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, state, query } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import { alarmPanelIcon } from "../../../common/entity/alarm_panel_icon";
|
import { alarmPanelIcon } from "../../../common/entity/alarm_panel_icon";
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import "../../../components/ha-label-badge";
|
import "../../../components/ha-chip";
|
||||||
import {
|
import {
|
||||||
callAlarmAction,
|
callAlarmAction,
|
||||||
FORMAT_NUMBER,
|
FORMAT_NUMBER,
|
||||||
} from "../../../data/alarm_control_panel";
|
} from "../../../data/alarm_control_panel";
|
||||||
|
import { UNAVAILABLE } from "../../../data/entity";
|
||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
import { findEntities } from "../common/find-entities";
|
import { findEntities } from "../common/find-entities";
|
||||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||||
@@ -144,19 +145,24 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const stateLabel = this._stateDisplay(stateObj.state);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-card
|
<ha-card>
|
||||||
.header=${this._config.name ||
|
<h1 class="card-header">
|
||||||
stateObj.attributes.friendly_name ||
|
${this._config.name ||
|
||||||
this._stateDisplay(stateObj.state)}
|
stateObj.attributes.friendly_name ||
|
||||||
>
|
stateLabel}
|
||||||
<ha-label-badge
|
<ha-chip
|
||||||
class=${classMap({ [stateObj.state]: true })}
|
hasIcon
|
||||||
.label=${this._stateIconLabel(stateObj.state)}
|
class=${classMap({ [stateObj.state]: true })}
|
||||||
@click=${this._handleMoreInfo}
|
@click=${this._handleMoreInfo}
|
||||||
>
|
>
|
||||||
<ha-svg-icon .path=${alarmPanelIcon(stateObj.state)}></ha-svg-icon>
|
<ha-svg-icon slot="icon" .path=${alarmPanelIcon(stateObj.state)}>
|
||||||
</ha-label-badge>
|
</ha-svg-icon>
|
||||||
|
${stateLabel}
|
||||||
|
</ha-chip>
|
||||||
|
</h1>
|
||||||
<div id="armActions" class="actions">
|
<div id="armActions" class="actions">
|
||||||
${(stateObj.state === "disarmed"
|
${(stateObj.state === "disarmed"
|
||||||
? this._config.states!
|
? this._config.states!
|
||||||
@@ -215,23 +221,16 @@ 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 {
|
private _actionDisplay(entityState: string): string {
|
||||||
return this.hass!.localize(`ui.card.alarm_control_panel.${entityState}`);
|
return this.hass!.localize(`ui.card.alarm_control_panel.${entityState}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _stateDisplay(entityState: string): string {
|
private _stateDisplay(entityState: string): string {
|
||||||
return this.hass!.localize(
|
return entityState === UNAVAILABLE
|
||||||
`component.alarm_control_panel.state._.${entityState}`
|
? this.hass!.localize("state.default.unavailable")
|
||||||
);
|
: this.hass!.localize(
|
||||||
|
`component.alarm_control_panel.state._.${entityState}`
|
||||||
|
) || entityState;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handlePadClick(e: MouseEvent): void {
|
private _handlePadClick(e: MouseEvent): void {
|
||||||
@@ -273,15 +272,20 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
|
|||||||
--alarm-state-color: var(--alarm-color-armed);
|
--alarm-state-color: var(--alarm-color-armed);
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-label-badge {
|
ha-chip {
|
||||||
--ha-label-badge-color: var(--alarm-state-color);
|
--ha-chip-background-color: var(--alarm-state-color);
|
||||||
--label-badge-text-color: var(--alarm-state-color);
|
--ha-chip-text-color: var(--text-primary-color);
|
||||||
--label-badge-background-color: var(--card-background-color);
|
line-height: initial;
|
||||||
color: var(--alarm-state-color);
|
}
|
||||||
position: absolute;
|
|
||||||
right: 12px;
|
.card-header {
|
||||||
top: 8px;
|
display: flex;
|
||||||
cursor: pointer;
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unavailable {
|
||||||
|
--alarm-state-color: var(--state-unavailable-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.disarmed {
|
.disarmed {
|
||||||
|
@@ -171,6 +171,7 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
ha-markdown {
|
ha-markdown {
|
||||||
padding: 0 16px 16px;
|
padding: 0 16px 16px;
|
||||||
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
ha-markdown.no-header {
|
ha-markdown.no-header {
|
||||||
padding-top: 16px;
|
padding-top: 16px;
|
||||||
|
@@ -7,14 +7,12 @@ import {
|
|||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
|
||||||
import { ifDefined } from "lit/directives/if-defined";
|
import { ifDefined } from "lit/directives/if-defined";
|
||||||
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
||||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||||
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
|
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
|
||||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import { UNAVAILABLE_STATES } from "../../../data/entity";
|
|
||||||
import { ActionHandlerEvent } from "../../../data/lovelace";
|
import { ActionHandlerEvent } from "../../../data/lovelace";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||||
@@ -135,9 +133,9 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
} else if (this._config.show_name) {
|
} else if (this._config.show_name) {
|
||||||
footer = html`<div class="footer">${name}</div>`;
|
footer = html`<div class="footer single">${name}</div>`;
|
||||||
} else if (this._config.show_state) {
|
} else if (this._config.show_state) {
|
||||||
footer = html`<div class="footer state">${entityState}</div>`;
|
footer = html`<div class="footer single">${entityState}</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
@@ -163,9 +161,6 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard {
|
|||||||
? "0"
|
? "0"
|
||||||
: undefined
|
: undefined
|
||||||
)}
|
)}
|
||||||
class=${classMap({
|
|
||||||
clickable: !UNAVAILABLE_STATES.includes(stateObj.state),
|
|
||||||
})}
|
|
||||||
></hui-image>
|
></hui-image>
|
||||||
${footer}
|
${footer}
|
||||||
</ha-card>
|
</ha-card>
|
||||||
@@ -182,7 +177,7 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
hui-image.clickable {
|
hui-image {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,8 +207,8 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.state {
|
.single {
|
||||||
text-align: right;
|
text-align: center;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -78,7 +78,7 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const configEntities = config.entities
|
const configEntities = config.entities
|
||||||
? processConfigEntities(config.entities)
|
? processConfigEntities(config.entities, false)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
this._entities = [];
|
this._entities = [];
|
||||||
|
@@ -87,7 +87,14 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getCardSize(): number {
|
public getCardSize(): number {
|
||||||
return this._config?.show_forecast !== false ? 5 : 2;
|
let cardSize = 0;
|
||||||
|
if (this._config?.show_current !== false) {
|
||||||
|
cardSize += 2;
|
||||||
|
}
|
||||||
|
if (this._config?.show_forecast !== false) {
|
||||||
|
cardSize += 3;
|
||||||
|
}
|
||||||
|
return cardSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public setConfig(config: WeatherForecastCardConfig): void {
|
public setConfig(config: WeatherForecastCardConfig): void {
|
||||||
@@ -168,6 +175,7 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
|||||||
stateObj.attributes.forecast?.length
|
stateObj.attributes.forecast?.length
|
||||||
? stateObj.attributes.forecast.slice(0, this._veryVeryNarrow ? 3 : 5)
|
? stateObj.attributes.forecast.slice(0, this._veryVeryNarrow ? 3 : 5)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
const weather = !forecast || this._config?.show_current !== false;
|
||||||
|
|
||||||
let hourly: boolean | undefined;
|
let hourly: boolean | undefined;
|
||||||
let dayNight: boolean | undefined;
|
let dayNight: boolean | undefined;
|
||||||
@@ -202,74 +210,81 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
|||||||
hasAction(this._config.tap_action) ? "0" : undefined
|
hasAction(this._config.tap_action) ? "0" : undefined
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div class="content">
|
${weather
|
||||||
<div class="icon-image">
|
? html`
|
||||||
${weatherStateIcon ||
|
<div class="content">
|
||||||
html`
|
<div class="icon-image">
|
||||||
<ha-state-icon
|
${weatherStateIcon ||
|
||||||
class="weather-icon"
|
html`
|
||||||
.state=${stateObj}
|
<ha-state-icon
|
||||||
></ha-state-icon>
|
class="weather-icon"
|
||||||
`}
|
.state=${stateObj}
|
||||||
</div>
|
></ha-state-icon>
|
||||||
<div class="info">
|
`}
|
||||||
<div class="name-state">
|
</div>
|
||||||
<div class="state">
|
<div class="info">
|
||||||
${computeStateDisplay(
|
<div class="name-state">
|
||||||
this.hass.localize,
|
<div class="state">
|
||||||
stateObj,
|
${computeStateDisplay(
|
||||||
this.hass.locale
|
this.hass.localize,
|
||||||
)}
|
stateObj,
|
||||||
</div>
|
this.hass.locale
|
||||||
<div class="name" .title=${name}>${name}</div>
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div class="temp-attribute">
|
<div class="name" .title=${name}>${name}</div>
|
||||||
<div class="temp">
|
</div>
|
||||||
${formatNumber(
|
<div class="temp-attribute">
|
||||||
stateObj.attributes.temperature,
|
<div class="temp">
|
||||||
this.hass.locale
|
${formatNumber(
|
||||||
)} <span>${getWeatherUnit(this.hass, "temperature")}</span>
|
stateObj.attributes.temperature,
|
||||||
</div>
|
this.hass.locale
|
||||||
<div class="attribute">
|
)} <span
|
||||||
${this._config.secondary_info_attribute !== undefined
|
>${getWeatherUnit(this.hass, "temperature")}</span
|
||||||
? html`
|
>
|
||||||
${this._config.secondary_info_attribute in
|
</div>
|
||||||
weatherAttrIcons
|
<div class="attribute">
|
||||||
|
${this._config.secondary_info_attribute !== undefined
|
||||||
? html`
|
? html`
|
||||||
<ha-svg-icon
|
${this._config.secondary_info_attribute in
|
||||||
class="attr-icon"
|
weatherAttrIcons
|
||||||
.path=${weatherAttrIcons[
|
? html`
|
||||||
this._config.secondary_info_attribute
|
<ha-svg-icon
|
||||||
]}
|
class="attr-icon"
|
||||||
></ha-svg-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
|
||||||
|
)}
|
||||||
|
`}
|
||||||
`
|
`
|
||||||
: this.hass!.localize(
|
: getSecondaryWeatherAttribute(this.hass, stateObj)}
|
||||||
`ui.card.weather.attributes.${this._config.secondary_info_attribute}`
|
</div>
|
||||||
)}
|
</div>
|
||||||
${this._config.secondary_info_attribute === "wind_speed"
|
</div>
|
||||||
? 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>
|
: ""}
|
||||||
</div>
|
|
||||||
${forecast
|
${forecast
|
||||||
? html`
|
? html`
|
||||||
<div class="forecast">
|
<div class="forecast">
|
||||||
|
@@ -387,6 +387,7 @@ export interface ThermostatCardConfig extends LovelaceCardConfig {
|
|||||||
export interface WeatherForecastCardConfig extends LovelaceCardConfig {
|
export interface WeatherForecastCardConfig extends LovelaceCardConfig {
|
||||||
entity: string;
|
entity: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
show_current?: boolean;
|
||||||
show_forecast?: boolean;
|
show_forecast?: boolean;
|
||||||
secondary_info_attribute?: string;
|
secondary_info_attribute?: string;
|
||||||
theme?: string;
|
theme?: string;
|
||||||
|
@@ -3,6 +3,7 @@ import { computeDomain } from "../../../common/entity/compute_domain";
|
|||||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||||
import { splitByGroups } from "../../../common/entity/split_by_groups";
|
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 { stringCompare } from "../../../common/string/compare";
|
||||||
import { LocalizeFunc } from "../../../common/translations/localize";
|
import { LocalizeFunc } from "../../../common/translations/localize";
|
||||||
import type { AreaRegistryEntry } from "../../../data/area_registry";
|
import type { AreaRegistryEntry } from "../../../data/area_registry";
|
||||||
@@ -15,11 +16,11 @@ import type { EntityRegistryEntry } from "../../../data/entity_registry";
|
|||||||
import { domainToName } from "../../../data/integration";
|
import { domainToName } from "../../../data/integration";
|
||||||
import { LovelaceCardConfig, LovelaceViewConfig } from "../../../data/lovelace";
|
import { LovelaceCardConfig, LovelaceViewConfig } from "../../../data/lovelace";
|
||||||
import { SENSOR_DEVICE_CLASS_BATTERY } from "../../../data/sensor";
|
import { SENSOR_DEVICE_CLASS_BATTERY } from "../../../data/sensor";
|
||||||
|
import { computeUserInitials } from "../../../data/user";
|
||||||
import {
|
import {
|
||||||
AlarmPanelCardConfig,
|
AlarmPanelCardConfig,
|
||||||
EntitiesCardConfig,
|
EntitiesCardConfig,
|
||||||
HumidifierCardConfig,
|
HumidifierCardConfig,
|
||||||
LightCardConfig,
|
|
||||||
PictureEntityCardConfig,
|
PictureEntityCardConfig,
|
||||||
ThermostatCardConfig,
|
ThermostatCardConfig,
|
||||||
} from "../cards/types";
|
} from "../cards/types";
|
||||||
@@ -31,6 +32,8 @@ const HIDE_DOMAIN = new Set([
|
|||||||
"device_tracker",
|
"device_tracker",
|
||||||
"geo_location",
|
"geo_location",
|
||||||
"persistent_notification",
|
"persistent_notification",
|
||||||
|
"script",
|
||||||
|
"sun",
|
||||||
"zone",
|
"zone",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -83,8 +86,7 @@ const splitByAreas = (
|
|||||||
|
|
||||||
export const computeCards = (
|
export const computeCards = (
|
||||||
states: Array<[string, HassEntity?]>,
|
states: Array<[string, HassEntity?]>,
|
||||||
entityCardOptions: Partial<EntitiesCardConfig>,
|
entityCardOptions: Partial<EntitiesCardConfig>
|
||||||
single = false
|
|
||||||
): LovelaceCardConfig[] => {
|
): LovelaceCardConfig[] => {
|
||||||
const cards: LovelaceCardConfig[] = [];
|
const cards: LovelaceCardConfig[] = [];
|
||||||
|
|
||||||
@@ -92,7 +94,7 @@ export const computeCards = (
|
|||||||
const entities: Array<string | LovelaceRowConfig> = [];
|
const entities: Array<string | LovelaceRowConfig> = [];
|
||||||
|
|
||||||
const titlePrefix = entityCardOptions.title
|
const titlePrefix = entityCardOptions.title
|
||||||
? `${entityCardOptions.title} `
|
? `${entityCardOptions.title} `.toLowerCase()
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
for (const [entityId, stateObj] of states) {
|
for (const [entityId, stateObj] of states) {
|
||||||
@@ -122,12 +124,6 @@ export const computeCards = (
|
|||||||
entity: entityId,
|
entity: entityId,
|
||||||
};
|
};
|
||||||
cards.push(cardConfig);
|
cards.push(cardConfig);
|
||||||
} else if (domain === "light" && single) {
|
|
||||||
const cardConfig: LightCardConfig = {
|
|
||||||
type: "light",
|
|
||||||
entity: entityId,
|
|
||||||
};
|
|
||||||
cards.push(cardConfig);
|
|
||||||
} else if (domain === "media_player") {
|
} else if (domain === "media_player") {
|
||||||
const cardConfig = {
|
const cardConfig = {
|
||||||
type: "media-control",
|
type: "media-control",
|
||||||
@@ -153,16 +149,18 @@ export const computeCards = (
|
|||||||
) {
|
) {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
} else {
|
} else {
|
||||||
let name: string;
|
let name: string | undefined;
|
||||||
const entityConf =
|
const entityConf =
|
||||||
titlePrefix &&
|
titlePrefix &&
|
||||||
stateObj &&
|
stateObj &&
|
||||||
// eslint-disable-next-line no-cond-assign
|
// eslint-disable-next-line no-cond-assign
|
||||||
(name = computeStateName(stateObj)) !== titlePrefix &&
|
(name = stripPrefixFromEntityName(
|
||||||
name.startsWith(titlePrefix)
|
computeStateName(stateObj),
|
||||||
|
titlePrefix
|
||||||
|
))
|
||||||
? {
|
? {
|
||||||
entity: entityId,
|
entity: entityId,
|
||||||
name: adjustName(name.substr(titlePrefix.length)),
|
name,
|
||||||
}
|
}
|
||||||
: entityId;
|
: entityId;
|
||||||
|
|
||||||
@@ -181,15 +179,6 @@ export const computeCards = (
|
|||||||
return cards;
|
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 = (
|
const computeDefaultViewStates = (
|
||||||
entities: HassEntities,
|
entities: HassEntities,
|
||||||
entityEntries: EntityRegistryEntry[]
|
entityEntries: EntityRegistryEntry[]
|
||||||
@@ -244,6 +233,62 @@ export const generateViewConfig = (
|
|||||||
|
|
||||||
let cards: LovelaceCardConfig[] = [];
|
let cards: LovelaceCardConfig[] = [];
|
||||||
|
|
||||||
|
if ("person" in ungroupedEntitites) {
|
||||||
|
const personCards: LovelaceCardConfig[] = [];
|
||||||
|
|
||||||
|
if (ungroupedEntitites.person.length === 1) {
|
||||||
|
cards.push({
|
||||||
|
type: "entities",
|
||||||
|
entities: ungroupedEntitites.person,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let backgroundColor: string | undefined;
|
||||||
|
let foregroundColor = "";
|
||||||
|
|
||||||
|
for (const personEntityId of ungroupedEntitites.person) {
|
||||||
|
const stateObj = entities[personEntityId];
|
||||||
|
|
||||||
|
let image = stateObj.attributes.entity_picture;
|
||||||
|
|
||||||
|
if (!image) {
|
||||||
|
if (backgroundColor === undefined) {
|
||||||
|
const computedStyle = getComputedStyle(document.body);
|
||||||
|
backgroundColor = encodeURIComponent(
|
||||||
|
computedStyle.getPropertyValue("--light-primary-color").trim()
|
||||||
|
);
|
||||||
|
foregroundColor = encodeURIComponent(
|
||||||
|
(
|
||||||
|
computedStyle.getPropertyValue("--text-light-primary-color") ||
|
||||||
|
computedStyle.getPropertyValue("--primary-text-color")
|
||||||
|
).trim()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const initials = computeUserInitials(
|
||||||
|
stateObj.attributes.friendly_name || ""
|
||||||
|
);
|
||||||
|
image = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 50 50' width='50' height='50' style='background-color:${backgroundColor}'%3E%3Cg%3E%3Ctext font-family='roboto' x='50%25' y='50%25' text-anchor='middle' stroke='${foregroundColor}' font-size='1.3em' dy='.3em'%3E${initials}%3C/text%3E%3C/g%3E%3C/svg%3E`;
|
||||||
|
}
|
||||||
|
|
||||||
|
personCards.push({
|
||||||
|
type: "picture-entity",
|
||||||
|
entity: personEntityId,
|
||||||
|
aspect_ratio: "1",
|
||||||
|
show_name: false,
|
||||||
|
image,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cards.push({
|
||||||
|
type: "grid",
|
||||||
|
square: true,
|
||||||
|
columns: 3,
|
||||||
|
cards: personCards,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
delete ungroupedEntitites.person;
|
||||||
|
}
|
||||||
|
|
||||||
splitted.groups.forEach((groupEntity) => {
|
splitted.groups.forEach((groupEntity) => {
|
||||||
cards = cards.concat(
|
cards = cards.concat(
|
||||||
computeCards(
|
computeCards(
|
||||||
|
@@ -52,7 +52,7 @@ export function hasConfigOrEntitiesChanged(
|
|||||||
|
|
||||||
const oldHass = changedProps.get("hass") as HomeAssistant;
|
const oldHass = changedProps.get("hass") as HomeAssistant;
|
||||||
|
|
||||||
const entities = processConfigEntities(element._config!.entities);
|
const entities = processConfigEntities(element._config!.entities, false);
|
||||||
|
|
||||||
return entities.some(
|
return entities.some(
|
||||||
(entity) =>
|
(entity) =>
|
||||||
|
@@ -5,7 +5,8 @@ import { EntityConfig, LovelaceRowConfig } from "../entity-rows/types";
|
|||||||
export const processConfigEntities = <
|
export const processConfigEntities = <
|
||||||
T extends EntityConfig | LovelaceRowConfig
|
T extends EntityConfig | LovelaceRowConfig
|
||||||
>(
|
>(
|
||||||
entities: Array<T | string>
|
entities: Array<T | string>,
|
||||||
|
checkEntityId = true
|
||||||
): T[] => {
|
): T[] => {
|
||||||
if (!entities || !Array.isArray(entities)) {
|
if (!entities || !Array.isArray(entities)) {
|
||||||
throw new Error("Entities need to be an array");
|
throw new Error("Entities need to be an array");
|
||||||
@@ -35,7 +36,7 @@ export const processConfigEntities = <
|
|||||||
throw new Error(`Invalid entity specified at position ${index}.`);
|
throw new Error(`Invalid entity specified at position ${index}.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isValidEntityId((config as EntityConfig).entity!)) {
|
if (checkEntityId && !isValidEntityId((config as EntityConfig).entity!)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Invalid entity ID at position ${index}: ${
|
`Invalid entity ID at position ${index}: ${
|
||||||
(config as EntityConfig).entity
|
(config as EntityConfig).entity
|
||||||
|
@@ -267,6 +267,9 @@ export class HuiEnergyPeriodSelector extends SubscribeMixin(LitElement) {
|
|||||||
ha-button-toggle-group {
|
ha-button-toggle-group {
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
}
|
}
|
||||||
|
mwc-button {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,10 +10,6 @@ import {
|
|||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { guard } from "lit/directives/guard";
|
import { guard } from "lit/directives/guard";
|
||||||
import type { SortableEvent } from "sortablejs";
|
import type { SortableEvent } from "sortablejs";
|
||||||
import Sortable, {
|
|
||||||
AutoScroll,
|
|
||||||
OnSpill,
|
|
||||||
} from "sortablejs/modular/sortable.core.esm";
|
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import "../../../components/entity/ha-entity-picker";
|
import "../../../components/entity/ha-entity-picker";
|
||||||
import type { HaEntityPicker } from "../../../components/entity/ha-entity-picker";
|
import type { HaEntityPicker } from "../../../components/entity/ha-entity-picker";
|
||||||
@@ -22,6 +18,8 @@ import { sortableStyles } from "../../../resources/ha-sortable-style";
|
|||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { EntityConfig } from "../entity-rows/types";
|
import { EntityConfig } from "../entity-rows/types";
|
||||||
|
|
||||||
|
let Sortable;
|
||||||
|
|
||||||
@customElement("hui-entity-editor")
|
@customElement("hui-entity-editor")
|
||||||
export class HuiEntityEditor extends LitElement {
|
export class HuiEntityEditor extends LitElement {
|
||||||
@property({ attribute: false }) protected hass?: HomeAssistant;
|
@property({ attribute: false }) protected hass?: HomeAssistant;
|
||||||
@@ -34,7 +32,7 @@ export class HuiEntityEditor extends LitElement {
|
|||||||
|
|
||||||
@state() private _renderEmptySortable = false;
|
@state() private _renderEmptySortable = false;
|
||||||
|
|
||||||
private _sortable?: Sortable;
|
private _sortable?;
|
||||||
|
|
||||||
public connectedCallback() {
|
public connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
@@ -86,11 +84,6 @@ export class HuiEntityEditor extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected firstUpdated(): void {
|
|
||||||
Sortable.mount(OnSpill);
|
|
||||||
Sortable.mount(new AutoScroll());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected updated(changedProps: PropertyValues): void {
|
protected updated(changedProps: PropertyValues): void {
|
||||||
super.updated(changedProps);
|
super.updated(changedProps);
|
||||||
|
|
||||||
@@ -128,7 +121,17 @@ export class HuiEntityEditor extends LitElement {
|
|||||||
this._renderEmptySortable = false;
|
this._renderEmptySortable = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _createSortable() {
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
this._sortable = new Sortable(this.shadowRoot!.querySelector(".entities"), {
|
this._sortable = new Sortable(this.shadowRoot!.querySelector(".entities"), {
|
||||||
animation: 150,
|
animation: 150,
|
||||||
fallbackClass: "sortable-fallback",
|
fallbackClass: "sortable-fallback",
|
||||||
|
@@ -192,7 +192,7 @@ export class HuiImage extends LitElement {
|
|||||||
: undefined,
|
: undefined,
|
||||||
backgroundImage:
|
backgroundImage:
|
||||||
useRatio && this._loadedImageSrc
|
useRatio && this._loadedImageSrc
|
||||||
? `url(${this._loadedImageSrc})`
|
? `url("${this._loadedImageSrc}")`
|
||||||
: undefined,
|
: undefined,
|
||||||
filter:
|
filter:
|
||||||
this._loadState === LoadState.Loaded || this.cameraView === "live"
|
this._loadState === LoadState.Loaded || this.cameraView === "live"
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user